1local function apply_scheme(palette, wnd)
2	if (palette and valid_vid(wnd.external, TYPE_FRAMESERVER)) then
3-- used to convey alpha, color scheme, etc. primarily for TUIs
4		for k,v in ipairs(palette) do
5			local ind = key_to_graphmode(k);
6			if (ind and type(v) == "table" and #v == 3) then
7				target_graphmode(wnd.external, ind, v[1], v[2], v[3]);
8			else
9				warning("apply_scheme(), broken key " .. k);
10			end
11		end
12-- commit
13		target_graphmode(wnd.external, 0);
14	end
15end
16
17local function run_group(group, prefix, wnd)
18	local ad = active_display();
19	local as = ad.selected;
20
21-- hack around the problem with many menu paths written using the dumb
22-- active_display().selected
23	if (wnd) then
24		ad.selected = wnd;
25	end
26
27-- this doesn't check / account for reattachments/migrations etc.
28	if (group and type(group) == "table") then
29		for k,v in ipairs(group) do
30			if (type(v) == "string" and string.starts_with(v, prefix)) then
31				dispatch_symbol(v);
32			end
33		end
34	end
35
36-- and restore if we didn't destroy something, but this method isn't safe
37-- from modification, and that's mentioned in the documentation
38	if (as and as.canvas) then
39		ad.selected = as;
40	end
41end
42
43local function run_domain(group, pal, set)
44	run_group(group, "/global/", nil);
45	if (set) then
46-- need a copy to survive UAF- self modification
47		local lst = {};
48		for k,v in ipairs(set) do
49			table.insert(lst, v);
50		end
51		for i,wnd in ipairs(lst) do
52			if (wnd.canvas) then
53				run_group(group, "/target/", wnd);
54			end
55			apply_scheme(pal, wnd);
56		end
57	end
58end
59
60local function tryload_scheme(v)
61	local res = system_load(v, 0);
62	if (not res) then
63		warning(string.format("devmaps/schemes, system_load on %s failed", v));
64		return;
65	end
66
67	local okstate, tbl = pcall(res);
68	if (not okstate) then
69		warning(string.format("devmaps/schemes, couldn't parse/extract %s", v));
70		return;
71	end
72
73-- FIXME: [a_Z,0-9 on name]
74	if (type(tbl) ~= "table" or not tbl.name or not tbl.label) then
75		warning(string.format("devmaps/schemes, no name/label field for %s", v));
76		return;
77	end
78
79-- pretty much all fields are optional as it stands
80	return tbl;
81end
82
83local schemes;
84local function scan_schemes()
85	schemes = {};
86	local list = glob_resource("devmaps/schemes/*.lua", APPL_RESOURCE);
87	for i,v in ipairs(list) do
88		local res = tryload_scheme("devmaps/schemes/" .. v);
89		if (res) then
90			table.insert(schemes, res);
91		end
92	end
93end
94
95function ui_scheme_menu(scope, tgt)
96	local res = {};
97
98-- load / parse on demand
99	if (not schemes) then
100		scan_schemes();
101		if (not schemes) then
102			return;
103		end
104	end
105
106	for k,v in ipairs(schemes) do
107		table.insert(res, {
108			name = v.name,
109			label = v.label,
110			kind = "action",
111			scheme = v,
112			handler = function()
113				if (scope == "global") then
114					local lst = {};
115					for wnd in all_windows(true) do
116						table.insert(lst, wnd);
117					end
118					run_domain(v.actions, v.palette, lst);
119				elseif (scope == "display") then
120					local lst = {};
121					for i, wnd in ipairs(tgt.windows) do
122						table.insert(lst, wnd);
123					end
124					run_domain(v.actions, nil, lst);
125					run_domain(v.display, v.palette, lst);
126				elseif (scope == "workspace") then
127					local lst = {};
128					for i,v in ipairs(tgt.children) do
129						table.insert(lst, v);
130					end
131					run_domain(v.actions, nil, lst);
132					run_domain(v.workspace, v.palette, lst);
133				elseif (scope == "window") then
134					run_domain(v.actions, nil, {tgt});
135					run_domain(v.window, v.palette, {tgt});
136				end
137			end
138		});
139	end
140
141	return res;
142end
143