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