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