session.c 29.3 KB
Newer Older
Sebastian Ramacher's avatar
Sebastian Ramacher committed
1 2
/* See LICENSE file for license and copyright information */

Sebastian Ramacher's avatar
Sebastian Ramacher committed
3
#include "session.h"
4 5 6 7 8

#include "callbacks.h"
#include "commands.h"
#include "config.h"
#include "css-definitions.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
9
#include "datastructures.h"
10
#include "entry.h"
11
#include "input-history.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
12
#include "internal.h"
13
#include "settings.h"
Sebastian Ramacher's avatar
Sebastian Ramacher committed
14
#include "shortcuts.h"
15
#include "template.h"
16 17
#include "utils.h"

Sebastian Ramacher's avatar
Sebastian Ramacher committed
18
#include <glib/gi18n-lib.h>
19
#include <pango/pango-font.h>
Sebastian Ramacher's avatar
Sebastian Ramacher committed
20 21 22 23 24 25 26
#include <stdlib.h>

#ifdef WITH_LIBNOTIFY
#include <libnotify/notify.h>
#endif


27 28 29 30 31
static int
cb_sort_settings(girara_setting_t* lhs, girara_setting_t* rhs)
{
  return g_strcmp0(girara_setting_get_name(lhs), girara_setting_get_name(rhs));
}
Moritz Lipp's avatar
Moritz Lipp committed
32

33 34 35 36 37 38 39 40 41 42 43
static void
ensure_gettext_initialized(void)
{
  static gsize initialized = 0;
  if (g_once_init_enter(&initialized) == TRUE) {
    bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    g_once_init_leave(&initialized, 1);
  }
}

44 45 46
static void
init_template_engine(GiraraTemplate* csstemplate)
{
47
  static const char variable_names[][24] = {
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
    "session",
    "default-fg",
    "default-bg",
    "inputbar-fg",
    "inputbar-bg",
    "statusbar-fg",
    "statusbar-bg",
    "completion-fg",
    "completion-bg",
    "completion-group-fg",
    "completion-group-bg",
    "completion-highlight-fg",
    "completion-highlight-bg",
    "notification-error-fg",
    "notification-error-bg",
    "notification-warning-fg",
    "notification-warning-bg",
    "notification-fg",
    "notification-bg",
William G Hatch's avatar
William G Hatch committed
67 68
    "scrollbar-fg",
    "scrollbar-bg",
69 70 71
    "bottombox-padding1",
    "bottombox-padding2",
    "bottombox-padding3",
Sebastian Ramacher's avatar
Sebastian Ramacher committed
72 73 74 75
    "bottombox-padding4",
    "font-family",
    "font-size",
    "font-weight"
76 77 78 79 80
  };

  for (size_t idx = 0; idx < LENGTH(variable_names); ++idx) {
    girara_template_add_variable(csstemplate, variable_names[idx]);
  }
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
}

void
css_template_fill_font(GiraraTemplate* csstemplate, const char* font)
{
  PangoFontDescription* descr = pango_font_description_from_string(font);
  if (descr == NULL) {
    return;
  }

  girara_template_set_variable_value(csstemplate, "font-family",
      pango_font_description_get_family(descr));

  char* size = g_strdup_printf("%d%s", pango_font_description_get_size(descr) / PANGO_SCALE,
      pango_font_description_get_size_is_absolute(descr) == FALSE ? "pt" : "");
  girara_template_set_variable_value(csstemplate, "font-size", size);
  g_free(size);

  switch (pango_font_description_get_weight(descr)) {
    case PANGO_WEIGHT_THIN:
      girara_template_set_variable_value(csstemplate, "font-weight", "thin");
      break;

    case PANGO_WEIGHT_ULTRALIGHT:
      girara_template_set_variable_value(csstemplate, "font-weight", "ultralight");
      break;

    case PANGO_WEIGHT_SEMILIGHT:
Sebastian Ramacher's avatar
Sebastian Ramacher committed
109
      girara_template_set_variable_value(csstemplate, "font-weight", "semilight");
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
      break;

    case PANGO_WEIGHT_LIGHT:
      girara_template_set_variable_value(csstemplate, "font-weight", "light");
      break;

    case PANGO_WEIGHT_BOOK:
      girara_template_set_variable_value(csstemplate, "font-weight", "book");
      break;

    case PANGO_WEIGHT_MEDIUM:
      girara_template_set_variable_value(csstemplate, "font-weight", "medium");
      break;

    case PANGO_WEIGHT_SEMIBOLD:
      girara_template_set_variable_value(csstemplate, "font-weight", "semibold");
      break;

    case PANGO_WEIGHT_BOLD:
      girara_template_set_variable_value(csstemplate, "font-weight", "bold");
      break;

    case PANGO_WEIGHT_ULTRABOLD:
      girara_template_set_variable_value(csstemplate, "font-weight", "ultrabold");
      break;

    case PANGO_WEIGHT_HEAVY:
      girara_template_set_variable_value(csstemplate, "font-weight", "heavy");
      break;

    case PANGO_WEIGHT_ULTRAHEAVY:
      girara_template_set_variable_value(csstemplate, "font-weight", "ultraheavy");
      break;

    default:
      girara_template_set_variable_value(csstemplate, "font-weight", "normal");
      break;
  }

  pango_font_description_free(descr);
150 151
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
152
static void
153
fill_template_with_values(girara_session_t* session)
154
{
155
  GiraraTemplate* csstemplate = session->private_data->csstemplate;
156

157 158
  girara_template_set_variable_value(csstemplate, "session",
      session->private_data->session_name);
159

160 161 162
  char* font = NULL;
  girara_setting_get(session, "font", &font);
  if (font != NULL) {
163
    css_template_fill_font(csstemplate, font);
164
    g_free(font);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
165
  } else {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
166 167 168
    girara_template_set_variable_value(csstemplate, "font-family", "monospace");
    girara_template_set_variable_value(csstemplate, "font-size", "9pt");
    girara_template_set_variable_value(csstemplate, "font-weight", "normal");
Sebastian Ramacher's avatar
Sebastian Ramacher committed
169
  };
170 171

  /* parse color values */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
  const char* color_settings[] = {
    "default-fg",
    "default-bg",
    "inputbar-fg",
    "inputbar-bg",
    "statusbar-fg",
    "statusbar-bg",
    "completion-fg",
    "completion-bg",
    "completion-group-fg",
    "completion-group-bg",
    "completion-highlight-fg",
    "completion-highlight-bg",
    "notification-error-fg",
    "notification-error-bg",
    "notification-warning-fg",
    "notification-warning-bg",
    "notification-fg",
    "notification-bg",
William G Hatch's avatar
William G Hatch committed
191
    "scrollbar-fg",
192
    "scrollbar-bg"
193 194
  };

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
195
  for (size_t i = 0; i < LENGTH(color_settings); ++i) {
196
    char* tmp_value = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
197
    girara_setting_get(session, color_settings[i], &tmp_value);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
198 199

    GdkRGBA color = { 0, 0, 0, 0 };
200
    if (tmp_value != NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
201
      gdk_rgba_parse(&color, tmp_value);
202 203 204
      g_free(tmp_value);
    }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
205
    char* colorstr = gdk_rgba_to_string(&color);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
206 207
    girara_template_set_variable_value(csstemplate, color_settings[i],
        colorstr);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
208
    g_free(colorstr);
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
  }

  /* we want inputbar_entry the same height as notification_text and statusbar,
    so that when inputbar_entry is hidden, the size of the bottom_box remains
    the same. We need to get rid of the builtin padding in the GtkEntry
    widget. */

  int ypadding = 2;         /* total amount of padding (top + bottom) */
  int xpadding = 8;         /* total amount of padding (left + right) */
  girara_setting_get(session, "statusbar-h-padding", &xpadding);
  girara_setting_get(session, "statusbar-v-padding", &ypadding);

  typedef struct padding_mapping_s {
    const char* identifier;
    char* value;
  } padding_mapping_t;

  const padding_mapping_t padding_mapping[] = {
227 228 229 230
    {"bottombox-padding1", g_strdup_printf("%d", ypadding - ypadding/2)},
    {"bottombox-padding2", g_strdup_printf("%d", xpadding/2)},
    {"bottombox-padding3", g_strdup_printf("%d", ypadding/2)},
    {"bottombox-padding4", g_strdup_printf("%d", xpadding - xpadding/2)},
231 232 233
  };

  for (size_t i = 0; i < LENGTH(padding_mapping); ++i) {
234 235
    girara_template_set_variable_value(csstemplate,
        padding_mapping[i].identifier, padding_mapping[i].value);
236 237
    g_free(padding_mapping[i].value);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
238 239 240 241 242
}

static void
css_template_changed(GiraraTemplate* csstemplate, girara_session_t* session)
{
243 244
  GtkCssProvider* provider = session->private_data->gtk.cssprovider;
  char* css_data           = girara_template_evaluate(csstemplate);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
245 246 247 248 249
  if (css_data == NULL) {
    girara_error("Error while evaluating templates.");
    return;
  }

250 251 252 253 254 255 256 257 258 259 260
  if (provider == NULL) {
    provider = session->private_data->gtk.cssprovider = gtk_css_provider_new();

    /* add CSS style provider */
    GdkDisplay* display = gdk_display_get_default();
    GdkScreen* screen = gdk_display_get_default_screen(display);
    gtk_style_context_add_provider_for_screen(screen,
                                              GTK_STYLE_PROVIDER(provider),
                                              GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
  }

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
261
  GError* error = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
262 263 264 265 266 267 268
  if (gtk_css_provider_load_from_data(provider, css_data, -1, &error) == FALSE) {
    girara_error("Unable to load CSS: %s", error->message);
    g_free(css_data);
    g_error_free(error);
    return;
  }
  g_free(css_data);
269 270
}

271 272 273 274 275
void
scrolled_window_set_scrollbar_visibility(GtkScrolledWindow* window,
                                         bool               show_horizontal,
                                         bool               show_vertical)
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
276 277
  GtkPolicyType hpolicy = GTK_POLICY_AUTOMATIC;
  GtkPolicyType vpolicy = GTK_POLICY_AUTOMATIC;
278

Sebastian Ramacher's avatar
Sebastian Ramacher committed
279 280
  if (show_horizontal == false) {
    hpolicy = GTK_POLICY_EXTERNAL;
281
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
282 283
  if (show_vertical == false) {
    vpolicy = GTK_POLICY_EXTERNAL;
284
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
285 286

  gtk_scrolled_window_set_policy(window, hpolicy, vpolicy);
287 288 289
}


Sebastian Ramacher's avatar
Sebastian Ramacher committed
290 291 292
girara_session_t*
girara_session_create()
{
293
  ensure_gettext_initialized();
294

295
  girara_session_t* session = g_slice_alloc0(sizeof(girara_session_t));
296
  session->private_data     = g_slice_alloc0(sizeof(girara_session_private_t));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
297

298 299
  girara_session_private_t* session_private = session->private_data;

Sebastian Ramacher's avatar
Sebastian Ramacher committed
300
  /* init values */
301 302 303 304 305 306
  session->bindings.mouse_events       = girara_list_new2(
      (girara_free_function_t) girara_mouse_event_free);
  session->bindings.commands           = girara_list_new2(
      (girara_free_function_t) girara_command_free);
  session->bindings.special_commands   = girara_list_new2(
      (girara_free_function_t) girara_special_command_free);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
307 308 309 310
  session->bindings.shortcuts          = girara_list_new2(
      (girara_free_function_t) girara_shortcut_free);
  session->bindings.inputbar_shortcuts = girara_list_new2(
      (girara_free_function_t) girara_inputbar_shortcut_free);
Moritz Lipp's avatar
Moritz Lipp committed
311

312
  session_private->elements.statusbar_items = girara_list_new2(
Sebastian Ramacher's avatar
Sebastian Ramacher committed
313
      (girara_free_function_t) girara_statusbar_item_free);
Moritz Lipp's avatar
Moritz Lipp committed
314

315
  /* settings */
316
  session_private->settings = girara_sorted_list_new2(
317
      (girara_compare_function_t) cb_sort_settings,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
318 319
      (girara_free_function_t) girara_setting_free);

320
  /* CSS style provider */
321
  GResource* css_resource = girara_css_get_resource();
Sebastian Ramacher's avatar
Sebastian Ramacher committed
322
  GBytes* css_data = g_resource_lookup_data(css_resource, "/org/pwmt/girara/CSS/girara.css_t", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
323
  if (css_data != NULL) {
324
    session_private->csstemplate = girara_template_new(g_bytes_get_data(css_data, NULL));
325 326 327
    g_bytes_unref(css_data);
  }

328 329
  session_private->gtk.cssprovider = NULL;
  init_template_engine(session_private->csstemplate);
330

Moritz Lipp's avatar
Moritz Lipp committed
331
  /* init modes */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
332
  session->modes.identifiers  = girara_list_new2(
Sebastian Ramacher's avatar
Sebastian Ramacher committed
333
      (girara_free_function_t) girara_mode_string_free);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
334
  girara_mode_t normal_mode   = girara_mode_add(session, "normal");
335
  girara_mode_t inputbar_mode = girara_mode_add(session, "inputbar");
Sebastian Ramacher's avatar
Sebastian Ramacher committed
336 337
  session->modes.normal       = normal_mode;
  session->modes.current_mode = normal_mode;
338
  session->modes.inputbar     = inputbar_mode;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
339

Moritz Lipp's avatar
Moritz Lipp committed
340
  /* config handles */
341
  session_private->config.handles           = girara_list_new2(
Sebastian Ramacher's avatar
Sebastian Ramacher committed
342
      (girara_free_function_t) girara_config_handle_free);
343
  session_private->config.shortcut_mappings = girara_list_new2(
Sebastian Ramacher's avatar
Sebastian Ramacher committed
344
      (girara_free_function_t) girara_shortcut_mapping_free);
345
  session_private->config.argument_mappings = girara_list_new2(
346
      (girara_free_function_t) girara_argument_mapping_free);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
347

Moritz Lipp's avatar
Moritz Lipp committed
348
  /* command history */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
349
  session->command_history = girara_input_history_new(NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
350

351 352
  /* load default values */
  girara_config_load_default(session);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
353

354
  /* create widgets */
355
  session->gtk.box                      = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
356 357
  session_private->gtk.overlay    = gtk_overlay_new();
  session_private->gtk.bottom_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
358 359
  session->gtk.statusbar_entries        = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
  session->gtk.inputbar_box             = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
360
  gtk_box_set_homogeneous(session->gtk.inputbar_box, TRUE);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
361 362
  session->gtk.view              = gtk_scrolled_window_new(NULL, NULL);
  session->gtk.viewport          = gtk_viewport_new(NULL, NULL);
363
  gtk_widget_add_events(session->gtk.viewport, GDK_SCROLL_MASK);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
364 365 366
  session->gtk.statusbar         = gtk_event_box_new();
  session->gtk.notification_area = gtk_event_box_new();
  session->gtk.notification_text = gtk_label_new(NULL);
Moritz Lipp's avatar
Moritz Lipp committed
367
  session->gtk.inputbar_dialog   = GTK_LABEL(gtk_label_new(NULL));
368
  session->gtk.inputbar_entry    = GTK_ENTRY(girara_entry_new());
Moritz Lipp's avatar
Moritz Lipp committed
369
  session->gtk.inputbar          = gtk_event_box_new();
Sebastian Ramacher's avatar
Sebastian Ramacher committed
370

371
  /* make notification text selectable */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
372
  gtk_label_set_selectable(GTK_LABEL(session->gtk.notification_text), TRUE);
373

374 375 376 377 378 379 380 381 382 383
  return session;
}

bool
girara_session_init(girara_session_t* session, const char* sessionname)
{
  if (session == NULL) {
    return false;
  }

384 385
  bool smooth_scroll = false;
  girara_setting_get(session, "smooth-scroll", &smooth_scroll);
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
386
  if (smooth_scroll == true) {
387 388 389
    gtk_widget_add_events(session->gtk.viewport, GDK_SMOOTH_SCROLL_MASK);
  }

390 391 392
  session->private_data->session_name = g_strdup(
      (sessionname == NULL) ? "girara" : sessionname);

393
  /* load CSS style */
394
  fill_template_with_values(session);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
395 396
  g_signal_connect(G_OBJECT(session->private_data->csstemplate), "changed",
      G_CALLBACK(css_template_changed), session);
397

Sebastian Ramacher's avatar
Sebastian Ramacher committed
398
  /* window */
399
#ifdef GDK_WINDOWING_X11
400
  if (session->gtk.embed != 0) {
401 402
    session->gtk.window = gtk_plug_new(session->gtk.embed);
  } else {
Moritz Lipp's avatar
Moritz Lipp committed
403
#endif
404
    session->gtk.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
405
#ifdef GDK_WINDOWING_X11
406
  }
Moritz Lipp's avatar
Moritz Lipp committed
407
#endif
408

Sebastian Ramacher's avatar
Sebastian Ramacher committed
409
  gtk_widget_set_name(session->gtk.window, session->private_data->session_name);
410

Sebastian Ramacher's avatar
Sebastian Ramacher committed
411 412
  /* apply CSS style */
  css_template_changed(session->private_data->csstemplate, session);
413

Sebastian Ramacher's avatar
Sebastian Ramacher committed
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
  GdkGeometry hints = {
    .base_height = 1,
    .base_width  = 1,
    .height_inc  = 0,
    .max_aspect  = 0,
    .max_height  = 0,
    .max_width   = 0,
    .min_aspect  = 0,
    .min_height  = 0,
    .min_width   = 0,
    .width_inc   = 0
  };

  gtk_window_set_geometry_hints(GTK_WINDOW(session->gtk.window), NULL, &hints, GDK_HINT_MIN_SIZE);

  /* view */
  session->signals.view_key_pressed = g_signal_connect(G_OBJECT(session->gtk.view), "key-press-event",
      G_CALLBACK(girara_callback_view_key_press_event), session);

Moritz Lipp's avatar
Moritz Lipp committed
433 434 435 436 437 438 439 440 441
  session->signals.view_button_press_event = g_signal_connect(G_OBJECT(session->gtk.view), "button-press-event",
      G_CALLBACK(girara_callback_view_button_press_event), session);

  session->signals.view_button_release_event = g_signal_connect(G_OBJECT(session->gtk.view), "button-release-event",
      G_CALLBACK(girara_callback_view_button_release_event), session);

  session->signals.view_motion_notify_event = g_signal_connect(G_OBJECT(session->gtk.view), "motion-notify-event",
      G_CALLBACK(girara_callback_view_button_motion_notify_event), session);

442
  session->signals.view_scroll_event = g_signal_connect(G_OBJECT(session->gtk.view), "scroll-event",
443 444
      G_CALLBACK(girara_callback_view_scroll_event), session);

445 446
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(session->gtk.view), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

447 448 449 450
  /* invisible scrollbars */
  char* guioptions = NULL;
  girara_setting_get(session, "guioptions", &guioptions);

451 452 453 454 455
  const bool show_hscrollbar = strchr(guioptions, 'h') != NULL;
  const bool show_vscrollbar = strchr(guioptions, 'v') != NULL;

  scrolled_window_set_scrollbar_visibility(
    GTK_SCROLLED_WINDOW(session->gtk.view), show_hscrollbar, show_vscrollbar);
456
  g_free(guioptions);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
457 458 459 460 461 462 463 464 465

  /* viewport */
  gtk_container_add(GTK_CONTAINER(session->gtk.view), session->gtk.viewport);
  gtk_viewport_set_shadow_type(GTK_VIEWPORT(session->gtk.viewport), GTK_SHADOW_NONE);

  /* statusbar */
  gtk_container_add(GTK_CONTAINER(session->gtk.statusbar), GTK_WIDGET(session->gtk.statusbar_entries));

  /* notification area */
466
  gtk_container_add(GTK_CONTAINER(session->gtk.notification_area), session->gtk.notification_text);
467 468
  gtk_widget_set_halign(session->gtk.notification_text, GTK_ALIGN_START);
  gtk_widget_set_valign(session->gtk.notification_text, GTK_ALIGN_CENTER);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
469 470 471
  gtk_label_set_use_markup(GTK_LABEL(session->gtk.notification_text), TRUE);

  /* inputbar */
Moritz Lipp's avatar
Moritz Lipp committed
472 473 474
  gtk_entry_set_has_frame(session->gtk.inputbar_entry, FALSE);
  gtk_editable_set_editable(GTK_EDITABLE(session->gtk.inputbar_entry), TRUE);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
475 476
  widget_add_class(GTK_WIDGET(session->gtk.inputbar_entry), "bottom_box");
  widget_add_class(session->gtk.notification_text, "bottom_box");
477

Moritz Lipp's avatar
Moritz Lipp committed
478 479 480 481 482 483 484
  session->signals.inputbar_key_pressed = g_signal_connect(
      G_OBJECT(session->gtk.inputbar_entry),
      "key-press-event",
      G_CALLBACK(girara_callback_inputbar_key_press_event),
      session
    );

485 486 487 488 489 490 491
  session->signals.inputbar_changed = g_signal_connect(
      G_OBJECT(session->gtk.inputbar_entry),
      "changed",
      G_CALLBACK(girara_callback_inputbar_changed_event),
      session
    );

Moritz Lipp's avatar
Moritz Lipp committed
492 493 494 495 496 497 498
  session->signals.inputbar_activate = g_signal_connect(
      G_OBJECT(session->gtk.inputbar_entry),
      "activate",
      G_CALLBACK(girara_callback_inputbar_activate),
      session
    );

Moritz Lipp's avatar
Moritz Lipp committed
499 500
  gtk_box_set_homogeneous(session->gtk.inputbar_box, FALSE);
  gtk_box_set_spacing(session->gtk.inputbar_box, 5);
Moritz Lipp's avatar
Moritz Lipp committed
501

502
  /* inputbar box */
Moritz Lipp's avatar
Moritz Lipp committed
503 504 505
  gtk_box_pack_start(GTK_BOX(session->gtk.inputbar_box),  GTK_WIDGET(session->gtk.inputbar_dialog), FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(session->gtk.inputbar_box),  GTK_WIDGET(session->gtk.inputbar_entry),  TRUE,  TRUE,  0);
  gtk_container_add(GTK_CONTAINER(session->gtk.inputbar), GTK_WIDGET(session->gtk.inputbar_box));
Sebastian Ramacher's avatar
Sebastian Ramacher committed
506

507
  /* bottom box */
508
  gtk_box_set_spacing(session->private_data->gtk.bottom_box, 0);
509

510 511 512
  gtk_box_pack_end(GTK_BOX(session->private_data->gtk.bottom_box), GTK_WIDGET(session->gtk.inputbar), TRUE, TRUE, 0);
  gtk_box_pack_end(GTK_BOX(session->private_data->gtk.bottom_box), GTK_WIDGET(session->gtk.notification_area), TRUE, TRUE, 0);
  gtk_box_pack_end(GTK_BOX(session->private_data->gtk.bottom_box), GTK_WIDGET(session->gtk.statusbar), TRUE, TRUE, 0);
513

Sebastian Ramacher's avatar
Sebastian Ramacher committed
514
  /* packing */
515
  gtk_box_set_spacing(session->gtk.box, 0);
516
  gtk_box_pack_start(session->gtk.box, GTK_WIDGET(session->gtk.view),   TRUE,  TRUE, 0);
517 518

  /* box */
519
  gtk_container_add(GTK_CONTAINER(session->private_data->gtk.overlay), GTK_WIDGET(session->gtk.box));
520
  /* overlay */
521 522
  g_object_set(session->private_data->gtk.bottom_box, "halign", GTK_ALIGN_FILL, NULL);
  g_object_set(session->private_data->gtk.bottom_box, "valign", GTK_ALIGN_END, NULL);
523

524 525
  gtk_overlay_add_overlay(GTK_OVERLAY(session->private_data->gtk.overlay), GTK_WIDGET(session->private_data->gtk.bottom_box));
  gtk_container_add(GTK_CONTAINER(session->gtk.window), GTK_WIDGET(session->private_data->gtk.overlay));
526

Sebastian Ramacher's avatar
Sebastian Ramacher committed
527
  /* statusbar */
528
  widget_add_class(GTK_WIDGET(session->gtk.statusbar), "statusbar");
Sebastian Ramacher's avatar
Sebastian Ramacher committed
529 530

  /* inputbar */
531
  widget_add_class(GTK_WIDGET(session->gtk.inputbar_box), "inputbar");
532 533 534
  widget_add_class(GTK_WIDGET(session->gtk.inputbar_entry), "inputbar");
  widget_add_class(GTK_WIDGET(session->gtk.inputbar), "inputbar");
  widget_add_class(GTK_WIDGET(session->gtk.inputbar_dialog), "inputbar");
Sebastian Ramacher's avatar
Sebastian Ramacher committed
535 536

  /* notification area */
537 538
  widget_add_class(session->gtk.notification_area, "notification");
  widget_add_class(session->gtk.notification_text, "notification");
539

Sebastian Ramacher's avatar
Sebastian Ramacher committed
540
  /* set window size */
541 542 543 544
  int window_width = 0;
  int window_height = 0;
  girara_setting_get(session, "window-width", &window_width);
  girara_setting_get(session, "window-height", &window_height);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
545

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
546
  if (window_width > 0 && window_height > 0) {
547
    gtk_window_set_default_size(GTK_WINDOW(session->gtk.window), window_width, window_height);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
548 549 550 551
  }

  gtk_widget_show_all(GTK_WIDGET(session->gtk.window));
  gtk_widget_hide(GTK_WIDGET(session->gtk.notification_area));
Moritz Lipp's avatar
Moritz Lipp committed
552
  gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar_dialog));
553

Moritz Lipp's avatar
Moritz Lipp committed
554 555 556
  if (session->global.autohide_inputbar == true) {
    gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar));
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
557

558 559 560 561
  if (session->global.hide_statusbar == true) {
    gtk_widget_hide(GTK_WIDGET(session->gtk.statusbar));
  }

562 563
  char* window_icon = NULL;
  girara_setting_get(session, "window-icon", &window_icon);
564
  if (window_icon != NULL) {
565
    girara_set_window_icon(session, window_icon);
566 567
    g_free(window_icon);
  }
568

Moritz Lipp's avatar
Moritz Lipp committed
569 570
  gtk_widget_grab_focus(GTK_WIDGET(session->gtk.view));

571
  return true;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
572 573
}

574 575 576 577 578
static void
girara_session_private_free(girara_session_private_t* session)
{
  g_return_if_fail(session != NULL);

579 580 581 582
  if (session->session_name != NULL) {
    g_free(session->session_name);
  }

583 584 585 586 587 588 589 590 591 592 593 594
  /* clean up config handles */
  girara_list_free(session->config.handles);
  session->config.handles = NULL;

  /* clean up shortcut mappings */
  girara_list_free(session->config.shortcut_mappings);
  session->config.shortcut_mappings = NULL;

  /* clean up argument mappings */
  girara_list_free(session->config.argument_mappings);
  session->config.argument_mappings = NULL;

595 596 597 598 599 600 601 602 603 604
  /* clean up buffer */
  if (session->buffer.command) {
    g_string_free(session->buffer.command, TRUE);
  }
  session->buffer.command = NULL;

  /* clean up statusbar items */
  girara_list_free(session->elements.statusbar_items);
  session->elements.statusbar_items = NULL;

605
  /* clean up CSS style provider */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
606 607
  g_clear_object(&session->gtk.cssprovider);
  g_clear_object(&session->csstemplate);
608

609 610 611 612 613 614 615
  /* clean up settings */
  girara_list_free(session->settings);
  session->settings = NULL;

  g_slice_free(girara_session_private_t, session);
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
616 617 618 619 620 621 622 623 624 625 626 627 628 629
bool
girara_session_destroy(girara_session_t* session)
{
  g_return_val_if_fail(session != NULL, FALSE);

  /* clean up shortcuts */
  girara_list_free(session->bindings.shortcuts);
  session->bindings.shortcuts = NULL;

  /* clean up inputbar shortcuts */
  girara_list_free(session->bindings.inputbar_shortcuts);
  session->bindings.inputbar_shortcuts = NULL;

  /* clean up commands */
630 631
  girara_list_free(session->bindings.commands);
  session->bindings.commands = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
632 633

  /* clean up special commands */
634 635
  girara_list_free(session->bindings.special_commands);
  session->bindings.special_commands = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
636 637

  /* clean up mouse events */
638 639
  girara_list_free(session->bindings.mouse_events);
  session->bindings.mouse_events = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
640

641
  /* clean up input histry */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
642
  g_clear_object(&session->command_history);
643

Sebastian Ramacher's avatar
Sebastian Ramacher committed
644 645 646 647 648 649
  /* clean up modes */
  girara_list_free(session->modes.identifiers);
  session->modes.identifiers = NULL;

  /* clean up buffer */
  if (session->global.buffer) {
650
    g_string_free(session->global.buffer, TRUE);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
651 652 653
  }
  session->global.buffer  = NULL;

654 655 656 657
  /* clean up private data */
  girara_session_private_free(session->private_data);
  session->private_data = NULL;

Sebastian Ramacher's avatar
Sebastian Ramacher committed
658 659 660 661 662
  /* clean up session */
  g_slice_free(girara_session_t, session);

  return TRUE;
}
663 664 665 666 667 668 669 670 671

char*
girara_buffer_get(girara_session_t* session)
{
  g_return_val_if_fail(session != NULL, NULL);

  return (session->global.buffer) ? g_strdup(session->global.buffer->str) : NULL;
}

672 673 674 675 676 677 678 679 680 681 682 683
void
girara_libnotify(girara_session_t* session, const char *summary,
    const char *body)
{
  if (session == NULL
      || summary == NULL
      || body == NULL) {
    return;
  }

#ifdef WITH_LIBNOTIFY

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
684
  const bool was_initialized = notify_is_initted();
685

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
686
  if (was_initialized == false) {
687 688 689
    notify_init(session->private_data->session_name);
  }

690 691 692 693 694 695 696 697 698 699
  NotifyNotification* libnotify_notification = NULL;
  char* icon_name = NULL;

  /* We use the NotifyNotification constructor at many branches because
   * libnotify does not have a notify_notification_set_image_from_name()
   * function, and accessing private fields is frowned upon and subject to API
   * changes.
   */
  icon_name = g_strdup(gtk_window_get_icon_name(GTK_WINDOW(session->gtk.window)));
  if (icon_name != NULL) {
700
    /* Icon can be loaded from theme with adequate quality for notification */
701 702 703
    libnotify_notification = notify_notification_new(summary, body, icon_name);
    g_free(icon_name);
  } else {
704
    /* Or extracted from the current window */
705 706 707 708 709 710
    GdkPixbuf* icon_pix = gtk_window_get_icon(GTK_WINDOW(session->gtk.window));
    if (icon_pix != NULL) {
      libnotify_notification = notify_notification_new(summary, body, NULL);
      notify_notification_set_image_from_pixbuf(libnotify_notification, icon_pix);
      g_object_unref(G_OBJECT(icon_pix));
    } else {
711
      /* Or from a default image as a last resort */
712 713 714 715
      libnotify_notification = notify_notification_new(summary, body, "info");
    }
  }

716
  g_return_if_fail(libnotify_notification != NULL);
Benoît Taine's avatar
Benoît Taine committed
717
  notify_notification_show(libnotify_notification, NULL);
718 719
  g_object_unref(G_OBJECT(libnotify_notification));

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
720
  if (was_initialized == false) {
721 722 723
    notify_uninit();
  }

724 725 726 727 728 729 730
#else

  girara_notify(session, GIRARA_WARNING, "Girara was compiled without libnotify support.");

#endif
}

731 732 733
void
girara_notify(girara_session_t* session, int level, const char* format, ...)
{
Moritz Lipp's avatar
Moritz Lipp committed
734 735 736 737 738
  if (session == NULL
      || session->gtk.notification_text == NULL
      || session->gtk.notification_area == NULL
      || session->gtk.inputbar == NULL
      || session->gtk.view == NULL) {
739 740 741
    return;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
742
  if (level == GIRARA_ERROR) {
743 744 745 746 747 748
    widget_add_class(session->gtk.notification_area, "notification-error");
    widget_add_class(session->gtk.notification_text, "notification-error");
  } else {
    widget_remove_class(session->gtk.notification_area, "notification-error");
    widget_remove_class(session->gtk.notification_text, "notification-error");
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
749
  if (level == GIRARA_WARNING) {
750 751 752 753 754
    widget_add_class(session->gtk.notification_area, "notification-warning");
    widget_add_class(session->gtk.notification_text, "notification-warning");
  } else {
    widget_remove_class(session->gtk.notification_area, "notification-warning");
    widget_remove_class(session->gtk.notification_text, "notification-warning");
755
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
756

757 758 759 760 761 762 763 764 765 766 767
  /* prepare message */
  va_list ap;
  va_start(ap, format);
  char* message = g_strdup_vprintf(format, ap);
  va_end(ap);

  gtk_label_set_markup(GTK_LABEL(session->gtk.notification_text), message);
  g_free(message);

  /* update visibility */
  gtk_widget_show(GTK_WIDGET(session->gtk.notification_area));
Moritz Lipp's avatar
Moritz Lipp committed
768
  gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar));
Moritz Lipp's avatar
Moritz Lipp committed
769 770

  gtk_widget_grab_focus(GTK_WIDGET(session->gtk.view));
771 772
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
773 774
void
girara_dialog(girara_session_t* session, const char* dialog, bool
Moritz Lipp's avatar
Moritz Lipp committed
775
    invisible, girara_callback_inputbar_key_press_event_t key_press_event,
Moritz Lipp's avatar
Moritz Lipp committed
776
    girara_callback_inputbar_activate_t activate_event, void* data)
Moritz Lipp's avatar
Moritz Lipp committed
777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
{
  if (session == NULL || session->gtk.inputbar == NULL
      || session->gtk.inputbar_dialog == NULL
      || session->gtk.inputbar_entry == NULL) {
    return;
  }

  gtk_widget_show(GTK_WIDGET(session->gtk.inputbar_dialog));

  /* set dialog message */
  if (dialog != NULL) {
    gtk_label_set_markup(session->gtk.inputbar_dialog, dialog);
  }

  /* set input visibility */
  if (invisible == true) {
    gtk_entry_set_visibility(session->gtk.inputbar_entry, FALSE);
  } else {
    gtk_entry_set_visibility(session->gtk.inputbar_entry, TRUE);
  }

  /* set handler */
  session->signals.inputbar_custom_activate        = activate_event;
  session->signals.inputbar_custom_key_press_event = key_press_event;
Moritz Lipp's avatar
Moritz Lipp committed
801
  session->signals.inputbar_custom_data            = data;
Moritz Lipp's avatar
Moritz Lipp committed
802 803

  /* focus inputbar */
804
  girara_sc_focus_inputbar(session, NULL, NULL, 0);
Moritz Lipp's avatar
Moritz Lipp committed
805 806
}

807 808 809 810 811 812 813
bool
girara_set_view(girara_session_t* session, GtkWidget* widget)
{
  g_return_val_if_fail(session != NULL, false);

  GtkWidget* child = gtk_bin_get_child(GTK_BIN(session->gtk.viewport));

Sebastian Ramacher's avatar
Sebastian Ramacher committed
814
  if (child != NULL) {
815 816 817 818 819 820
    g_object_ref(child);
    gtk_container_remove(GTK_CONTAINER(session->gtk.viewport), child);
  }

  gtk_container_add(GTK_CONTAINER(session->gtk.viewport), widget);
  gtk_widget_show_all(widget);
821
  gtk_widget_grab_focus(session->gtk.view);
822 823 824

  return true;
}
Sebastian Ramacher's avatar
Sebastian Ramacher committed
825 826 827 828 829 830 831 832 833 834 835 836 837

void
girara_mode_set(girara_session_t* session, girara_mode_t mode)
{
  g_return_if_fail(session != NULL);

  session->modes.current_mode = mode;
}

girara_mode_t
girara_mode_add(girara_session_t* session, const char* name)
{
  g_return_val_if_fail(session  != NULL, FALSE);
838
  g_return_val_if_fail(name != NULL && name[0] != '\0', FALSE);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
839 840

  girara_mode_t last_index = 0;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
841
  GIRARA_LIST_FOREACH_BODY(session->modes.identifiers, girara_mode_string_t*, mode,
Sebastian Ramacher's avatar
Sebastian Ramacher committed
842 843 844
    if (mode->index > last_index) {
      last_index = mode->index;
    }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
845
  );
Sebastian Ramacher's avatar
Sebastian Ramacher committed
846 847 848 849 850 851 852

  /* create new mode identifier */
  girara_mode_string_t* mode = g_slice_new(girara_mode_string_t);
  mode->index = last_index + 1;
  mode->name = g_strdup(name);
  girara_list_append(session->modes.identifiers, mode);

Moritz Lipp's avatar
Moritz Lipp committed
853
  return mode->index;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
854 855 856 857 858
}

void
girara_mode_string_free(girara_mode_string_t* mode)
{
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
859
  if (mode == NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
860 861 862 863 864 865 866 867 868 869 870 871 872 873
    return;
  }

  g_free(mode->name);
  g_slice_free(girara_mode_string_t, mode);
}

girara_mode_t
girara_mode_get(girara_session_t* session)
{
  g_return_val_if_fail(session != NULL, 0);

  return session->modes.current_mode;
}
874 875 876 877 878 879 880 881 882 883 884 885 886

bool
girara_set_window_title(girara_session_t* session, const char* name)
{
  if (session == NULL || session->gtk.window == NULL || name == NULL) {
    return false;
  }

  gtk_window_set_title(GTK_WINDOW(session->gtk.window), name);

  return true;
}

887 888 889 890 891 892 893
bool
girara_set_window_icon(girara_session_t* session, const char* name)
{
  if (session == NULL || session->gtk.window == NULL || name == NULL) {
    return false;
  }

894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
  char* path        = girara_fix_path(name);
  GtkWindow* window = GTK_WINDOW(session->gtk.window);

  girara_debug("Loading window icon from file: %s", path);
  GError* error = NULL;
  gtk_window_set_icon_from_file(window, path, &error);
  free(path);

  if (error == NULL) {
    return true;
  }

  girara_debug("Failed to load window icon (file): %s", error->message);
  g_error_free(error);

  girara_debug("Loading window icon with name: %s", name);
  gtk_window_set_icon_name(window, name);
911 912 913 914

  return true;
}

Moritz Lipp's avatar
Moritz Lipp committed
915 916 917
girara_list_t*
girara_get_command_history(girara_session_t* session)
{
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
918
  g_return_val_if_fail(session != NULL, NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
919
  return girara_input_history_list(session->command_history);
Moritz Lipp's avatar
Moritz Lipp committed
920
}
921 922 923 924 925 926 927 928 929

GiraraTemplate*
girara_session_get_template(girara_session_t* session)
{
  g_return_val_if_fail(session != NULL, NULL);

  return session->private_data->csstemplate;
}

930
void
931
girara_session_set_template(girara_session_t* session, GiraraTemplate* template, bool init_variables)
932
{
933
  g_return_if_fail(session != NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
934
  g_return_if_fail(template != NULL);
935

Sebastian Ramacher's avatar
Sebastian Ramacher committed
936
  g_clear_object(&session->private_data->csstemplate);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
937

938
  session->private_data->csstemplate = template;
939 940
  if (init_variables == true) {
    init_template_engine(template);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
941
    fill_template_with_values(session);
942 943
  }

944 945 946
  css_template_changed(template, session);
}