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