1 /* SPDX-License-Identifier: Zlib */
2 
3 #include "shortcuts.h"
4 
5 #include "datastructures.h"
6 #include "input-history.h"
7 #include "internal.h"
8 #include "log.h"
9 #include "session.h"
10 #include "settings.h"
11 
12 #include <gtk/gtk.h>
13 #include <string.h>
14 
15 static void girara_toggle_widget_visibility(GtkWidget* widget);
16 
17 bool
girara_shortcut_add(girara_session_t * session,guint modifier,guint key,const char * buffer,girara_shortcut_function_t function,girara_mode_t mode,int argument_n,void * argument_data)18 girara_shortcut_add(girara_session_t* session, guint modifier, guint key, const char* buffer, girara_shortcut_function_t function, girara_mode_t mode, int argument_n, void* argument_data)
19 {
20   g_return_val_if_fail(session != NULL, false);
21   g_return_val_if_fail(buffer || key || modifier, false);
22   g_return_val_if_fail(function != NULL, false);
23 
24   girara_argument_t argument = {argument_n, (argument_data != NULL) ?
25     g_strdup(argument_data) : NULL};
26 
27   /* search for existing binding */
28   bool found_existing_shortcut = false;
29   GIRARA_LIST_FOREACH_BODY_WITH_ITER(session->bindings.shortcuts, girara_shortcut_t*, iter, shortcuts_it,
30     if (((shortcuts_it->mask == modifier && shortcuts_it->key == key && (modifier != 0 || key != 0)) ||
31        (buffer && shortcuts_it->buffered_command && !g_strcmp0(shortcuts_it->buffered_command, buffer)))
32         && ((shortcuts_it->mode == mode) || (mode == 0)))
33     {
34       if (shortcuts_it->argument.data != NULL) {
35         g_free(shortcuts_it->argument.data);
36       }
37 
38       shortcuts_it->function  = function;
39       shortcuts_it->argument  = argument;
40       found_existing_shortcut = true;
41 
42       if (mode != 0) {
43         girara_list_iterator_free(iter);
44         return true;
45       }
46     }
47   );
48 
49   if (found_existing_shortcut == true) {
50     return true;
51   }
52 
53   /* add new shortcut */
54   girara_shortcut_t* shortcut = g_slice_new(girara_shortcut_t);
55 
56   shortcut->mask             = modifier;
57   shortcut->key              = key;
58   shortcut->buffered_command = g_strdup(buffer);
59   shortcut->function         = function;
60   shortcut->mode             = mode;
61   shortcut->argument         = argument;
62   girara_list_append(session->bindings.shortcuts, shortcut);
63 
64   return true;
65 }
66 
67 bool
girara_shortcut_remove(girara_session_t * session,guint modifier,guint key,const char * buffer,girara_mode_t mode)68 girara_shortcut_remove(girara_session_t* session, guint modifier, guint key, const char* buffer, girara_mode_t mode)
69 {
70   g_return_val_if_fail(session != NULL, false);
71   g_return_val_if_fail(buffer || key || modifier, false);
72 
73   bool handled = false;
74   /* search for existing binding */
75   GIRARA_LIST_FOREACH_BODY(session->bindings.shortcuts, girara_shortcut_t*, shortcuts_it,
76     if (((shortcuts_it->mask == modifier && shortcuts_it->key == key && (modifier != 0 || key != 0)) ||
77        (buffer && shortcuts_it->buffered_command && !g_strcmp0(shortcuts_it->buffered_command, buffer)))
78         && shortcuts_it->mode == mode) {
79       girara_list_remove(session->bindings.shortcuts, shortcuts_it);
80       handled = true;
81       break;
82     }
83   );
84 
85   return handled;
86 }
87 
88 void
girara_shortcut_free(girara_shortcut_t * shortcut)89 girara_shortcut_free(girara_shortcut_t* shortcut)
90 {
91   g_return_if_fail(shortcut != NULL);
92   g_free(shortcut->buffered_command);
93   g_free(shortcut->argument.data);
94   g_slice_free(girara_shortcut_t, shortcut);
95 }
96 
97 bool
girara_inputbar_shortcut_add(girara_session_t * session,guint modifier,guint key,girara_shortcut_function_t function,int argument_n,void * argument_data)98 girara_inputbar_shortcut_add(girara_session_t* session, guint modifier, guint key, girara_shortcut_function_t function, int argument_n, void* argument_data)
99 {
100   g_return_val_if_fail(session  != NULL, false);
101   g_return_val_if_fail(function != NULL, false);
102 
103   girara_argument_t argument = {argument_n, argument_data};
104   bool found = false;
105 
106   /* search for existing special command */
107   GIRARA_LIST_FOREACH_BODY(session->bindings.inputbar_shortcuts, girara_inputbar_shortcut_t*, inp_sh_it,
108     if (inp_sh_it->mask == modifier && inp_sh_it->key == key) {
109       inp_sh_it->function = function;
110       inp_sh_it->argument = argument;
111 
112       found = true;
113       break;
114     }
115   );
116 
117   if (found == false) {
118     /* create new inputbar shortcut */
119     girara_inputbar_shortcut_t* inputbar_shortcut = g_slice_new(girara_inputbar_shortcut_t);
120 
121     inputbar_shortcut->mask     = modifier;
122     inputbar_shortcut->key      = key;
123     inputbar_shortcut->function = function;
124     inputbar_shortcut->argument = argument;
125 
126     girara_list_append(session->bindings.inputbar_shortcuts, inputbar_shortcut);
127   }
128 
129   return true;
130 }
131 
132 bool
girara_inputbar_shortcut_remove(girara_session_t * session,guint modifier,guint key)133 girara_inputbar_shortcut_remove(girara_session_t* session, guint modifier, guint key)
134 {
135   g_return_val_if_fail(session  != NULL, false);
136 
137   /* search for existing special command */
138   GIRARA_LIST_FOREACH_BODY(session->bindings.inputbar_shortcuts, girara_inputbar_shortcut_t*, inp_sh_it,
139     if (inp_sh_it->mask == modifier && inp_sh_it->key == key) {
140       girara_list_remove(session->bindings.inputbar_shortcuts, inp_sh_it);
141       break;
142     }
143   );
144 
145   return true;
146 }
147 
148 void
girara_inputbar_shortcut_free(girara_inputbar_shortcut_t * inputbar_shortcut)149 girara_inputbar_shortcut_free(girara_inputbar_shortcut_t* inputbar_shortcut)
150 {
151   g_slice_free(girara_inputbar_shortcut_t, inputbar_shortcut);
152 }
153 
154 bool
girara_isc_activate(girara_session_t * session,girara_argument_t * UNUSED (argument),girara_event_t * UNUSED (event),unsigned int UNUSED (t))155 girara_isc_activate(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
156 {
157     girara_callback_inputbar_activate(session->gtk.inputbar_entry, session);
158     return true;
159 }
160 
161 bool
girara_isc_abort(girara_session_t * session,girara_argument_t * UNUSED (argument),girara_event_t * UNUSED (event),unsigned int UNUSED (t))162 girara_isc_abort(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
163 {
164   g_return_val_if_fail(session != NULL, false);
165 
166   /* hide completion */
167   girara_argument_t arg = { GIRARA_HIDE, NULL };
168   girara_isc_completion(session, &arg, NULL, 0);
169 
170   /* clear inputbar */
171   gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), 0, -1);
172 
173   /* grab view */
174   gtk_widget_grab_focus(GTK_WIDGET(session->gtk.view));
175 
176   /* hide inputbar */
177   gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar_dialog));
178   if (session->global.autohide_inputbar == true) {
179     gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar));
180   }
181 
182   /* Begin from the last command when navigating through history */
183   girara_input_history_reset(session->command_history);
184 
185   /* reset custom functions */
186   session->signals.inputbar_custom_activate        = NULL;
187   session->signals.inputbar_custom_key_press_event = NULL;
188   gtk_entry_set_visibility(session->gtk.inputbar_entry, TRUE);
189 
190   return true;
191 }
192 
193 bool
girara_isc_string_manipulation(girara_session_t * session,girara_argument_t * argument,girara_event_t * UNUSED (event),unsigned int UNUSED (t))194 girara_isc_string_manipulation(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
195 {
196   g_return_val_if_fail(session != NULL, false);
197 
198   gchar *separator = NULL;
199   girara_setting_get(session, "word-separator", &separator);
200   gchar *input  = gtk_editable_get_chars(GTK_EDITABLE(session->gtk.inputbar_entry), 0, -1);
201   int    length = strlen(input);
202   int pos       = gtk_editable_get_position(GTK_EDITABLE(session->gtk.inputbar_entry));
203   int i;
204 
205   switch (argument->n) {
206     case GIRARA_DELETE_LAST_WORD:
207       if (pos == 1 && (input[0] == ':' || input[0] == '/')) {
208         break;
209       }
210       if (pos == 0) {
211         break;
212       }
213 
214       i = pos - 1;
215 
216       /* remove trailing spaces */
217       for (; i >= 0 && input[i] == ' '; i--) {
218       }
219 
220       /* find the beginning of the word */
221       while ((i == (pos - 1)) || ((i > 0) && separator != NULL && !strchr(separator, input[i]))) {
222         i--;
223       }
224 
225       gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry),  i + 1, pos);
226       gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), i + 1);
227       break;
228     case GIRARA_DELETE_LAST_CHAR:
229       if (length != 1 && pos == 1 && (input[0] == ':' || input[0] == '/')) {
230         break;
231       }
232       if (length == 1 && pos == 1) {
233         girara_isc_abort(session, argument, NULL, 0);
234       }
235       gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), pos - 1, pos);
236       break;
237     case GIRARA_DELETE_TO_LINE_START:
238       gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), 1, pos);
239       break;
240     case GIRARA_NEXT_CHAR:
241       gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), pos + 1);
242       break;
243     case GIRARA_PREVIOUS_CHAR:
244       gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), (pos == 1) ? 1 : pos - 1);
245       break;
246     case GIRARA_DELETE_CURR_CHAR:
247       if (length != 1 && pos == 0 && (input[0] == ':' || input[0] == '/')){
248         break;
249       }
250       if(length == 1 && pos == 0) {
251         girara_isc_abort(session, argument, NULL, 0);
252       }
253       gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), pos, pos + 1);
254       break;
255     case GIRARA_DELETE_TO_LINE_END:
256       gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), pos, length);
257       break;
258     case GIRARA_GOTO_START:
259       gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), 1);
260       break;
261     case GIRARA_GOTO_END:
262       gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1);
263       break;
264   }
265 
266   g_free(separator);
267   g_free(input);
268 
269   return false;
270 }
271 
272 bool
girara_isc_command_history(girara_session_t * session,girara_argument_t * argument,girara_event_t * UNUSED (event),unsigned int UNUSED (t))273 girara_isc_command_history(girara_session_t* session, girara_argument_t*
274     argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
275 {
276   g_return_val_if_fail(session != NULL, false);
277 
278   char* temp = gtk_editable_get_chars(GTK_EDITABLE(session->gtk.inputbar_entry), 0, -1);
279   const char* command = argument->n == GIRARA_NEXT ?
280     girara_input_history_next(session->command_history, temp) :
281     girara_input_history_previous(session->command_history, temp);
282   g_free(temp);
283 
284   if (command != NULL) {
285     gtk_entry_set_text(session->gtk.inputbar_entry, command);
286     gtk_widget_grab_focus(GTK_WIDGET(session->gtk.inputbar_entry));
287     gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1);
288   }
289 
290   return true;
291 }
292 
293 /* default shortcut implementation */
294 bool
girara_sc_focus_inputbar(girara_session_t * session,girara_argument_t * argument,girara_event_t * UNUSED (event),unsigned int UNUSED (t))295 girara_sc_focus_inputbar(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
296 {
297   g_return_val_if_fail(session != NULL, false);
298   g_return_val_if_fail(session->gtk.inputbar_entry != NULL, false);
299 
300   if (gtk_widget_get_visible(GTK_WIDGET(session->gtk.inputbar)) == false) {
301     gtk_widget_show(GTK_WIDGET(session->gtk.inputbar));
302   }
303 
304   if (gtk_widget_get_visible(GTK_WIDGET(session->gtk.notification_area)) == true) {
305     gtk_widget_hide(GTK_WIDGET(session->gtk.notification_area));
306   }
307 
308   gtk_widget_grab_focus(GTK_WIDGET(session->gtk.inputbar_entry));
309 
310   if (argument != NULL && argument->data != NULL) {
311     gtk_entry_set_text(session->gtk.inputbar_entry, (char*) argument->data);
312 
313     /* we save the X clipboard that will be clear by "grab_focus" */
314     gchar* x_clipboard_text = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
315 
316     gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1);
317 
318     if (x_clipboard_text != NULL) {
319       /* we reset the X clipboard with saved text */
320       gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), x_clipboard_text, -1);
321       g_free(x_clipboard_text);
322     }
323   }
324 
325   return true;
326 }
327 
328 bool
girara_sc_abort(girara_session_t * session,girara_argument_t * UNUSED (argument),girara_event_t * UNUSED (event),unsigned int UNUSED (t))329 girara_sc_abort(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
330 {
331   g_return_val_if_fail(session != NULL, false);
332 
333   girara_isc_abort(session, NULL, NULL, 0);
334 
335   gtk_widget_hide(GTK_WIDGET(session->gtk.notification_area));
336 
337   if (session->global.autohide_inputbar == false) {
338     gtk_widget_show(GTK_WIDGET(session->gtk.inputbar));
339   }
340 
341   return false;
342 }
343 
344 bool
girara_sc_quit(girara_session_t * session,girara_argument_t * UNUSED (argument),girara_event_t * UNUSED (event),unsigned int UNUSED (t))345 girara_sc_quit(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
346 {
347   g_return_val_if_fail(session != NULL, false);
348 
349   girara_argument_t arg = { GIRARA_HIDE, NULL };
350   girara_isc_completion(session, &arg, NULL, 0);
351 
352   gtk_main_quit();
353 
354   return false;
355 }
356 
357 static void
girara_toggle_widget_visibility(GtkWidget * widget)358 girara_toggle_widget_visibility(GtkWidget* widget)
359 {
360   if (widget == NULL) {
361     return;
362   }
363 
364   if (gtk_widget_get_visible(widget) == TRUE) {
365     gtk_widget_hide(widget);
366   } else {
367     gtk_widget_show(widget);
368   }
369 }
370 
371 bool
girara_sc_toggle_inputbar(girara_session_t * session,girara_argument_t * UNUSED (argument),girara_event_t * UNUSED (event),unsigned int UNUSED (t))372 girara_sc_toggle_inputbar(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
373 {
374   g_return_val_if_fail(session != NULL, false);
375 
376   girara_toggle_widget_visibility(GTK_WIDGET(session->gtk.inputbar));
377 
378   return true;
379 }
380 
381 bool
girara_sc_toggle_statusbar(girara_session_t * session,girara_argument_t * UNUSED (argument),girara_event_t * UNUSED (event),unsigned int UNUSED (t))382 girara_sc_toggle_statusbar(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
383 {
384   g_return_val_if_fail(session != NULL, false);
385 
386   girara_toggle_widget_visibility(GTK_WIDGET(session->gtk.statusbar));
387 
388   return true;
389 }
390 
391 static girara_list_t*
argument_to_argument_list(girara_argument_t * argument)392 argument_to_argument_list(girara_argument_t* argument) {
393   girara_list_t* argument_list = girara_list_new();
394   if (argument_list == NULL) {
395     return NULL;
396   }
397 
398   gchar** argv = NULL;
399   gint argc    = 0;
400 
401   girara_list_set_free_function(argument_list, g_free);
402   if (g_shell_parse_argv((const gchar*) argument->data, &argc, &argv, NULL) != FALSE) {
403     for (int i = 0; i < argc; i++) {
404       char* arg = g_strdup(argv[i]);
405       girara_list_append(argument_list, arg);
406     }
407 
408     return argument_list;
409   }
410 
411   girara_list_free(argument_list);
412   return NULL;
413 }
414 
415 bool
girara_sc_set(girara_session_t * session,girara_argument_t * argument,girara_event_t * UNUSED (event),unsigned int UNUSED (t))416 girara_sc_set(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
417 {
418   g_return_val_if_fail(session != NULL, false);
419 
420   if (argument == NULL || argument->data == NULL) {
421     return false;
422   }
423 
424   /* create argument list */
425   girara_list_t* argument_list = argument_to_argument_list(argument);
426   if (argument_list == NULL) {
427     return false;
428   }
429 
430   /* call set */
431   girara_cmd_set(session, argument_list);
432 
433   /* cleanup */
434   girara_list_free(argument_list);
435 
436   return false;
437 }
438 
439 bool
girara_sc_exec(girara_session_t * session,girara_argument_t * argument,girara_event_t * UNUSED (event),unsigned int UNUSED (t))440 girara_sc_exec(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
441 {
442   g_return_val_if_fail(session != NULL, false);
443 
444   if (argument == NULL || argument->data == NULL) {
445     return false;
446   }
447 
448   /* create argument list */
449   girara_list_t* argument_list = argument_to_argument_list(argument);
450   if (argument_list == NULL) {
451     return false;
452   }
453 
454   /* call exec */
455   girara_cmd_exec(session, argument_list);
456 
457   /* cleanup */
458   girara_list_free(argument_list);
459 
460   return false;
461 }
462 
463 static bool
simulate_key_press(girara_session_t * session,int state,int key)464 simulate_key_press(girara_session_t* session, int state, int key)
465 {
466   if (session == NULL || session->gtk.box == NULL) {
467     return false;
468   }
469 
470   GdkEvent* event = gdk_event_new(GDK_KEY_PRESS);
471 
472   event->any.type       = GDK_KEY_PRESS;
473   event->key.window     = g_object_ref(gtk_widget_get_parent_window(GTK_WIDGET(session->gtk.box)));
474   event->key.send_event = false;
475   event->key.time       = GDK_CURRENT_TIME;
476   event->key.state      = state;
477   event->key.keyval     = key;
478 
479   GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(session->gtk.box));
480   GdkKeymapKey* keys  = NULL;
481   gint number_of_keys = 0;
482 
483   if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_for_display(display),
484         event->key.keyval, &keys, &number_of_keys) == FALSE) {
485     gdk_event_free(event);
486     return false;
487   }
488 
489   event->key.hardware_keycode = keys[0].keycode;
490   event->key.group            = keys[0].group;
491 
492   g_free(keys);
493 
494   gdk_event_put(event);
495   gdk_event_free(event);
496 
497   gtk_main_iteration_do(FALSE);
498 
499   return true;
500 }
501 
502 static int
update_state_by_keyval(int state,int keyval)503 update_state_by_keyval(int state, int keyval)
504 {
505   /* The following is probably not true for some keyboard layouts. */
506   if ((keyval >= '!' && keyval <= '/')
507       || (keyval >= ':' && keyval <= '@')
508       || (keyval >= '[' && keyval <= '`')
509       || (keyval >= '{' && keyval <= '~')
510       ) {
511     state |= GDK_SHIFT_MASK;
512   }
513 
514   return state;
515 }
516 
517 bool
girara_sc_feedkeys(girara_session_t * session,girara_argument_t * argument,girara_event_t * UNUSED (event),unsigned int t)518 girara_sc_feedkeys(girara_session_t* session, girara_argument_t* argument,
519     girara_event_t* UNUSED(event), unsigned int t)
520 {
521   if (session == NULL || argument == NULL) {
522     return false;
523   }
524 
525   if (g_mutex_trylock(&session->private_data->feedkeys_mutex) == FALSE) {
526     girara_error("Recursive use of feedkeys detected. Aborting evaluation.");
527     return false;
528   }
529 
530   typedef struct gdk_keyboard_button_s {
531     char* identifier;
532     int keyval;
533   } gdk_keyboard_button_t;
534 
535   static const gdk_keyboard_button_t gdk_keyboard_buttons[] = {
536     {"BackSpace", GDK_KEY_BackSpace},
537     {"CapsLock",  GDK_KEY_Caps_Lock},
538     {"Down",      GDK_KEY_Down},
539     {"Esc",       GDK_KEY_Escape},
540     {"F10",       GDK_KEY_F10},
541     {"F11",       GDK_KEY_F11},
542     {"F12",       GDK_KEY_F12},
543     {"F1",        GDK_KEY_F1},
544     {"F2",        GDK_KEY_F2},
545     {"F3",        GDK_KEY_F3},
546     {"F4",        GDK_KEY_F4},
547     {"F5",        GDK_KEY_F5},
548     {"F6",        GDK_KEY_F6},
549     {"F7",        GDK_KEY_F7},
550     {"F8",        GDK_KEY_F8},
551     {"F9",        GDK_KEY_F9},
552     {"Left",      GDK_KEY_Left},
553     {"PageDown",  GDK_KEY_Page_Down},
554     {"PageUp",    GDK_KEY_Page_Up},
555     {"Home",      GDK_KEY_Home},
556     {"End",       GDK_KEY_End},
557     {"Return",    GDK_KEY_Return},
558     {"Right",     GDK_KEY_Right},
559     {"Space",     GDK_KEY_space},
560     {"Super",     GDK_KEY_Super_L},
561     {"Tab",       GDK_KEY_Tab},
562     {"ShiftTab",  GDK_KEY_ISO_Left_Tab},
563     {"Up",        GDK_KEY_Up}
564   };
565 
566   char* input               = (char*) argument->data;
567   unsigned int input_length = strlen(input);
568 
569   t = MAX(1, t);
570   for (unsigned int c = 0; c < t; c++) {
571     for (unsigned i = 0; i < input_length; i++) {
572       int state  = 0;
573       int keyval = input[i];
574 
575       /* possible special button */
576       if ((input_length - i) >= 3 && input[i] == '<') {
577         char* end = strchr(input + i, '>');
578         if (end == NULL) {
579           goto single_key;
580         }
581 
582         const int length = end - (input + i) - 1;
583         char* tmp  = g_strndup(input + i + 1, length);
584         bool found = false;
585 
586         /* Multi key shortcut */
587         if (length > 2 && tmp[1] == '-') {
588           switch (tmp[0]) {
589             case 'S':
590               state = GDK_SHIFT_MASK;
591               break;
592             case 'A':
593               state = GDK_MOD1_MASK;
594               break;
595             case 'C':
596               state = GDK_CONTROL_MASK;
597               break;
598             default:
599               break;
600           }
601 
602           if (length == 3) {
603             keyval = tmp[2];
604             found  = true;
605           } else {
606             for (unsigned int j = 0; j < LENGTH(gdk_keyboard_buttons); ++j) {
607               if (g_strcmp0(tmp + 2, gdk_keyboard_buttons[j].identifier) == 0) {
608                 keyval = gdk_keyboard_buttons[j].keyval;
609                 found = true;
610                 break;
611               }
612             }
613           }
614         /* Possible special key */
615         } else {
616           for (unsigned int j = 0; j < LENGTH(gdk_keyboard_buttons); ++j) {
617             if (g_strcmp0(tmp, gdk_keyboard_buttons[j].identifier) == 0) {
618               keyval = gdk_keyboard_buttons[j].keyval;
619               found = true;
620               break;
621             }
622           }
623         }
624 
625         g_free(tmp);
626 
627         /* parsed special key */
628         if (found == true) {
629           i += length + 1;
630         }
631       }
632 
633 single_key:
634       state = update_state_by_keyval(state, keyval);
635       simulate_key_press(session, state, keyval);
636     }
637   }
638 
639   g_mutex_unlock(&session->private_data->feedkeys_mutex);
640   return true;
641 }
642 
643 bool
girara_shortcut_mapping_add(girara_session_t * session,const char * identifier,girara_shortcut_function_t function)644 girara_shortcut_mapping_add(girara_session_t* session, const char* identifier, girara_shortcut_function_t function)
645 {
646   g_return_val_if_fail(session  != NULL, false);
647 
648   if (function == NULL || identifier == NULL) {
649     return false;
650   }
651 
652   girara_session_private_t* session_private = session->private_data;
653   bool found = false;
654 
655   GIRARA_LIST_FOREACH_BODY(session_private->config.shortcut_mappings, girara_shortcut_mapping_t*, data,
656     if (g_strcmp0(data->identifier, identifier) == 0) {
657       data->function = function;
658       found = true;
659       break;
660     }
661   );
662 
663   if (found == false) {
664     /* add new config handle */
665     girara_shortcut_mapping_t* mapping = g_slice_new(girara_shortcut_mapping_t);
666 
667     mapping->identifier = g_strdup(identifier);
668     mapping->function   = function;
669     girara_list_append(session_private->config.shortcut_mappings, mapping);
670   }
671 
672   return true;
673 }
674 
675 void
girara_shortcut_mapping_free(girara_shortcut_mapping_t * mapping)676 girara_shortcut_mapping_free(girara_shortcut_mapping_t* mapping)
677 {
678   if (mapping == NULL) {
679     return;
680   }
681 
682   g_free(mapping->identifier);
683   g_slice_free(girara_shortcut_mapping_t, mapping);
684 }
685 
686 bool
girara_argument_mapping_add(girara_session_t * session,const char * identifier,int value)687 girara_argument_mapping_add(girara_session_t* session, const char* identifier, int value)
688 {
689   g_return_val_if_fail(session  != NULL, false);
690 
691   if (identifier == NULL) {
692     return false;
693   }
694 
695   girara_session_private_t* session_private = session->private_data;
696   bool found = false;
697 
698   GIRARA_LIST_FOREACH_BODY(session_private->config.argument_mappings, girara_argument_mapping_t*, mapping,
699     if (g_strcmp0(mapping->identifier, identifier) == 0) {
700       mapping->value = value;
701       found = true;
702       break;
703     }
704   );
705 
706   if (found == false) {
707     /* add new config handle */
708     girara_argument_mapping_t* mapping = g_slice_new(girara_argument_mapping_t);
709 
710     mapping->identifier = g_strdup(identifier);
711     mapping->value      = value;
712     girara_list_append(session_private->config.argument_mappings, mapping);
713   }
714 
715   return true;
716 }
717 
718 void
girara_argument_mapping_free(girara_argument_mapping_t * argument_mapping)719 girara_argument_mapping_free(girara_argument_mapping_t* argument_mapping)
720 {
721   if (argument_mapping == NULL) {
722     return;
723   }
724 
725   g_free(argument_mapping->identifier);
726   g_slice_free(girara_argument_mapping_t, argument_mapping);
727 }
728 
729 bool
girara_mouse_event_add(girara_session_t * session,guint mask,guint button,girara_shortcut_function_t function,girara_mode_t mode,girara_event_type_t event_type,int argument_n,void * argument_data)730 girara_mouse_event_add(girara_session_t* session, guint mask, guint button,
731     girara_shortcut_function_t function, girara_mode_t mode, girara_event_type_t
732     event_type, int argument_n, void* argument_data)
733 {
734   g_return_val_if_fail(session  != NULL, false);
735   g_return_val_if_fail(function != NULL, false);
736 
737   girara_argument_t argument = {argument_n, argument_data};
738   bool found = false;
739 
740   /* search for existing binding */
741   GIRARA_LIST_FOREACH_BODY(session->bindings.mouse_events, girara_mouse_event_t*, me_it,
742     if (me_it->mask == mask && me_it->button == button &&
743        me_it->mode == mode && me_it->event_type == event_type) {
744       me_it->function = function;
745       me_it->argument = argument;
746 
747       found = true;
748       break;
749     }
750   );
751 
752   if (found == false) {
753     /* add new mouse event */
754     girara_mouse_event_t* mouse_event = g_slice_new(girara_mouse_event_t);
755 
756     mouse_event->mask       = mask;
757     mouse_event->button     = button;
758     mouse_event->function   = function;
759     mouse_event->mode       = mode;
760     mouse_event->event_type = event_type;
761     mouse_event->argument   = argument;
762     girara_list_append(session->bindings.mouse_events, mouse_event);
763   }
764 
765   return true;
766 }
767 
768 bool
girara_mouse_event_remove(girara_session_t * session,guint mask,guint button,girara_mode_t mode)769 girara_mouse_event_remove(girara_session_t* session, guint mask, guint button, girara_mode_t mode)
770 {
771   g_return_val_if_fail(session != NULL, false);
772 
773   bool found = false;
774   /* search for existing binding */
775   GIRARA_LIST_FOREACH_BODY(session->bindings.mouse_events, girara_mouse_event_t*, me_it,
776     if (me_it->mask == mask && me_it->button == button &&
777        me_it->mode == mode) {
778       girara_list_remove(session->bindings.mouse_events, me_it);
779       found = true;
780       break;
781     }
782   );
783 
784   return found;
785 }
786 
787 void
girara_mouse_event_free(girara_mouse_event_t * mouse_event)788 girara_mouse_event_free(girara_mouse_event_t* mouse_event)
789 {
790   if (mouse_event == NULL) {
791     return;
792   }
793   g_slice_free(girara_mouse_event_t, mouse_event);
794 }
795