shortcuts.c 22.5 KB
Newer Older
1
/* SPDX-License-Identifier: Zlib */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
2 3

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
5
#include "datastructures.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
6
#include "input-history.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
7
#include "internal.h"
8
#include "log.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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>
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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)
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
23

Moritz Lipp's avatar
Moritz Lipp committed
24 25
  girara_argument_t argument = {argument_n, (argument_data != NULL) ?
    g_strdup(argument_data) : NULL};
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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)))
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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;
      }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
46
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
47
  );
Sebastian Ramacher's avatar
Sebastian Ramacher committed
48

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
105 106

  /* search for existing special command */
107
  GIRARA_LIST_FOREACH_BODY(session->bindings.inputbar_shortcuts, girara_inputbar_shortcut_t*, inp_sh_it,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
114
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
115
  );
Sebastian Ramacher's avatar
Sebastian Ramacher committed
116

117 118 119
  if (found == false) {
    /* create new inputbar shortcut */
    girara_inputbar_shortcut_t* inputbar_shortcut = g_slice_new(girara_inputbar_shortcut_t);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
128

Moritz Lipp's avatar
Moritz Lipp committed
129
  return true;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
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
}

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

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

  /* clear inputbar */
Moritz Lipp's avatar
Moritz Lipp committed
171
  gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), 0, -1);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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));
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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;
Moritz Lipp's avatar
Moritz Lipp committed
188
  gtk_entry_set_visibility(session->gtk.inputbar_entry, TRUE);
189

Sebastian Ramacher's avatar
Sebastian Ramacher committed
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))
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
203 204 205 206
  int i;

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

      i = pos - 1;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
215 216

      /* remove trailing spaces */
217 218
      for (; i >= 0 && input[i] == ' '; i--) {
      }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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]))) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
234
      }
Moritz Lipp's avatar
Moritz Lipp committed
235
      gtk_editable_delete_text(GTK_EDITABLE(session->gtk.inputbar_entry), pos - 1, pos);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
242 243
      break;
    case GIRARA_PREVIOUS_CHAR:
244
      gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), (pos == 1) ? 1 : pos - 1);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
245
      break;
246
    case GIRARA_DELETE_CURR_CHAR:
247
      if (length != 1 && pos == 0 && (input[0] == ':' || input[0] == '/')){
Sebastian Ramacher's avatar
CS  
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;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
264 265 266 267 268 269 270 271
  }

  g_free(separator);
  g_free(input);

  return false;
}

Sebastian Ramacher's avatar
CS  
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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;
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
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))
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
302 303
  }

Moritz Lipp's avatar
Moritz Lipp committed
304
  if (gtk_widget_get_visible(GTK_WIDGET(session->gtk.notification_area)) == true) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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))
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
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));
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
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))
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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
CS  
Sebastian Ramacher committed
364
  if (gtk_widget_get_visible(widget) == TRUE) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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))
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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))
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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++) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
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;
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
502 503
static int
update_state_by_keyval(int state, int keyval)
504
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
505
  /* The following is probably not true for some keyboard layouts. */
506 507 508 509 510
  if ((keyval >= '!' && keyval <= '/')
      || (keyval >= ':' && keyval <= '@')
      || (keyval >= '[' && keyval <= '`')
      || (keyval >= '{' && keyval <= '~')
      ) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
511
    state |= GDK_SHIFT_MASK;
512
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
513 514

  return state;
515 516
}

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

525 526 527 528 529
  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
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
  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
555 556
    {"Home",      GDK_KEY_Home},
    {"End",       GDK_KEY_End},
Moritz Lipp's avatar
Moritz Lipp committed
557 558 559 560 561 562 563 564 565 566 567 568
    {"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
569
  t = MAX(1, t);
Moritz Lipp's avatar
Moritz Lipp committed
570 571 572 573 574 575 576 577 578 579 580 581
  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;
        }

582
        const int length = end - (input + i) - 1;
Moritz Lipp's avatar
Moritz Lipp committed
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
        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 {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
606 607 608
            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
609 610 611 612 613 614 615
                found = true;
                break;
              }
            }
          }
        /* Possible special key */
        } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
616
          for (unsigned int j = 0; j < LENGTH(gdk_keyboard_buttons); ++j) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
617 618
            if (g_strcmp0(tmp, gdk_keyboard_buttons[j].identifier) == 0) {
              keyval = gdk_keyboard_buttons[j].keyval;
Moritz Lipp's avatar
Moritz Lipp committed
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
              found = true;
              break;
            }
          }
        }

        g_free(tmp);

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

single_key:
Sebastian Ramacher's avatar
Sebastian Ramacher committed
634
      state = update_state_by_keyval(state, keyval);
Moritz Lipp's avatar
Moritz Lipp committed
635 636 637 638
      simulate_key_press(session, state, keyval);
    }
  }

639
  g_mutex_unlock(&session->private_data->feedkeys_mutex);
Moritz Lipp's avatar
Moritz Lipp committed
640 641 642
  return true;
}

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

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

652
  girara_session_private_t* session_private = session->private_data;
653 654 655
  bool found = false;

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

663 664 665
  if (found == false) {
    /* add new config handle */
    girara_shortcut_mapping_t* mapping = g_slice_new(girara_shortcut_mapping_t);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
666

667 668 669 670
    mapping->identifier = g_strdup(identifier);
    mapping->function   = function;
    girara_list_append(session_private->config.shortcut_mappings, mapping);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685

  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
CS  
Sebastian Ramacher committed
686 687
bool
girara_argument_mapping_add(girara_session_t* session, const char* identifier, int value)
Sebastian Ramacher's avatar
Sebastian Ramacher committed
688 689 690 691 692 693 694
{
  g_return_val_if_fail(session  != NULL, false);

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

695
  girara_session_private_t* session_private = session->private_data;
696 697 698
  bool found = false;

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

706 707 708
  if (found == false) {
    /* add new config handle */
    girara_argument_mapping_t* mapping = g_slice_new(girara_argument_mapping_t);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
709

710 711 712 713
    mapping->identifier = g_strdup(identifier);
    mapping->value      = value;
    girara_list_append(session_private->config.argument_mappings, mapping);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
714 715 716 717 718 719 720 721 722 723

  return true;
}

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
725 726 727 728 729
  g_free(argument_mapping->identifier);
  g_slice_free(girara_argument_mapping_t, argument_mapping);
}

bool
Moritz Lipp's avatar
Moritz Lipp committed
730 731 732
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)
Sebastian Ramacher's avatar
Sebastian Ramacher committed
733 734 735 736 737
{
  g_return_val_if_fail(session  != NULL, false);
  g_return_val_if_fail(function != NULL, false);

  girara_argument_t argument = {argument_n, argument_data};
738
  bool found = false;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
739 740

  /* search for existing binding */
741
  GIRARA_LIST_FOREACH_BODY(session->bindings.mouse_events, girara_mouse_event_t*, me_it,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
742
    if (me_it->mask == mask && me_it->button == button &&
743
       me_it->mode == mode && me_it->event_type == event_type) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
744 745
      me_it->function = function;
      me_it->argument = argument;
746 747 748

      found = true;
      break;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
749
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
750
  );
Sebastian Ramacher's avatar
Sebastian Ramacher committed
751

752 753 754 755 756 757 758 759 760 761 762 763
  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);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
764 765 766 767

  return true;
}

768 769 770
bool
girara_mouse_event_remove(girara_session_t* session, guint mask, guint button, girara_mode_t mode)
{
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
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) {
Moritz Lipp's avatar
Moritz Lipp committed
778
      girara_list_remove(session->bindings.mouse_events, me_it);
779 780
      found = true;
      break;
781
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
782
  );
783

784
  return found;
785 786
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
787 788 789 790 791 792 793 794
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);
}