1 /*
2 * This is free software; you can redistribute it and/or modify it under
3 * the terms of the GNU Library General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * General Public License for more details.
11 *
12 * You should have received a copy of the GNU Library General Public
13 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
14 */
15
16 #include <tilda-config.h>
17
18 #include "debug.h"
19 #include "tilda.h"
20 #include "configsys.h"
21 #include "tilda_window.h"
22 #include "tilda_terminal.h"
23 #include "key_grabber.h"
24 #include "vte-util.h"
25
26 #include <math.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <glib-object.h>
33 #include <glib.h>
34 #include <glib/gi18n.h>
35 #include <vte/vte.h>
36 #include <X11/Xlib.h>
37 #include <gdk/gdkx.h>
38
39 static tilda_term* tilda_window_get_current_terminal (tilda_window *tw);
40
41 static gboolean show_confirmation_dialog (tilda_window *tw,
42 const char *message);
43
44 static void tilda_window_apply_transparency (tilda_window *tw,
45 gboolean status);
46
47 static gboolean update_tilda_window_size (gpointer user_data);
48
49 static GdkFilterReturn window_filter_function (GdkXEvent *xevent,
50 GdkEvent *event,
51 gpointer data);
52
53 static void
tilda_window_setup_alpha_mode(tilda_window * tw)54 tilda_window_setup_alpha_mode (tilda_window *tw)
55 {
56 GdkScreen *screen;
57 GdkVisual *visual;
58
59 screen = gtk_widget_get_screen (GTK_WIDGET (tw->window));
60 visual = gdk_screen_get_rgba_visual (screen);
61 if (visual == NULL) {
62 visual = gdk_screen_get_system_visual (screen);
63 }
64 if (visual != NULL && gdk_screen_is_composited (screen)) {
65 /* Set RGBA colormap if possible so VTE can use real alpha
66 * channels for transparency. */
67
68 gtk_widget_set_visual (GTK_WIDGET (tw->window), visual);
69 tw->have_argb_visual = TRUE;
70 } else {
71 tw->have_argb_visual = FALSE;
72 }
73 }
74
75
find_tt_in_g_list(tilda_window * tw,gint pos)76 static tilda_term* find_tt_in_g_list (tilda_window *tw, gint pos)
77 {
78 DEBUG_FUNCTION ("find_tt_in_g_list");
79 DEBUG_ASSERT (tw != NULL);
80 DEBUG_ASSERT (tw->terms != NULL);
81
82 GtkWidget *page, *current_page;
83 GList *terms = g_list_first (tw->terms);
84 tilda_term *result=NULL;
85
86 current_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (tw->notebook), pos);
87
88 do
89 {
90 page = TILDA_TERM(terms->data)->hbox;
91 if (page == current_page)
92 {
93 result = terms->data;
94 break;
95 }
96 } while ((terms = terms->next) != NULL);
97
98 return result;
99 }
100
tilda_window_close_current_tab(tilda_window * tw)101 void tilda_window_close_current_tab (tilda_window *tw)
102 {
103 DEBUG_FUNCTION ("close_current_tab");
104 DEBUG_ASSERT (tw != NULL);
105
106 gboolean can_close = TRUE;
107
108 if (config_getbool ("confirm_close_tab")) {
109 char * message = _("Are you sure you want to close this tab?");
110
111 can_close = show_confirmation_dialog (tw, message);
112 }
113
114 if (can_close) {
115 gint pos = gtk_notebook_get_current_page (GTK_NOTEBOOK (tw->notebook));
116 tilda_window_close_tab (tw, pos, FALSE);
117 }
118 }
119
120 static gboolean
show_confirmation_dialog(tilda_window * tw,const char * message)121 show_confirmation_dialog (tilda_window *tw, const char *message)
122 {
123 gboolean result;
124
125 GtkWidget *dialog
126 = gtk_message_dialog_new (GTK_WINDOW (tw->window),
127 GTK_DIALOG_DESTROY_WITH_PARENT,
128 GTK_MESSAGE_QUESTION,
129 GTK_BUTTONS_OK_CANCEL,
130 NULL);
131
132 gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG(dialog), message);
133
134 gtk_window_set_keep_above (GTK_WINDOW (dialog), TRUE);
135 gint response = gtk_dialog_run (GTK_DIALOG (dialog));
136
137 switch (response) {
138 case GTK_RESPONSE_OK:
139 result = TRUE;
140 break;
141 default:
142 result = FALSE;
143 break;
144 }
145
146 gtk_widget_destroy (dialog);
147
148 return result;
149 }
150
tilda_window_set_tab_position(tilda_window * tw,enum notebook_tab_positions pos)151 gint tilda_window_set_tab_position (tilda_window *tw, enum notebook_tab_positions pos)
152 {
153 const GtkPositionType gtk_pos[] = {GTK_POS_TOP, GTK_POS_BOTTOM, GTK_POS_LEFT, GTK_POS_RIGHT };
154
155 if ((pos < 0) || (pos > 4)) {
156 g_printerr (_("You have a bad tab_pos in your configuration file\n"));
157 pos = NB_TOP;
158 }
159
160 if(NB_HIDDEN == pos) {
161 gtk_notebook_set_show_tabs (GTK_NOTEBOOK(tw->notebook), FALSE);
162 }
163 else {
164 gtk_notebook_set_tab_pos (GTK_NOTEBOOK (tw->notebook), gtk_pos[pos]);
165 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (tw->notebook)) > 1)
166 gtk_notebook_set_show_tabs (GTK_NOTEBOOK(tw->notebook), TRUE);
167 }
168
169 return 0;
170 }
171
172
tilda_window_set_fullscreen(tilda_window * tw)173 void tilda_window_set_fullscreen(tilda_window *tw)
174 {
175 DEBUG_FUNCTION ("tilda_window_set_fullscreen");
176 DEBUG_ASSERT (tw != NULL);
177
178 if (tw->fullscreen == TRUE) {
179 gtk_window_fullscreen (GTK_WINDOW (tw->window));
180 }
181 else {
182 gtk_window_unfullscreen (GTK_WINDOW (tw->window));
183 }
184 }
185
toggle_fullscreen_cb(tilda_window * tw)186 gint toggle_fullscreen_cb (tilda_window *tw)
187 {
188 DEBUG_FUNCTION ("toggle_fullscreen_cb");
189 DEBUG_ASSERT (tw != NULL);
190
191 tw->fullscreen = !tw->fullscreen;
192
193 tilda_window_set_fullscreen(tw);
194
195 // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the
196 // keystroke into the vte terminal widget.
197 return GDK_EVENT_STOP;
198 }
199
toggle_transparency_cb(tilda_window * tw)200 static gint toggle_transparency_cb (tilda_window *tw)
201 {
202 DEBUG_FUNCTION ("toggle_transparency");
203 DEBUG_ASSERT (tw != NULL);
204 tilda_window_toggle_transparency(tw);
205 return GDK_EVENT_STOP;
206 }
207
tilda_window_toggle_transparency(tilda_window * tw)208 void tilda_window_toggle_transparency (tilda_window *tw)
209 {
210 gboolean status = !config_getbool ("enable_transparency");
211 config_setbool ("enable_transparency", status);
212 tilda_window_apply_transparency (tw, status);
213 }
214
tilda_window_refresh_transparency(tilda_window * tw)215 void tilda_window_refresh_transparency (tilda_window *tw)
216 {
217 gboolean status = config_getbool ("enable_transparency");
218 tilda_window_apply_transparency (tw, status);
219 }
220
tilda_window_apply_transparency(tilda_window * tw,gboolean status)221 static void tilda_window_apply_transparency (tilda_window *tw, gboolean status)
222 {
223 tilda_term *tt;
224 guint i;
225
226 GdkRGBA bg;
227 bg.red = GUINT16_TO_FLOAT(config_getint ("back_red"));
228 bg.green = GUINT16_TO_FLOAT(config_getint ("back_green"));
229 bg.blue = GUINT16_TO_FLOAT(config_getint ("back_blue"));
230 bg.alpha = (status ? GUINT16_TO_FLOAT(config_getint ("back_alpha")) : 1.0);
231
232 for (i=0; i<g_list_length (tw->terms); i++) {
233 tt = g_list_nth_data (tw->terms, i);
234 vte_terminal_set_color_background(VTE_TERMINAL(tt->vte_term), &bg);
235 }
236 }
237
238 static gboolean
search_gregex_cb(TildaSearchBox * search,GRegex * regex,TildaSearchDirection direction,gboolean wrap_on_search,tilda_window * tw)239 search_gregex_cb (TildaSearchBox *search,
240 GRegex *regex,
241 TildaSearchDirection direction,
242 gboolean wrap_on_search,
243 tilda_window *tw)
244 {
245 VteTerminal *vte_terminal;
246 tilda_term *term;
247
248 term = tilda_window_get_current_terminal (tw);
249
250 vte_terminal = VTE_TERMINAL (term->vte_term);
251
252 vte_terminal_search_set_gregex (vte_terminal, regex, (GRegexMatchFlags) 0);
253
254 vte_terminal_search_set_wrap_around (vte_terminal, wrap_on_search);
255
256 if (direction == SEARCH_BACKWARD)
257 return vte_terminal_search_find_previous (vte_terminal);
258 else
259 return vte_terminal_search_find_next (vte_terminal);
260 }
261
262 static gboolean
search_cb(TildaSearchBox * search,VteRegex * regex,TildaSearchDirection direction,gboolean wrap_on_search,tilda_window * tw)263 search_cb (TildaSearchBox *search,
264 VteRegex *regex,
265 TildaSearchDirection direction,
266 gboolean wrap_on_search,
267 tilda_window *tw)
268 {
269 VteTerminal *vte_terminal;
270 tilda_term *term;
271
272 term = tilda_window_get_current_terminal (tw);
273
274 vte_terminal = VTE_TERMINAL (term->vte_term);
275
276 vte_terminal_search_set_regex (vte_terminal, regex, (GRegexMatchFlags) 0);
277
278 vte_terminal_search_set_wrap_around (vte_terminal, wrap_on_search);
279
280 if (direction == SEARCH_BACKWARD)
281 return vte_terminal_search_find_previous (vte_terminal);
282 else
283 return vte_terminal_search_find_next (vte_terminal);
284 }
285
286 static void
search_focus_out_cb(TildaSearchBox * box,tilda_window * tw)287 search_focus_out_cb (TildaSearchBox *box,
288 tilda_window *tw)
289 {
290 gtk_widget_grab_focus (tilda_window_get_current_terminal (tw)->vte_term);
291 }
292
293 static gboolean
toggle_searchbar_cb(tilda_window * tw)294 toggle_searchbar_cb (tilda_window *tw)
295 {
296 tilda_window_toggle_searchbar (tw);
297
298 return GDK_EVENT_STOP;
299 }
300
301 void
tilda_window_toggle_searchbar(tilda_window * tw)302 tilda_window_toggle_searchbar (tilda_window *tw)
303 {
304 tilda_search_box_toggle (TILDA_SEARCH_BOX (tw->search));
305 }
306
307 /* Zoom helpers */
308 static const double zoom_factors[] = {
309 TERMINAL_SCALE_MINIMUM,
310 TERMINAL_SCALE_XXXXX_SMALL,
311 TERMINAL_SCALE_XXXX_SMALL,
312 TERMINAL_SCALE_XXX_SMALL,
313 PANGO_SCALE_XX_SMALL,
314 PANGO_SCALE_X_SMALL,
315 PANGO_SCALE_SMALL,
316 PANGO_SCALE_MEDIUM,
317 PANGO_SCALE_LARGE,
318 PANGO_SCALE_X_LARGE,
319 PANGO_SCALE_XX_LARGE,
320 TERMINAL_SCALE_XXX_LARGE,
321 TERMINAL_SCALE_XXXX_LARGE,
322 TERMINAL_SCALE_XXXXX_LARGE,
323 TERMINAL_SCALE_MAXIMUM
324 };
325
find_larger_zoom_factor(double current,double * found)326 static gboolean find_larger_zoom_factor (double current, double *found) {
327 guint i;
328
329 for (i = 0; i < G_N_ELEMENTS (zoom_factors); ++i)
330 {
331 /* Find a font that's larger than this one */
332 if ((zoom_factors[i] - current) > 1e-6)
333 {
334 *found = zoom_factors[i];
335 return TRUE;
336 }
337 }
338
339 return FALSE;
340 }
341
find_smaller_zoom_factor(double current,double * found)342 static gboolean find_smaller_zoom_factor (double current, double *found) {
343 int i;
344
345 i = (int) G_N_ELEMENTS (zoom_factors) - 1;
346 while (i >= 0)
347 {
348 /* Find a font that's smaller than this one */
349 if ((current - zoom_factors[i]) > 1e-6)
350 {
351 *found = zoom_factors[i];
352 return TRUE;
353 }
354
355 --i;
356 }
357
358 return FALSE;
359 }
360
361 /* Increase and Decrease and reset affects all tabs at once */
normalize_font_size(tilda_window * tw)362 static gboolean normalize_font_size(tilda_window *tw)
363 {
364 tilda_term *tt;
365 guint i;
366 tw->current_scale_factor = PANGO_SCALE_MEDIUM;
367
368 for (i=0; i<g_list_length (tw->terms); i++) {
369 tt = g_list_nth_data (tw->terms, i);
370 tilda_term_adjust_font_scale(tt, tw->current_scale_factor);
371 }
372 return GDK_EVENT_STOP;
373 }
374
increase_font_size(tilda_window * tw)375 static gboolean increase_font_size (tilda_window *tw)
376 {
377 tilda_term *tt;
378 guint i;
379 if(!find_larger_zoom_factor (tw->current_scale_factor, &tw->current_scale_factor)) {
380 return GDK_EVENT_STOP;
381 }
382
383 for (i=0; i<g_list_length (tw->terms); i++) {
384 tt = g_list_nth_data (tw->terms, i);
385 tilda_term_adjust_font_scale(tt, tw->current_scale_factor);
386 }
387 return GDK_EVENT_STOP;
388 }
389
decrease_font_size(tilda_window * tw)390 static gboolean decrease_font_size (tilda_window *tw)
391 {
392 tilda_term *tt;
393 guint i;
394 if(!find_smaller_zoom_factor (tw->current_scale_factor, &tw->current_scale_factor)) {
395 return GDK_EVENT_STOP;
396 }
397
398 for (i=0; i<g_list_length (tw->terms); i++) {
399 tt = g_list_nth_data (tw->terms, i);
400 tilda_term_adjust_font_scale(tt, tw->current_scale_factor);
401 }
402 return GDK_EVENT_STOP;
403 }
404
tilda_window_next_tab(tilda_window * tw)405 gint tilda_window_next_tab (tilda_window *tw)
406 {
407 DEBUG_FUNCTION ("next_tab");
408 DEBUG_ASSERT (tw != NULL);
409
410 int num_pages;
411 int current_page;
412 GtkNotebook *notebook;
413
414 notebook = GTK_NOTEBOOK (tw->notebook);
415
416 /* If we are on the last page, go to first page */
417 num_pages = gtk_notebook_get_n_pages (notebook);
418 current_page = gtk_notebook_get_current_page (notebook);
419
420 if ((num_pages - 1) == current_page)
421 gtk_notebook_set_current_page (notebook, 0);
422 else
423 gtk_notebook_next_page (notebook);
424
425 // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the
426 // keystroke into the vte terminal widget.
427 return GDK_EVENT_STOP;
428 }
429
tilda_window_prev_tab(tilda_window * tw)430 gint tilda_window_prev_tab (tilda_window *tw)
431 {
432 DEBUG_FUNCTION ("prev_tab");
433 DEBUG_ASSERT (tw != NULL);
434
435 int num_pages;
436 int current_page;
437 GtkNotebook *notebook;
438
439 notebook = GTK_NOTEBOOK (tw->notebook);
440
441 num_pages = gtk_notebook_get_n_pages (notebook);
442 current_page = gtk_notebook_get_current_page (notebook);
443
444 if (current_page == 0)
445 gtk_notebook_set_current_page (notebook, (num_pages - 1));
446 else
447 gtk_notebook_prev_page (notebook);
448
449 // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the
450 // keystroke into the vte terminal widget.
451 return GDK_EVENT_STOP;
452 }
453
454 enum tab_direction { TAB_LEFT = -1, TAB_RIGHT = 1 };
455
move_tab(tilda_window * tw,int direction)456 static gint move_tab (tilda_window *tw, int direction)
457 {
458 DEBUG_FUNCTION ("move_tab");
459 DEBUG_ASSERT (tw != NULL);
460
461 int num_pages;
462 int current_page_index;
463 int new_page_index;
464 GtkWidget* current_page;
465 GtkNotebook* notebook;
466
467 notebook = GTK_NOTEBOOK (tw->notebook);
468
469 num_pages = gtk_notebook_get_n_pages (notebook);
470
471 if (num_pages > 1) {
472 current_page_index = gtk_notebook_get_current_page (notebook);
473 current_page = gtk_notebook_get_nth_page (notebook,
474 current_page_index);
475
476 /* wrap over if new_page_index over-/underflows */
477 new_page_index = (current_page_index + direction) % num_pages;
478
479 gtk_notebook_reorder_child (notebook, current_page, new_page_index);
480 }
481
482 // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the
483 // keystroke into the vte terminal widget.
484 return GDK_EVENT_STOP;
485
486 }
move_tab_left(tilda_window * tw)487 static gint move_tab_left (tilda_window *tw)
488 {
489 DEBUG_FUNCTION ("move_tab_left");
490 return move_tab(tw, TAB_LEFT);
491 }
492
move_tab_right(tilda_window * tw)493 static gint move_tab_right (tilda_window *tw)
494 {
495 DEBUG_FUNCTION ("move_tab_right");
496 return move_tab(tw, TAB_RIGHT);
497 }
498
focus_term(GtkWidget * widget,gpointer data)499 static gboolean focus_term (GtkWidget *widget, gpointer data)
500 {
501 DEBUG_FUNCTION ("focus_term");
502 DEBUG_ASSERT (data != NULL);
503 DEBUG_ASSERT (widget != NULL);
504
505 GList *list;
506 GtkWidget *box;
507 tilda_window *tw = TILDA_WINDOW(data);
508 GtkWidget *n = GTK_WIDGET (tw->notebook);
509
510 box = gtk_notebook_get_nth_page (GTK_NOTEBOOK(n), gtk_notebook_get_current_page(GTK_NOTEBOOK(n)));
511 if (box != NULL) {
512 list = gtk_container_get_children (GTK_CONTAINER(box));
513 gtk_widget_grab_focus (list->data);
514 }
515
516 // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the
517 // keystroke into the vte terminal widget.
518 return GDK_EVENT_STOP;
519 }
520
auto_hide_tick(gpointer data)521 static gboolean auto_hide_tick(gpointer data)
522 {
523 DEBUG_FUNCTION ("auto_hide_tick");
524 DEBUG_ASSERT (data != NULL);
525
526 tilda_window *tw = TILDA_WINDOW(data);
527 tw->auto_hide_current_time += tw->timer_resolution;
528 if ((tw->auto_hide_current_time >= tw->auto_hide_max_time) || tw->current_state == STATE_UP)
529 {
530 pull(tw, PULL_UP, TRUE);
531 tw->auto_hide_tick_handler = 0;
532 return FALSE;
533 }
534
535 return TRUE;
536 }
537
538 /* Start auto hide tick */
start_auto_hide_tick(tilda_window * tw)539 static void start_auto_hide_tick(tilda_window *tw)
540 {
541 DEBUG_FUNCTION("start_auto_hide_tick");
542 // If the delay is less or equal to 1000ms then the timer is not precise
543 // enough, because it takes already about 200ms to register it, so we
544 // rather sleep for the given amount of time.
545 const guint32 MAX_SLEEP_TIME = 1000;
546
547 if ((tw->auto_hide_tick_handler == 0) && (tw->disable_auto_hide == FALSE)) {
548 /* If there is no timer registered yet, then the auto_hide_tick_handler
549 * has a value of zero. Next we need to make sure that the auto hide
550 * max time is greater then zero, or else we can pull up immediately,
551 * without the trouble of registering a timer.
552 */
553 if (tw->auto_hide_max_time > MAX_SLEEP_TIME) {
554 tw->auto_hide_current_time = 0;
555 tw->auto_hide_tick_handler = g_timeout_add(tw->timer_resolution, auto_hide_tick, tw);
556 } else if (tw->auto_hide_max_time > 0 && tw->auto_hide_max_time <= MAX_SLEEP_TIME) {
557 // auto_hide_max_time is in milli seconds, so we need to convert to
558 // microseconds by multiplying with 1000
559 g_usleep (tw->auto_hide_max_time * 1000);
560 pull(tw, PULL_UP, TRUE);
561 } else {
562 pull(tw, PULL_UP, TRUE);
563 }
564 }
565 }
566
567 /* Stop auto hide tick */
stop_auto_hide_tick(tilda_window * tw)568 static void stop_auto_hide_tick(tilda_window *tw)
569 {
570 if (tw->auto_hide_tick_handler != 0)
571 {
572 g_source_remove(tw->auto_hide_tick_handler);
573 tw->auto_hide_tick_handler = 0;
574 }
575 }
576
mouse_enter(GtkWidget * widget,G_GNUC_UNUSED GdkEvent * event,gpointer data)577 static gboolean mouse_enter (GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, gpointer data)
578 {
579 DEBUG_FUNCTION ("mouse_enter");
580 DEBUG_ASSERT (data != NULL);
581 DEBUG_ASSERT (widget != NULL);
582
583 tilda_window *tw = TILDA_WINDOW(data);
584 stop_auto_hide_tick(tw);
585
586 return GDK_EVENT_STOP;
587 }
588
mouse_leave(GtkWidget * widget,G_GNUC_UNUSED GdkEvent * event,gpointer data)589 static gboolean mouse_leave (GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, gpointer data)
590 {
591 DEBUG_FUNCTION ("mouse_leave");
592 DEBUG_ASSERT (data != NULL);
593 DEBUG_ASSERT (widget != NULL);
594
595 GdkEventCrossing *ev = (GdkEventCrossing*)event;
596 tilda_window *tw = TILDA_WINDOW(data);
597
598 if ((ev->mode != GDK_CROSSING_NORMAL) || (tw->auto_hide_on_mouse_leave == FALSE))
599 return GDK_EVENT_STOP;
600
601 start_auto_hide_tick(tw);
602
603 return GDK_EVENT_STOP;
604 }
605
focus_out_event_cb(GtkWidget * widget,G_GNUC_UNUSED GdkEvent * event,gpointer data)606 static gboolean focus_out_event_cb (GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, gpointer data)
607 {
608 DEBUG_FUNCTION ("focus_out_event_cb");
609 DEBUG_ASSERT (data != NULL);
610 DEBUG_ASSERT (widget != NULL);
611
612 tilda_window *tw = TILDA_WINDOW(data);
613
614 /**
615 * When the tilda 'key' to pull down/up the tilda window is pressed, then tilda will inevitably loose focus. The
616 * problem is that we cannot distinguish whether it was focused before the key press occurred. Checking the xevent
617 * type here allows us to distinguish these two cases:
618 *
619 * * We loose focus because of a KeyPress event
620 * * We loose focus because another window gained focus or some other reason.
621 *
622 * Depending on one of the two cases we set the tw->focus_loss_on_keypress to true or false. We can then
623 * check this flag in the pull() function that shows or hides the tilda window. This helps us to decide if
624 * we just need to focus tilda or if we should hide it.
625 */
626 XEvent xevent;
627 XPeekEvent(gdk_x11_display_get_xdisplay(gdk_window_get_display(gtk_widget_get_window(tw->window))), &xevent);
628
629 if(xevent.type == KeyPress) {
630 tw->focus_loss_on_keypress = TRUE;
631 } else {
632 tw->focus_loss_on_keypress = FALSE;
633 }
634
635 if (tw->auto_hide_on_focus_lost == FALSE)
636 return GDK_EVENT_PROPAGATE;
637
638 start_auto_hide_tick(tw);
639
640 return GDK_EVENT_PROPAGATE;
641 }
642
goto_tab(tilda_window * tw,guint i)643 static void goto_tab (tilda_window *tw, guint i)
644 {
645 DEBUG_FUNCTION ("goto_tab");
646 DEBUG_ASSERT (tw != NULL);
647
648 gtk_notebook_set_current_page (GTK_NOTEBOOK (tw->notebook), i);
649 }
650
goto_tab_generic(tilda_window * tw,guint tab_number)651 static gboolean goto_tab_generic (tilda_window *tw, guint tab_number)
652 {
653 DEBUG_FUNCTION ("goto_tab_generic");
654 DEBUG_ASSERT (tw != NULL);
655
656 if (g_list_length (tw->terms) > (tab_number-1))
657 {
658 goto_tab (tw, tab_number - 1);
659 }
660
661 return GDK_EVENT_STOP;
662 }
663
664 /* These all just call the generic function since they're all basically the same
665 * anyway. Unfortunately, they can't just be macros, since we need to be able to
666 * create a pointer to them for callbacks. */
goto_tab_1(tilda_window * tw)667 static gboolean goto_tab_1 (tilda_window *tw) { return goto_tab_generic (tw, 1); }
goto_tab_2(tilda_window * tw)668 static gboolean goto_tab_2 (tilda_window *tw) { return goto_tab_generic (tw, 2); }
goto_tab_3(tilda_window * tw)669 static gboolean goto_tab_3 (tilda_window *tw) { return goto_tab_generic (tw, 3); }
goto_tab_4(tilda_window * tw)670 static gboolean goto_tab_4 (tilda_window *tw) { return goto_tab_generic (tw, 4); }
goto_tab_5(tilda_window * tw)671 static gboolean goto_tab_5 (tilda_window *tw) { return goto_tab_generic (tw, 5); }
goto_tab_6(tilda_window * tw)672 static gboolean goto_tab_6 (tilda_window *tw) { return goto_tab_generic (tw, 6); }
goto_tab_7(tilda_window * tw)673 static gboolean goto_tab_7 (tilda_window *tw) { return goto_tab_generic (tw, 7); }
goto_tab_8(tilda_window * tw)674 static gboolean goto_tab_8 (tilda_window *tw) { return goto_tab_generic (tw, 8); }
goto_tab_9(tilda_window * tw)675 static gboolean goto_tab_9 (tilda_window *tw) { return goto_tab_generic (tw, 9); }
goto_tab_10(tilda_window * tw)676 static gboolean goto_tab_10 (tilda_window *tw) { return goto_tab_generic (tw, 10); }
677
ccopy(tilda_window * tw)678 static gint ccopy (tilda_window *tw)
679 {
680 DEBUG_FUNCTION ("ccopy");
681 DEBUG_ASSERT (tw != NULL);
682 DEBUG_ASSERT (tw->notebook != NULL);
683
684 GtkWidget *current_page;
685 GList *list;
686
687 gint pos = gtk_notebook_get_current_page (GTK_NOTEBOOK (tw->notebook));
688 current_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (tw->notebook), pos);
689 list = gtk_container_get_children (GTK_CONTAINER(current_page));
690 if GTK_IS_SCROLLBAR(list->data) {
691 list = list->next;
692 }
693
694 vte_terminal_copy_clipboard_format (VTE_TERMINAL(list->data), VTE_FORMAT_TEXT);
695
696 return GDK_EVENT_STOP;
697 }
698
cpaste(tilda_window * tw)699 static gint cpaste (tilda_window *tw)
700 {
701 DEBUG_FUNCTION ("cpaste");
702 DEBUG_ASSERT (tw != NULL);
703 DEBUG_ASSERT (tw->notebook != NULL);
704
705 GtkWidget *current_page;
706 GList *list;
707
708 gint pos = gtk_notebook_get_current_page (GTK_NOTEBOOK (tw->notebook));
709 current_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (tw->notebook), pos);
710 list = gtk_container_get_children (GTK_CONTAINER (current_page));
711 if GTK_IS_SCROLLBAR(list->data) {
712 list = list->next;
713 }
714 vte_terminal_paste_clipboard (VTE_TERMINAL(list->data));
715
716 return GDK_EVENT_STOP;
717 }
718
719 /* Tie a single keyboard shortcut to a callback function */
tilda_add_config_accelerator_by_path(const gchar * key,const gchar * path,GCallback callback_func,tilda_window * tw)720 static gint tilda_add_config_accelerator_by_path(const gchar* key, const gchar* path, GCallback callback_func, tilda_window *tw)
721 {
722 guint accel_key;
723 GdkModifierType accel_mods;
724 GClosure *temp;
725
726 gtk_accelerator_parse (config_getstr(key), &accel_key, &accel_mods);
727 if (! ((accel_key == 0) && (accel_mods == 0)) ) // make sure it parsed properly
728 {
729 temp = g_cclosure_new_swap (callback_func, tw, NULL);
730 gtk_accel_map_add_entry(path, accel_key, accel_mods);
731 gtk_accel_group_connect_by_path(tw->accel_group, path, temp);
732 }
733
734 return 0;
735 }
736
tilda_window_update_keyboard_accelerators(const gchar * path,const gchar * value)737 gboolean tilda_window_update_keyboard_accelerators (const gchar* path, const gchar* value) {
738 guint accel_key;
739 GdkModifierType accel_mods;
740 gtk_accelerator_parse (value, &accel_key, &accel_mods);
741
742 return gtk_accel_map_change_entry(path, accel_key, accel_mods, FALSE);
743 }
744
745 /* This function does the setup of the keyboard acceleratos. It should only be called once when the tilda window is
746 * initialized. Use tilda_window_update_keyboard_accelerators to update keybindings that have been changed by the user.
747 */
tilda_window_setup_keyboard_accelerators(tilda_window * tw)748 static gint tilda_window_setup_keyboard_accelerators (tilda_window *tw)
749 {
750
751 /* Create Accel Group to add key codes for quit, next, prev and new tabs */
752 tw->accel_group = gtk_accel_group_new ();
753 gtk_window_add_accel_group (GTK_WINDOW (tw->window), tw->accel_group);
754
755 /* Set up keyboard shortcuts for Exit, Next Tab, Previous Tab,
756 Move Tab, Add Tab, Close Tab, Copy, and Paste using key
757 combinations defined in the config. */
758 tilda_add_config_accelerator_by_path("addtab_key", "<tilda>/context/New Tab", G_CALLBACK(tilda_window_add_tab), tw);
759 tilda_add_config_accelerator_by_path("closetab_key", "<tilda>/context/Close Tab", G_CALLBACK(tilda_window_close_current_tab), tw);
760 tilda_add_config_accelerator_by_path("copy_key", "<tilda>/context/Copy", G_CALLBACK(ccopy), tw);
761 tilda_add_config_accelerator_by_path("paste_key", "<tilda>/context/Paste", G_CALLBACK(cpaste), tw);
762 tilda_add_config_accelerator_by_path("fullscreen_key", "<tilda>/context/Toggle Fullscreen", G_CALLBACK(toggle_fullscreen_cb), tw);
763 tilda_add_config_accelerator_by_path("quit_key", "<tilda>/context/Quit", G_CALLBACK(tilda_window_confirm_quit), tw);
764 tilda_add_config_accelerator_by_path("toggle_transparency_key", "<tilda>/context/Toggle Transparency", G_CALLBACK(toggle_transparency_cb), tw);
765 tilda_add_config_accelerator_by_path("toggle_searchbar_key", "<tilda>/context/Toggle Searchbar", G_CALLBACK(toggle_searchbar_cb), tw);
766
767 tilda_add_config_accelerator_by_path("nexttab_key", "<tilda>/context/Next Tab", G_CALLBACK(tilda_window_next_tab), tw);
768 tilda_add_config_accelerator_by_path("prevtab_key", "<tilda>/context/Previous Tab", G_CALLBACK(tilda_window_prev_tab), tw);
769 tilda_add_config_accelerator_by_path("movetableft_key", "<tilda>/context/Move Tab Left", G_CALLBACK(move_tab_left), tw);
770 tilda_add_config_accelerator_by_path("movetabright_key", "<tilda>/context/Move Tab Right", G_CALLBACK(move_tab_right), tw);
771
772 tilda_add_config_accelerator_by_path("increase_font_size_key", "<tilda>/context/Increase Font Size", G_CALLBACK(increase_font_size), tw);
773 tilda_add_config_accelerator_by_path("decrease_font_size_key", "<tilda>/context/Decrease Font Size", G_CALLBACK(decrease_font_size), tw);
774 tilda_add_config_accelerator_by_path("normalize_font_size_key", "<tilda>/context/Normalize Font Size", G_CALLBACK(normalize_font_size), tw);
775
776 /* Set up keyboard shortcuts for Goto Tab # using key combinations defined in the config*/
777 /* Know a better way? Then you do. */
778 tilda_add_config_accelerator_by_path("gototab_1_key", "<tilda>/context/Goto Tab 1", G_CALLBACK(goto_tab_1), tw);
779 tilda_add_config_accelerator_by_path("gototab_2_key", "<tilda>/context/Goto Tab 2", G_CALLBACK(goto_tab_2), tw);
780 tilda_add_config_accelerator_by_path("gototab_3_key", "<tilda>/context/Goto Tab 3", G_CALLBACK(goto_tab_3), tw);
781 tilda_add_config_accelerator_by_path("gototab_4_key", "<tilda>/context/Goto Tab 4", G_CALLBACK(goto_tab_4), tw);
782 tilda_add_config_accelerator_by_path("gototab_5_key", "<tilda>/context/Goto Tab 5", G_CALLBACK(goto_tab_5), tw);
783 tilda_add_config_accelerator_by_path("gototab_6_key", "<tilda>/context/Goto Tab 6", G_CALLBACK(goto_tab_6), tw);
784 tilda_add_config_accelerator_by_path("gototab_7_key", "<tilda>/context/Goto Tab 7", G_CALLBACK(goto_tab_7), tw);
785 tilda_add_config_accelerator_by_path("gototab_8_key", "<tilda>/context/Goto Tab 8", G_CALLBACK(goto_tab_8), tw);
786 tilda_add_config_accelerator_by_path("gototab_9_key", "<tilda>/context/Goto Tab 9", G_CALLBACK(goto_tab_9), tw);
787 tilda_add_config_accelerator_by_path("gototab_10_key", "<tilda>/context/Goto Tab 10", G_CALLBACK(goto_tab_10), tw);
788
789 return 0;
790 }
791
tilda_window_get_current_terminal(tilda_window * tw)792 static tilda_term* tilda_window_get_current_terminal (tilda_window *tw) {
793 gint pos = gtk_notebook_get_current_page (GTK_NOTEBOOK (tw->notebook));
794 if (pos >= 0) {
795 GList *found = g_list_nth (tw->terms, (guint) pos);
796 if (found) {
797 return found->data;
798 }
799 }
800 return NULL;
801 }
802
tilda_window_set_icon(tilda_window * tw,gchar * filename)803 static gint tilda_window_set_icon (tilda_window *tw, gchar *filename)
804 {
805 GdkPixbuf *window_icon = gdk_pixbuf_new_from_file (filename, NULL);
806
807 if (window_icon == NULL)
808 {
809 TILDA_PERROR ();
810 DEBUG_ERROR ("Cannot open window icon");
811 g_printerr (_("Unable to set tilda's icon: %s\n"), filename);
812 return 1;
813 }
814
815 gtk_window_set_icon (GTK_WINDOW(tw->window), window_icon);
816 g_object_unref (window_icon);
817
818 return 0;
819 }
820
delete_event_callback(G_GNUC_UNUSED GtkWidget * widget,G_GNUC_UNUSED GdkEvent * event,G_GNUC_UNUSED gpointer user_data)821 static gboolean delete_event_callback (G_GNUC_UNUSED GtkWidget *widget,
822 G_GNUC_UNUSED GdkEvent *event,
823 G_GNUC_UNUSED gpointer user_data)
824 {
825 gtk_main_quit ();
826 return FALSE;
827 }
828
829 /* Detect changes in GtkNotebook tab order and update the tw->terms list to reflect such changes. */
page_reordered_cb(GtkNotebook * notebook,GtkWidget * child,guint page_num,tilda_window * tw)830 static void page_reordered_cb (GtkNotebook *notebook,
831 GtkWidget *child,
832 guint page_num,
833 tilda_window *tw) {
834 DEBUG_FUNCTION ("page_reordered_cb");
835 GList *terminals;
836 tilda_term *terminal;
837 guint i;
838
839 terminals = tw->terms;
840
841 for (i = 0; i < g_list_length (terminals); i++)
842 {
843 terminal = g_list_nth_data (terminals, i);
844 if (terminal->hbox == child) {
845 terminals = g_list_remove (terminals, terminal);
846 tw->terms = g_list_insert (terminals, terminal, page_num);
847 break;
848 }
849 }
850 }
851
tilda_window_init(const gchar * config_file,const gint instance,tilda_window * tw)852 gboolean tilda_window_init (const gchar *config_file, const gint instance, tilda_window *tw)
853 {
854 DEBUG_FUNCTION ("tilda_window_init");
855 DEBUG_ASSERT (instance >= 0);
856
857 GtkCssProvider *provider;
858 GtkStyleContext *style_context;
859
860 /* Set the instance number */
861 tw->instance = instance;
862
863 /* Set the config file */
864 tw->config_file = g_strdup (config_file);
865
866 /* Create the main window */
867 tw->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
868
869 GError* error = NULL;
870 GtkBuilder *gtk_builder = gtk_builder_new ();
871
872 #if ENABLE_NLS
873 gtk_builder_set_translation_domain (gtk_builder, PACKAGE);
874 #endif
875
876 if(!gtk_builder_add_from_resource (gtk_builder, "/org/tilda/tilda.ui", &error)) {
877 fprintf (stderr, "Error: %s\n", error->message);
878 g_error_free(error);
879 return FALSE;
880 }
881 tw->gtk_builder = gtk_builder;
882
883 /* The gdk_x11_get_server_time call will hang if GDK_PROPERTY_CHANGE_MASK is not set */
884 gdk_window_set_events(gdk_screen_get_root_window (gtk_widget_get_screen (tw->window)), GDK_PROPERTY_CHANGE_MASK);
885
886 /* Generic timer resolution */
887 tw->timer_resolution = config_getint("timer_resolution");
888
889 /* Auto hide support */
890 tw->auto_hide_tick_handler = 0;
891 tw->auto_hide_max_time = config_getint("auto_hide_time");
892 tw->auto_hide_on_mouse_leave = config_getbool("auto_hide_on_mouse_leave");
893 tw->auto_hide_on_focus_lost = config_getbool("auto_hide_on_focus_lost");
894 tw->disable_auto_hide = FALSE;
895 tw->focus_loss_on_keypress = FALSE;
896
897 PangoFontDescription *description = pango_font_description_from_string(config_getstr("font"));
898 gint size = pango_font_description_get_size(description);
899 tw->unscaled_font_size = size;
900 tw->current_scale_factor = PANGO_SCALE_MEDIUM;
901
902 if(1 == config_getint("non_focus_pull_up_behaviour")) {
903 tw->hide_non_focused = TRUE;
904 }
905 else {
906 tw->hide_non_focused = FALSE;
907 }
908
909 tw->fullscreen = config_getbool("start_fullscreen");
910 tilda_window_set_fullscreen(tw);
911
912 /* Set up all window properties */
913 if (config_getbool ("pinned"))
914 gtk_window_stick (GTK_WINDOW(tw->window));
915
916 if(config_getbool ("set_as_desktop"))
917 gtk_window_set_type_hint(GTK_WINDOW(tw->window), GDK_WINDOW_TYPE_HINT_DESKTOP);
918 gtk_window_set_skip_taskbar_hint (GTK_WINDOW(tw->window), config_getbool ("notaskbar"));
919 gtk_window_set_keep_above (GTK_WINDOW(tw->window), config_getbool ("above"));
920 gtk_window_set_decorated (GTK_WINDOW(tw->window), FALSE);
921 gtk_widget_set_size_request (GTK_WIDGET(tw->window), 0, 0);
922 tilda_window_set_icon (tw, g_build_filename (DATADIR, "pixmaps", "tilda.png", NULL));
923 tilda_window_setup_alpha_mode (tw);
924
925 gtk_widget_set_app_paintable (GTK_WIDGET (tw->window), TRUE);
926
927 /* Add keyboard accelerators */
928 tw->accel_group = NULL; /* We can redefine the accelerator group from the wizard; this shows that it's our first time defining it. */
929 tilda_window_setup_keyboard_accelerators (tw);
930
931 /* Create the notebook */
932 tw->notebook = gtk_notebook_new ();
933
934 /* Here we setup the CSS settings for the GtkNotebook.
935 * If the option "Show notebook border" in the preferences is not
936 * checked. Then we disable the border. Otherwise nothing is changed.
937 *
938 * Depending on the theme it might be necessary to set different
939 * CSS rules, so it is not possible to find a solution that fits
940 * everyone. The following configuration works well with the GNOME
941 * default theme Adwaita, but for example has problems under Ubuntu.
942 * Note that for bigger modifications the user can create a style.css
943 * file in tildas config directory, which will be parsed by
944 * load_custom_css_file() in tilda.c on start up.
945 */
946 gtk_notebook_set_show_tabs (GTK_NOTEBOOK(tw->notebook), FALSE);
947 gtk_notebook_set_show_border (GTK_NOTEBOOK (tw->notebook),
948 config_getbool("notebook_border"));
949 gtk_notebook_set_scrollable (GTK_NOTEBOOK(tw->notebook), TRUE);
950 tilda_window_set_tab_position (tw, config_getint ("tab_pos"));
951
952 provider = gtk_css_provider_new ();
953 style_context = gtk_widget_get_style_context(tw->notebook);
954 gtk_style_context_add_provider (style_context,
955 GTK_STYLE_PROVIDER(provider),
956 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
957
958 if(!config_getbool("notebook_border")) {
959 /**
960 * Calling gtk_notebook_set_show_border is not enough. We need to
961 * disable the border explicitly by using CSS.
962 */
963 gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider),
964 " .notebook {\n"
965 " border: none;\n"
966 "}\n", -1, NULL);
967 }
968
969 g_object_unref (provider);
970
971 /* Create the linked list of terminals */
972 tw->terms = NULL;
973
974 /* Add the initial terminal */
975 if (!tilda_window_add_tab (tw))
976 {
977 return FALSE;
978 }
979
980 /* This is required in key_grabber.c to get the x11 server time,
981 * since the specification requires this flag to be set when
982 * gdk_x11_get_server_time() is called.
983 **/
984 gtk_widget_add_events (tw->window, GDK_PROPERTY_CHANGE_MASK );
985
986 /* Connect signal handlers */
987 g_signal_connect (G_OBJECT(tw->window), "delete-event", G_CALLBACK (delete_event_callback), tw);
988 g_signal_connect (G_OBJECT(tw->window), "show", G_CALLBACK (focus_term), tw);
989
990 g_signal_connect (G_OBJECT(tw->window), "focus-out-event", G_CALLBACK (focus_out_event_cb), tw);
991 g_signal_connect (G_OBJECT(tw->window), "enter-notify-event", G_CALLBACK (mouse_enter), tw);
992 g_signal_connect (G_OBJECT(tw->window), "leave-notify-event", G_CALLBACK (mouse_leave), tw);
993
994 /* We need this signal to detect changes in the order of tabs so that we can keep the order
995 * of tilda_terms in the tw->terms structure in sync with the order of tabs. */
996 g_signal_connect (G_OBJECT(tw->notebook), "page-reordered", G_CALLBACK (page_reordered_cb), tw);
997
998 /* Setup the tilda window. The tilda window consists of a top level window that contains the following widgets:
999 * * The main_box holds a GtkNotebook with all the terminal tabs
1000 * * The search_box holds the TildaSearchBox widget
1001 */
1002 GtkWidget *main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1003 tw->search = tilda_search_box_new ();
1004
1005 gtk_container_add (GTK_CONTAINER(tw->window), main_box);
1006 gtk_box_pack_start (GTK_BOX (main_box), tw->notebook, TRUE, TRUE, 0);
1007 gtk_box_pack_start (GTK_BOX (main_box), tw->search, FALSE, TRUE, 0);
1008
1009 if (VTE_CHECK_VERSION_RUMTIME (0, 56, 1)) {
1010 g_signal_connect (tw->search, "search",
1011 G_CALLBACK (search_cb), tw);
1012 } else {
1013 g_signal_connect (tw->search, "search-gregex",
1014 G_CALLBACK (search_gregex_cb), tw);
1015 }
1016 g_signal_connect (tw->search, "focus-out",
1017 G_CALLBACK (search_focus_out_cb), tw);
1018
1019 /* Show the widgets */
1020 gtk_widget_show_all (main_box);
1021 gtk_widget_set_visible(tw->search, FALSE);
1022 /* the tw->window widget will be shown later, by pull() */
1023
1024 /* Position the window */
1025 tw->current_state = STATE_UP;
1026
1027 GdkRectangle rectangle;
1028 config_get_configured_window_size (&rectangle);
1029
1030 gint width = rectangle.width;
1031 gint height = rectangle.height;
1032
1033 gtk_window_set_default_size (GTK_WINDOW(tw->window),
1034 width,
1035 height);
1036
1037 gtk_window_resize (GTK_WINDOW(tw->window), width, height);
1038
1039 /* Create GDK resources now, to prevent crashes later on */
1040 gtk_widget_realize (tw->window);
1041 generate_animation_positions (tw);
1042
1043 /* Initialize wizard window reference to NULL */
1044 tw->wizard_window = NULL;
1045
1046 GdkScreen *screen;
1047 GdkWindow *root;
1048 GdkEventMask mask;
1049
1050 screen = gtk_widget_get_screen (GTK_WIDGET (tw->window));
1051 root = gdk_screen_get_root_window (screen);
1052
1053 mask = gdk_window_get_events (root);
1054 mask |= GDK_PROPERTY_CHANGE_MASK;
1055
1056 gdk_window_set_events (root, mask);
1057
1058 gdk_window_add_filter (root, window_filter_function, tw);
1059
1060 return TRUE;
1061 }
1062
tilda_window_free(tilda_window * tw)1063 gint tilda_window_free (tilda_window *tw)
1064 {
1065
1066 /* Close each tab which still exists.
1067 * This will free their data structures automatically. */
1068 if (tw->notebook != NULL) {
1069 gint num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK(tw->notebook));
1070 while (num_pages > 0)
1071 {
1072 /* Close the 0th tab, which should always exist while we have
1073 * some pages left in the notebook. */
1074 tilda_window_close_tab (tw, 0, TRUE);
1075
1076 num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK(tw->notebook));
1077 }
1078 }
1079
1080 g_free (tw->config_file);
1081 gtk_widget_destroy (tw->search);
1082 if (tw->gtk_builder != NULL) {
1083 g_object_unref (G_OBJECT(tw->gtk_builder));
1084 }
1085
1086 tw->size_update_event_source = 0;
1087
1088 return 0;
1089 }
1090
tilda_window_add_tab(tilda_window * tw)1091 gint tilda_window_add_tab (tilda_window *tw)
1092 {
1093 DEBUG_FUNCTION ("tilda_window_add_tab");
1094 DEBUG_ASSERT (tw != NULL);
1095
1096 tilda_term *tt;
1097 GtkWidget *label;
1098 gint index;
1099
1100 tt = tilda_term_init (tw);
1101
1102 if (tt == NULL)
1103 {
1104 TILDA_PERROR ();
1105 g_printerr (_("Out of memory, cannot create tab\n"));
1106 return FALSE;
1107 }
1108
1109 /* Create page and append to notebook */
1110 label = gtk_label_new (config_getstr("title"));
1111 /* Strangely enough, prepend puts pages on the end */
1112 index = gtk_notebook_append_page (GTK_NOTEBOOK(tw->notebook), tt->hbox, label);
1113 gtk_notebook_set_current_page (GTK_NOTEBOOK(tw->notebook), index);
1114 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK(tw->notebook), tt->hbox, TRUE);
1115
1116 if(config_getbool ("expand_tabs")) {
1117 gtk_container_child_set (GTK_CONTAINER(tw->notebook),
1118 GTK_WIDGET(tt->hbox),
1119 "tab-expand", TRUE,
1120 "tab-fill", TRUE,
1121 NULL);
1122 }
1123
1124 /* We should show the tabs if there are more than one tab in the notebook
1125 * (or show single tab is set),
1126 * and tab position is not set to hidden */
1127 if ((gtk_notebook_get_n_pages (GTK_NOTEBOOK (tw->notebook)) > 1 ||
1128 config_getbool("show_single_tab")) &&
1129 config_getint("tab_pos") != NB_HIDDEN)
1130 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (tw->notebook), TRUE);
1131
1132 /* The new terminal should grab the focus automatically */
1133 gtk_widget_grab_focus (tt->vte_term);
1134
1135 return GDK_EVENT_STOP; //index;
1136 }
1137
tilda_window_close_tab(tilda_window * tw,gint tab_index,gboolean force_exit)1138 gint tilda_window_close_tab (tilda_window *tw, gint tab_index, gboolean force_exit)
1139 {
1140 DEBUG_FUNCTION ("tilda_window_close_tab");
1141 DEBUG_ASSERT (tw != NULL);
1142 DEBUG_ASSERT (tab_index >= 0);
1143
1144 tilda_term *tt;
1145 GtkWidget *child;
1146
1147 child = gtk_notebook_get_nth_page (GTK_NOTEBOOK(tw->notebook), tab_index);
1148
1149 if (child == NULL)
1150 {
1151 DEBUG_ERROR ("Bad tab_index specified");
1152 return -1;
1153 }
1154
1155 tt = find_tt_in_g_list (tw, tab_index);
1156
1157 gtk_notebook_remove_page (GTK_NOTEBOOK (tw->notebook), tab_index);
1158
1159 /* We should hide the tabs if there is only one tab left */
1160 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (tw->notebook)) == 1 &&
1161 !config_getbool("show_single_tab"))
1162 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (tw->notebook), FALSE);
1163
1164 /* With no pages left, either leave the program or create a new
1165 * terminal */
1166 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (tw->notebook)) < 1) {
1167 if (force_exit == TRUE) {
1168 /**
1169 * It is necessary to check the main_level because if CTRL_C was used
1170 * or the "Quit" option from the context menu then gtk_main_quit has
1171 * already been called and cannot call it again.
1172 */
1173 if(gtk_main_level () > 0) {
1174 gtk_main_quit ();
1175 }
1176 } else {
1177 /* These can stay here. They don't need to go into a header
1178 * because they are only used at this point in the code. */
1179 enum on_last_terminal_exit { EXIT_TILDA,
1180 RESTART_TERMINAL,
1181 RESTART_TERMINAL_AND_HIDE };
1182
1183 /* Check the user's preference for what to do when the last
1184 * terminal is closed. Take the appropriate action */
1185 switch (config_getint ("on_last_terminal_exit"))
1186 {
1187 case RESTART_TERMINAL:
1188 tilda_window_add_tab (tw);
1189 break;
1190 case RESTART_TERMINAL_AND_HIDE:
1191 tilda_window_add_tab (tw);
1192 pull (tw, PULL_UP, TRUE);
1193 break;
1194 case EXIT_TILDA:
1195 default:
1196 /**
1197 * It is necessary to check the main_level because if CTRL_C was used
1198 * or the "Quit" option from the context menu then gtk_main_quit has
1199 * already been called and cannot call it again.
1200 */
1201 if(gtk_main_level () > 0) {
1202 gtk_main_quit ();
1203 }
1204 break;
1205 }
1206 }
1207 }
1208
1209 /* Remove the tilda_term from the list of terminals */
1210 tw->terms = g_list_remove (tw->terms, tt);
1211
1212 /* Free the terminal, we are done with it */
1213 tilda_term_free (tt);
1214
1215 return GDK_EVENT_STOP;
1216 }
1217
tilda_window_confirm_quit(tilda_window * tw)1218 gint tilda_window_confirm_quit (tilda_window *tw)
1219 {
1220 DEBUG_FUNCTION(__FUNCTION__);
1221
1222 gboolean can_quit = TRUE;
1223
1224 if(config_getbool("prompt_on_exit")) {
1225 char * message = _("Are you sure you want to Quit?");
1226
1227 can_quit = show_confirmation_dialog (tw, message);
1228 }
1229
1230 if (can_quit) {
1231 gtk_main_quit ();
1232 }
1233
1234 return GDK_EVENT_STOP;
1235 }
1236
tilda_window_find_monitor_number(tilda_window * tw)1237 GdkMonitor* tilda_window_find_monitor_number (tilda_window *tw)
1238 {
1239 DEBUG_FUNCTION ("tilda_window_find_monitor_number");
1240
1241 GdkDisplay *display = gdk_display_get_default ();
1242 gint n_monitors = gdk_display_get_n_monitors (gdk_display_get_default ());
1243
1244 gchar *show_on_monitor = config_getstr("show_on_monitor");
1245
1246 for(int i = 0; i < n_monitors; ++i) {
1247 GdkMonitor *monitor = gdk_display_get_monitor (display, i);
1248 const gchar *monitor_name = gdk_monitor_get_model (monitor);
1249 if(0 == g_strcmp0 (show_on_monitor, monitor_name)) {
1250 return monitor;
1251 }
1252 }
1253
1254 return gdk_display_get_primary_monitor (display);
1255 }
1256
tilda_window_find_centering_coordinate(tilda_window * tw,enum dimensions dimension)1257 gint tilda_window_find_centering_coordinate (tilda_window *tw,
1258 enum dimensions dimension)
1259 {
1260 DEBUG_FUNCTION ("tilda_window_find_centering_coordinate");
1261
1262 gdouble monitor_dimension = 0;
1263 gdouble tilda_dimension = 0;
1264 GdkMonitor *monitor = tilda_window_find_monitor_number (tw);
1265 GdkRectangle rectangle;
1266 gdk_monitor_get_workarea (monitor, &rectangle);
1267
1268 GdkRectangle tilda_rectangle;
1269 config_get_configured_window_size (&tilda_rectangle);
1270
1271 if (dimension == HEIGHT) {
1272 monitor_dimension = rectangle.height;
1273 tilda_dimension = tilda_rectangle.height;
1274 } else if (dimension == WIDTH) {
1275 monitor_dimension = rectangle.width;
1276 tilda_dimension = tilda_rectangle.width;
1277 }
1278 const gdouble screen_center = monitor_dimension / 2.0;
1279 const gdouble tilda_center = tilda_dimension / 2.0;
1280 gint center = (int) (screen_center - tilda_center);
1281
1282 if(dimension == HEIGHT) {
1283 center += rectangle.y;
1284 } else if (dimension == WIDTH) {
1285 center += rectangle.x;
1286 }
1287 return center;
1288 }
1289
1290 void
tilda_window_update_window_position(tilda_window * tw)1291 tilda_window_update_window_position (tilda_window *tw)
1292 {
1293 DEBUG_FUNCTION ("tilda_window_update_window_position");
1294 /**
1295 * If the screen size changed we might also need to recenter the
1296 * tilda window.
1297 */
1298 gint pos_x, pos_y;
1299 gboolean centered_horizontally = config_getbool ("centered_horizontally");
1300 gboolean centered_vertically = config_getbool ("centered_vertically");
1301
1302 if (centered_horizontally) {
1303 pos_x = tilda_window_find_centering_coordinate (tw, WIDTH);
1304 config_setint ("x_pos", pos_x);
1305 pos_y = (gint) config_getint ("y_pos");
1306 gtk_window_move (GTK_WINDOW (tw->window), pos_x, pos_y);
1307 }
1308
1309 if (centered_vertically) {
1310 pos_y = tilda_window_find_centering_coordinate (tw, HEIGHT);
1311 config_setint ("y_pos", pos_y);
1312 pos_x = (gint) config_getint ("x_pos");
1313 gtk_window_move (GTK_WINDOW (tw->window), pos_x, pos_y);
1314 }
1315 }
1316
update_tilda_window_size(gpointer user_data)1317 static gboolean update_tilda_window_size (gpointer user_data)
1318 {
1319 tilda_window *tw = user_data;
1320
1321 g_debug ("Updating tilda window size in idle handler to "
1322 "match new size of workarea.");
1323
1324 /* 1. Get current tilda window size */
1325 int windowHeight = gtk_widget_get_allocated_height (GTK_WIDGET (tw->window));
1326 int windowWidth = gtk_widget_get_allocated_width (GTK_WIDGET (tw->window));
1327
1328 gint newWidth = windowWidth;
1329 gint newHeight = windowHeight;
1330
1331 /* 2. Get the desired size and update the tilda window size if necessary. */
1332 GdkRectangle configured_geometry;
1333 config_get_configured_window_size (&configured_geometry);
1334
1335 if (configured_geometry.width - windowWidth >= 1) {
1336 newWidth = configured_geometry.width;
1337 }
1338
1339 if (configured_geometry.height - windowHeight >= 1) {
1340 newHeight = configured_geometry.height;
1341 }
1342
1343 gtk_window_resize (GTK_WINDOW (tw->window),
1344 newWidth,
1345 newHeight);
1346
1347 tilda_window_update_window_position (tw);
1348
1349 /* 3. Returning G_SOURCE_REMOVE below will clear the event source in Gtk.
1350 * Thus, we need to reset the ID such that a new event source can be
1351 * registered if the workarea changes again. */
1352 tw->size_update_event_source = 0;
1353
1354 return G_SOURCE_REMOVE;
1355 }
1356
1357 /**
1358 * This function inspects the incoming XEvents to find property events that
1359 * update the workarea and as a result queues an update function which updates
1360 * the tilda window size in case the workarea has changed.
1361 *
1362 * The reason we need this function is that in the signal handlers of
1363 * the signals GdkScreen::monitors-changed and GdkScreen::size-changed
1364 * we cannot reliably get the updated workarea. This is because the workarea
1365 * is computed and update by the window manager only after the monitor
1366 * resolution has changed. Thus, if we call gdk_monitor_get_workarea
1367 * from the monitors-changed or size-changed signal handlers we will
1368 * not get the correct workarea because the window manager has not yet
1369 * updated the corresponding XProperty.
1370 */
window_filter_function(GdkXEvent * gdk_xevent,GdkEvent * event,gpointer user_data)1371 static GdkFilterReturn window_filter_function (GdkXEvent *gdk_xevent,
1372 GdkEvent *event,
1373 gpointer user_data)
1374 {
1375 tilda_window *tw = user_data;
1376
1377 XEvent *xevent = (XEvent *) gdk_xevent;
1378
1379 switch (xevent->type)
1380 {
1381 case PropertyNotify:
1382 {
1383 XPropertyEvent *propertyEvent;
1384
1385 propertyEvent = (XPropertyEvent *) xevent;
1386
1387 const Atom WORKAREA_ATOM = XInternAtom (propertyEvent->display,
1388 "_NET_WORKAREA", True);
1389
1390 if (propertyEvent->atom != WORKAREA_ATOM)
1391 return GDK_FILTER_CONTINUE;
1392
1393 if (tw->size_update_event_source != 0)
1394 return GDK_FILTER_CONTINUE;
1395
1396 tw->size_update_event_source = g_idle_add (update_tilda_window_size,
1397 user_data);
1398
1399 break;
1400 }
1401 default:break;
1402 }
1403
1404 return GDK_FILTER_CONTINUE;
1405 }
1406