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