Openbox plugin: add missed ToggleShowDesktop action into list.
[lxde/lxhotkey.git] / plugins / openbox / openbox.c
1 /*
2 * Copyright (C) 2016 Andriy Grytsenko <andrej@rep.kiev.ua>
3 *
4 * This file is a part of LXHotkey project.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #define WANT_OPTIONS_EQUAL
26
27 #include "lxhotkey.h"
28
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31
32 #include <libfm/fm-extra.h>
33 #include <glib/gi18n.h>
34
35 #include <fnmatch.h>
36
37 #define LXKEYS_OB_ERROR lxhotkey_ob_error_quark()
38 static GQuark lxhotkey_ob_error_quark(void)
39 {
40 static GQuark q = 0;
41
42 if G_UNLIKELY(q == 0)
43 q = g_quark_from_static_string("lxhotkey-ob-error");
44
45 return q;
46 }
47 enum LXHotkeyObError {
48 LXKEYS_FILE_ERROR,
49 LXKEYS_PARSE_ERROR
50 };
51
52
53 /* simple functions to manage LXHotkeyAttr data type */
54 static inline LXHotkeyAttr *lxhotkey_attr_new(void)
55 {
56 return g_slice_new0(LXHotkeyAttr);
57 }
58
59 #define free_options(acts) g_list_free_full(acts, (GDestroyNotify)lkxeys_attr_free)
60
61 static void lkxeys_attr_free(LXHotkeyAttr *data)
62 {
63 g_free(data->name);
64 g_list_free_full(data->values, g_free);
65 free_options(data->subopts);
66 g_slice_free(LXHotkeyAttr, data);
67 }
68
69 static void lkxeys_action_free(LXHotkeyGlobal *data)
70 {
71 free_options(data->actions);
72 g_free(data->accel1);
73 g_free(data->accel2);
74 g_free(data);
75 }
76
77 static void lkxeys_app_free(LXHotkeyApp *data)
78 {
79 g_free(data->exec);
80 free_options(data->options);
81 g_free(data->accel1);
82 g_free(data->accel2);
83 g_free(data);
84 }
85
86 /* convert from OB format (A-Return) into GDK format (<Alt>Return) */
87 static gchar *obkey_to_key(const gchar *obkey)
88 {
89 GString *str = g_string_sized_new(16);
90
91 while (*obkey) {
92 if (obkey[1] == '-')
93 switch(obkey[0]) {
94 case 'S':
95 g_string_append(str, "<Shift>");
96 break;
97 case 'C':
98 g_string_append(str, "<Control>");
99 break;
100 case 'A':
101 g_string_append(str, "<Alt>");
102 break;
103 case 'W':
104 g_string_append(str, "<Super>");
105 break;
106 case 'M':
107 g_string_append(str, "<Meta>");
108 break;
109 case 'H':
110 g_string_append(str, "<Hyper>");
111 break;
112 default:
113 goto _add_rest;
114 }
115 else
116 _add_rest:
117 break;
118 obkey += 2;
119 }
120 g_string_append(str, obkey);
121 return g_string_free(str, FALSE);
122 }
123
124 /* convert from GDK format (<Alt>Return) into OB format (A-Return) */
125 static gchar *key_to_obkey(const gchar *key)
126 {
127 GString *str = g_string_sized_new(16);
128 gboolean in_lt = FALSE;
129
130 while (*key) {
131 if (in_lt) {
132 if (*key++ == '>')
133 in_lt = FALSE;
134 } else if (*key == '<') {
135 key++;
136 in_lt = TRUE;
137 if (strncmp(key, "Shift", 5) == 0) {
138 g_string_append(str, "S-");
139 key += 5;
140 } else if (strncmp(key, "Contr", 5) == 0 ||
141 strncmp(key, "Ctr", 3) == 0) {
142 g_string_append(str, "C-");
143 key += 3;
144 } else if (strncmp(key, "Alt", 3) == 0) {
145 g_string_append(str, "A-");
146 key += 3;
147 } else if (strncmp(key, "Super", 5) == 0) {
148 g_string_append(str, "W-");
149 key += 5;
150 } else if (strncmp(key, "Meta", 4) == 0) {
151 g_string_append(str, "M-");
152 key += 4;
153 } else if (strncmp(key, "Hyper", 5) == 0) {
154 g_string_append(str, "H-");
155 key += 5;
156 }
157 } else
158 g_string_append_c(str, *key++);
159 }
160 return g_string_free(str, FALSE);
161 }
162
163
164 static gboolean restart_openbox(GError **error)
165 {
166 Display *dpy = XOpenDisplay(NULL);
167 XEvent ce;
168 gboolean ret = TRUE;
169
170 ce.xclient.type = ClientMessage;
171 ce.xclient.message_type = XInternAtom(dpy, "_OB_CONTROL", True);
172 ce.xclient.display = dpy;
173 ce.xclient.window = RootWindow(dpy, DefaultScreen(dpy));
174 ce.xclient.format = 32;
175 ce.xclient.data.l[0] = 1; /* reconfigure */
176 ce.xclient.data.l[1] = 0;
177 ce.xclient.data.l[2] = 0;
178 ce.xclient.data.l[3] = 0;
179 ce.xclient.data.l[4] = 0;
180 if (ce.xclient.message_type == None ||
181 XSendEvent(dpy, ce.xclient.window, False,
182 SubstructureNotifyMask | SubstructureRedirectMask, &ce) == 0) {
183 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
184 _("Failed to reconfigure Openbox."));
185 ret = FALSE;
186 }
187 XCloseDisplay(dpy);
188 return ret;
189 }
190
191
192 /*
193 * Actions/options list supported by Openbox.
194 *
195 * This array is a bit tricky since it does not contain GList pointers, but
196 * those pointers will be expanded on demand.
197 */
198
199 #define TO_BE_CONVERTED(a) (GList *)(a)
200 #define TO_BE_PREVIOUS TO_BE_CONVERTED(1) /* reuse GList */
201 #define BOOLEAN_VALUES TO_BE_CONVERTED(2) /* reuse GList */
202
203 static char * values_enabled[] = { N_("yes"), N_("no"), NULL };
204
205 static LXHotkeyAttr options_startupnotify[] = {
206 { N_("enabled"), BOOLEAN_VALUES, NULL, NULL, FALSE },
207 { N_("wmclass"), NULL, NULL, NULL, FALSE },
208 { N_("name"), NULL, NULL, NULL, FALSE },
209 { N_("icon"), NULL, NULL, NULL, FALSE },
210 { NULL }
211 };
212
213 static LXHotkeyAttr options_Execute[] = {
214 { N_("command"), NULL, NULL, NULL, FALSE },
215 { N_("prompt"), NULL, NULL, NULL, FALSE },
216 { N_("startupnotify"), NULL, TO_BE_CONVERTED(options_startupnotify), NULL, FALSE },
217 { NULL }
218 };
219
220 static char * values_x[] = { "#", "%", N_("center"), NULL };
221 static char * values_monitor[] = { N_("default"), N_("primary"), N_("mouse"),
222 N_("active"), N_("all"), "#", NULL };
223
224 static LXHotkeyAttr options_position[] = {
225 { "x", TO_BE_CONVERTED(values_x), NULL, NULL, FALSE },
226 { "y", TO_BE_PREVIOUS, NULL, NULL, FALSE },
227 { N_("monitor"), TO_BE_CONVERTED(values_monitor), NULL, NULL, FALSE },
228 { NULL }
229 };
230
231 static LXHotkeyAttr options_ShowMenu[] = {
232 { N_("menu"), NULL, NULL, NULL, FALSE },
233 { N_("position"), NULL, TO_BE_CONVERTED(options_position), NULL, FALSE },
234 { NULL }
235 };
236
237 static char * values_dialog[] = { N_("list"), N_("icons"), N_("none"), NULL };
238
239 static LXHotkeyAttr options_NextWindow[] = {
240 { N_("dialog"), TO_BE_CONVERTED(values_dialog), NULL, NULL, FALSE },
241 { N_("bar"), BOOLEAN_VALUES, NULL, NULL, FALSE },
242 { N_("raise"), BOOLEAN_VALUES, NULL, NULL, FALSE },
243 { N_("allDesktops"), BOOLEAN_VALUES, NULL, NULL, FALSE },
244 { N_("panels"), BOOLEAN_VALUES, NULL, NULL, FALSE },
245 { N_("desktop"), BOOLEAN_VALUES, NULL, NULL, FALSE },
246 { N_("linear"), BOOLEAN_VALUES, NULL, NULL, FALSE },
247 { N_("interactive"), BOOLEAN_VALUES, NULL, NULL, FALSE },
248 /* TRANSLATORS: finalactions means actions when done */
249 { N_("finalactions"), NULL, NULL, NULL, TRUE },
250 { NULL }
251 };
252
253 static char * values_direction[] = { N_("north"), N_("northeast"), N_("east"),
254 N_("southeast"), N_("south"), N_("southwest"),
255 N_("west"), N_("northwest"), NULL };
256
257 static LXHotkeyAttr options_DirectionalCycleWindows[] = {
258 { N_("direction"), TO_BE_CONVERTED(values_direction), NULL, NULL, FALSE },
259 { N_("dialog"), BOOLEAN_VALUES, NULL, NULL, FALSE },
260 { N_("bar"), BOOLEAN_VALUES, NULL, NULL, FALSE },
261 { N_("raise"), BOOLEAN_VALUES, NULL, NULL, FALSE },
262 { N_("panels"), BOOLEAN_VALUES, NULL, NULL, FALSE },
263 { N_("desktops"), BOOLEAN_VALUES, NULL, NULL, FALSE },
264 { N_("finalactions"), NULL, NULL, NULL, TRUE },
265 { NULL }
266 };
267
268 /* TRANSLATORS: these values are in regard to desktop */
269 static char * values_to[] = { "#", N_("current"), N_("next"), N_("previous"),
270 N_("last"), N_("north"), N_("up"), N_("south"),
271 N_("down"), N_("west"), N_("left"), N_("east"),
272 N_("right"), NULL };
273
274 static LXHotkeyAttr options_GoToDesktop[] = {
275 { N_("to"), TO_BE_CONVERTED(values_to), NULL, NULL, FALSE },
276 { N_("wrap"), BOOLEAN_VALUES, NULL, NULL, FALSE },
277 { NULL }
278 };
279
280 static char * values_where[] = { N_("current"), N_("last"), NULL };
281
282 static LXHotkeyAttr options_AddDesktop[] = {
283 { N_("where"), TO_BE_CONVERTED(values_where), NULL, NULL, FALSE },
284 { NULL }
285 };
286
287 static LXHotkeyAttr options_ToggleShowDesktop[] = {
288 { N_("strict"), BOOLEAN_VALUES, NULL, NULL, FALSE },
289 { NULL }
290 };
291
292 static LXHotkeyAttr options_Restart[] = {
293 { N_("command"), NULL, NULL, NULL, FALSE },
294 { NULL }
295 };
296
297 static LXHotkeyAttr options_Exit[] = {
298 { N_("prompt"), BOOLEAN_VALUES, NULL, NULL, FALSE },
299 { NULL }
300 };
301
302 static char * values_directionM[] = { N_("both"), N_("horizontal"), N_("vertical"), NULL };
303
304 static LXHotkeyAttr options_ToggleMaximize[] = {
305 { N_("direction"), TO_BE_CONVERTED(values_directionM), NULL, NULL, FALSE },
306 { NULL }
307 };
308
309 static char * values_toS[] = { "#", N_("current"), N_("next"), N_("previous"),
310 N_("last"), N_("north"), N_("up"), N_("south"),
311 N_("down"), N_("west"), N_("left"), N_("east"),
312 N_("right"), NULL };
313
314 static LXHotkeyAttr options_SendToDesktop[] = {
315 { N_("to"), TO_BE_CONVERTED(values_toS), NULL, NULL, FALSE },
316 { N_("wrap"), BOOLEAN_VALUES, NULL, NULL, FALSE },
317 { N_("follow"), BOOLEAN_VALUES, NULL, NULL, FALSE },
318 { NULL }
319 };
320
321 static char * values_edge[] = { N_("top"), N_("left"), N_("right"), N_("bottom"),
322 N_("topleft"), N_("topright"), N_("bottomleft"),
323 N_("bottomright"), NULL };
324
325 static LXHotkeyAttr options_Resize[] = {
326 { N_("edge"), TO_BE_CONVERTED(values_edge), NULL, NULL, FALSE },
327 { NULL }
328 };
329
330 static char * values_xM[] = { N_("#"), N_("current"), N_("center"), NULL };
331 /* TRANSLATORS: % in config means some fraction value, usually measured in percents */
332 static char * values_width[] = { "#", N_("%"), "current", NULL };
333 static char * values_monitorM[] = { "#", N_("current"), N_("all"), N_("next"), N_("prev"), NULL };
334
335 static LXHotkeyAttr options_MoveResizeTo[] = {
336 { "x", TO_BE_CONVERTED(values_xM), NULL, NULL, FALSE },
337 { "y", TO_BE_PREVIOUS, NULL, NULL, FALSE },
338 { N_("width"), TO_BE_CONVERTED(values_width), NULL, NULL, FALSE },
339 { N_("height"), TO_BE_PREVIOUS, NULL, NULL, FALSE },
340 { N_("monitor"), TO_BE_CONVERTED(values_monitorM), NULL, NULL, FALSE },
341 { NULL }
342 };
343
344 /* TRANSLATORS: # in config means either pixels, or monitor number */
345 static char * values_xR[] = { N_("#"), NULL };
346
347 static LXHotkeyAttr options_MoveRelative[] = {
348 { "x", TO_BE_CONVERTED(values_xR), NULL, NULL, FALSE },
349 { "y", TO_BE_PREVIOUS, NULL, NULL, FALSE },
350 { NULL }
351 };
352
353 static char * values_xRR[] = { "#", NULL };
354
355 static LXHotkeyAttr options_ResizeRelative[] = {
356 { N_("left"), TO_BE_CONVERTED(values_xRR), NULL, NULL, FALSE },
357 { N_("right"), TO_BE_PREVIOUS, NULL, NULL, FALSE },
358 { N_("top"), TO_BE_PREVIOUS, NULL, NULL, FALSE },
359 { N_("bottom"), TO_BE_PREVIOUS, NULL, NULL, FALSE },
360 { NULL }
361 };
362
363 static char * values_directionE[] = { N_("north"), N_("south"), N_("west"), N_("east"), NULL };
364
365 static LXHotkeyAttr options_MoveToEdge[] = {
366 { N_("direction"), TO_BE_CONVERTED(values_directionE), NULL, NULL, FALSE },
367 { NULL }
368 };
369
370 static char * values_layer[] = { N_("top"), N_("normal"), N_("bottom"), NULL };
371
372 static LXHotkeyAttr options_SendToLayer[] = {
373 { N_("layer"), TO_BE_CONVERTED(values_layer), NULL, NULL, FALSE },
374 { NULL }
375 };
376
377 static LXHotkeyAttr list_actions[] = {
378 /* global actions */
379 { N_("Execute"), NULL, TO_BE_CONVERTED(options_Execute), NULL, FALSE },
380 { N_("ShowMenu"), NULL, TO_BE_CONVERTED(options_ShowMenu), NULL, FALSE },
381 { N_("NextWindow"), NULL, TO_BE_CONVERTED(options_NextWindow), NULL, FALSE },
382 { N_("PreviousWindow"), NULL, TO_BE_PREVIOUS, NULL, FALSE },
383 { N_("DirectionalCycleWindows"), NULL, TO_BE_CONVERTED(options_DirectionalCycleWindows), NULL, FALSE },
384 { N_("DirectionalTargetWindow"), NULL, TO_BE_PREVIOUS, NULL, FALSE },
385 { N_("GoToDesktop"), NULL, TO_BE_CONVERTED(options_GoToDesktop), NULL, FALSE },
386 { N_("AddDesktop"), NULL, TO_BE_CONVERTED(options_AddDesktop), NULL, FALSE },
387 { N_("RemoveDesktop"), NULL, TO_BE_PREVIOUS, NULL, FALSE },
388 { N_("ToggleShowDesktop"), NULL, TO_BE_CONVERTED(options_ToggleShowDesktop), NULL, FALSE },
389 { N_("ToggleDockAutohide"), NULL, NULL, NULL, FALSE },
390 { N_("Reconfigure"), NULL, NULL, NULL, FALSE },
391 { N_("Restart"), NULL, TO_BE_CONVERTED(options_Restart), NULL, FALSE },
392 { N_("Exit"), NULL, TO_BE_CONVERTED(options_Exit), NULL, FALSE },
393 /* windows actions */
394 { N_("Focus"), NULL, NULL, NULL, FALSE },
395 { N_("Raise"), NULL, NULL, NULL, FALSE },
396 { N_("Lower"), NULL, NULL, NULL, FALSE },
397 { N_("RaiseLower"), NULL, NULL, NULL, FALSE },
398 { N_("Unfocus"), NULL, NULL, NULL, FALSE },
399 { N_("FocusToBottom"), NULL, NULL, NULL, FALSE },
400 { N_("Iconify"), NULL, NULL, NULL, FALSE },
401 { N_("Close"), NULL, NULL, NULL, FALSE },
402 { N_("ToggleShade"), NULL, NULL, NULL, FALSE },
403 { N_("Shade"), NULL, NULL, NULL, FALSE },
404 { N_("Unshade"), NULL, NULL, NULL, FALSE },
405 { N_("ToggleOmnipresent"), NULL, NULL, NULL, FALSE },
406 { N_("ToggleMaximize"), NULL, TO_BE_CONVERTED(options_ToggleMaximize), NULL, FALSE },
407 { N_("Maximize"), NULL, TO_BE_PREVIOUS, NULL, FALSE },
408 { N_("Unmaximize"), NULL, TO_BE_PREVIOUS, NULL, FALSE },
409 { N_("ToggleFullscreen"), NULL, NULL, NULL, FALSE },
410 { N_("ToggleDecorations"), NULL, NULL, NULL, FALSE },
411 { N_("Decorate"), NULL, NULL, NULL, FALSE },
412 { N_("Undecorate"), NULL, NULL, NULL, FALSE },
413 { N_("SendToDesktop"), NULL, TO_BE_CONVERTED(options_SendToDesktop), NULL, FALSE },
414 { N_("Move"), NULL, NULL, NULL, FALSE },
415 { N_("Resize"), NULL, TO_BE_CONVERTED(options_Resize), NULL, FALSE },
416 { N_("MoveResizeTo"), NULL, TO_BE_CONVERTED(options_MoveResizeTo), NULL, FALSE },
417 { N_("MoveRelative"), NULL, TO_BE_CONVERTED(options_MoveRelative), NULL, FALSE },
418 { N_("ResizeRelative"), NULL, TO_BE_CONVERTED(options_ResizeRelative), NULL, FALSE },
419 { N_("MoveToEdge"), NULL, TO_BE_CONVERTED(options_MoveToEdge), NULL, FALSE },
420 { N_("GrowToEdge"), NULL, TO_BE_PREVIOUS, NULL, FALSE },
421 { N_("ShrinkToEdge"), NULL, TO_BE_PREVIOUS, NULL, FALSE },
422 { N_("GrowToFill"), NULL, NULL, NULL, FALSE },
423 { N_("ToggleAlwaysOnTop"), NULL, NULL, NULL, FALSE },
424 { N_("ToggleAlwaysOnBottom"), NULL, NULL, NULL, FALSE },
425 { N_("SendToLayer"), NULL, TO_BE_CONVERTED(options_SendToLayer), NULL, FALSE },
426 // FIXME: support for If/ForEach/Stop ?
427 { NULL }
428 };
429
430 static GList *boolean_values = NULL;
431 static GList *available_wm_actions = NULL;
432 static GList *available_app_options = NULL;
433
434 static GList *convert_values(gpointer data)
435 {
436 char ** array;
437 GList *list = NULL;
438
439 for (array = data; array[0] != NULL; array++) {
440 /* g_debug("creating GList for string '%s'", array[0]); */
441 list = g_list_prepend(list, array[0]);
442 }
443 return g_list_reverse(list);
444 }
445
446 static GList *convert_options(gpointer data)
447 {
448 LXHotkeyAttr *array, *last = NULL;
449 GList *list = NULL;
450
451 for (array = data; array->name != NULL; array++) {
452 list = g_list_prepend(list, array);
453 if (last && array->values == TO_BE_PREVIOUS)
454 array->values = last->values;
455 else if (array->values == BOOLEAN_VALUES) {
456 if (boolean_values == NULL)
457 boolean_values = convert_values(values_enabled);
458 array->values = boolean_values;
459 } else if (array->values != NULL)
460 array->values = convert_values(array->values);
461 if (last && array->subopts == TO_BE_PREVIOUS)
462 array->subopts = last->subopts;
463 else if (array->subopts != NULL) {
464 if (array->subopts == TO_BE_CONVERTED(options_Execute))
465 array->subopts = available_app_options = convert_options(array->subopts);
466 else
467 array->subopts = convert_options(array->subopts);
468 }
469 last = array;
470 }
471 return g_list_reverse(list);
472 }
473
474
475 typedef struct {
476 FmXmlFileItem *parent; /* R/O */
477 GList *list; /* contains LXHotkeyAttr items for finished actions */
478 } ObActionsList;
479
480 typedef struct {
481 char *path;
482 FmXmlFile *xml;
483 FmXmlFileItem *keyboard; /* the <keyboard> section */
484 GList *actions; /* no-exec actions, in reverse order */
485 GList *execs; /* exec-only actions, in reverse order */
486 GList *stack; /* only for build - elements are ObActionsList */
487 GList *added_tags; /* only for edit */
488 } ObXmlFile;
489
490 static FmXmlFileTag ObXmlFile_keyboard; /* section that we work on */
491 static FmXmlFileTag ObXmlFile_keybind; /* subsection, for each binding */
492 static FmXmlFileTag ObXmlFile_action; /* may be multiple for a binding */
493 static FmXmlFileTag ObXmlFile_command; /* for <action name="Execute"> */
494 static FmXmlFileTag ObXmlFile_execute; /* obsolete alias for command */
495
496 static inline void clear_stack(ObXmlFile *cfg)
497 {
498 while (cfg->stack != NULL) {
499 free_options(((ObActionsList *)cfg->stack->data)->list);
500 g_free(cfg->stack->data);
501 cfg->stack = g_list_delete_link(cfg->stack, cfg->stack);
502 }
503 }
504
505 static gboolean tag_handler_keyboard(FmXmlFileItem *item, GList *children,
506 char * const *attribute_names,
507 char * const *attribute_values,
508 guint n_attributes, gint line, gint pos,
509 GError **error, gpointer user_data)
510 {
511 ObXmlFile *cfg = user_data;
512
513 if (cfg->keyboard) {
514 /* FIXME: merge duplicate section? */
515 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
516 _("Duplicate <keyboard> section in the rc.xml file."));
517 return FALSE;
518 }
519 cfg->keyboard = item;
520 return TRUE;
521 }
522
523 static gboolean tag_handler_keybind(FmXmlFileItem *item, GList *children,
524 char * const *attribute_names,
525 char * const *attribute_values,
526 guint n_attributes, gint line, gint pos,
527 GError **error, gpointer user_data)
528 {
529 ObXmlFile *cfg = user_data;
530 ObActionsList *oblist;
531 GList *actions, *l;
532 gchar *key;
533 const char *exec_line = NULL;
534 LXHotkeyAttr *action;
535 LXHotkeyApp *app = NULL;
536 LXHotkeyGlobal *act;
537 guint i;
538
539 if (!cfg->stack) { /* empty keybind tag, just ignore it and remove */
540 g_warning("Openbox config: empty keybind tag in rc.xml, going to remove it");
541 fm_xml_file_item_destroy(item);
542 return TRUE;
543 }
544 oblist = cfg->stack->data;
545 if (oblist->parent != item) { /* corruption! */
546 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
547 _("Internal error."));
548 return FALSE;
549 }
550 /* just remove top stack item, all actions are already there */
551 actions = oblist->list;
552 g_free(oblist);
553 cfg->stack = g_list_delete_link(cfg->stack, cfg->stack);
554 /* and clear junk if there were actions there - e.g. from mouse section */
555 clear_stack(cfg);
556 action = actions->data;
557 /* decide where to put: execs or actions */
558 if (children && !children->next && /* exactly one child which is an action */
559 fm_xml_file_item_get_tag(children->data) == ObXmlFile_action) {
560 if (strcmp(action->name, "Execute") == 0) { /* and action is Execute */
561 FmXmlFileItem *exec = fm_xml_file_item_find_child(children->data,
562 ObXmlFile_command);
563 if (!exec)
564 exec = fm_xml_file_item_find_child(children->data, ObXmlFile_execute);
565 /* if exec is NULL then it's invalid action after all */
566 if (exec)
567 {
568 /* not empty exec line was verified in the handler */
569 exec_line = fm_xml_file_item_get_data(fm_xml_file_item_find_child(exec, FM_XML_FILE_TEXT), NULL);
570 for (l = cfg->execs; l; l = l->next)
571 /* if exec line is equal to one gathered already */
572 if (strcmp(((LXHotkeyApp *)l->data)->exec, exec_line) == 0 &&
573 /* and it has no secondary keybinding */
574 ((LXHotkeyApp *)l->data)->accel2 == NULL &&
575 /* and options are also equal */
576 options_equal(((LXHotkeyApp *)l->data)->options, action->subopts))
577 {
578 /* then just add this keybinding to found one */
579 app = (LXHotkeyApp *)l->data;
580 break;
581 }
582 }
583 }
584 }
585 /* find a "key" attribute and save its value as accel1 */
586 for (i = 0; i < n_attributes; i++)
587 if (g_strcmp0(attribute_names[i], "key") == 0)
588 break;
589 if (i == n_attributes) { /* no name in XML! */
590 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
591 _("rc.xml error: no key is set for a keybind."));
592 free_options(actions);
593 return FALSE;
594 } else
595 key = obkey_to_key(attribute_values[i]);
596 /* if that exec already exists then reuse it and set as accel2 */
597 if (app) {
598 app->accel2 = key;
599 app->data2 = item;
600 /* otherwise create LXHotkeyApp or LXHotkeyGlobal and add it to the list */
601 } else if (exec_line) {
602 app = g_new0(LXHotkeyApp, 1);
603 app->accel1 = key;
604 app->exec = g_strdup(exec_line);
605 app->data1 = item;
606 app->options = action->subopts;
607 /* remove exec line from options, it should be in XML but not in LXHotkeyApp */
608 for (l = app->options; l; ) {
609 LXHotkeyAttr *opt = l->data;
610 l = l->next;
611 if (strcmp(opt->name, "command") == 0 || strcmp(opt->name, "execute") == 0) {
612 app->options = g_list_remove(app->options, opt);
613 lkxeys_attr_free(opt);
614 }
615 }
616 action->subopts = NULL;
617 cfg->execs = g_list_prepend(cfg->execs, app);
618 } else {
619 for (l = cfg->actions; l; l = l->next)
620 /* if the same actions list was gathered already */
621 if (options_equal((act = l->data)->actions, actions)
622 /* and it has no secondary keybinding */
623 && act->accel2 == NULL)
624 break;
625 if (l == NULL) {
626 act = g_new0(LXHotkeyGlobal, 1);
627 act->accel1 = key;
628 act->data1 = item;
629 act->actions = actions;
630 actions = NULL;
631 cfg->actions = g_list_prepend(cfg->actions, act);
632 } else { /* actions exist in list so reuse it adding a second keybinding */
633 act->accel2 = key;
634 act->data2 = item;
635 }
636 }
637 free_options(actions);
638 return TRUE;
639 }
640
641 /* collect all children of FmXmlFileItem into LXHotkeyAttr list
642 removing from stack if found there
643 since actions cannot be mixed with options, just
644 ignore anything not collected into any ObActionsList */
645 static GList *resolve_item(GList **stack, GList *children, GList **value,
646 GError **error)
647 {
648 GList *child, *l, *items = NULL;
649 ObActionsList *act; /* stack item */
650 FmXmlFileItem *item; /* child item */
651 LXHotkeyAttr *data;
652
653 for (child = children; child; child = child->next) {
654 item = child->data;
655 if (fm_xml_file_item_get_tag(item) == FM_XML_FILE_TEXT) { /* value! */
656 *value = g_list_prepend(*value,
657 g_strdup(fm_xml_file_item_get_data(item, NULL)));
658 continue;
659 }
660 if (fm_xml_file_item_get_tag(item) == ObXmlFile_action) { /* stray action? */
661 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
662 _("Invalid rc.xml: action with a sub-action."));
663 free_options(items);
664 return NULL;
665 }
666 data = lxhotkey_attr_new();
667 data->name = g_strdup(fm_xml_file_item_get_tag_name(item));
668 /* find if item is in stack, then remove from there and use ready list */
669 for (l = *stack; l; l = l->next) {
670 act = l->data;
671 if (act->parent == item) {
672 *stack = g_list_delete_link(*stack, l);
673 data->subopts = act->list;
674 data->has_actions = TRUE;
675 g_free(act); /* release the ObActionsList */
676 break;
677 }
678 }
679 /* otherwise collect children by recursive call */
680 if (l == NULL) {
681 GError *err = NULL;
682
683 l = fm_xml_file_item_get_children(item);
684 data->subopts = resolve_item(stack, l, &data->values, &err);
685 g_list_free(l);
686 if (err) {
687 g_propagate_error(error, err);
688 free_options(items);
689 lkxeys_attr_free(data);
690 return NULL;
691 }
692 }
693 /* add the item to the list */
694 items = g_list_prepend(items, data);
695 }
696 return g_list_reverse(items);
697 }
698
699 static gboolean is_on_stack(GList *stack, FmXmlFileItem *item)
700 {
701 while (stack) {
702 if (((ObActionsList *)stack->data)->parent == item)
703 return TRUE;
704 stack = stack->next;
705 }
706 return FALSE;
707 }
708
709 /* push new item on the stack, return top item */
710 static ObActionsList *put_on_stack_top(ObXmlFile *cfg, FmXmlFileItem *parent)
711 {
712 ObActionsList *oblist = g_new0(ObActionsList, 1);
713
714 oblist->parent = parent;
715 cfg->stack = g_list_prepend(cfg->stack, oblist);
716 return oblist;
717 }
718
719 /* (parent/children) stack (changes)
720 <k><a1><f><a2/> = (f/-) f (+f)
721 +<a3><x><a4/> => (x/-) f x:4 (+x)
722 +<a5/> => (x/-) f x:45 ()
723 +</x></a3> => (f/x:45) f:3-x:45 (-x)
724 +</f></a1> => (k/f:3) k:1-f:3 (-f +k)
725 +<a6><h><a7><t><a8/> => (t/-) k:1 t:8 (+t)
726 +</t></a7> => (h/t:8) k:1 h:7-t:8 (-t +h)
727 +</h></a6> => (k/h:7) k:16-h:7 (-h)
728 +<a9><b><c><d><a0><e><g><az/> => (g/-) k:16 g:z (+g)
729 +</g></e></a0> => (d/e-g:z) k:16 d:0-e-g:z (-g +d)
730 +</d></c></b></a9> => (k/b-c-d:0) k:169-b-c-d:0 (-d)
731 +<a3><l><m><ay/> => (m/-) k:169 m:y (+m)
732 +</m><n><a2><o/></a2> => (n/o) k:169 m:y n:2-o (+n)
733 +</n></l></a3> => (k/l-{m:y|n:2}) k:1693-l-{m:y|n:2} (-m -n)
734 +</k> => (-/1693) - (-k)
735 */
736
737 static gboolean tag_handler_action(FmXmlFileItem *item, GList *children,
738 char * const *attribute_names,
739 char * const *attribute_values,
740 guint n_attributes, gint line, gint pos,
741 GError **error, gpointer user_data)
742 {
743 /* if parent is on top of stack then it's another action of the same parent */
744 /* if parent exists deeper on stack after resolving then it's curruption */
745 /* if parent doesn't exist on stack but some of children is then that child
746 is finished, replace with this parent on stack after resolving children */
747 /* if parent doesn't exist on stack neither any child is then it got
748 deeper so just add it on stack */
749 ObXmlFile *cfg = user_data;
750 LXHotkeyAttr *data;
751 ObActionsList *oblist;
752 FmXmlFileItem *parent;
753 GError *err = NULL;
754 guint i;
755
756 /* if section keyboard already finished then ignore this */
757 if (cfg->keyboard) {
758 /* see notes in tag_handler_keyboard() as well */
759 return TRUE;
760 }
761
762 /* create a LXHotkeyAttr */
763 data = lxhotkey_attr_new();
764 //data->has_actions = FALSE; /* action can have only options, not sub-actions! */
765 /* resolve all children of this action, clearing from stack */
766 data->subopts = resolve_item(&cfg->stack, children, &data->values, &err);
767 if (err) {
768 g_propagate_error(error, err);
769 lkxeys_attr_free(data);
770 return FALSE;
771 }
772 /* find a "name" attribute and set it as name */
773 for (i = 0; i < n_attributes; i++)
774 if (g_strcmp0(attribute_names[i], "name") == 0)
775 break;
776 if (i == n_attributes) { /* no name in XML! */
777 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
778 _("rc.xml error: no name is set for action."));
779 lkxeys_attr_free(data);
780 return FALSE;
781 } else
782 data->name = g_strdup(attribute_values[i]);
783 /* add this action to the parent's list */
784 parent = fm_xml_file_item_get_parent(item);
785 if (!is_on_stack(cfg->stack, parent))
786 oblist = put_on_stack_top(cfg, parent);
787 else if ((oblist = cfg->stack->data)->parent != parent) { /* corruption */
788 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
789 _("Internal error."));
790 lkxeys_attr_free(data);
791 return FALSE;
792 }
793 oblist->list = g_list_append(oblist->list, data);
794 return TRUE;
795 }
796
797 static gboolean tag_handler_command(FmXmlFileItem *item, GList *children,
798 char * const *attribute_names,
799 char * const *attribute_values,
800 guint n_attributes, gint line, gint pos,
801 GError **error, gpointer user_data)
802 {
803 FmXmlFileItem *text = fm_xml_file_item_find_child(item, FM_XML_FILE_TEXT);
804
805 if (text == NULL) {
806 /* check if value is not empty */
807 g_set_error(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
808 _("rc.xml error: empty tag <%s> is prohibited."),
809 fm_xml_file_item_get_tag_name(item));
810 return FALSE;
811 }
812 return TRUE;
813 }
814
815
816 static void obcfg_free(gpointer config)
817 {
818 ObXmlFile *cfg = (ObXmlFile *)config;
819
820 g_free(cfg->path);
821 g_object_unref(cfg->xml);
822 g_list_free_full(cfg->actions, (GDestroyNotify)lkxeys_action_free);
823 g_list_free_full(cfg->execs, (GDestroyNotify)lkxeys_app_free);
824 clear_stack(cfg);
825 g_list_free(cfg->added_tags);
826 g_free(cfg);
827 }
828
829 static gpointer obcfg_load(gpointer config, GError **error)
830 {
831 ObXmlFile *cfg = (ObXmlFile *)config;
832 gchar *contents = NULL;
833 GError *err = NULL;
834 gsize len;
835
836 if (config) {
837 /* just discard any changes if any exist */
838 FmXmlFile *old_xml = cfg->xml;
839
840 cfg->xml = fm_xml_file_new(old_xml);
841 g_object_unref(old_xml);
842 g_list_free_full(cfg->actions, (GDestroyNotify)lkxeys_action_free);
843 g_list_free_full(cfg->execs, (GDestroyNotify)lkxeys_app_free);
844 cfg->actions = NULL;
845 cfg->execs = NULL;
846 cfg->keyboard = NULL;
847 } else {
848 const char *session;
849
850 /* prepare data */
851 cfg = g_new0(ObXmlFile, 1);
852 cfg->xml = fm_xml_file_new(NULL);
853 /* register handlers */
854 ObXmlFile_keyboard = fm_xml_file_set_handler(cfg->xml, "keyboard",
855 &tag_handler_keyboard, FALSE, NULL);
856 ObXmlFile_keybind = fm_xml_file_set_handler(cfg->xml, "keybind",
857 &tag_handler_keybind, FALSE, NULL);
858 ObXmlFile_action = fm_xml_file_set_handler(cfg->xml, "action",
859 &tag_handler_action, FALSE, NULL);
860 ObXmlFile_command = fm_xml_file_set_handler(cfg->xml, "command",
861 &tag_handler_command, FALSE, NULL);
862 ObXmlFile_execute = fm_xml_file_set_handler(cfg->xml, "execute",
863 &tag_handler_command, FALSE, NULL);
864 /* let try to detect rc.xml file currently in use:
865 with Lubuntu it's lubuntu-rc.xml, with lxde session it's lxde-rc.xml */
866 session = g_getenv("DESKTOP_SESSION");
867 if (session == NULL)
868 session = g_getenv("GDMSESSION");
869 if (session == NULL)
870 session = g_getenv("XDG_CURRENT_DESKTOP");
871 if (g_strcmp0(session, "Lubuntu") == 0)
872 cfg->path = g_build_filename(g_get_user_config_dir(), "openbox",
873 "lubuntu-rc.xml", NULL);
874 else if (g_strcmp0(session, "LXDE") == 0)
875 cfg->path = g_build_filename(g_get_user_config_dir(), "openbox",
876 "lxde-rc.xml", NULL);
877 else
878 cfg->path = g_build_filename(g_get_user_config_dir(), "openbox",
879 "rc.xml", NULL);
880 }
881
882 /* try to load ~/.config/openbox/$xml */
883 if (!g_file_get_contents(cfg->path, &contents, &len, NULL)) {
884 /* if it does not exist then try to load $XDG_SYSTEM_CONFDIR/openbox/rc.xml */
885 const gchar * const *dirs;
886 char *path = NULL;
887
888 for (dirs = g_get_system_config_dirs(); dirs[0]; dirs++) {
889 path = g_build_filename(dirs[0], "openbox", "rc.xml", NULL);
890 if (g_file_get_contents(path, &contents, &len, NULL))
891 break;
892 g_free(path);
893 path = NULL;
894 }
895 if (path == NULL) { /* failed to load */
896 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
897 _("Could not find the rc.xml file anywhere."));
898 obcfg_free(cfg);
899 return NULL;
900 }
901 g_free(path);
902 }
903 /* parse the found rc.xml file */
904 if (!fm_xml_file_parse_data(cfg->xml, contents, len, &err, cfg)
905 || fm_xml_file_finish_parse(cfg->xml, &err) == NULL) {
906 g_propagate_error(error, err);
907 g_free(contents);
908 obcfg_free(cfg);
909 return NULL;
910 }
911 g_free(contents);
912 return cfg;
913 }
914
915 static gboolean obcfg_save(gpointer config, GError **error)
916 {
917 ObXmlFile *cfg = (ObXmlFile *)config;
918 char *contents;
919 gsize len;
920 gboolean ret = FALSE;
921
922 /* save as ~/.config/openbox/$xml */
923 contents = fm_xml_file_to_data(cfg->xml, &len, error);
924 if (contents) {
925 /* workaround on libfm-extra bug on save data without DTD */
926 if (contents[0] == '\n')
927 ret = g_file_set_contents(cfg->path, contents+1, len-1, error);
928 else
929 ret = g_file_set_contents(cfg->path, contents, len, error);
930 g_free(contents);
931 }
932 if (ret)
933 ret = restart_openbox(error);
934 return ret;
935 }
936
937 static GList *obcfg_get_wm_keys(gpointer config, const char *mask, GError **error)
938 {
939 ObXmlFile *cfg = (ObXmlFile *)config;
940 GList *list = NULL, *l;
941 LXHotkeyGlobal *data;
942
943 if (cfg == NULL)
944 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
945 _("No WM configuration is available."));
946 else for (l = cfg->actions; l; l = l->next) {
947 data = l->data;
948 if (mask == NULL || fnmatch(mask, data->accel1, 0) == 0
949 || (data->accel2 != NULL && fnmatch(mask, data->accel2, 0) == 0))
950 list = g_list_prepend(list, data);
951 }
952 return list;
953 }
954
955 static gboolean tag_null_handler(FmXmlFileItem *item, GList *children,
956 char * const *attribute_names,
957 char * const *attribute_values,
958 guint n_attributes, gint line, gint pos,
959 GError **error, gpointer user_data)
960 {
961 return TRUE;
962 }
963
964 /* if opts==NULL then don't copy any LXHotkeyAttr below it */
965 static FmXmlFileItem *make_new_xml_item(ObXmlFile *cfg, LXHotkeyAttr *opt,
966 GList **opts, gboolean is_action)
967 {
968 FmXmlFileItem *item, *sub;
969 GList *l;
970 LXHotkeyAttr *act = NULL;
971
972 if (is_action) {
973 item = fm_xml_file_item_new(ObXmlFile_action);
974 fm_xml_file_item_set_attribute(item, "name", opt->name);
975 } else {
976 FmXmlFileTag tag = FM_XML_FILE_TAG_NOT_HANDLED;
977
978 /* find a tag in list by opt->name */
979 for (l = cfg->added_tags; l; l = l->next)
980 if (g_strcmp0(fm_xml_file_get_tag_name(cfg->xml, GPOINTER_TO_UINT(l->data)),
981 opt->name) == 0)
982 break;
983 if (l == NULL) {
984 /* if not found then add to list */
985 tag = fm_xml_file_set_handler(cfg->xml, opt->name, &tag_null_handler, FALSE, NULL);
986 cfg->added_tags = g_list_prepend(cfg->added_tags, GUINT_TO_POINTER(tag));
987 } else
988 tag = GPOINTER_TO_UINT(l->data);
989 item = fm_xml_file_item_new(tag);
990 if (opt->values)
991 fm_xml_file_item_append_text(item, opt->values->data, -1, FALSE);
992 }
993 if (opts != NULL) {
994 /* make a copy if requested */
995 act = lxhotkey_attr_new();
996 act->name = g_strdup(opt->name);
997 if (opt->values)
998 act->values = g_list_prepend(NULL, g_strdup(opt->values->data));
999 act->has_actions = opt->has_actions;
1000 *opts = g_list_append(*opts, act);
1001 }
1002 for (l = opt->subopts; l; l = l->next) {
1003 sub = make_new_xml_item(cfg, l->data, act ? &act->subopts : NULL,
1004 opt->has_actions);
1005 fm_xml_file_item_append_child(item, sub);
1006 }
1007 return item;
1008 }
1009
1010 /* if opts==NULL then don't make any LXHotkeyAttr below it */
1011 static FmXmlFileItem *make_new_xml_binding(ObXmlFile *cfg, GList *actions,
1012 const gchar *accel, GList **opts,
1013 const gchar *exec)
1014 {
1015 FmXmlFileItem *binding = fm_xml_file_item_new(ObXmlFile_keybind);
1016 FmXmlFileItem *item, *opt;
1017 char *obkey = key_to_obkey(accel);
1018
1019 fm_xml_file_item_set_attribute(binding, "key", obkey);
1020 g_free(obkey);
1021 fm_xml_file_item_append_child(cfg->keyboard, binding);
1022 if (exec) {
1023 /* make <action name='Execute'><command>exec</command>..opts..</action>
1024 instead of ..<acts>.. */
1025 item = fm_xml_file_item_new(ObXmlFile_action);
1026 fm_xml_file_item_set_attribute(item, "name", "Execute");
1027 fm_xml_file_item_append_child(binding, item);
1028 opt = fm_xml_file_item_new(ObXmlFile_command);
1029 fm_xml_file_item_append_text(opt, exec, -1, FALSE);
1030 fm_xml_file_item_append_child(item, opt);
1031 }
1032 else
1033 item = binding;
1034 for (; actions; actions = actions->next) {
1035 opt = make_new_xml_item(cfg, actions->data, opts, (exec == NULL));
1036 fm_xml_file_item_append_child(item, opt);
1037 }
1038 return binding;
1039 }
1040
1041 static inline void replace_key(FmXmlFileItem *item, const char *key, char **kptr)
1042 {
1043 char *obkey = key_to_obkey(key);
1044
1045 fm_xml_file_item_set_attribute(item, "key", obkey);
1046 g_free(obkey);
1047 g_free(*kptr);
1048 *kptr = g_strdup(key);
1049 }
1050
1051 static gboolean obcfg_set_wm_key(gpointer config, LXHotkeyGlobal *data, GError **error)
1052 {
1053 ObXmlFile *cfg = (ObXmlFile *)config;
1054 GList *l, *ll;
1055 LXHotkeyGlobal *act = NULL;
1056
1057 if (cfg == NULL) {
1058 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
1059 _("No WM configuration is available."));
1060 return FALSE;
1061 } else if (data->actions == NULL) {
1062 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
1063 _("Keybinding should activate at least one action."));
1064 return FALSE;
1065 }
1066 /* find if that action(s) is present */
1067 for (ll = cfg->actions; ll; ll = ll->next)
1068 if (options_equal((act = ll->data)->actions, data->actions))
1069 break;
1070 /* find if those keys are already bound elsewhere */
1071 for (l = cfg->actions; l; l = l->next) {
1072 if (l == ll)
1073 /* it's our action */
1074 continue;
1075 if (data->accel1) {
1076 if (strcmp(data->accel1, ((LXHotkeyGlobal *)l->data)->accel1) == 0 ||
1077 g_strcmp0(data->accel1, ((LXHotkeyGlobal *)l->data)->accel2) == 0)
1078 goto _accel1_bound;
1079 }
1080 if (data->accel2) {
1081 if (g_strcmp0(data->accel2, ((LXHotkeyGlobal *)l->data)->accel1) == 0 ||
1082 g_strcmp0(data->accel2, ((LXHotkeyGlobal *)l->data)->accel2) == 0)
1083 goto _accel2_bound;
1084 }
1085 }
1086 for (l = cfg->execs; l; l = l->next) {
1087 if (data->accel1) {
1088 if (strcmp(data->accel1, ((LXHotkeyApp *)l->data)->accel1) == 0 ||
1089 g_strcmp0(data->accel1, ((LXHotkeyApp *)l->data)->accel2) == 0) {
1090 _accel1_bound:
1091 g_set_error(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
1092 _("Hotkey '%s' is already bound to an action."),
1093 data->accel1);
1094 return FALSE;
1095 }
1096 }
1097 if (data->accel2) {
1098 if (g_strcmp0(data->accel2, ((LXHotkeyApp *)l->data)->accel1) == 0 ||
1099 g_strcmp0(data->accel2, ((LXHotkeyApp *)l->data)->accel2) == 0) {
1100 _accel2_bound:
1101 g_set_error(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
1102 _("Hotkey '%s' is already bound to an action."),
1103 data->accel2);
1104 return FALSE;
1105 }
1106 }
1107 }
1108 /* if found then either change keys or remove the keybinding */
1109 if (ll != NULL) {
1110 if (data->accel1 == NULL) {
1111 /* removal requested */
1112 if (act->data1)
1113 fm_xml_file_item_destroy(act->data1);
1114 if (act->data2)
1115 fm_xml_file_item_destroy(act->data2);
1116 lkxeys_action_free(act);
1117 cfg->actions = g_list_delete_link(cfg->actions, ll);
1118 } else {
1119 if (data->accel2 == NULL) {
1120 /* new data contains only one binding */
1121 if (g_strcmp0(act->accel1, data->accel1) == 0) {
1122 /* accel1 not changed, just clear accel2 */
1123 if (act->data2)
1124 fm_xml_file_item_destroy(act->data2);
1125 g_free(act->accel2);
1126 act->accel2 = NULL;
1127 } else if (g_strcmp0(act->accel2, data->accel1) == 0) {
1128 /* accel1 was removed */
1129 if (act->data1)
1130 fm_xml_file_item_destroy(act->data1);
1131 g_free(act->accel1);
1132 act->accel1 = act->accel2;
1133 act->accel2 = NULL;
1134 } else {
1135 /* full change */
1136 replace_key(act->data1, data->accel1, &act->accel1);
1137 if (act->data2)
1138 fm_xml_file_item_destroy(act->data2);
1139 g_free(act->accel2);
1140 act->accel2 = NULL;
1141 }
1142 } else if (act->accel2 == NULL) {
1143 /* new data has two bindings and old data has 1 */
1144 if (g_strcmp0(act->accel1, data->accel1) == 0) {
1145 /* add data->accel2 */
1146 act->data2 = make_new_xml_binding(cfg, data->actions, data->accel2, NULL, NULL);
1147 act->accel2 = g_strdup(data->accel2);
1148 } else if (g_strcmp0(act->accel1, data->accel2) == 0) {
1149 /* add data->accel1 */
1150 act->data2 = make_new_xml_binding(cfg, data->actions, data->accel1, NULL, NULL);
1151 act->accel2 = g_strdup(data->accel1);
1152 } else {
1153 /* replace key act->accel1 and add data->accel2 */
1154 replace_key(act->data1, data->accel1, &act->accel1);
1155 act->data2 = make_new_xml_binding(cfg, data->actions, data->accel2, NULL, NULL);
1156 act->accel2 = g_strdup(data->accel2);
1157 }
1158 } else {
1159 /* both keys are present in old and new data */
1160 if (g_strcmp0(act->accel1, data->accel1) == 0) {
1161 if (g_strcmp0(act->accel2, data->accel2) != 0)
1162 /* just accel2 is changed */
1163 replace_key(act->data2, data->accel2, &act->accel2);
1164 /* else nothing changed */
1165 } else if (g_strcmp0(act->accel1, data->accel2) == 0) {
1166 if (g_strcmp0(act->accel2, data->accel1) != 0)
1167 /* replace accel2 with data->accel1 */
1168 replace_key(act->data2, data->accel1, &act->accel2);
1169 /* else keys just swapped */
1170 } else if (g_strcmp0(act->accel2, data->accel2) == 0) {
1171 /* just accel1 is changed */
1172 replace_key(act->data1, data->accel1, &act->accel1);
1173 } else if (g_strcmp0(act->accel2, data->accel1) == 0) {
1174 /* replace accel1 with data->accel2 */
1175 replace_key(act->data1, data->accel2, &act->accel1);
1176 } else {
1177 /* both keys changed */
1178 replace_key(act->data1, data->accel1, &act->accel1);
1179 replace_key(act->data2, data->accel2, &act->accel2);
1180 }
1181 }
1182 }
1183 /* if not found then add a new keybinding */
1184 } else if (data->accel1) {
1185 act = g_new0(LXHotkeyGlobal, 1);
1186 act->data1 = make_new_xml_binding(cfg, data->actions, data->accel1, &act->actions, NULL);
1187 act->accel1 = g_strdup(data->accel1);
1188 /* do the same for accel2 if requested */
1189 if (data->accel2) {
1190 act->data2 = make_new_xml_binding(cfg, data->actions, data->accel2, NULL, NULL);
1191 act->accel2 = g_strdup(data->accel2);
1192 }
1193 cfg->actions = g_list_prepend(cfg->actions, act);
1194 }
1195 return TRUE;
1196 }
1197
1198 static GList *obcfg_get_app_keys(gpointer config, const char *mask, GError **error)
1199 {
1200 ObXmlFile *cfg = (ObXmlFile *)config;
1201 GList *list = NULL, *l;
1202 LXHotkeyApp *data;
1203
1204 if (cfg == NULL)
1205 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
1206 _("No WM configuration is available."));
1207 else for (l = cfg->execs; l; l = l->next) {
1208 data = l->data;
1209 if (mask == NULL || fnmatch(mask, data->accel1, 0) == 0
1210 || (data->accel2 != NULL && fnmatch(mask, data->accel2, 0) == 0))
1211 list = g_list_prepend(list, data);
1212 }
1213 return list;
1214 }
1215
1216 static gboolean obcfg_set_app_key(gpointer config, LXHotkeyApp *data, GError **error)
1217 {
1218 ObXmlFile *cfg = (ObXmlFile *)config;
1219 GList *l, *ll;
1220 LXHotkeyApp *app = NULL;
1221
1222 if (cfg == NULL) {
1223 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
1224 _("No WM configuration is available."));
1225 return FALSE;
1226 } else if (data->exec == NULL || data->exec[0] == '\0') {
1227 g_set_error_literal(error, LXKEYS_OB_ERROR, LXKEYS_PARSE_ERROR,
1228 _("The exec line cannot be empty."));
1229 return FALSE;
1230 }
1231 /* find if that action(s) is present */
1232 for (ll = cfg->execs; ll; ll = ll->next)
1233 if (g_strcmp0((app = ll->data)->exec, data->exec) == 0
1234 && options_equal(app->options, data->options))
1235 break;
1236 /* find if those keys are already bound elsewhere */
1237 for (l = cfg->actions; l; l = l->next) {
1238 if (data->accel1) {
1239 if (strcmp(data->accel1, ((LXHotkeyGlobal *)l->data)->accel1) == 0 ||
1240 g_strcmp0(data->accel1, ((LXHotkeyGlobal *)l->data)->accel2) == 0)
1241 goto _accel1_bound;
1242 }
1243 if (data->accel2) {
1244 if (g_strcmp0(data->accel2, ((LXHotkeyGlobal *)l->data)->accel1) == 0 ||
1245 g_strcmp0(data->accel2, ((LXHotkeyGlobal *)l->data)->accel2) == 0)
1246 goto _accel2_bound;
1247 }
1248 }
1249 for (l = cfg->execs; l; l = l->next) {
1250 if (l == ll)
1251 /* it's our action */
1252 continue;
1253 if (data->accel1) {
1254 if (strcmp(data->accel1, ((LXHotkeyApp *)l->data)->accel1) == 0 ||
1255 g_strcmp0(data->accel1, ((LXHotkeyApp *)l->data)->accel2) == 0) {
1256 _accel1_bound:
1257 g_set_error(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
1258 _("Hotkey '%s' is already bound to an action."),
1259 data->accel1);
1260 return FALSE;
1261 }
1262 }
1263 if (data->accel2) {
1264 if (g_strcmp0(data->accel2, ((LXHotkeyApp *)l->data)->accel1) == 0 ||
1265 g_strcmp0(data->accel2, ((LXHotkeyApp *)l->data)->accel2) == 0) {
1266 _accel2_bound:
1267 g_set_error(error, LXKEYS_OB_ERROR, LXKEYS_FILE_ERROR,
1268 _("Hotkey '%s' is already bound to an action."),
1269 data->accel2);
1270 return FALSE;
1271 }
1272 }
1273 }
1274 /* if found then either change keys or remove the keybinding */
1275 if (ll != NULL) {
1276 if (data->accel1 == NULL) {
1277 /* removal requested */
1278 if (app->data1)
1279 fm_xml_file_item_destroy(app->data1);
1280 if (app->data2)
1281 fm_xml_file_item_destroy(app->data2);
1282 lkxeys_app_free(app);
1283 cfg->execs = g_list_delete_link(cfg->execs, ll);
1284 } else {
1285 if (data->accel2 == NULL) {
1286 /* new data contains only one binding */
1287 if (g_strcmp0(app->accel1, data->accel1) == 0) {
1288 /* accel1 not changed, just clear accel2 */
1289 if (app->data2)
1290 fm_xml_file_item_destroy(app->data2);
1291 g_free(app->accel2);
1292 app->accel2 = NULL;
1293 } else if (g_strcmp0(app->accel2, data->accel1) == 0) {
1294 /* accel1 was removed */
1295 if (app->data1)
1296 fm_xml_file_item_destroy(app->data1);
1297 g_free(app->accel1);
1298 app->accel1 = app->accel2;
1299 app->accel2 = NULL;
1300 } else {
1301 /* full change */
1302 replace_key(app->data1, data->accel1, &app->accel1);
1303 if (app->data2)
1304 fm_xml_file_item_destroy(app->data2);
1305 g_free(app->accel2);
1306 app->accel2 = NULL;
1307 }
1308 } else if (app->accel2 == NULL) {
1309 /* new data has two bindings and old data has 1 */
1310 if (g_strcmp0(app->accel1, data->accel1) == 0) {
1311 /* add data->accel2 */
1312 app->data2 = make_new_xml_binding(cfg, data->options, data->accel2, NULL, data->exec);
1313 app->accel2 = g_strdup(data->accel2);
1314 } else if (g_strcmp0(app->accel1, data->accel2) == 0) {
1315 /* add data->accel1 */
1316 app->data2 = make_new_xml_binding(cfg, data->options, data->accel1, NULL, data->exec);
1317 app->accel2 = g_strdup(data->accel1);
1318 } else {
1319 /* replace key app->accel1 and add data->accel2 */
1320 replace_key(app->data1, data->accel1, &app->accel1);
1321 app->data2 = make_new_xml_binding(cfg, data->options, data->accel2, NULL, data->exec);
1322 app->accel2 = g_strdup(data->accel2);
1323 }
1324 } else {
1325 /* both keys are present in old and new data */
1326 if (g_strcmp0(app->accel1, data->accel1) == 0) {
1327 if (g_strcmp0(app->accel2, data->accel2) != 0)
1328 /* just accel2 is changed */
1329 replace_key(app->data2, data->accel2, &app->accel2);
1330 /* else nothing changed */
1331 } else if (g_strcmp0(app->accel1, data->accel2) == 0) {
1332 if (g_strcmp0(app->accel2, data->accel1) != 0)
1333 /* replace accel2 with data->accel1 */
1334 replace_key(app->data2, data->accel1, &app->accel2);
1335 /* else keys just swapped */
1336 } else if (g_strcmp0(app->accel2, data->accel2) == 0) {
1337 /* just accel1 is changed */
1338 replace_key(app->data1, data->accel1, &app->accel1);
1339 } else if (g_strcmp0(app->accel2, data->accel1) == 0) {
1340 /* replace accel1 with data->accel2 */
1341 replace_key(app->data1, data->accel2, &app->accel1);
1342 } else {
1343 /* both keys changed */
1344 replace_key(app->data1, data->accel1, &app->accel1);
1345 replace_key(app->data2, data->accel2, &app->accel2);
1346 }
1347 }
1348 }
1349 /* if not found then add a new keybinding */
1350 } else if (data->accel1) {
1351 app = g_new0(LXHotkeyApp, 1);
1352 app->exec = g_strdup(data->exec);
1353 app->data1 = make_new_xml_binding(cfg, data->options, data->accel1, &app->options, data->exec);
1354 app->accel1 = g_strdup(data->accel1);
1355 /* do the same for accel2 if requested */
1356 if (data->accel2) {
1357 app->data2 = make_new_xml_binding(cfg, data->options, data->accel2, NULL, data->exec);
1358 app->accel2 = g_strdup(data->accel2);
1359 }
1360 cfg->execs = g_list_prepend(cfg->execs, app);
1361 }
1362 return TRUE;
1363 }
1364
1365 static GList *obcfg_get_wm_actions(gpointer config, GError **error)
1366 {
1367 if (!available_wm_actions)
1368 available_wm_actions = convert_options(list_actions);
1369 return available_wm_actions;
1370 }
1371
1372
1373 static GList *obcfg_get_app_options(gpointer config, GError **error)
1374 {
1375 if (!available_wm_actions)
1376 {
1377 GList *l, *opts = NULL;
1378 LXHotkeyAttr *opt;
1379
1380 available_wm_actions = convert_options(list_actions);
1381 for (l = available_app_options; l; l = l->next)
1382 {
1383 opt = l->data;
1384 if (strcmp(opt->name, "command") != 0)
1385 /* remove exec line from available options, it's App->exec */
1386 opts = g_list_prepend(opts, l->data);
1387 }
1388 available_app_options = g_list_reverse(opts);
1389 }
1390 return available_app_options;
1391 }
1392
1393
1394 FM_DEFINE_MODULE(lxhotkey, Openbox)
1395
1396 LXHotkeyPluginInit fm_module_init_lxhotkey = {
1397 .load = obcfg_load,
1398 .save = obcfg_save,
1399 .free = obcfg_free,
1400 .get_wm_keys = obcfg_get_wm_keys,
1401 .set_wm_key = obcfg_set_wm_key,
1402 .get_wm_actions = obcfg_get_wm_actions,
1403 .get_app_keys = obcfg_get_app_keys,
1404 .set_app_key = obcfg_set_app_key,
1405 .get_app_options = obcfg_get_app_options
1406 };