shortcuts.c 22.5 KB
Newer Older
1
/* SPDX-License-Identifier: Zlib */
2 3

#include "shortcuts.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
4

5
#include "datastructures.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
6
#include "input-history.h"
7
#include "internal.h"
8
#include "log.h"
9 10 11 12
#include "session.h"
#include "settings.h"

#include <gtk/gtk.h>
Sebastian Ramacher's avatar
Sebastian Ramacher committed
13
#include <string.h>
14 15 16 17

static void girara_toggle_widget_visibility(GtkWidget* widget);

bool
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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
{
Moritz Lipp's avatar
Moritz Lipp committed
20 21 22
  g_return_val_if_fail(session != NULL, false);
  g_return_val_if_fail(buffer || key || modifier, false);
  g_return_val_if_fail(function != NULL, false);
23

Moritz Lipp's avatar
Moritz Lipp committed
24 25
  girara_argument_t argument = {argument_n, (argument_data != NULL) ?
    g_strdup(argument_data) : NULL};
26 27

  /* search for existing binding */
Moritz Lipp's avatar
Moritz Lipp committed
28
  bool found_existing_shortcut = false;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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)) ||
Sebastian Ramacher's avatar
Sebastian Ramacher committed
31
       (buffer && shortcuts_it->buffered_command && !g_strcmp0(shortcuts_it->buffered_command, buffer)))
Moritz Lipp's avatar
Moritz Lipp committed
32
        && ((shortcuts_it->mode == mode) || (mode == 0)))
33
    {
Moritz Lipp's avatar
Moritz Lipp committed
34 35 36 37
      if (shortcuts_it->argument.data != NULL) {
        g_free(shortcuts_it->argument.data);
      }

Moritz Lipp's avatar
Moritz Lipp committed
38 39 40 41 42 43 44 45
      shortcuts_it->function  = function;
      shortcuts_it->argument  = argument;
      found_existing_shortcut = true;

      if (mode != 0) {
        girara_list_iterator_free(iter);
        return true;
      }
46
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
47
  );
48

Moritz Lipp's avatar
Moritz Lipp committed
49 50 51 52
  if (found_existing_shortcut == true) {
    return true;
  }

53 54 55 56 57
  /* add new shortcut */
  girara_shortcut_t* shortcut = g_slice_new(girara_shortcut_t);

  shortcut->mask             = modifier;
  shortcut->key              = key;
58
  shortcut->buffered_command = g_strdup(buffer);
59 60 61 62 63
  shortcut->function         = function;
  shortcut->mode             = mode;
  shortcut->argument         = argument;
  girara_list_append(session->bindings.shortcuts, shortcut);

Moritz Lipp's avatar
Moritz Lipp committed
64
  return true;
65 66
}

67 68 69
bool
girara_shortcut_remove(girara_session_t* session, guint modifier, guint key, const char* buffer, girara_mode_t mode)
{
Moritz Lipp's avatar
Moritz Lipp committed
70 71
  g_return_val_if_fail(session != NULL, false);
  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)) ||
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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 81
      handled = true;
      break;
82
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
83
  );
84

85
  return handled;
86 87
}

88 89 90
void
girara_shortcut_free(girara_shortcut_t* shortcut)
{
Moritz Lipp's avatar
Moritz Lipp committed
91
  g_return_if_fail(shortcut != NULL);
92
  g_free(shortcut->buffered_command);
Moritz Lipp's avatar
Moritz Lipp committed
93
  g_free(shortcut->argument.data);
94 95 96 97 98 99 100 101 102 103
  g_slice_free(girara_shortcut_t, shortcut);
}

bool
girara_inputbar_shortcut_add(girara_session_t* session, guint modifier, guint key, girara_shortcut_function_t function, int argument_n, void* argument_data)
{
  g_return_val_if_fail(session  != NULL, false);
  g_return_val_if_fail(function != NULL, false);

  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 109 110 111
    if (inp_sh_it->mask == modifier && inp_sh_it->key == key) {
      inp_sh_it->function = function;
      inp_sh_it->argument = argument;

112 113
      found = true;
      break;
114
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
115
  );
116

117 118 119
  if (found == false) {
    /* create new inputbar shortcut */
    girara_inputbar_shortcut_t* inputbar_shortcut = g_slice_new(girara_inputbar_shortcut_t);
120

121 122 123 124 125 126 127
    inputbar_shortcut->mask     = modifier;
    inputbar_shortcut->key      = key;
    inputbar_shortcut->function = function;
    inputbar_shortcut->argument = argument;

    girara_list_append(session->bindings.inputbar_shortcuts, inputbar_shortcut);
  }
128

Moritz Lipp's avatar
Moritz Lipp committed
129
  return true;
130 131
}

132 133 134 135 136 137
bool
girara_inputbar_shortcut_remove(girara_session_t* session, guint modifier, guint key)
{
  g_return_val_if_fail(session  != NULL, false);

  /* search for existing special command */
138
  GIRARA_LIST_FOREACH_BODY(session->bindings.inputbar_shortcuts, girara_inputbar_shortcut_t*, inp_sh_it,
139 140
    if (inp_sh_it->mask == modifier && inp_sh_it->key == key) {
      girara_list_remove(session->bindings.inputbar_shortcuts, inp_sh_it);
141
      break;
142
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
143
  );
144

Moritz Lipp's avatar
Moritz Lipp committed
145
  return true;
146 147
}

148 149 150 151 152
void
girara_inputbar_shortcut_free(girara_inputbar_shortcut_t* inputbar_shortcut)
{
  g_slice_free(girara_inputbar_shortcut_t, inputbar_shortcut);
}
Moritz Lipp's avatar
Moritz Lipp committed
153

154 155 156 157
bool
girara_isc_activate(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
{
    girara_callback_inputbar_activate(session->gtk.inputbar_entry, session);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
158
    return true;
159 160
}

161
bool
162
girara_isc_abort(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
163
{
Moritz Lipp's avatar
Moritz Lipp committed
164 165
  g_return_val_if_fail(session != NULL, false);

166 167
  /* hide completion */
  girara_argument_t arg = { GIRARA_HIDE, NULL };
168
  girara_isc_completion(session, &arg, NULL, 0);
169 170

  /* clear inputbar */
Moritz Lipp's avatar
Moritz Lipp committed
171
  gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), 0, -1);
172 173 174 175 176

  /* grab view */
  gtk_widget_grab_focus(GTK_WIDGET(session->gtk.view));

  /* hide inputbar */
Moritz Lipp's avatar
Moritz Lipp committed
177
  gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar_dialog));
Moritz Lipp's avatar
Moritz Lipp committed
178 179 180
  if (session->global.autohide_inputbar == true) {
    gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar));
  }
181

182
  /* Begin from the last command when navigating through history */
183
  girara_input_history_reset(session->command_history);
184

185 186 187
  /* reset custom functions */
  session->signals.inputbar_custom_activate        = NULL;
  session->signals.inputbar_custom_key_press_event = NULL;
188
  gtk_entry_set_visibility(session->gtk.inputbar_entry, TRUE);
189

190 191 192 193
  return true;
}

bool
194
girara_isc_string_manipulation(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
195
{
Moritz Lipp's avatar
Moritz Lipp committed
196 197
  g_return_val_if_fail(session != NULL, false);

198 199
  gchar *separator = NULL;
  girara_setting_get(session, "word-separator", &separator);
Moritz Lipp's avatar
Moritz Lipp committed
200
  gchar *input  = gtk_editable_get_chars(GTK_EDITABLE(session->gtk.inputbar_entry), 0, -1);
201
  int    length = strlen(input);
Moritz Lipp's avatar
Moritz Lipp committed
202
  int pos       = gtk_editable_get_position(GTK_EDITABLE(session->gtk.inputbar_entry));
203 204 205 206
  int i;

  switch (argument->n) {
    case GIRARA_DELETE_LAST_WORD:
207
      if (pos == 1 && (input[0] == ':' || input[0] == '/')) {
208
        break;
209
      }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
210
      if (pos == 0) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
211
        break;
212 213 214
      }

      i = pos - 1;
215 216

      /* remove trailing spaces */
217 218
      for (; i >= 0 && input[i] == ' '; i--) {
      }
219 220

      /* find the beginning of the word */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
221
      while ((i == (pos - 1)) || ((i > 0) && separator != NULL && !strchr(separator, input[i]))) {
222 223 224
        i--;
      }

Moritz Lipp's avatar
Moritz Lipp committed
225 226
      gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry),  i + 1, pos);
      gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), i + 1);
227 228
      break;
    case GIRARA_DELETE_LAST_CHAR:
229
      if (length != 1 && pos == 1 && (input[0] == ':' || input[0] == '/')) {
230 231
        break;
      }
232
      if (length == 1 && pos == 1) {
233
        girara_isc_abort(session, argument, NULL, 0);
234
      }
Moritz Lipp's avatar
Moritz Lipp committed
235
      gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), pos - 1, pos);
236 237
      break;
    case GIRARA_DELETE_TO_LINE_START:
Moritz Lipp's avatar
Moritz Lipp committed
238
      gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), 1, pos);
239 240
      break;
    case GIRARA_NEXT_CHAR:
Moritz Lipp's avatar
Moritz Lipp committed
241
      gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), pos + 1);
242 243
      break;
    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] == '/')){
Sebastian Ramacher's avatar
Sebastian Ramacher committed
248
        break;
249
      }
250
      if(length == 1 && pos == 0) {
251
        girara_isc_abort(session, argument, NULL, 0);
252
      }
Moritz Lipp's avatar
Moritz Lipp committed
253
      gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), pos, pos + 1);
254 255
      break;
    case GIRARA_DELETE_TO_LINE_END:
Moritz Lipp's avatar
Moritz Lipp committed
256
      gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), pos, length);
257 258
      break;
    case GIRARA_GOTO_START:
Moritz Lipp's avatar
Moritz Lipp committed
259
      gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), 1);
260 261
      break;
    case GIRARA_GOTO_END:
Moritz Lipp's avatar
Moritz Lipp committed
262
      gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1);
263
      break;
264 265 266 267 268 269 270 271
  }

  g_free(separator);
  g_free(input);

  return false;
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
272 273
bool
girara_isc_command_history(girara_session_t* session, girara_argument_t*
Moritz Lipp's avatar
Moritz Lipp committed
274 275
    argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
{
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 281
    girara_input_history_next(session->command_history, temp) :
    girara_input_history_previous(session->command_history, temp);
Marwan Tanager's avatar
Marwan Tanager committed
282 283
  g_free(temp);

284 285 286 287
  if (command != NULL) {
    gtk_entry_set_text(session->gtk.inputbar_entry, command);
    gtk_widget_grab_focus(GTK_WIDGET(session->gtk.inputbar_entry));
    gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1);
288 289
  }

Moritz Lipp's avatar
Moritz Lipp committed
290 291 292
  return true;
}

293 294
/* default shortcut implementation */
bool
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);
Moritz Lipp's avatar
Moritz Lipp committed
298
  g_return_val_if_fail(session->gtk.inputbar_entry != NULL, false);
299

Moritz Lipp's avatar
Moritz Lipp committed
300
  if (gtk_widget_get_visible(GTK_WIDGET(session->gtk.inputbar)) == false) {
Moritz Lipp's avatar
Moritz Lipp committed
301
    gtk_widget_show(GTK_WIDGET(session->gtk.inputbar));
302 303
  }

Moritz Lipp's avatar
Moritz Lipp committed
304
  if (gtk_widget_get_visible(GTK_WIDGET(session->gtk.notification_area)) == true) {
305 306 307
    gtk_widget_hide(GTK_WIDGET(session->gtk.notification_area));
  }

Moritz Lipp's avatar
Moritz Lipp committed
308 309 310
  gtk_widget_grab_focus(GTK_WIDGET(session->gtk.inputbar_entry));

  if (argument != NULL && argument->data != NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
311
    gtk_entry_set_text(session->gtk.inputbar_entry, (char*) argument->data);
312 313 314 315

    /* we save the X clipboard that will be clear by "grab_focus" */
    gchar* x_clipboard_text = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY));

Moritz Lipp's avatar
Moritz Lipp committed
316
    gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1);
317 318 319 320 321 322 323 324 325 326 327 328

    if (x_clipboard_text != NULL) {
      /* we reset the X clipboard with saved text */
      gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), x_clipboard_text, -1);
      g_free(x_clipboard_text);
    }
  }

  return true;
}

bool
329
girara_sc_abort(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
330 331 332
{
  g_return_val_if_fail(session != NULL, false);

333
  girara_isc_abort(session, NULL, NULL, 0);
Moritz Lipp's avatar
Moritz Lipp committed
334

335 336
  gtk_widget_hide(GTK_WIDGET(session->gtk.notification_area));

Moritz Lipp's avatar
Moritz Lipp committed
337 338 339 340
  if (session->global.autohide_inputbar == false) {
    gtk_widget_show(GTK_WIDGET(session->gtk.inputbar));
  }

341 342 343 344
  return false;
}

bool
345
girara_sc_quit(girara_session_t* session, girara_argument_t* UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t))
346 347 348 349
{
  g_return_val_if_fail(session != NULL, false);

  girara_argument_t arg = { GIRARA_HIDE, NULL };
350
  girara_isc_completion(session, &arg, NULL, 0);
351 352 353 354 355 356 357 358 359 360 361 362 363

  gtk_main_quit();

  return false;
}

static void
girara_toggle_widget_visibility(GtkWidget* widget)
{
  if (widget == NULL) {
    return;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
364
  if (gtk_widget_get_visible(widget) == TRUE) {
365 366 367 368 369 370 371
    gtk_widget_hide(widget);
  } else {
    gtk_widget_show(widget);
  }
}

bool
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 375 376 377 378 379 380 381
{
  g_return_val_if_fail(session != NULL, false);

  girara_toggle_widget_visibility(GTK_WIDGET(session->gtk.inputbar));

  return true;
}

bool
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 385 386 387 388 389 390
{
  g_return_val_if_fail(session != NULL, false);

  girara_toggle_widget_visibility(GTK_WIDGET(session->gtk.statusbar));

  return true;
}

391 392
static girara_list_t*
argument_to_argument_list(girara_argument_t* argument) {
393 394
  girara_list_t* argument_list = girara_list_new();
  if (argument_list == NULL) {
395
    return NULL;
396 397 398 399 400 401 402
  }

  gchar** argv = NULL;
  gint argc    = 0;

  girara_list_set_free_function(argument_list, g_free);
  if (g_shell_parse_argv((const gchar*) argument->data, &argc, &argv, NULL) != FALSE) {
403
    for (int i = 0; i < argc; i++) {
404 405
      char* arg = g_strdup(argv[i]);
      girara_list_append(argument_list, arg);
406
    }
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426

    return argument_list;
  }

  girara_list_free(argument_list);
  return NULL;
}

bool
girara_sc_set(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
{
  g_return_val_if_fail(session != NULL, false);

  if (argument == NULL || argument->data == NULL) {
    return false;
  }

  /* create argument list */
  girara_list_t* argument_list = argument_to_argument_list(argument);
  if (argument_list == NULL) {
427 428 429 430 431 432 433 434 435 436 437 438
    return false;
  }

  /* call set */
  girara_cmd_set(session, argument_list);

  /* cleanup */
  girara_list_free(argument_list);

  return false;
}

439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
bool
girara_sc_exec(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
{
  g_return_val_if_fail(session != NULL, false);

  if (argument == NULL || argument->data == NULL) {
    return false;
  }

  /* create argument list */
  girara_list_t* argument_list = argument_to_argument_list(argument);
  if (argument_list == NULL) {
    return false;
  }

  /* call exec */
  girara_cmd_exec(session, argument_list);

  /* cleanup */
  girara_list_free(argument_list);

  return false;
}

463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
static bool
simulate_key_press(girara_session_t* session, int state, int key)
{
  if (session == NULL || session->gtk.box == NULL) {
    return false;
  }

  GdkEvent* event = gdk_event_new(GDK_KEY_PRESS);

  event->any.type       = GDK_KEY_PRESS;
  event->key.window     = g_object_ref(gtk_widget_get_parent_window(GTK_WIDGET(session->gtk.box)));
  event->key.send_event = false;
  event->key.time       = GDK_CURRENT_TIME;
  event->key.state      = state;
  event->key.keyval     = key;

  GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(session->gtk.box));
  GdkKeymapKey* keys  = NULL;
  gint number_of_keys = 0;

  if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_for_display(display),
        event->key.keyval, &keys, &number_of_keys) == FALSE) {
    gdk_event_free(event);
    return false;
  }

  event->key.hardware_keycode = keys[0].keycode;
  event->key.group            = keys[0].group;

  g_free(keys);

  gdk_event_put(event);
  gdk_event_free(event);

  gtk_main_iteration_do(FALSE);

  return true;
}

502 503 504 505 506 507 508
static void
update_state_by_keyval(int *state, int keyval)
{
  if (state == NULL) {
    return;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
509
  /* The following is probably not true for some keyboard layouts. */
510 511 512 513 514 515 516 517 518
  if ((keyval >= '!' && keyval <= '/')
      || (keyval >= ':' && keyval <= '@')
      || (keyval >= '[' && keyval <= '`')
      || (keyval >= '{' && keyval <= '~')
      ) {
    *state |= GDK_SHIFT_MASK;
  }
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
519 520
bool
girara_sc_feedkeys(girara_session_t* session, girara_argument_t* argument,
Moritz Lipp's avatar
Moritz Lipp committed
521 522 523 524 525 526
    girara_event_t* UNUSED(event), unsigned int t)
{
  if (session == NULL || argument == NULL) {
    return false;
  }

527 528 529 530 531
  if (g_mutex_trylock(&session->private_data->feedkeys_mutex) == FALSE) {
    girara_error("Recursive use of feedkeys detected. Aborting evaluation.");
    return false;
  }

Moritz Lipp's avatar
Moritz Lipp committed
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
  typedef struct gdk_keyboard_button_s {
    char* identifier;
    int keyval;
  } gdk_keyboard_button_t;

  static const gdk_keyboard_button_t gdk_keyboard_buttons[] = {
    {"BackSpace", GDK_KEY_BackSpace},
    {"CapsLock",  GDK_KEY_Caps_Lock},
    {"Down",      GDK_KEY_Down},
    {"Esc",       GDK_KEY_Escape},
    {"F10",       GDK_KEY_F10},
    {"F11",       GDK_KEY_F11},
    {"F12",       GDK_KEY_F12},
    {"F1",        GDK_KEY_F1},
    {"F2",        GDK_KEY_F2},
    {"F3",        GDK_KEY_F3},
    {"F4",        GDK_KEY_F4},
    {"F5",        GDK_KEY_F5},
    {"F6",        GDK_KEY_F6},
    {"F7",        GDK_KEY_F7},
    {"F8",        GDK_KEY_F8},
    {"F9",        GDK_KEY_F9},
    {"Left",      GDK_KEY_Left},
    {"PageDown",  GDK_KEY_Page_Down},
    {"PageUp",    GDK_KEY_Page_Up},
Sebastian Ramacher's avatar
Sebastian Ramacher committed
557 558
    {"Home",      GDK_KEY_Home},
    {"End",       GDK_KEY_End},
Moritz Lipp's avatar
Moritz Lipp committed
559 560 561 562 563 564 565 566 567 568 569 570
    {"Return",    GDK_KEY_Return},
    {"Right",     GDK_KEY_Right},
    {"Space",     GDK_KEY_space},
    {"Super",     GDK_KEY_Super_L},
    {"Tab",       GDK_KEY_Tab},
    {"ShiftTab",  GDK_KEY_ISO_Left_Tab},
    {"Up",        GDK_KEY_Up}
  };

  char* input               = (char*) argument->data;
  unsigned int input_length = strlen(input);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
571
  t = MAX(1, t);
Moritz Lipp's avatar
Moritz Lipp committed
572 573 574 575 576 577 578 579 580 581 582 583
  for (unsigned int c = 0; c < t; c++) {
    for (unsigned i = 0; i < input_length; i++) {
      int state  = 0;
      int keyval = input[i];

      /* possible special button */
      if ((input_length - i) >= 3 && input[i] == '<') {
        char* end = strchr(input + i, '>');
        if (end == NULL) {
          goto single_key;
        }

584
        const int length = end - (input + i) - 1;
Moritz Lipp's avatar
Moritz Lipp committed
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
        char* tmp  = g_strndup(input + i + 1, length);
        bool found = false;

        /* Multi key shortcut */
        if (length > 2 && tmp[1] == '-') {
          switch (tmp[0]) {
            case 'S':
              state = GDK_SHIFT_MASK;
              break;
            case 'A':
              state = GDK_MOD1_MASK;
              break;
            case 'C':
              state = GDK_CONTROL_MASK;
              break;
            default:
              break;
          }

          if (length == 3) {
            keyval = tmp[2];
            found  = true;
          } else {
608 609 610
            for (unsigned int j = 0; j < LENGTH(gdk_keyboard_buttons); ++j) {
              if (g_strcmp0(tmp + 2, gdk_keyboard_buttons[j].identifier) == 0) {
                keyval = gdk_keyboard_buttons[j].keyval;
Moritz Lipp's avatar
Moritz Lipp committed
611 612 613 614 615 616 617
                found = true;
                break;
              }
            }
          }
        /* Possible special key */
        } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
618
          for (unsigned int j = 0; j < LENGTH(gdk_keyboard_buttons); ++j) {
619 620
            if (g_strcmp0(tmp, gdk_keyboard_buttons[j].identifier) == 0) {
              keyval = gdk_keyboard_buttons[j].keyval;
Moritz Lipp's avatar
Moritz Lipp committed
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
              found = true;
              break;
            }
          }
        }

        g_free(tmp);

        /* parsed special key */
        if (found == true) {
          i += length + 1;
        }
      }

single_key:

637
      update_state_by_keyval(&state, keyval);
Moritz Lipp's avatar
Moritz Lipp committed
638 639 640 641
      simulate_key_press(session, state, keyval);
    }
  }

642
  g_mutex_unlock(&session->private_data->feedkeys_mutex);
Moritz Lipp's avatar
Moritz Lipp committed
643 644 645
  return true;
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
646 647
bool
girara_shortcut_mapping_add(girara_session_t* session, const char* identifier, girara_shortcut_function_t function)
648
{
Moritz Lipp's avatar
Moritz Lipp committed
649
  g_return_val_if_fail(session  != NULL, false);
650 651 652 653 654

  if (function == NULL || identifier == NULL) {
    return false;
  }

655
  girara_session_private_t* session_private = session->private_data;
656 657 658
  bool found = false;

  GIRARA_LIST_FOREACH_BODY(session_private->config.shortcut_mappings, girara_shortcut_mapping_t*, data,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
659
    if (g_strcmp0(data->identifier, identifier) == 0) {
660
      data->function = function;
661 662
      found = true;
      break;
663
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
664
  );
665

666 667 668
  if (found == false) {
    /* add new config handle */
    girara_shortcut_mapping_t* mapping = g_slice_new(girara_shortcut_mapping_t);
669

670 671 672 673
    mapping->identifier = g_strdup(identifier);
    mapping->function   = function;
    girara_list_append(session_private->config.shortcut_mappings, mapping);
  }
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688

  return true;
}

void
girara_shortcut_mapping_free(girara_shortcut_mapping_t* mapping)
{
  if (mapping == NULL) {
    return;
  }

  g_free(mapping->identifier);
  g_slice_free(girara_shortcut_mapping_t, mapping);
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
689 690
bool
girara_argument_mapping_add(girara_session_t* session, const char* identifier, int value)
691 692 693 694 695 696 697
{
  g_return_val_if_fail(session  != NULL, false);

  if (identifier == NULL) {
    return false;
  }

698
  girara_session_private_t* session_private = session->private_data;
699 700 701
  bool found = false;

  GIRARA_LIST_FOREACH_BODY(session_private->config.argument_mappings, girara_argument_mapping_t*, mapping,
702 703
    if (g_strcmp0(mapping->identifier, identifier) == 0) {
      mapping->value = value;
704 705
      found = true;
      break;
706
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
707
  );
708

709 710 711
  if (found == false) {
    /* add new config handle */
    girara_argument_mapping_t* mapping = g_slice_new(girara_argument_mapping_t);
712

713 714 715 716
    mapping->identifier = g_strdup(identifier);
    mapping->value      = value;
    girara_list_append(session_private->config.argument_mappings, mapping);
  }
717 718 719 720 721 722 723 724 725 726

  return true;
}

void
girara_argument_mapping_free(girara_argument_mapping_t* argument_mapping)
{
  if (argument_mapping == NULL) {
    return;
  }
727

728 729 730 731 732
  g_free(argument_mapping->identifier);
  g_slice_free(girara_argument_mapping_t, argument_mapping);
}

bool
733 734 735
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)
736 737 738 739 740
{
  g_return_val_if_fail(session  != NULL, false);
  g_return_val_if_fail(function != NULL, false);

  girara_argument_t argument = {argument_n, argument_data};
741
  bool found = false;
742 743

  /* search for existing binding */
744
  GIRARA_LIST_FOREACH_BODY(session->bindings.mouse_events, girara_mouse_event_t*, me_it,
745
    if (me_it->mask == mask && me_it->button == button &&
746
       me_it->mode == mode && me_it->event_type == event_type) {
747 748
      me_it->function = function;
      me_it->argument = argument;
749 750 751

      found = true;
      break;
752
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
753
  );
754

755 756 757 758 759 760 761 762 763 764 765 766
  if (found == false) {
    /* add new mouse event */
    girara_mouse_event_t* mouse_event = g_slice_new(girara_mouse_event_t);

    mouse_event->mask       = mask;
    mouse_event->button     = button;
    mouse_event->function   = function;
    mouse_event->mode       = mode;
    mouse_event->event_type = event_type;
    mouse_event->argument   = argument;
    girara_list_append(session->bindings.mouse_events, mouse_event);
  }
767 768 769 770

  return true;
}

771 772 773 774 775
bool
girara_mouse_event_remove(girara_session_t* session, guint mask, guint button, girara_mode_t mode)
{
  g_return_val_if_fail(session  != NULL, false);

776
  bool found = false;
777
  /* search for existing binding */
778
  GIRARA_LIST_FOREACH_BODY(session->bindings.mouse_events, girara_mouse_event_t*, me_it,
779
    if (me_it->mask == mask && me_it->button == button &&
780
       me_it->mode == mode) {
Moritz Lipp's avatar
Moritz Lipp committed
781
      girara_list_remove(session->bindings.mouse_events, me_it);
782 783
      found = true;
      break;
784
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
785
  );
786

787
  return found;
788 789
}

790 791 792 793 794 795 796 797
void
girara_mouse_event_free(girara_mouse_event_t* mouse_event)
{
  if (mouse_event == NULL) {
    return;
  }
  g_slice_free(girara_mouse_event_t, mouse_event);
}