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 "tilda_window.h"
21 #include "tilda_terminal.h"
22 #include "configsys.h"
23 #include "wizard.h" /* wizard */
24 
25 #include <stdio.h>
26 #include <stdlib.h> /* malloc */
27 #include <gtk/gtk.h>
28 #include <glib-object.h>
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 #include <vte/vte.h>
32 #include <string.h>
33 
34 #define HTTP_REGEXP "(ftp|http)s?://[\\[\\]-a-zA-Z0-9.?!$%&/=_~#.,:;+]*"
35 
36 static void start_shell (tilda_term *tt, gboolean ignore_custom_command);
37 static void start_default_shell (tilda_term *tt);
38 
39 static gint tilda_term_config_defaults (tilda_term *tt);
40 
41 static void child_exited_cb (GtkWidget *widget, gint status, gpointer data);
42 static void window_title_changed_cb (GtkWidget *widget, gpointer data);
43 static int button_press_cb (GtkWidget *widget, GdkEventButton *event, gpointer data);
44 static gboolean key_press_cb (GtkWidget *widget, GdkEvent  *event, tilda_term *terminal);
45 static void iconify_window_cb (GtkWidget *widget, gpointer data);
46 static void deiconify_window_cb (GtkWidget *widget, gpointer data);
47 static void raise_window_cb (GtkWidget *widget, gpointer data);
48 static void lower_window_cb (GtkWidget *widget, gpointer data);
49 static void maximize_window_cb (GtkWidget *widget, gpointer data);
50 static void restore_window_cb (GtkWidget *widget, gpointer data);
51 static void refresh_window_cb (GtkWidget *widget, gpointer data);
52 static void move_window_cb (GtkWidget *widget, guint x, guint y, gpointer data);
53 
54 static gchar *get_default_command (void);
55 gchar *get_working_directory (tilda_term *terminal);
56 
tilda_term_free(tilda_term * term)57 gint tilda_term_free (tilda_term *term)
58 {
59     DEBUG_FUNCTION ("tilda_term_free");
60     DEBUG_ASSERT (term != NULL);
61 
62     g_free (term->initial_working_dir);
63 
64     g_signal_handlers_disconnect_by_func (term->vte_term, child_exited_cb, term);
65 
66     g_clear_object (&term->hbox);
67     g_clear_object (&term->scrollbar);
68     g_clear_object (&term->vte_term);
69 
70     g_regex_unref (term->http_regexp);
71 
72     term->http_regexp = NULL;
73 
74     g_free (term);
75 
76     return 0;
77 }
78 
tilda_terminal_switch_page_cb(GtkNotebook * notebook,GtkWidget * page,guint page_num,tilda_window * tw)79 static void tilda_terminal_switch_page_cb (GtkNotebook *notebook,
80                                            GtkWidget   *page,
81                                            guint        page_num,
82                                            tilda_window *tw)
83 {
84     DEBUG_FUNCTION ("tilda_terminal_switch_page_cb");
85     guint counter = 0;
86     tilda_term *term = NULL;
87     for(GList *item=tw->terms; item != NULL; item=item->next) {
88         if(counter == page_num) {
89             term = (tilda_term*) item->data;
90         }
91         counter++;
92     }
93 
94     char * current_title = tilda_terminal_get_title (term);
95 
96     if (current_title != NULL) {
97         gtk_window_set_title (GTK_WINDOW (tw->window), current_title);
98     }
99 
100     g_free (current_title);
101 }
102 
tilda_term_init(struct tilda_window_ * tw)103 struct tilda_term_ *tilda_term_init (struct tilda_window_ *tw)
104 {
105     DEBUG_FUNCTION ("tilda_term_init");
106     DEBUG_ASSERT (tw != NULL);
107 
108     int ret;
109     struct tilda_term_ *term;
110     GError *error = NULL;
111     tilda_term *current_tt;
112     gint current_tt_index;
113 
114     term = g_new0 (tilda_term, 1);
115 
116     /* Add to GList list of tilda_term structures in tilda_window structure */
117     tw->terms = g_list_append (tw->terms, term);
118 
119     /* Check for a failed allocation */
120     if (!term)
121         return NULL;
122 
123     /* Set the PID to unset value */
124     term->pid = -1;
125 
126     /* Add the parent window reference */
127     term->tw = tw;
128 
129     /* Create a non-homogenous hbox, with 0px spacing between members */
130     term->hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
131     g_object_ref (term->hbox);
132 
133     /* Create the terminal */
134     term->vte_term = vte_terminal_new ();
135     g_object_ref (term->vte_term);
136 
137     /* Create the scrollbar for the terminal */
138     term->scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL,
139         gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (VTE_TERMINAL(term->vte_term))));
140     g_object_ref (term->scrollbar);
141 
142     gtk_widget_set_no_show_all (term->scrollbar, TRUE);
143 
144     /* Initialize to false, we have not yet dropped to the default shell */
145     term->dropped_to_default_shell = FALSE;
146 
147     /* Set properties of the terminal */
148     tilda_term_config_defaults (term);
149 
150     /* Update the font scale because the newly created terminal uses the default font size */
151     tilda_term_adjust_font_scale(term, tw->current_scale_factor);
152 
153     /* Pack everything into the hbox */
154     gtk_box_pack_end (GTK_BOX(term->hbox), term->scrollbar, FALSE, FALSE, 0);
155     gtk_box_pack_end (GTK_BOX(term->hbox), term->vte_term, TRUE, TRUE, 0);
156     gtk_widget_show (term->scrollbar);
157 
158     /* Set the scrollbar position */
159     tilda_term_set_scrollbar_position (term, config_getint ("scrollbar_pos"));
160 
161     /** Signal Connection **/
162     g_signal_connect (G_OBJECT(term->vte_term), "child-exited",
163                       G_CALLBACK(child_exited_cb), term);
164     g_signal_connect (G_OBJECT(term->vte_term), "window-title-changed",
165                       G_CALLBACK(window_title_changed_cb), term);
166     g_signal_connect (G_OBJECT(term->vte_term), "button-press-event",
167                       G_CALLBACK(button_press_cb), term);
168     g_signal_connect (G_OBJECT(term->vte_term), "key-press-event",
169 		      G_CALLBACK(key_press_cb), term); //needs GDK_KEY_PRESS_MASK
170 
171     /* Connect to application request signals. */
172     g_signal_connect (G_OBJECT(term->vte_term), "iconify-window",
173                       G_CALLBACK(iconify_window_cb), tw->window);
174     g_signal_connect (G_OBJECT(term->vte_term), "deiconify-window",
175                       G_CALLBACK(deiconify_window_cb), tw->window);
176     g_signal_connect (G_OBJECT(term->vte_term), "raise-window",
177                       G_CALLBACK(raise_window_cb), tw->window);
178     g_signal_connect (G_OBJECT(term->vte_term), "lower-window",
179                       G_CALLBACK(lower_window_cb), tw->window);
180     g_signal_connect (G_OBJECT(term->vte_term), "maximize-window",
181                       G_CALLBACK(maximize_window_cb), tw->window);
182     g_signal_connect (G_OBJECT(term->vte_term), "restore-window",
183                       G_CALLBACK(restore_window_cb), tw->window);
184     g_signal_connect (G_OBJECT(term->vte_term), "refresh-window",
185                       G_CALLBACK(refresh_window_cb), tw->window);
186     g_signal_connect (G_OBJECT(term->vte_term), "move-window",
187                       G_CALLBACK(move_window_cb), tw->window);
188     g_signal_connect (G_OBJECT (tw->notebook), "switch-page",
189                       G_CALLBACK (tilda_terminal_switch_page_cb), tw);
190 
191     /* Match URL's, etc */
192     term->http_regexp=g_regex_new(HTTP_REGEXP, G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY, &error);
193     ret = vte_terminal_match_add_gregex(VTE_TERMINAL(term->vte_term), term->http_regexp,0);
194     vte_terminal_match_set_cursor_type (VTE_TERMINAL(term->vte_term), ret, GDK_HAND2);
195 
196     /* Show the child widgets */
197     gtk_widget_show (term->vte_term);
198     gtk_widget_show (term->hbox);
199 
200     /* Get current term's working directory */
201     current_tt_index = gtk_notebook_get_current_page (GTK_NOTEBOOK(tw->notebook));
202     current_tt = g_list_nth_data (tw->terms, current_tt_index);
203     if (current_tt != NULL)
204     {
205         term->initial_working_dir = tilda_term_get_cwd (current_tt);
206     }
207 
208     /* Fork the appropriate command into the terminal */
209     start_shell (term, FALSE);
210 
211     return term;
212 }
213 
tilda_term_set_scrollbar_position(tilda_term * tt,enum tilda_term_scrollbar_positions pos)214 void tilda_term_set_scrollbar_position (tilda_term *tt, enum tilda_term_scrollbar_positions pos)
215 {
216     DEBUG_FUNCTION ("tilda_term_set_scrollbar_position");
217     DEBUG_ASSERT (tt != NULL);
218     DEBUG_ASSERT (pos == LEFT || pos == RIGHT || pos == DISABLED);
219 
220     if (pos == DISABLED) {
221         gtk_widget_hide (tt->scrollbar);
222     } else {
223         /* We have already asserted that it's either disabled (already taken care of),
224          * left, or right, so no need to check twice. */
225         gtk_box_reorder_child (GTK_BOX(tt->hbox), tt->scrollbar, (pos == LEFT) ? 0 : 1);
226         gtk_widget_show (tt->scrollbar);
227     }
228 }
229 
window_title_changed_cb(GtkWidget * widget,gpointer data)230 static void window_title_changed_cb (GtkWidget *widget, gpointer data)
231 {
232     DEBUG_FUNCTION ("window_title_changed_cb");
233     DEBUG_ASSERT (widget != NULL);
234     DEBUG_ASSERT (data != NULL);
235 
236     tilda_term *tt = TILDA_TERM(data);
237     gchar * title = tilda_terminal_get_title (tt);
238     gchar * full_title = tilda_terminal_get_full_title (tt);
239     GtkWidget *label;
240 
241     label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (tt->tw->notebook), tt->hbox);
242 
243     /* We need to check if the widget that received the title change is the currently
244      * active tab. If not we should not update the window title. */
245     gint page = gtk_notebook_get_current_page (GTK_NOTEBOOK (tt->tw->notebook));
246 
247     gboolean active;
248     if (page >= 0) {
249         tilda_term *currente_term;
250         currente_term = g_list_nth_data (tt->tw->terms, (guint) page);
251         active = widget == currente_term->vte_term;
252     } else {
253         active = TRUE;
254     }
255 
256     gtk_label_set_text (GTK_LABEL(label), title);
257 
258     if (active) {
259         gtk_window_set_title (GTK_WINDOW (tt->tw->window), title);
260     }
261 
262     if(config_getbool("show_title_tooltip"))
263       gtk_widget_set_tooltip_text(label, full_title);
264     else
265       gtk_widget_set_tooltip_text(label, "");
266 
267     g_free (title);
268 }
269 
iconify_window_cb(G_GNUC_UNUSED GtkWidget * widget,gpointer data)270 static void iconify_window_cb (G_GNUC_UNUSED GtkWidget *widget, gpointer data)
271 {
272     DEBUG_FUNCTION ("iconify_window_cb");
273     DEBUG_ASSERT (data != NULL);
274 
275     if (GTK_IS_WIDGET(data))
276         if (gtk_widget_get_window ((GTK_WIDGET (data))))
277             gdk_window_iconify (gtk_widget_get_window ((GTK_WIDGET (data))));
278 }
279 
deiconify_window_cb(G_GNUC_UNUSED GtkWidget * widget,gpointer data)280 static void deiconify_window_cb (G_GNUC_UNUSED GtkWidget *widget, gpointer data)
281 {
282     DEBUG_FUNCTION ("deiconify_window_cb");
283     DEBUG_ASSERT (data != NULL);
284 
285     if (GTK_IS_WIDGET(data))
286         if (gtk_widget_get_window ((GTK_WIDGET (data))))
287             gdk_window_deiconify (gtk_widget_get_window ((GTK_WIDGET (data))));
288 }
289 
raise_window_cb(G_GNUC_UNUSED GtkWidget * widget,gpointer data)290 static void raise_window_cb (G_GNUC_UNUSED GtkWidget *widget, gpointer data)
291 {
292     DEBUG_FUNCTION ("raise_window_cb");
293     DEBUG_ASSERT (data != NULL);
294 
295     if (GTK_IS_WIDGET(data))
296         if (gtk_widget_get_window (GTK_WIDGET (data)))
297             gdk_window_raise (gtk_widget_get_window (GTK_WIDGET (data)));
298 }
299 
lower_window_cb(G_GNUC_UNUSED GtkWidget * widget,gpointer data)300 static void lower_window_cb (G_GNUC_UNUSED GtkWidget *widget, gpointer data)
301 {
302     DEBUG_FUNCTION ("lower_window_cb");
303     DEBUG_ASSERT (data != NULL);
304 
305     if (GTK_IS_WIDGET(data))
306         if (gtk_widget_get_window (GTK_WIDGET (data)))
307             gdk_window_lower (gtk_widget_get_window (GTK_WIDGET (data)));
308 }
309 
maximize_window_cb(G_GNUC_UNUSED GtkWidget * widget,gpointer data)310 static void maximize_window_cb (G_GNUC_UNUSED GtkWidget *widget, gpointer data)
311 {
312     DEBUG_FUNCTION ("maximize_window_cb");
313     DEBUG_ASSERT (data != NULL);
314 
315     if (GTK_IS_WIDGET(data))
316         if (gtk_widget_get_window (GTK_WIDGET(data)))
317             gdk_window_maximize (gtk_widget_get_window (GTK_WIDGET(data)));
318 }
319 
restore_window_cb(G_GNUC_UNUSED GtkWidget * widget,gpointer data)320 static void restore_window_cb (G_GNUC_UNUSED GtkWidget *widget, gpointer data)
321 {
322     DEBUG_FUNCTION ("restore_window_cb");
323     DEBUG_ASSERT (data != NULL);
324 
325     if (GTK_IS_WIDGET(data))
326         if (gtk_widget_get_window (GTK_WIDGET (data)))
327             gdk_window_unmaximize (gtk_widget_get_window (GTK_WIDGET (data)));
328 }
329 
refresh_window_cb(G_GNUC_UNUSED GtkWidget * widget,gpointer data)330 static void refresh_window_cb (G_GNUC_UNUSED GtkWidget *widget, gpointer data)
331 {
332     DEBUG_FUNCTION ("refresh_window_cb");
333     DEBUG_ASSERT (data != NULL);
334 
335     GdkRectangle rect;
336     if (GTK_IS_WIDGET(data))
337     {
338         if (gtk_widget_get_window (GTK_WIDGET (data)))
339         {
340             rect.x = rect.y = 0;
341             GtkAllocation allocation;
342             gtk_widget_get_allocation (GTK_WIDGET (data), &allocation);
343             rect.width = allocation.width;
344             rect.height = allocation.height;
345             gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET(data)), &rect, TRUE);
346         }
347     }
348 }
349 
move_window_cb(G_GNUC_UNUSED GtkWidget * widgets,guint x,guint y,gpointer data)350 static void move_window_cb (G_GNUC_UNUSED GtkWidget *widgets, guint x, guint y, gpointer data)
351 {
352     DEBUG_FUNCTION ("move_window_cb");
353     DEBUG_ASSERT (data != NULL);
354 
355     if (GTK_IS_WIDGET(data))
356         if (gtk_widget_get_window (GTK_WIDGET (data)))
357             gdk_window_move (gtk_widget_get_window (GTK_WIDGET (data)), x, y);
358 }
359 
tilda_term_adjust_font_scale(tilda_term * term,gdouble scale)360 void tilda_term_adjust_font_scale(tilda_term *term, gdouble scale) {
361     DEBUG_FUNCTION ("tilda_term_adjust_font_scale");
362 
363     VteTerminal *terminal = VTE_TERMINAL(term->vte_term);
364     /* We need the tilda_term object to access the unscaled
365      * font size and current scale factor */
366     PangoFontDescription *desired;
367 
368     desired = pango_font_description_copy (vte_terminal_get_font(terminal));
369     pango_font_description_set_size (desired, (gint) (term->tw->unscaled_font_size * scale));
370     vte_terminal_set_font (terminal, desired);
371     pango_font_description_free (desired);
372 }
373 
374 /* Returns the working directory of the terminal
375  *
376  * @param tt the tilda_term to get working directory of
377  *
378  * SUCCESS: return non-NULL char* that should be freed with g_free when done
379  * FAILURE: return NULL
380  */
tilda_term_get_cwd(struct tilda_term_ * tt)381 char* tilda_term_get_cwd(struct tilda_term_* tt)
382 {
383     char *file;
384     char *cwd;
385     GError *error = NULL;
386 
387     if (tt->pid < 0)
388     {
389         return NULL;
390     }
391 
392     file = g_strdup_printf ("/proc/%d/cwd", tt->pid);
393     cwd = g_file_read_link (file, &error);
394     g_free (file);
395 
396     if (cwd == NULL)
397     {
398         g_printerr (_("Problem reading link %s: %s\n"), file, error->message);
399         g_error_free (error);
400     }
401 
402     return cwd;
403 }
404 
405 static void
shell_spawned_cb(VteTerminal * terminal,GPid pid,GError * error,gpointer user_data)406 shell_spawned_cb (VteTerminal *terminal,
407                   GPid pid,
408                   GError *error,
409                   gpointer user_data)
410 {
411     DEBUG_FUNCTION ("shell_spawned_cb");
412 
413     tilda_term *tt;
414 
415     tt = user_data;
416 
417     if (error)
418     {
419         if (tt->dropped_to_default_shell)
420         {
421             g_printerr (_("Unable to launch default shell: %s\n"), get_default_command ());
422         } else {
423             g_printerr (_("Unable to launch custom command: %s\n"), config_getstr ("command"));
424             g_printerr (_("Launching custom command failed with error: %s\n"), error->message);
425             g_printerr (_("Launching default shell instead\n"));
426 
427             start_default_shell (tt);
428             tt->dropped_to_default_shell = TRUE;
429         }
430 
431         return;
432     }
433 
434     tt->pid = pid;
435 }
436 
437 /* Fork a shell into the VTE Terminal
438  *
439  * @param tt the tilda_term to fork into
440  */
start_shell(tilda_term * tt,gboolean ignore_custom_command)441 static void start_shell (tilda_term *tt, gboolean ignore_custom_command)
442 {
443     DEBUG_FUNCTION ("start_shell");
444     DEBUG_ASSERT (tt != NULL);
445 
446     gint ret;
447     gint argc;
448     gchar **argv;
449     GError *error = NULL;
450 
451     if (config_getbool ("run_command") && !ignore_custom_command)
452     {
453         ret = g_shell_parse_argv (config_getstr ("command"), &argc, &argv, &error);
454 
455         /* Check for error */
456         if (ret == FALSE)
457         {
458             g_printerr (_("Problem parsing custom command: %s\n"), error->message);
459             g_printerr (_("Launching default shell instead\n"));
460 
461             g_error_free (error);
462 
463             start_default_shell (tt);
464         }
465 
466         gchar *working_dir = get_working_directory (tt);
467         gint command_timeout = config_getint ("command_timeout_ms");
468 
469         char **envv = malloc(2*sizeof(void *));
470         envv[0] = getenv("PATH");
471         envv[1] = NULL;
472 
473         vte_terminal_spawn_async (VTE_TERMINAL (tt->vte_term),
474                                   VTE_PTY_DEFAULT, /* VtePtyFlags pty_flags */
475                                   working_dir, /* const char *working_directory */
476                                   argv, /* char **argv */
477                                   envv, /* char **envv */
478                                   G_SPAWN_SEARCH_PATH,    /* GSpawnFlags spawn_flags */
479                                   NULL, /* GSpawnChildSetupFunc child_setup */
480                                   NULL, /* gpointer child_setup_data */
481                                   NULL, /* GDestroyNotify child_setup_data_destroy */
482                                   command_timeout, /* timeout in ms */
483                                   NULL, /* GCancellable * cancellable, */
484                                   shell_spawned_cb,  /* VteTerminalSpawnAsyncCallback callback */
485                                   tt);   /* user_data */
486 
487         g_strfreev (argv);
488         g_free (envv);
489     } else {
490         start_default_shell (tt);
491     }
492 }
493 
494 static void
start_default_shell(tilda_term * tt)495 start_default_shell (tilda_term *tt)
496 {
497     gchar  **argv;
498 
499 
500     /* If we have dropped to the default shell before, then this time, we
501      * do not spawn a new shell, but instead close the current shell. This will
502      * cause the current tab to close.
503      */
504     if (tt->dropped_to_default_shell) {
505         gint index = gtk_notebook_page_num (GTK_NOTEBOOK(tt->tw->notebook),
506             tt->hbox);
507         tilda_window_close_tab (tt->tw, index, FALSE);
508 
509         return;
510     }
511 
512     gchar *default_command = get_default_command ();
513     gchar *working_dir = get_working_directory (tt);
514     gint command_timeout = config_getint ("command_timeout_ms");
515 
516     /* We need to create a NULL terminated list of arguments.
517      * The first item is the command to execute in the shell, in this
518      * case there are no further arguments being passed. */
519     GSpawnFlags flags = 0;
520     gchar* argv1 = NULL;
521     if(config_getbool("command_login_shell")) {
522         argv1 = g_strdup_printf("-%s", default_command);
523         argv = malloc(3 * sizeof(void *));
524         argv[0] = default_command;
525         argv[1] = argv1;
526         argv[2] = NULL;
527         /* This is needed so that argv[1] becomes the argv[0] of the new process. Otherwise
528          * glib just duplicates argv[0] when it executes the command and it is not possible
529          * to modify the argv[0] that the new command sees.
530          */
531         flags |= G_SPAWN_FILE_AND_ARGV_ZERO;
532     } else {
533         argv = malloc(1 * sizeof(void *));
534         argv[0] = default_command;
535         argv[1] = NULL;
536     }
537 
538     vte_terminal_spawn_async (VTE_TERMINAL (tt->vte_term),
539                               VTE_PTY_DEFAULT, /* VtePtyFlags pty_flags */
540                               working_dir, /* const char *working_directory */
541                               argv, /* char **argv */
542                               NULL, /* char **envv */
543                               flags,    /* GSpawnFlags spawn_flags */
544                               NULL, /* GSpawnChildSetupFunc child_setup */
545                               NULL, /* gpointer child_setup_data */
546                               NULL, /* GDestroyNotify child_setup_data_destroy */
547                               command_timeout, /* timeout in ms */
548                               NULL, /* GCancellable * cancellable, */
549                               shell_spawned_cb,  /* VteTerminalSpawnAsyncCallback callback */
550                               tt);   /* user_data */
551 
552     g_free(argv1);
553     g_free (argv);
554 }
555 
556 gchar *
get_default_command()557 get_default_command ()
558 {
559     /* No custom command, get it from the environment */
560     gchar *default_command = (gchar *) g_getenv ("SHELL");
561 
562     /* Check for error */
563     if (default_command == NULL)
564         default_command = "/bin/sh";
565 
566     return default_command;
567 }
568 
get_working_directory(tilda_term * terminal)569 gchar *get_working_directory (tilda_term *terminal)
570 {
571     gchar *working_dir;
572 
573     working_dir = terminal->initial_working_dir;
574 
575     if (working_dir == NULL || config_getbool ("inherit_working_dir") == FALSE)
576     {
577         working_dir = config_getstr ("working_dir");
578     }
579 
580     return working_dir;
581 }
582 
child_exited_cb(GtkWidget * widget,gint status,gpointer data)583 static void child_exited_cb (GtkWidget *widget, gint status, gpointer data)
584 {
585     DEBUG_FUNCTION ("child_exited_cb");
586     DEBUG_ASSERT (widget != NULL);
587     DEBUG_ASSERT (data != NULL);
588 
589     tilda_term *tt = TILDA_TERM(data);
590 
591     gint index = gtk_notebook_page_num (GTK_NOTEBOOK(tt->tw->notebook), tt->hbox);
592 
593     /* Make sure we got a valid index */
594     if (index == -1)
595     {
596         DEBUG_ERROR ("Bad notebook tab\n");
597         return;
598     }
599 
600     /* These can stay here. They don't need to go into a header because
601      * they are only used at this point in the code. */
602     enum command_exit { DROP_TO_DEFAULT_SHELL, RESTART_COMMAND, EXIT_TERMINAL };
603 
604     /* Check the user's preference for what to do when the child terminal
605      * is closed. Take the appropriate action */
606     switch (config_getint ("command_exit"))
607     {
608         case EXIT_TERMINAL:
609             tilda_window_close_tab (tt->tw, index, FALSE);
610             break;
611         case RESTART_COMMAND:
612             vte_terminal_feed (VTE_TERMINAL(tt->vte_term), "\r\n\r\n", 4);
613             start_shell (tt, FALSE);
614             break;
615         case DROP_TO_DEFAULT_SHELL:
616             tt->initial_working_dir = NULL;
617             start_default_shell (tt);
618             tt->dropped_to_default_shell = TRUE;
619             break;
620         default:
621             break;
622     }
623 }
624 
625 /**
626  * tilda_term_config_defaults ()
627  *
628  * Read and set all of the defaults for this terminal from the current configuration.
629  *
630  * Success: return 0
631  * Failure: return non-zero
632  */
tilda_term_config_defaults(tilda_term * tt)633 static gint tilda_term_config_defaults (tilda_term *tt)
634 {
635     DEBUG_FUNCTION ("tilda_term_config_defaults");
636     DEBUG_ASSERT (tt != NULL);
637     GdkRGBA fg, bg, cc;
638     GdkRGBA *current_palette;
639     gchar* word_chars;
640     gint cursor_shape;
641 
642     /** Colors & Palette **/
643     bg.red   =    GUINT16_TO_FLOAT(config_getint ("back_red"));
644     bg.green =    GUINT16_TO_FLOAT(config_getint ("back_green"));
645     bg.blue  =    GUINT16_TO_FLOAT(config_getint ("back_blue"));
646 
647     bg.alpha =    (config_getbool("enable_transparency") ? GUINT16_TO_FLOAT(config_getint ("back_alpha")) : 1.0);
648 
649     fg.red   =    GUINT16_TO_FLOAT(config_getint ("text_red"));
650     fg.green =    GUINT16_TO_FLOAT(config_getint ("text_green"));
651     fg.blue  =    GUINT16_TO_FLOAT(config_getint ("text_blue"));
652     fg.alpha =    1.0;
653 
654     cc.red   =    GUINT16_TO_FLOAT(config_getint ("cursor_red"));
655     cc.green =    GUINT16_TO_FLOAT(config_getint ("cursor_green"));
656     cc.blue  =    GUINT16_TO_FLOAT(config_getint ("cursor_blue"));
657     cc.alpha = 1.0;
658 
659     current_palette = tilda_palettes_get_current_palette ();
660 
661     for(guint i = 0; i < TILDA_COLOR_PALETTE_SIZE; i++) {
662         current_palette[i].red   = GUINT16_TO_FLOAT(config_getnint ("palette", i*3));
663         current_palette[i].green = GUINT16_TO_FLOAT(config_getnint ("palette", i*3+1));
664         current_palette[i].blue  = GUINT16_TO_FLOAT(config_getnint ("palette", i*3+2));
665         current_palette[i].alpha = 1.0;
666     }
667 
668     vte_terminal_set_colors (VTE_TERMINAL(tt->vte_term),
669                              &fg,
670                              &bg,
671                              current_palette,
672                              TILDA_COLOR_PALETTE_SIZE);
673 
674     /** Bells **/
675     vte_terminal_set_audible_bell (VTE_TERMINAL(tt->vte_term), config_getbool ("bell"));
676 
677     /** Cursor **/
678     vte_terminal_set_cursor_blink_mode (VTE_TERMINAL(tt->vte_term),
679             (config_getbool ("blinks"))?VTE_CURSOR_BLINK_ON:VTE_CURSOR_BLINK_OFF);
680     vte_terminal_set_color_cursor (VTE_TERMINAL(tt->vte_term), &cc);
681     vte_terminal_set_color_cursor_foreground (VTE_TERMINAL(tt->vte_term), &bg);
682 
683     cursor_shape = config_getint("cursor_shape");
684     if (cursor_shape < 0 || cursor_shape > 2) {
685         config_setint("cursor_shape", 0);
686         cursor_shape = 0;
687     }
688     vte_terminal_set_cursor_shape(VTE_TERMINAL(tt->vte_term),
689                                   (VteCursorShape) cursor_shape);
690 
691     /** Scrolling **/
692     vte_terminal_set_scroll_on_output (VTE_TERMINAL(tt->vte_term), config_getbool ("scroll_on_output"));
693     vte_terminal_set_scroll_on_keystroke (VTE_TERMINAL(tt->vte_term), config_getbool ("scroll_on_key"));
694 
695     /** Mouse **/
696     vte_terminal_set_mouse_autohide (VTE_TERMINAL(tt->vte_term), FALSE); /* TODO: make this configurable */
697 
698     /** Text Properties **/
699     PangoFontDescription *description =
700         pango_font_description_from_string (config_getstr ("font"));
701     vte_terminal_set_font (VTE_TERMINAL (tt->vte_term), description);
702 
703     /** Scrollback **/
704     vte_terminal_set_scrollback_lines (VTE_TERMINAL(tt->vte_term), config_getbool("scroll_history_infinite") ? -1 : config_getint ("lines"));
705 
706     /** Keys **/
707     switch (config_getint ("backspace_key"))
708     {
709         case ASCII_DELETE:
710             vte_terminal_set_backspace_binding (VTE_TERMINAL(tt->vte_term), VTE_ERASE_ASCII_DELETE);
711             break;
712         case DELETE_SEQUENCE:
713             vte_terminal_set_backspace_binding (VTE_TERMINAL(tt->vte_term), VTE_ERASE_DELETE_SEQUENCE);
714             break;
715         case ASCII_BACKSPACE:
716             vte_terminal_set_backspace_binding (VTE_TERMINAL(tt->vte_term), VTE_ERASE_ASCII_BACKSPACE);
717             break;
718         case AUTO:
719         default:
720             vte_terminal_set_backspace_binding (VTE_TERMINAL(tt->vte_term), VTE_ERASE_AUTO);
721             break;
722     }
723 
724     switch (config_getint ("delete_key"))
725     {
726         case ASCII_DELETE:
727             vte_terminal_set_delete_binding (VTE_TERMINAL(tt->vte_term), VTE_ERASE_ASCII_DELETE);
728             break;
729         case DELETE_SEQUENCE:
730             vte_terminal_set_delete_binding (VTE_TERMINAL(tt->vte_term), VTE_ERASE_DELETE_SEQUENCE);
731             break;
732         case ASCII_BACKSPACE:
733             vte_terminal_set_delete_binding (VTE_TERMINAL(tt->vte_term), VTE_ERASE_ASCII_BACKSPACE);
734             break;
735         case AUTO:
736         default:
737             vte_terminal_set_delete_binding (VTE_TERMINAL(tt->vte_term), VTE_ERASE_AUTO);
738             break;
739     }
740 
741     /** Word chars **/
742     word_chars =  config_getstr ("word_chars");
743     if (NULL == word_chars || '\0' == *word_chars) {
744         word_chars = DEFAULT_WORD_CHARS;
745     }
746 
747     vte_terminal_set_word_char_exceptions (VTE_TERMINAL (tt->vte_term), word_chars);
748 
749     return 0;
750 }
751 
752 static void
menu_copy_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)753 menu_copy_cb (GSimpleAction *action,
754               GVariant      *parameter,
755               gpointer       user_data)
756 {
757     DEBUG_FUNCTION ("menu_copy_cb");
758     DEBUG_ASSERT (user_data != NULL);
759 
760     tilda_term *tt = TILDA_TERM(user_data);
761 
762     vte_terminal_copy_clipboard_format (VTE_TERMINAL (tt->vte_term), VTE_FORMAT_TEXT);
763 }
764 
765 static void
menu_paste_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)766 menu_paste_cb (GSimpleAction *action,
767                GVariant      *parameter,
768                gpointer       user_data)
769 {
770     DEBUG_FUNCTION ("menu_paste_cb");
771     DEBUG_ASSERT (user_data != NULL);
772 
773     tilda_term *tt = TILDA_TERM(user_data);
774 
775     vte_terminal_paste_clipboard (VTE_TERMINAL (tt->vte_term));
776 }
777 
778 
779 static void
menu_preferences_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)780 menu_preferences_cb (GSimpleAction *action,
781                      GVariant      *parameter,
782                      gpointer       user_data)
783 {
784     DEBUG_FUNCTION ("menu_config_cb");
785     DEBUG_ASSERT (user_data != NULL);
786 
787     /* Show the config wizard */
788     wizard (TILDA_WINDOW(user_data));
789 }
790 
791 static void
menu_quit_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)792 menu_quit_cb (GSimpleAction *action,
793               GVariant      *parameter,
794               gpointer       user_data)
795 {
796     DEBUG_FUNCTION ("menu_quit_cb");
797 
798     tilda_window_confirm_quit(TILDA_WINDOW(user_data));
799 }
800 
801 static void
menu_add_tab_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)802 menu_add_tab_cb (GSimpleAction *action,
803                  GVariant      *parameter,
804                  gpointer       user_data)
805 {
806     DEBUG_FUNCTION ("menu_add_tab_cb");
807     DEBUG_ASSERT (user_data != NULL);
808 
809     tilda_window_add_tab (TILDA_WINDOW(user_data));
810 }
811 
812 static void
menu_fullscreen_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)813 menu_fullscreen_cb (GSimpleAction *action,
814                     GVariant      *parameter,
815                     gpointer       user_data)
816 {
817     DEBUG_FUNCTION ("menu_fullscreen_cb");
818     DEBUG_ASSERT (user_data != NULL);
819 
820     toggle_fullscreen_cb (TILDA_WINDOW(user_data));
821 }
822 
823 static void
menu_searchbar_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)824 menu_searchbar_cb(GSimpleAction *action,
825                     GVariant      *parameter,
826                     gpointer       user_data)
827 {
828     DEBUG_FUNCTION ("menu_fullscreen_cb");
829     DEBUG_ASSERT (user_data != NULL);
830 
831     tilda_window_toggle_searchbar (TILDA_WINDOW(user_data));
832 }
833 
834 static void
menu_close_tab_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)835 menu_close_tab_cb (GSimpleAction *action,
836                    GVariant      *parameter,
837                    gpointer       user_data)
838 {
839     DEBUG_FUNCTION ("menu_close_tab_cb");
840     DEBUG_ASSERT (user_data != NULL);
841 
842     tilda_window_close_current_tab (TILDA_WINDOW(user_data));
843 }
844 
on_popup_hide(GtkWidget * widget,tilda_window * tw)845 static void on_popup_hide (GtkWidget *widget, tilda_window *tw)
846 {
847     DEBUG_FUNCTION("on_popup_hide");
848     DEBUG_ASSERT(widget != NULL);
849     DEBUG_ASSERT(tw != NULL);
850 
851     tw->disable_auto_hide = FALSE;
852 }
853 
854 static void
popup_menu(tilda_window * tw,tilda_term * tt,GdkEvent * event)855 popup_menu (tilda_window *tw, tilda_term *tt, GdkEvent * event)
856 {
857     DEBUG_FUNCTION ("popup_menu");
858     DEBUG_ASSERT (tw != NULL);
859     DEBUG_ASSERT (tt != NULL);
860 
861     GtkBuilder *builder = gtk_builder_new();
862 
863     gtk_builder_add_from_resource (builder, "/org/tilda/menu.ui", NULL);
864 
865     /* Create the action group */
866     GSimpleActionGroup *action_group = g_simple_action_group_new();
867 
868     /* We need two different lists of entries because the
869      * because the actions have different scope, some concern the
870      * tilda_window and others concern the current terminal, so
871      * when we add them to the action_group we need to pass different
872      * user_data (tw or tt).
873      *
874      * Note: Using designated initializers here, allows us to skip the remaining fields which are NULL anyway and
875      * also gets rid of missing field initializer warnings.
876      */
877     GActionEntry entries_for_tilda_window[] = {
878         { .name="new-tab", menu_add_tab_cb },
879         { .name="close-tab", menu_close_tab_cb },
880         { .name="fullscreen", menu_fullscreen_cb },
881         { .name="searchbar", menu_searchbar_cb },
882         { .name="preferences", menu_preferences_cb },
883         { .name="quit", menu_quit_cb }
884     };
885 
886     GActionEntry entries_for_tilda_terminal[] = {
887         { .name="copy", menu_copy_cb},
888         { .name="paste", menu_paste_cb}
889     };
890 
891     g_action_map_add_action_entries(G_ACTION_MAP(action_group),
892             entries_for_tilda_window, G_N_ELEMENTS(entries_for_tilda_window), tw);
893     g_action_map_add_action_entries(G_ACTION_MAP(action_group),
894             entries_for_tilda_terminal, G_N_ELEMENTS(entries_for_tilda_terminal), tt);
895 
896     gtk_widget_insert_action_group(tw->window, "window", G_ACTION_GROUP(action_group));
897 
898     GMenuModel *menu_model = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
899     GtkWidget *menu = gtk_menu_new_from_model(menu_model);
900     gtk_menu_attach_to_widget(GTK_MENU(menu), tw->window, NULL);
901 
902     gtk_menu_set_accel_group(GTK_MENU(menu), tw->accel_group);
903     gtk_menu_set_accel_path(GTK_MENU(menu), "<tilda>/context");
904 
905     /* Disable auto hide */
906     tw->disable_auto_hide = TRUE;
907     g_signal_connect (G_OBJECT(menu), "unmap", G_CALLBACK(on_popup_hide), tw);
908 
909     gtk_menu_popup_at_pointer (GTK_MENU (menu), event);
910 }
911 
button_press_cb(G_GNUC_UNUSED GtkWidget * widget,GdkEventButton * event,gpointer data)912 static int button_press_cb (G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, gpointer data)
913 {
914     DEBUG_FUNCTION ("button_press_cb");
915     DEBUG_ASSERT (data != NULL);
916 
917     VteTerminal *terminal;
918     tilda_term *tt;
919     gchar *match;
920     gint tag;
921     gchar *cmd;
922     gchar *web_browser_cmd;
923     gboolean ret = FALSE;
924 
925     tt = TILDA_TERM(data);
926 
927     switch (event->button)
928     {
929         case 9:
930             tilda_window_next_tab (tt->tw);
931             break;
932         case 8:
933             tilda_window_prev_tab (tt->tw);
934             break;
935         case 3: /* Right Click */
936             popup_menu (tt->tw, tt, (GdkEvent *) event);
937             break;
938         case 2: /* Middle Click */
939             break;
940         case 1: /* Left Click */
941             terminal  = VTE_TERMINAL(tt->vte_term);
942 
943 #if VTE_CHECK_VERSION(0, 38, 0)
944             match = vte_terminal_match_check_event (terminal,
945                                                     (GdkEvent *) event,
946                                                     &tag);
947 #else
948             gint ypad = gtk_widget_get_margin_bottom(GTK_WIDGET(terminal));
949 
950             glong column = (glong) ((event->x - ypad) /
951                                     vte_terminal_get_char_width (terminal));
952             glong row = (glong) ((event->y - ypad) /
953                                  vte_terminal_get_char_height (terminal));
954 
955             match = vte_terminal_match_check (terminal,
956                                               column,
957                                               row,
958                                               &tag);
959 #endif
960 
961             /* Check if we can launch a web browser, and do so if possible */
962             if (match != NULL)
963             {
964                 g_debug ("Got a Left Click -- Matched: `%s' (%d)", match, tag);
965 
966                 web_browser_cmd = g_strescape (config_getstr ("web_browser"), NULL);
967                 cmd = g_strdup_printf ("%s %s", web_browser_cmd, match);
968 
969                 g_debug ("Launching command: `%s'", cmd);
970 
971                 ret = g_spawn_command_line_async(cmd, NULL);
972 
973                 /* Check that the command launched */
974                 if (!ret)
975                 {
976                     g_critical (_("Failed to launch the web browser. The command was `%s'\n"), cmd);
977                     TILDA_PERROR ();
978                 }
979 
980                 g_free (web_browser_cmd);
981                 g_free (cmd);
982                 g_free (match);
983             }
984 
985             break;
986         default:
987             break;
988     }
989 
990     return FALSE;
991 }
992 
key_press_cb(GtkWidget * widget,GdkEvent * event,tilda_term * terminal)993 gboolean key_press_cb (GtkWidget *widget,
994                        GdkEvent  *event,
995                        tilda_term *terminal)
996 {
997     if(event->type == GDK_KEY_PRESS) {
998         GdkEventKey *keyevent = (GdkEventKey*) event;
999         if(keyevent->keyval == GDK_KEY_Menu) {
1000             popup_menu(terminal->tw, terminal, event);
1001         }
1002     }
1003     return GDK_EVENT_PROPAGATE;
1004 }
1005 
tilda_terminal_get_full_title(tilda_term * tt)1006 gchar * tilda_terminal_get_full_title (tilda_term *tt)
1007 {
1008     DEBUG_FUNCTION ("tilda_terminal_get_title");
1009 
1010     const gchar *vte_title;
1011     gchar *window_title;
1012     gchar *initial;
1013     gchar *title;
1014 
1015     vte_title = vte_terminal_get_window_title (VTE_TERMINAL (tt->vte_term));
1016     window_title = g_strdup (vte_title);
1017     initial = g_strdup (config_getstr ("title"));
1018 
1019     /* These are not needed anywhere else. If they ever are, move them to a header file */
1020     enum d_set_title { NOT_DISPLAYED, AFTER_INITIAL, BEFORE_INITIAL, REPLACE_INITIAL };
1021 
1022     switch (config_getint ("d_set_title"))
1023     {
1024         case REPLACE_INITIAL:
1025             title = (window_title != NULL) ? g_strdup (window_title)
1026                                            : g_strdup (_("Untitled"));
1027             break;
1028 
1029         case BEFORE_INITIAL:
1030             title = (window_title != NULL) ? g_strdup_printf ("%s - %s", window_title, initial)
1031                                            : g_strdup (initial);
1032             break;
1033 
1034         case AFTER_INITIAL:
1035             title = (window_title != NULL) ? g_strdup_printf ("%s - %s", initial, window_title)
1036                                            : g_strdup (initial);
1037             break;
1038 
1039         case NOT_DISPLAYED:
1040             title = g_strdup (initial);
1041             break;
1042 
1043         default:
1044             g_printerr (_("Bad value for \"d_set_title\" in config file\n"));
1045             title = g_strdup ("");
1046             break;
1047     }
1048 
1049     g_free (window_title);
1050     g_free (initial);
1051 
1052     return title;
1053 }
1054 
tilda_terminal_get_title(tilda_term * tt)1055 gchar *tilda_terminal_get_title (tilda_term *tt)
1056 {
1057     DEBUG_FUNCTION ("tilda_terminal_get_title");
1058     DEBUG_ASSERT (tt != NULL);
1059 
1060     gchar * title;
1061     gchar * final_title;
1062 
1063     title = tilda_terminal_get_full_title (tt);
1064 
1065     /* These are not needed anywhere else. If they ever are, move them to a header file */
1066     enum { SHOW_FULL_TITLE, SHOW_FIRST_N_CHARS, SHOW_LAST_N_CHARS };
1067 
1068     guint max_length = (guint) config_getint ("title_max_length");
1069     guint title_behaviour = config_getint("title_behaviour");
1070 
1071     if (strlen (title) > max_length) {
1072         if (title_behaviour == SHOW_FULL_TITLE) {
1073             final_title = g_strdup (title);
1074         } else if (title_behaviour == SHOW_FIRST_N_CHARS) {
1075             final_title = g_strdup_printf ("%.*s...", max_length, title);
1076         } else if (title_behaviour == SHOW_LAST_N_CHARS) {
1077             gchar *titleOffset = title + strlen(title) - max_length;
1078             final_title = g_strdup_printf ("...%s", titleOffset);
1079         } else {
1080             g_printerr ("Bad value for \"title_behaviour\" in config file.\n");
1081             final_title = g_strdup (title);
1082         }
1083     } else {
1084         final_title = g_strdup (title);
1085     }
1086 
1087     g_free (title);
1088 
1089     return final_title;
1090 }
1091