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
girara_session_t*
Sebastian Ramacher's avatar
Sebastian Ramacher committed
291
girara_session_create(void)
Sebastian Ramacher's avatar
Sebastian Ramacher committed
292
{
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
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
683
  const bool was_initialized = notify_is_initted();
684

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

689 690 691 692 693 694 695 696 697 698
  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) {
699
    /* Icon can be loaded from theme with adequate quality for notification */
700 701 702
    libnotify_notification = notify_notification_new(summary, body, icon_name);
    g_free(icon_name);
  } else {
703
    /* Or extracted from the current window */
704 705 706 707 708 709
    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 {
710
      /* Or from a default image as a last resort */
711 712 713 714
      libnotify_notification = notify_notification_new(summary, body, "info");
    }
  }

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

Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
719
  if (was_initialized == false) {
720 721
    notify_uninit();
  }
722 723 724 725 726
#else
  girara_notify(session, GIRARA_WARNING, "Girara was compiled without libnotify support.");
#endif
}

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

Sebastian Ramacher's avatar
Sebastian Ramacher committed
738
  if (level == GIRARA_ERROR) {
739 740 741 742 743 744
    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
745
  if (level == GIRARA_WARNING) {
746 747 748 749 750
    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");
751
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
752

753 754 755 756 757 758 759 760 761 762 763
  /* 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
764
  gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar));
Moritz Lipp's avatar
Moritz Lipp committed
765 766

  gtk_widget_grab_focus(GTK_WIDGET(session->gtk.view));
767 768
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
769 770
void
girara_dialog(girara_session_t* session, const char* dialog, bool
Moritz Lipp's avatar
Moritz Lipp committed
771
    invisible, girara_callback_inputbar_key_press_event_t key_press_event,
Moritz Lipp's avatar
Moritz Lipp committed
772
    girara_callback_inputbar_activate_t activate_event, void* data)
Moritz Lipp's avatar
Moritz Lipp committed
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
{
  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
797
  session->signals.inputbar_custom_data            = data;
Moritz Lipp's avatar
Moritz Lipp committed
798 799

  /* focus inputbar */
800
  girara_sc_focus_inputbar(session, NULL, NULL, 0);
Moritz Lipp's avatar
Moritz Lipp committed
801 802
}

803 804 805 806 807 808 809
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
810
  if (child != NULL) {
811 812 813 814 815 816
    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);
817
  gtk_widget_grab_focus(session->gtk.view);
818 819 820

  return true;
}
Sebastian Ramacher's avatar
Sebastian Ramacher committed
821 822 823 824 825 826 827 828 829 830 831 832 833

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);
834
  g_return_val_if_fail(name != NULL && name[0] != '\0', FALSE);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
835 836

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

  /* 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
849
  return mode->index;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
850 851 852 853 854
}

void
girara_mode_string_free(girara_mode_string_t* mode)
{
Sebastian Ramacher's avatar
CS  
Sebastian Ramacher committed
855
  if (mode == NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
856 857 858 859 860 861 862 863 864 865 866 867 868 869
    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;
}
870 871 872 873 874 875 876 877 878 879 880 881 882

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;
}

883 884 885 886 887 888 889
bool
girara_set_window_icon(girara_session_t* session, const char* name)
{
  if (session == NULL || session->gtk.window == NULL || name == NULL) {
    return false;
  }

890 891 892 893 894 895
  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);
896
  g_free(path);
897 898 899 900 901 902 903 904 905 906

  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);
907 908 909 910

  return true;
}

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

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

  return session->private_data->csstemplate;
}

926
void
927
girara_session_set_template(girara_session_t* session, GiraraTemplate* template, bool init_variables)
928
{
929
  g_return_if_fail(session != NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
930
  g_return_if_fail(template != NULL);
931

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

934
  session->private_data->csstemplate = template;
935 936
  if (init_variables == true) {
    init_template_engine(template);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
937
    fill_template_with_values(session);
938 939
  }

940 941 942
  css_template_changed(template, session);
}