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