1 /* SPDX-License-Identifier: Zlib */
2
3 #include "session.h"
4
5 #include "callbacks.h"
6 #include "commands.h"
7 #include "config.h"
8 #include "css-definitions.h"
9 #include "datastructures.h"
10 #include "entry.h"
11 #include "input-history.h"
12 #include "internal.h"
13 #include "settings.h"
14 #include "shortcuts.h"
15 #include "template.h"
16 #include "utils.h"
17
18 #include <glib/gi18n-lib.h>
19 #include <pango/pango-font.h>
20 #include <stdlib.h>
21
22 #ifdef WITH_LIBNOTIFY
23 #include <libnotify/notify.h>
24 #endif
25
26
27 static int
cb_sort_settings(const void * data1,const void * data2)28 cb_sort_settings(const void* data1, const void* data2)
29 {
30 const girara_setting_t* lhs = data1;
31 const girara_setting_t* rhs = data2;
32
33 return g_strcmp0(girara_setting_get_name(lhs), girara_setting_get_name(rhs));
34 }
35
36 static void
ensure_gettext_initialized(void)37 ensure_gettext_initialized(void)
38 {
39 static gsize initialized = 0;
40 if (g_once_init_enter(&initialized) == TRUE) {
41 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
42 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
43 g_once_init_leave(&initialized, 1);
44 }
45 }
46
47 static void
init_template_engine(GiraraTemplate * csstemplate)48 init_template_engine(GiraraTemplate* csstemplate)
49 {
50 static const char variable_names[][24] = {
51 "session",
52 "default-fg",
53 "default-bg",
54 "inputbar-fg",
55 "inputbar-bg",
56 "statusbar-fg",
57 "statusbar-bg",
58 "completion-fg",
59 "completion-bg",
60 "completion-group-fg",
61 "completion-group-bg",
62 "completion-highlight-fg",
63 "completion-highlight-bg",
64 "notification-error-fg",
65 "notification-error-bg",
66 "notification-warning-fg",
67 "notification-warning-bg",
68 "notification-fg",
69 "notification-bg",
70 "scrollbar-fg",
71 "scrollbar-bg",
72 "bottombox-padding1",
73 "bottombox-padding2",
74 "bottombox-padding3",
75 "bottombox-padding4",
76 "font-family",
77 "font-size",
78 "font-weight"
79 };
80
81 for (size_t idx = 0; idx < LENGTH(variable_names); ++idx) {
82 girara_template_add_variable(csstemplate, variable_names[idx]);
83 }
84 }
85
86 void
css_template_fill_font(GiraraTemplate * csstemplate,const char * font)87 css_template_fill_font(GiraraTemplate* csstemplate, const char* font)
88 {
89 PangoFontDescription* descr = pango_font_description_from_string(font);
90 if (descr == NULL) {
91 return;
92 }
93
94 girara_template_set_variable_value(csstemplate, "font-family",
95 pango_font_description_get_family(descr));
96
97 const int font_size = pango_font_description_get_size(descr);
98 if (!font_size) {
99 girara_debug("no font size given, defaulting to 9");
100 girara_template_set_variable_value(csstemplate, "font-size", "9pt");
101 } else {
102 char* size = g_strdup_printf("%d%s", pango_font_description_get_size(descr) / PANGO_SCALE,
103 pango_font_description_get_size_is_absolute(descr) == FALSE ? "pt" : "");
104 girara_template_set_variable_value(csstemplate, "font-size", size);
105 g_free(size);
106 }
107
108 switch (pango_font_description_get_weight(descr)) {
109 case PANGO_WEIGHT_THIN:
110 girara_template_set_variable_value(csstemplate, "font-weight", "thin");
111 break;
112
113 case PANGO_WEIGHT_ULTRALIGHT:
114 girara_template_set_variable_value(csstemplate, "font-weight", "ultralight");
115 break;
116
117 case PANGO_WEIGHT_SEMILIGHT:
118 girara_template_set_variable_value(csstemplate, "font-weight", "semilight");
119 break;
120
121 case PANGO_WEIGHT_LIGHT:
122 girara_template_set_variable_value(csstemplate, "font-weight", "light");
123 break;
124
125 case PANGO_WEIGHT_BOOK:
126 girara_template_set_variable_value(csstemplate, "font-weight", "book");
127 break;
128
129 case PANGO_WEIGHT_MEDIUM:
130 girara_template_set_variable_value(csstemplate, "font-weight", "medium");
131 break;
132
133 case PANGO_WEIGHT_SEMIBOLD:
134 girara_template_set_variable_value(csstemplate, "font-weight", "semibold");
135 break;
136
137 case PANGO_WEIGHT_BOLD:
138 girara_template_set_variable_value(csstemplate, "font-weight", "bold");
139 break;
140
141 case PANGO_WEIGHT_ULTRABOLD:
142 girara_template_set_variable_value(csstemplate, "font-weight", "ultrabold");
143 break;
144
145 case PANGO_WEIGHT_HEAVY:
146 girara_template_set_variable_value(csstemplate, "font-weight", "heavy");
147 break;
148
149 case PANGO_WEIGHT_ULTRAHEAVY:
150 girara_template_set_variable_value(csstemplate, "font-weight", "ultraheavy");
151 break;
152
153 default:
154 girara_template_set_variable_value(csstemplate, "font-weight", "normal");
155 break;
156 }
157
158 pango_font_description_free(descr);
159 }
160
161 static void
fill_template_with_values(girara_session_t * session)162 fill_template_with_values(girara_session_t* session)
163 {
164 GiraraTemplate* csstemplate = session->private_data->csstemplate;
165
166 girara_template_set_variable_value(csstemplate, "session",
167 session->private_data->session_name);
168
169 char* font = NULL;
170 girara_setting_get(session, "font", &font);
171 if (font != NULL) {
172 css_template_fill_font(csstemplate, font);
173 g_free(font);
174 } else {
175 girara_template_set_variable_value(csstemplate, "font-family", "monospace");
176 girara_template_set_variable_value(csstemplate, "font-size", "9pt");
177 girara_template_set_variable_value(csstemplate, "font-weight", "normal");
178 };
179
180 /* parse color values */
181 static const char* const color_settings[] = {
182 "default-fg",
183 "default-bg",
184 "inputbar-fg",
185 "inputbar-bg",
186 "statusbar-fg",
187 "statusbar-bg",
188 "completion-fg",
189 "completion-bg",
190 "completion-group-fg",
191 "completion-group-bg",
192 "completion-highlight-fg",
193 "completion-highlight-bg",
194 "notification-error-fg",
195 "notification-error-bg",
196 "notification-warning-fg",
197 "notification-warning-bg",
198 "notification-fg",
199 "notification-bg",
200 "scrollbar-fg",
201 "scrollbar-bg"
202 };
203
204 for (size_t i = 0; i < LENGTH(color_settings); ++i) {
205 char* tmp_value = NULL;
206 girara_setting_get(session, color_settings[i], &tmp_value);
207
208 GdkRGBA color = { 0, 0, 0, 0 };
209 if (tmp_value != NULL) {
210 gdk_rgba_parse(&color, tmp_value);
211 g_free(tmp_value);
212 }
213
214 char* colorstr = gdk_rgba_to_string(&color);
215 girara_template_set_variable_value(csstemplate, color_settings[i],
216 colorstr);
217 g_free(colorstr);
218 }
219
220 /* we want inputbar_entry the same height as notification_text and statusbar,
221 so that when inputbar_entry is hidden, the size of the bottom_box remains
222 the same. We need to get rid of the builtin padding in the GtkEntry
223 widget. */
224
225 int ypadding = 2; /* total amount of padding (top + bottom) */
226 int xpadding = 8; /* total amount of padding (left + right) */
227 girara_setting_get(session, "statusbar-h-padding", &xpadding);
228 girara_setting_get(session, "statusbar-v-padding", &ypadding);
229
230 typedef struct padding_mapping_s {
231 const char* identifier;
232 char* value;
233 } padding_mapping_t;
234
235 const padding_mapping_t padding_mapping[] = {
236 {"bottombox-padding1", g_strdup_printf("%d", ypadding - ypadding/2)},
237 {"bottombox-padding2", g_strdup_printf("%d", xpadding/2)},
238 {"bottombox-padding3", g_strdup_printf("%d", ypadding/2)},
239 {"bottombox-padding4", g_strdup_printf("%d", xpadding - xpadding/2)},
240 };
241
242 for (size_t i = 0; i < LENGTH(padding_mapping); ++i) {
243 girara_template_set_variable_value(csstemplate,
244 padding_mapping[i].identifier, padding_mapping[i].value);
245 g_free(padding_mapping[i].value);
246 }
247 }
248
249 static void
css_template_changed(GiraraTemplate * csstemplate,girara_session_t * session)250 css_template_changed(GiraraTemplate* csstemplate, girara_session_t* session)
251 {
252 GtkCssProvider* provider = session->private_data->gtk.cssprovider;
253 char* css_data = girara_template_evaluate(csstemplate);
254 if (css_data == NULL) {
255 girara_error("Error while evaluating templates.");
256 return;
257 }
258
259 if (provider == NULL) {
260 provider = session->private_data->gtk.cssprovider = gtk_css_provider_new();
261
262 /* add CSS style provider */
263 GdkDisplay* display = gdk_display_get_default();
264 GdkScreen* screen = gdk_display_get_default_screen(display);
265 gtk_style_context_add_provider_for_screen(screen,
266 GTK_STYLE_PROVIDER(provider),
267 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
268 }
269
270 GError* error = NULL;
271 if (gtk_css_provider_load_from_data(provider, css_data, -1, &error) == FALSE) {
272 girara_error("Unable to load CSS: %s", error->message);
273 g_free(css_data);
274 g_error_free(error);
275 return;
276 }
277 g_free(css_data);
278 }
279
280 void
scrolled_window_set_scrollbar_visibility(GtkScrolledWindow * window,bool show_horizontal,bool show_vertical)281 scrolled_window_set_scrollbar_visibility(GtkScrolledWindow* window,
282 bool show_horizontal,
283 bool show_vertical)
284 {
285 GtkPolicyType hpolicy = GTK_POLICY_AUTOMATIC;
286 GtkPolicyType vpolicy = GTK_POLICY_AUTOMATIC;
287
288 if (show_horizontal == false) {
289 hpolicy = GTK_POLICY_EXTERNAL;
290 }
291 if (show_vertical == false) {
292 vpolicy = GTK_POLICY_EXTERNAL;
293 }
294
295 gtk_scrolled_window_set_policy(window, hpolicy, vpolicy);
296 }
297
298
299 girara_session_t*
girara_session_create(void)300 girara_session_create(void)
301 {
302 ensure_gettext_initialized();
303
304 girara_session_t* session = g_slice_alloc0(sizeof(girara_session_t));
305 session->private_data = g_slice_alloc0(sizeof(girara_session_private_t));
306
307 girara_session_private_t* session_private = session->private_data;
308
309 /* init values */
310 session->bindings.mouse_events = girara_list_new2(
311 (girara_free_function_t) girara_mouse_event_free);
312 session->bindings.commands = girara_list_new2(
313 (girara_free_function_t) girara_command_free);
314 session->bindings.special_commands = girara_list_new2(
315 (girara_free_function_t) girara_special_command_free);
316 session->bindings.shortcuts = girara_list_new2(
317 (girara_free_function_t) girara_shortcut_free);
318 session->bindings.inputbar_shortcuts = girara_list_new2(
319 (girara_free_function_t) girara_inputbar_shortcut_free);
320
321 session_private->elements.statusbar_items = girara_list_new2(
322 (girara_free_function_t) girara_statusbar_item_free);
323 g_mutex_init(&session_private->feedkeys_mutex);
324
325 /* settings */
326 session_private->settings = girara_sorted_list_new2(
327 cb_sort_settings,
328 (girara_free_function_t) girara_setting_free);
329
330 /* CSS style provider */
331 GResource* css_resource = girara_css_get_resource();
332 GBytes* css_data = g_resource_lookup_data(css_resource, "/org/pwmt/girara/CSS/girara.css_t", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
333 if (css_data != NULL) {
334 session_private->csstemplate = girara_template_new(g_bytes_get_data(css_data, NULL));
335 g_bytes_unref(css_data);
336 }
337
338 session_private->gtk.cssprovider = NULL;
339 init_template_engine(session_private->csstemplate);
340
341 /* init modes */
342 session->modes.identifiers = girara_list_new2(
343 (girara_free_function_t) girara_mode_string_free);
344 girara_mode_t normal_mode = girara_mode_add(session, "normal");
345 girara_mode_t inputbar_mode = girara_mode_add(session, "inputbar");
346 session->modes.normal = normal_mode;
347 session->modes.current_mode = normal_mode;
348 session->modes.inputbar = inputbar_mode;
349
350 /* config handles */
351 session_private->config.handles = girara_list_new2(
352 (girara_free_function_t) girara_config_handle_free);
353 session_private->config.shortcut_mappings = girara_list_new2(
354 (girara_free_function_t) girara_shortcut_mapping_free);
355 session_private->config.argument_mappings = girara_list_new2(
356 (girara_free_function_t) girara_argument_mapping_free);
357
358 /* command history */
359 session->command_history = girara_input_history_new(NULL);
360
361 /* load default values */
362 girara_config_load_default(session);
363
364 /* create widgets */
365 session->gtk.box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
366 session_private->gtk.overlay = gtk_overlay_new();
367 session_private->gtk.bottom_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
368 session->gtk.statusbar_entries = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
369 session->gtk.inputbar_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
370 gtk_box_set_homogeneous(session->gtk.inputbar_box, TRUE);
371 session->gtk.view = gtk_scrolled_window_new(NULL, NULL);
372 session->gtk.viewport = gtk_viewport_new(NULL, NULL);
373 gtk_widget_add_events(session->gtk.viewport, GDK_SCROLL_MASK);
374 session->gtk.statusbar = gtk_event_box_new();
375 session->gtk.notification_area = gtk_event_box_new();
376 session->gtk.notification_text = gtk_label_new(NULL);
377 session->gtk.inputbar_dialog = GTK_LABEL(gtk_label_new(NULL));
378 session->gtk.inputbar_entry = GTK_ENTRY(girara_entry_new());
379 session->gtk.inputbar = gtk_event_box_new();
380
381 /* make notification text selectable */
382 gtk_label_set_selectable(GTK_LABEL(session->gtk.notification_text), TRUE);
383
384 return session;
385 }
386
387 static void
screen_changed(GtkWidget * widget,GdkScreen * GIRARA_UNUSED (old_screen),gpointer GIRARA_UNUSED (userdata))388 screen_changed(GtkWidget* widget, GdkScreen* GIRARA_UNUSED(old_screen), gpointer GIRARA_UNUSED(userdata)) {
389 GdkScreen* screen = gtk_widget_get_screen(widget);
390 GdkVisual* visual = gdk_screen_get_rgba_visual(screen);
391 if (!visual) {
392 visual = gdk_screen_get_system_visual(screen);
393 }
394 gtk_widget_set_visual(widget, visual);
395 }
396
397 bool
girara_session_init(girara_session_t * session,const char * sessionname)398 girara_session_init(girara_session_t* session, const char* sessionname)
399 {
400 if (session == NULL) {
401 return false;
402 }
403
404 /* set session name */
405 session->private_data->session_name = g_strdup(
406 (sessionname == NULL) ? "girara" : sessionname);
407
408 /* enable smooth scroll events */
409 gtk_widget_add_events(session->gtk.viewport, GDK_SMOOTH_SCROLL_MASK);
410
411 /* load CSS style */
412 fill_template_with_values(session);
413 g_signal_connect(G_OBJECT(session->private_data->csstemplate), "changed",
414 G_CALLBACK(css_template_changed), session);
415
416 /* window */
417 #ifdef GDK_WINDOWING_X11
418 if (session->gtk.embed != 0) {
419 session->gtk.window = gtk_plug_new(session->gtk.embed);
420 } else {
421 #endif
422 session->gtk.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
423 #ifdef GDK_WINDOWING_X11
424 }
425 #endif
426
427 gtk_widget_set_name(session->gtk.window, session->private_data->session_name);
428
429 g_signal_connect(G_OBJECT(session->gtk.window), "screen-changed", G_CALLBACK(screen_changed), NULL);
430 screen_changed(GTK_WIDGET(session->gtk.window), NULL, NULL);
431
432 /* apply CSS style */
433 css_template_changed(session->private_data->csstemplate, session);
434
435 GdkGeometry hints = {
436 .base_height = 1,
437 .base_width = 1,
438 .height_inc = 0,
439 .max_aspect = 0,
440 .max_height = 0,
441 .max_width = 0,
442 .min_aspect = 0,
443 .min_height = 0,
444 .min_width = 0,
445 .width_inc = 0
446 };
447
448 gtk_window_set_geometry_hints(GTK_WINDOW(session->gtk.window), NULL, &hints, GDK_HINT_MIN_SIZE);
449
450 /* view */
451 session->signals.view_key_pressed = g_signal_connect(G_OBJECT(session->gtk.view), "key-press-event",
452 G_CALLBACK(girara_callback_view_key_press_event), session);
453
454 session->signals.view_button_press_event = g_signal_connect(G_OBJECT(session->gtk.view), "button-press-event",
455 G_CALLBACK(girara_callback_view_button_press_event), session);
456
457 session->signals.view_button_release_event = g_signal_connect(G_OBJECT(session->gtk.view), "button-release-event",
458 G_CALLBACK(girara_callback_view_button_release_event), session);
459
460 session->signals.view_motion_notify_event = g_signal_connect(G_OBJECT(session->gtk.view), "motion-notify-event",
461 G_CALLBACK(girara_callback_view_button_motion_notify_event), session);
462
463 session->signals.view_scroll_event = g_signal_connect(G_OBJECT(session->gtk.view), "scroll-event",
464 G_CALLBACK(girara_callback_view_scroll_event), session);
465
466 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(session->gtk.view), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
467
468 /* invisible scrollbars */
469 char* guioptions = NULL;
470 girara_setting_get(session, "guioptions", &guioptions);
471
472 const bool show_hscrollbar = guioptions != NULL && strchr(guioptions, 'h') != NULL;
473 const bool show_vscrollbar = guioptions != NULL && strchr(guioptions, 'v') != NULL;
474 g_free(guioptions);
475
476 scrolled_window_set_scrollbar_visibility(
477 GTK_SCROLLED_WINDOW(session->gtk.view), show_hscrollbar, show_vscrollbar);
478
479 /* viewport */
480 gtk_container_add(GTK_CONTAINER(session->gtk.view), session->gtk.viewport);
481 gtk_viewport_set_shadow_type(GTK_VIEWPORT(session->gtk.viewport), GTK_SHADOW_NONE);
482
483 /* statusbar */
484 gtk_container_add(GTK_CONTAINER(session->gtk.statusbar), GTK_WIDGET(session->gtk.statusbar_entries));
485
486 /* notification area */
487 gtk_container_add(GTK_CONTAINER(session->gtk.notification_area), session->gtk.notification_text);
488 gtk_widget_set_halign(session->gtk.notification_text, GTK_ALIGN_START);
489 gtk_widget_set_valign(session->gtk.notification_text, GTK_ALIGN_CENTER);
490 gtk_label_set_use_markup(GTK_LABEL(session->gtk.notification_text), TRUE);
491
492 /* inputbar */
493 gtk_entry_set_has_frame(session->gtk.inputbar_entry, FALSE);
494 gtk_editable_set_editable(GTK_EDITABLE(session->gtk.inputbar_entry), TRUE);
495
496 widget_add_class(GTK_WIDGET(session->gtk.inputbar_entry), "bottom_box");
497 widget_add_class(session->gtk.notification_text, "bottom_box");
498 widget_add_class(GTK_WIDGET(session->gtk.statusbar_entries), "bottom_box");
499
500 session->signals.inputbar_key_pressed = g_signal_connect(
501 G_OBJECT(session->gtk.inputbar_entry),
502 "key-press-event",
503 G_CALLBACK(girara_callback_inputbar_key_press_event),
504 session
505 );
506
507 session->signals.inputbar_changed = g_signal_connect(
508 G_OBJECT(session->gtk.inputbar_entry),
509 "changed",
510 G_CALLBACK(girara_callback_inputbar_changed_event),
511 session
512 );
513
514 session->signals.inputbar_activate = g_signal_connect(
515 G_OBJECT(session->gtk.inputbar_entry),
516 "activate",
517 G_CALLBACK(girara_callback_inputbar_activate),
518 session
519 );
520
521 gtk_box_set_homogeneous(session->gtk.inputbar_box, FALSE);
522 gtk_box_set_spacing(session->gtk.inputbar_box, 5);
523
524 /* inputbar box */
525 gtk_box_pack_start(GTK_BOX(session->gtk.inputbar_box), GTK_WIDGET(session->gtk.inputbar_dialog), FALSE, FALSE, 0);
526 gtk_box_pack_start(GTK_BOX(session->gtk.inputbar_box), GTK_WIDGET(session->gtk.inputbar_entry), TRUE, TRUE, 0);
527 gtk_container_add(GTK_CONTAINER(session->gtk.inputbar), GTK_WIDGET(session->gtk.inputbar_box));
528
529 /* bottom box */
530 gtk_box_set_spacing(session->private_data->gtk.bottom_box, 0);
531
532 gtk_box_pack_end(GTK_BOX(session->private_data->gtk.bottom_box), GTK_WIDGET(session->gtk.inputbar), TRUE, TRUE, 0);
533 gtk_box_pack_end(GTK_BOX(session->private_data->gtk.bottom_box), GTK_WIDGET(session->gtk.notification_area), TRUE, TRUE, 0);
534 gtk_box_pack_end(GTK_BOX(session->private_data->gtk.bottom_box), GTK_WIDGET(session->gtk.statusbar), TRUE, TRUE, 0);
535
536 /* packing */
537 gtk_box_set_spacing(session->gtk.box, 0);
538 gtk_box_pack_start(session->gtk.box, GTK_WIDGET(session->gtk.view), TRUE, TRUE, 0);
539
540 /* box */
541 gtk_container_add(GTK_CONTAINER(session->private_data->gtk.overlay), GTK_WIDGET(session->gtk.box));
542 /* overlay */
543 g_object_set(session->private_data->gtk.bottom_box, "halign", GTK_ALIGN_FILL, NULL);
544 g_object_set(session->private_data->gtk.bottom_box, "valign", GTK_ALIGN_END, NULL);
545
546 gtk_overlay_add_overlay(GTK_OVERLAY(session->private_data->gtk.overlay), GTK_WIDGET(session->private_data->gtk.bottom_box));
547 gtk_container_add(GTK_CONTAINER(session->gtk.window), GTK_WIDGET(session->private_data->gtk.overlay));
548
549 /* statusbar */
550 widget_add_class(GTK_WIDGET(session->gtk.statusbar), "statusbar");
551
552 /* inputbar */
553 widget_add_class(GTK_WIDGET(session->gtk.inputbar_box), "inputbar");
554 widget_add_class(GTK_WIDGET(session->gtk.inputbar_entry), "inputbar");
555 widget_add_class(GTK_WIDGET(session->gtk.inputbar), "inputbar");
556 widget_add_class(GTK_WIDGET(session->gtk.inputbar_dialog), "inputbar");
557
558 /* notification area */
559 widget_add_class(session->gtk.notification_area, "notification");
560 widget_add_class(session->gtk.notification_text, "notification");
561
562 /* set window size */
563 int window_width = 0;
564 int window_height = 0;
565 girara_setting_get(session, "window-width", &window_width);
566 girara_setting_get(session, "window-height", &window_height);
567
568 if (window_width > 0 && window_height > 0) {
569 gtk_window_set_default_size(GTK_WINDOW(session->gtk.window), window_width, window_height);
570 }
571
572 gtk_widget_show_all(GTK_WIDGET(session->gtk.window));
573 gtk_widget_hide(GTK_WIDGET(session->gtk.notification_area));
574 gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar_dialog));
575
576 if (session->global.autohide_inputbar == true) {
577 gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar));
578 }
579
580 if (session->global.hide_statusbar == true) {
581 gtk_widget_hide(GTK_WIDGET(session->gtk.statusbar));
582 }
583
584 char* window_icon = NULL;
585 girara_setting_get(session, "window-icon", &window_icon);
586 if (window_icon != NULL && strlen(window_icon) != 0) {
587 girara_set_window_icon(session, window_icon);
588 }
589 g_free(window_icon);
590
591 gtk_widget_grab_focus(GTK_WIDGET(session->gtk.view));
592
593 return true;
594 }
595
596 static void
girara_session_private_free(girara_session_private_t * session)597 girara_session_private_free(girara_session_private_t* session)
598 {
599 g_return_if_fail(session != NULL);
600
601 if (session->session_name != NULL) {
602 g_free(session->session_name);
603 }
604
605 /* clean up config handles */
606 girara_list_free(session->config.handles);
607 session->config.handles = NULL;
608
609 /* clean up shortcut mappings */
610 girara_list_free(session->config.shortcut_mappings);
611 session->config.shortcut_mappings = NULL;
612
613 /* clean up argument mappings */
614 girara_list_free(session->config.argument_mappings);
615 session->config.argument_mappings = NULL;
616
617 /* clean up buffer */
618 if (session->buffer.command) {
619 g_string_free(session->buffer.command, TRUE);
620 }
621 session->buffer.command = NULL;
622
623 /* clean up statusbar items */
624 girara_list_free(session->elements.statusbar_items);
625 session->elements.statusbar_items = NULL;
626
627 /* clean up CSS style provider */
628 g_clear_object(&session->gtk.cssprovider);
629 g_clear_object(&session->csstemplate);
630
631 /* clean up settings */
632 girara_list_free(session->settings);
633 session->settings = NULL;
634
635 /* clean up mutex */
636 g_mutex_clear(&session->feedkeys_mutex);
637
638 g_slice_free(girara_session_private_t, session);
639 }
640
641 bool
girara_session_destroy(girara_session_t * session)642 girara_session_destroy(girara_session_t* session)
643 {
644 g_return_val_if_fail(session != NULL, FALSE);
645
646 /* clean up shortcuts */
647 girara_list_free(session->bindings.shortcuts);
648 session->bindings.shortcuts = NULL;
649
650 /* clean up inputbar shortcuts */
651 girara_list_free(session->bindings.inputbar_shortcuts);
652 session->bindings.inputbar_shortcuts = NULL;
653
654 /* clean up commands */
655 girara_list_free(session->bindings.commands);
656 session->bindings.commands = NULL;
657
658 /* clean up special commands */
659 girara_list_free(session->bindings.special_commands);
660 session->bindings.special_commands = NULL;
661
662 /* clean up mouse events */
663 girara_list_free(session->bindings.mouse_events);
664 session->bindings.mouse_events = NULL;
665
666 /* clean up input histry */
667 g_clear_object(&session->command_history);
668
669 /* clean up modes */
670 girara_list_free(session->modes.identifiers);
671 session->modes.identifiers = NULL;
672
673 /* clean up buffer */
674 if (session->global.buffer) {
675 g_string_free(session->global.buffer, TRUE);
676 }
677 session->global.buffer = NULL;
678
679 /* clean up private data */
680 girara_session_private_free(session->private_data);
681 session->private_data = NULL;
682
683 /* clean up session */
684 g_slice_free(girara_session_t, session);
685
686 return TRUE;
687 }
688
689 char*
girara_buffer_get(girara_session_t * session)690 girara_buffer_get(girara_session_t* session)
691 {
692 g_return_val_if_fail(session != NULL, NULL);
693
694 return (session->global.buffer) ? g_strdup(session->global.buffer->str) : NULL;
695 }
696
697 void
girara_libnotify(girara_session_t * session,const char * summary,const char * body)698 girara_libnotify(girara_session_t* session, const char *summary,
699 const char *body)
700 {
701 if (session == NULL
702 || summary == NULL
703 || body == NULL) {
704 return;
705 }
706
707 #ifdef WITH_LIBNOTIFY
708 const bool was_initialized = notify_is_initted();
709
710 if (was_initialized == false) {
711 notify_init(session->private_data->session_name);
712 }
713
714 NotifyNotification* libnotify_notification = NULL;
715 char* icon_name = NULL;
716
717 /* We use the NotifyNotification constructor at many branches because
718 * libnotify does not have a notify_notification_set_image_from_name()
719 * function, and accessing private fields is frowned upon and subject to API
720 * changes.
721 */
722 icon_name = g_strdup(gtk_window_get_icon_name(GTK_WINDOW(session->gtk.window)));
723 if (icon_name != NULL) {
724 /* Icon can be loaded from theme with adequate quality for notification */
725 libnotify_notification = notify_notification_new(summary, body, icon_name);
726 g_free(icon_name);
727 } else {
728 /* Or extracted from the current window */
729 GdkPixbuf* icon_pix = gtk_window_get_icon(GTK_WINDOW(session->gtk.window));
730 if (icon_pix != NULL) {
731 libnotify_notification = notify_notification_new(summary, body, NULL);
732 notify_notification_set_image_from_pixbuf(libnotify_notification, icon_pix);
733 g_object_unref(G_OBJECT(icon_pix));
734 } else {
735 /* Or from a default image as a last resort */
736 libnotify_notification = notify_notification_new(summary, body, "info");
737 }
738 }
739
740 g_return_if_fail(libnotify_notification != NULL);
741 notify_notification_show(libnotify_notification, NULL);
742 g_object_unref(G_OBJECT(libnotify_notification));
743
744 if (was_initialized == false) {
745 notify_uninit();
746 }
747 #else
748 girara_notify(session, GIRARA_WARNING, "Girara was compiled without libnotify support.");
749 #endif
750 }
751
752 void
girara_notify(girara_session_t * session,int level,const char * format,...)753 girara_notify(girara_session_t* session, int level, const char* format, ...)
754 {
755 if (session == NULL
756 || session->gtk.notification_text == NULL
757 || session->gtk.notification_area == NULL
758 || session->gtk.inputbar == NULL
759 || session->gtk.view == NULL) {
760 return;
761 }
762
763 if (level == GIRARA_ERROR) {
764 widget_add_class(session->gtk.notification_area, "notification-error");
765 widget_add_class(session->gtk.notification_text, "notification-error");
766 } else {
767 widget_remove_class(session->gtk.notification_area, "notification-error");
768 widget_remove_class(session->gtk.notification_text, "notification-error");
769 }
770 if (level == GIRARA_WARNING) {
771 widget_add_class(session->gtk.notification_area, "notification-warning");
772 widget_add_class(session->gtk.notification_text, "notification-warning");
773 } else {
774 widget_remove_class(session->gtk.notification_area, "notification-warning");
775 widget_remove_class(session->gtk.notification_text, "notification-warning");
776 }
777
778 /* prepare message */
779 va_list ap;
780 va_start(ap, format);
781 char* message = g_strdup_vprintf(format, ap);
782 va_end(ap);
783
784 gtk_label_set_markup(GTK_LABEL(session->gtk.notification_text), message);
785 g_free(message);
786
787 /* update visibility */
788 gtk_widget_show(GTK_WIDGET(session->gtk.notification_area));
789 gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar));
790
791 gtk_widget_grab_focus(GTK_WIDGET(session->gtk.view));
792 }
793
794 void
girara_dialog(girara_session_t * session,const char * dialog,bool invisible,girara_callback_inputbar_key_press_event_t key_press_event,girara_callback_inputbar_activate_t activate_event,void * data)795 girara_dialog(girara_session_t* session, const char* dialog, bool
796 invisible, girara_callback_inputbar_key_press_event_t key_press_event,
797 girara_callback_inputbar_activate_t activate_event, void* data)
798 {
799 if (session == NULL || session->gtk.inputbar == NULL
800 || session->gtk.inputbar_dialog == NULL
801 || session->gtk.inputbar_entry == NULL) {
802 return;
803 }
804
805 gtk_widget_show(GTK_WIDGET(session->gtk.inputbar_dialog));
806
807 /* set dialog message */
808 if (dialog != NULL) {
809 gtk_label_set_markup(session->gtk.inputbar_dialog, dialog);
810 }
811
812 /* set input visibility */
813 if (invisible == true) {
814 gtk_entry_set_visibility(session->gtk.inputbar_entry, FALSE);
815 } else {
816 gtk_entry_set_visibility(session->gtk.inputbar_entry, TRUE);
817 }
818
819 /* set handler */
820 session->signals.inputbar_custom_activate = activate_event;
821 session->signals.inputbar_custom_key_press_event = key_press_event;
822 session->signals.inputbar_custom_data = data;
823
824 /* focus inputbar */
825 girara_sc_focus_inputbar(session, NULL, NULL, 0);
826 }
827
828 bool
girara_set_view(girara_session_t * session,GtkWidget * widget)829 girara_set_view(girara_session_t* session, GtkWidget* widget)
830 {
831 g_return_val_if_fail(session != NULL, false);
832
833 GtkWidget* child = gtk_bin_get_child(GTK_BIN(session->gtk.viewport));
834
835 if (child != NULL) {
836 g_object_ref(child);
837 gtk_container_remove(GTK_CONTAINER(session->gtk.viewport), child);
838 }
839
840 gtk_container_add(GTK_CONTAINER(session->gtk.viewport), widget);
841 gtk_widget_show_all(widget);
842 gtk_widget_grab_focus(session->gtk.view);
843
844 return true;
845 }
846
847 void
girara_mode_set(girara_session_t * session,girara_mode_t mode)848 girara_mode_set(girara_session_t* session, girara_mode_t mode)
849 {
850 g_return_if_fail(session != NULL);
851
852 session->modes.current_mode = mode;
853 }
854
855 girara_mode_t
girara_mode_add(girara_session_t * session,const char * name)856 girara_mode_add(girara_session_t* session, const char* name)
857 {
858 g_return_val_if_fail(session != NULL, FALSE);
859 g_return_val_if_fail(name != NULL && name[0] != '\0', FALSE);
860
861 girara_mode_t last_index = 0;
862 GIRARA_LIST_FOREACH_BODY(session->modes.identifiers, girara_mode_string_t*, mode,
863 if (mode->index > last_index) {
864 last_index = mode->index;
865 }
866 );
867
868 /* create new mode identifier */
869 girara_mode_string_t* mode = g_slice_new(girara_mode_string_t);
870 mode->index = last_index + 1;
871 mode->name = g_strdup(name);
872 girara_list_append(session->modes.identifiers, mode);
873
874 return mode->index;
875 }
876
877 void
girara_mode_string_free(girara_mode_string_t * mode)878 girara_mode_string_free(girara_mode_string_t* mode)
879 {
880 if (mode == NULL) {
881 return;
882 }
883
884 g_free(mode->name);
885 g_slice_free(girara_mode_string_t, mode);
886 }
887
888 girara_mode_t
girara_mode_get(girara_session_t * session)889 girara_mode_get(girara_session_t* session)
890 {
891 g_return_val_if_fail(session != NULL, 0);
892
893 return session->modes.current_mode;
894 }
895
896 bool
girara_set_window_title(girara_session_t * session,const char * name)897 girara_set_window_title(girara_session_t* session, const char* name)
898 {
899 if (session == NULL || session->gtk.window == NULL || name == NULL) {
900 return false;
901 }
902
903 gtk_window_set_title(GTK_WINDOW(session->gtk.window), name);
904
905 return true;
906 }
907
908 bool
girara_set_window_icon(girara_session_t * session,const char * name)909 girara_set_window_icon(girara_session_t* session, const char* name)
910 {
911 if (session == NULL || session->gtk.window == NULL || name == NULL) {
912 return false;
913 }
914
915 if (strlen(name) == 0) {
916 girara_warning("Empty icon name.");
917 return false;
918 }
919
920 GtkWindow* window = GTK_WINDOW(session->gtk.window);
921 char* path = girara_fix_path(name);
922 bool success = true;
923
924 if (g_file_test(path, G_FILE_TEST_EXISTS) == TRUE) {
925 girara_debug("Loading window icon from file: %s", path);
926
927 GError* error = NULL;
928 success = gtk_window_set_icon_from_file(window, path, &error);
929
930 if (success == false) {
931 girara_debug("Failed to load window icon (file): %s", error->message);
932 g_error_free(error);
933 }
934 } else {
935 girara_debug("Loading window icon with name: %s", name);
936 gtk_window_set_icon_name(window, name);
937 }
938
939 g_free(path);
940 return success;
941 }
942
943 girara_list_t*
girara_get_command_history(girara_session_t * session)944 girara_get_command_history(girara_session_t* session)
945 {
946 g_return_val_if_fail(session != NULL, NULL);
947 return girara_input_history_list(session->command_history);
948 }
949
950 GiraraTemplate*
girara_session_get_template(girara_session_t * session)951 girara_session_get_template(girara_session_t* session)
952 {
953 g_return_val_if_fail(session != NULL, NULL);
954
955 return session->private_data->csstemplate;
956 }
957
958 void
girara_session_set_template(girara_session_t * session,GiraraTemplate * template,bool init_variables)959 girara_session_set_template(girara_session_t* session, GiraraTemplate* template, bool init_variables)
960 {
961 g_return_if_fail(session != NULL);
962 g_return_if_fail(template != NULL);
963
964 g_clear_object(&session->private_data->csstemplate);
965
966 session->private_data->csstemplate = template;
967 if (init_variables == true) {
968 init_template_engine(template);
969 fill_template_with_values(session);
970 }
971
972 css_template_changed(template, session);
973 }
974
975