1-- Copyright 2015-2019, Björn Ståhl
2-- License: 3-Clause BSD
3-- Reference: http://durden.arcan-fe.com
4--
5-- Description: Global / Persistent configuration management.
6-- Deals with key lookup, update hooks and so on. For actual default
7-- configuration values, see config.lua
8--
9
10local log, fmt = suppl_add_logfn("config");
11
12-- here for the time being, will move with internationalization
13LBL_YES = "yes";
14LBL_NO = "no";
15LBL_FLIP = "toggle";
16LBL_BIND_COMBINATION = "Press and hold the desired combination, %s to Cancel";
17LBL_BIND_KEYSYM = "Press and hold single key to bind keysym %s, %s to Cancel";
18LBL_BIND_COMBINATION_REP = "Press and hold or repeat- press, %s to Cancel";
19LBL_UNBIND_COMBINATION = "Press and hold the combination to unbind, %s to Cancel";
20LBL_METAGUARD = "Query Rebind in %d keypresses";
21LBL_METAGUARD_META = "Rebind (meta keys) in %.2f seconds, %s to Cancel";
22LBL_METAGUARD_BASIC = "Rebind (basic keys) in %.2f seconds, %s to Cancel";
23LBL_METAGUARD_MENU = "Rebind (global menu) in %.2f seconds, %s to Cancel";
24LBL_METAGUARD_TMENU = "Rebind (target menu) in %.2f seconds, %s to Cancel";
25
26HC_PALETTE = {
27	"\\#efd469",
28	"\\#43abc9",
29	"\\#cd594a",
30	"\\#b5c689",
31	"\\#f58b4c",
32	"\\#ed6785",
33	"\\#d0d0d0",
34};
35
36local defaults = system_load("config.lua")();
37local listeners = {};
38
39function gconfig_listen(key, id, fun)
40	if (listeners[key] == nil) then
41		listeners[key] = {};
42	end
43	listeners[key][id] = fun;
44end
45
46-- for tools and other plugins to enable their own values
47function gconfig_register(key, val)
48	if (not defaults[key]) then
49		local v = get_key(key);
50		if (v ~= nil) then
51			if (type(val) == "number") then
52				v = tonumber(v);
53			elseif (type(val) == "boolean") then
54				v = v == "true";
55			end
56			defaults[key] = v;
57		else
58			defaults[key] = val;
59		end
60	end
61end
62
63function gconfig_set(key, val, force)
64	if (type(val) ~= type(defaults[key])) then
65		log(fmt(
66			"key=%s:kind=error:type_in=%s:type_out=%s:value=%s",
67			key, type(val), type(defaults[key]), val
68		));
69		return;
70	end
71
72-- lua5.1 timebomb, 5.2 adds to_lstring on args, 5.1 does not, might
73-- need to go with a fixup where .format walks ... and tostrings boolean
74	log(fmt("key=%s:kind=set:new_value=%s", key, tostring(val)));
75	defaults[key] = val;
76
77	if (force) then
78		store_key(key, tostring(val));
79	end
80
81	if (listeners[key]) then
82		for k,v in pairs(listeners[key]) do
83			v(key, val);
84		end
85	end
86end
87
88function gconfig_get(key)
89	return defaults[key];
90end
91
92--
93-- these need special consideration, packing and unpacking so treat
94-- them separately
95--
96
97gconfig_buttons = {
98	all = {},
99	float = {
100	},
101	tile = {
102	},
103};
104
105gconfig_statusbar_buttons = {
106};
107
108-- for the sake of convenience, : is blocked from being a valid vsym as
109-- it is used as a separator elsewhere (suppl_valid_vsymbol)
110local function btn_str(v)
111	return string.format("%s:%s:%s", v.direction, v.label, v.command);
112end
113
114local function str_to_btn(dst, v)
115	local str, rest = string.split_first(v, "=");
116	local dir, rest = string.split_first(rest, ":");
117	local key, rest = string.split_first(rest, ":");
118	local base, cmd = string.split_first(rest, ":");
119
120	if (#dir > 0 and #rest > 0 and #key > 0) then
121		local ind = string.sub(str, 10);
122
123		table.insert(dst, {
124			label = key,
125			command = cmd,
126			direction = dir,
127			ind = tonumber(ind)
128		});
129		return true;
130	end
131end
132
133function gconfig_statusbar_rebuild(nosynch)
134--double negative, but oh well - save the current state as config
135	if (not nosynch) then
136		drop_keys("sbar_btn_%");
137		drop_keys("sbar_btn_alt_%");
138		drop_keys("sbar_btn_drag_%");
139		local keys_out = {};
140
141		for i,v in ipairs(gconfig_statusbar_buttons) do
142			keys_out["sbar_btn_" .. tostring(i)] = btn_str(v);
143			if (v.alt_command) then
144				keys_out["sbar_btn_alt_" .. tostring(i)] = v.alt_command;
145			end
146			if (v.drag_command) then
147				keys_out["sbar_btn_drag_" .. tostring(i)] = v.drag_command;
148			end
149		end
150		store_key(keys_out);
151	end
152
153	gconfig_statusbar_buttons = {};
154	local ofs = 0;
155	for _,v in ipairs(match_keys("sbar_btn_%")) do
156		if (str_to_btn(gconfig_statusbar_buttons, v)) then
157			local ent = gconfig_statusbar_buttons[#gconfig_statusbar_buttons];
158			if (ent.ind) then
159				ent.alt_command = get_key("sbar_btn_alt_" .. tostring(ent.ind));
160				ent.drag_command = get_key("sbar_btn_drag_" .. tostring(ent.ind));
161			end
162		end
163	end
164
165-- will take care of synching against gconfig_statusbar,
166-- but only if the tiler itself expose that method (i.e.
167-- gconf can be loaded before)
168	if all_tilers_iter then
169		for tiler in all_tilers_iter() do
170			if (tiler.rebuild_statusbar_custom) then
171				tiler:rebuild_statusbar_custom(gconfig_statusbar_buttons);
172			end
173		end
174	end
175end
176
177function gconfig_buttons_rebuild(nosynch)
178	local keys = {};
179
180-- delete the keys, then rebuild buttons so we use the same code for both
181-- update dynamically and for initial load
182	if (not nosynch) then
183		drop_keys("tbar_btn_all_%");
184		drop_keys("tbar_btn_float_%");
185		drop_keys("tbar_btn_tile_%");
186
187		local keys_out = {};
188		for _, group in ipairs({"all", "float", "tile"}) do
189			for i,v in ipairs(gconfig_buttons[group]) do
190				keys_out["tbar_btn_" .. group .. "_" .. tostring(i)] = btn_str(v);
191			end
192		end
193		store_key(keys_out);
194	end
195
196	for _, group in ipairs({"all", "float", "tile"}) do
197		gconfig_buttons[group] = {};
198		for _,v in ipairs(match_keys("tbar_btn_" .. group .. "_%")) do
199			str_to_btn(gconfig_buttons[group], v);
200		end
201	end
202end
203
204local function gconfig_setup()
205	for k,vl in pairs(defaults) do
206		local v = get_key(k);
207		if (v) then
208			if (type(vl) == "number") then
209				defaults[k] = tonumber(v);
210-- naive packing for tables (only used with colors currently), just
211-- use : as delimiter and split/concat to manage - just sanity check/
212-- ignore on count and assume same type.
213			elseif (type(vl) == "table") then
214				local lst = string.split(v, ':');
215				local ok = true;
216				for i=1,#lst do
217					if (not vl[i]) then
218						ok = false;
219						break;
220					end
221					if (type(vl[i]) == "number") then
222						lst[i] = tonumber(lst[i]);
223						if (not lst[i]) then
224							ok = false;
225							break;
226						end
227					elseif (type(vl[i]) == "boolean") then
228						lst[i] = lst[i] == "true";
229					end
230				end
231				if (ok) then
232					defaults[k] = lst;
233				end
234			elseif (type(vl) == "boolean") then
235				defaults[k] = v == "true";
236			else
237				defaults[k] = v;
238			end
239		end
240	end
241
242-- separate handling for mouse
243	local ms = mouse_state();
244	mouse_acceleration(defaults.mouse_factor, defaults.mouse_factor);
245	ms.autohide = defaults.mouse_autohide;
246	ms.hover_ticks = defaults.mouse_hovertime;
247	ms.drag_delta = defaults.mouse_dragdelta;
248	ms.hide_base = defaults.mouse_hidetime;
249	for i=1,8 do
250		ms.btns_bounce[i] = defaults["mouse_debounce_" .. tostring(i)];
251	end
252
253-- and for the high-contrast palette used for widgets, ...
254	for _,v in ipairs(match_keys("hc_palette_%")) do
255		local cl = string.split(v, "=");
256		local ind = tonumber(string.sub(cl[1], 12));
257		if ind then
258			HC_PALETTE[ind] = cl[2];
259		end
260	end
261
262-- and for global state of titlebar and statusbar
263	gconfig_buttons_rebuild(true);
264	gconfig_statusbar_rebuild(true);
265end
266
267local mask_state = false;
268function gconfig_mask_temp(state)
269	mask_state = state;
270end
271
272-- shouldn't store all of default overrides in database, just from a
273-- filtered subset
274function gconfig_shutdown()
275	local ktbl = {};
276	for k,v in pairs(defaults) do
277		if (type(v) ~= "table") then
278			ktbl[k] = tostring(v);
279		else
280			ktbl[k] = table.concat(v, ':');
281		end
282	end
283
284	if not mask_state then
285		for i,v in ipairs(match_keys("durden_temp_%")) do
286			local k = string.split(v, "=")[1];
287			ktbl[k] = "";
288		end
289	end
290
291	for i,v in ipairs(HC_PALETTE) do
292		ktbl["hc_palette_" .. tostring(i)] = v
293	end
294
295	store_key(ktbl);
296end
297
298gconfig_setup();
299