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