1 /*
2 * Copyright © 2001 Havoc Pennington
3 * Copyright © 2007, 2008, 2010 Christian Persch
4 * Copyright (C) 2012-2021 MATE Developers
5 *
6 * Mate-terminal is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Mate-terminal is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <config.h>
21
22 #ifdef __FreeBSD__
23 #include <sys/types.h>
24 #include <sys/sysctl.h>
25 #include <sys/param.h>
26 #include <sys/user.h>
27 #ifdef HAVE_KINFO_GETFILE
28 #include <libutil.h>
29 #endif
30 #endif
31 #include <string.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <sys/wait.h>
35
36 #include <gio/gio.h>
37 #include <gtk/gtk.h>
38 #include <gdk/gdkkeysyms.h>
39
40 #include <gdk/gdk.h>
41 #include <gdk/gdkx.h>
42 #include <gdk-pixbuf/gdk-pixbuf.h>
43 #include <cairo.h>
44
45 #include "terminal-accels.h"
46 #include "terminal-app.h"
47 #include "terminal-debug.h"
48 #include "terminal-intl.h"
49 #include "terminal-marshal.h"
50 #include "terminal-profile.h"
51 #include "terminal-screen-container.h"
52 #include "terminal-util.h"
53 #include "terminal-window.h"
54 #include "terminal-info-bar.h"
55
56 #include "eggshell.h"
57
58 #define PCRE2_CODE_UNIT_WIDTH 0
59 #include <pcre2.h>
60
61 #define URL_MATCH_CURSOR (GDK_HAND2)
62 #define SKEY_MATCH_CURSOR (GDK_HAND2)
63
64 typedef struct
65 {
66 int tag;
67 TerminalURLFlavour flavor;
68 } TagData;
69
70 struct _TerminalScreenPrivate
71 {
72 TerminalProfile *profile; /* may be NULL at times */
73 guint profile_changed_id;
74 guint profile_forgotten_id;
75 char *raw_title, *raw_icon_title;
76 char *cooked_title, *cooked_icon_title;
77 char *override_title;
78 gboolean icon_title_set;
79 char *initial_working_directory;
80 char **initial_env;
81 char **override_command;
82 int child_pid;
83 double font_scale;
84 gboolean user_title; /* title was manually set */
85 GSList *match_tags;
86 guint launch_child_source_id;
87 gulong bg_image_callback_id;
88 GdkPixbuf *bg_image;
89 };
90
91 enum
92 {
93 PROFILE_SET,
94 SHOW_POPUP_MENU,
95 MATCH_CLICKED,
96 CLOSE_SCREEN,
97 LAST_SIGNAL
98 };
99
100 enum
101 {
102 PROP_0,
103 PROP_PROFILE,
104 PROP_ICON_TITLE,
105 PROP_ICON_TITLE_SET,
106 PROP_OVERRIDE_COMMAND,
107 PROP_TITLE,
108 PROP_INITIAL_ENVIRONMENT
109 };
110
111 enum
112 {
113 TARGET_COLOR,
114 TARGET_BGIMAGE,
115 TARGET_RESET_BG,
116 TARGET_MOZ_URL,
117 TARGET_NETSCAPE_URL,
118 TARGET_TAB
119 };
120
121 static void terminal_screen_dispose (GObject *object);
122 static void terminal_screen_finalize (GObject *object);
123 static void terminal_screen_drag_data_received (GtkWidget *widget,
124 GdkDragContext *context,
125 gint x,
126 gint y,
127 GtkSelectionData *selection_data,
128 guint info,
129 guint time);
130 static void terminal_screen_system_font_notify_cb (TerminalApp *app,
131 GParamSpec *pspec,
132 TerminalScreen *screen);
133 static void terminal_screen_change_font (TerminalScreen *screen);
134 static gboolean terminal_screen_popup_menu (GtkWidget *widget);
135 static gboolean terminal_screen_button_press (GtkWidget *widget,
136 GdkEventButton *event);
137 static void terminal_screen_launch_child_on_idle (TerminalScreen *screen);
138 static void terminal_screen_child_exited (VteTerminal *terminal, int status);
139
140 static void terminal_screen_window_title_changed (VteTerminal *vte_terminal,
141 TerminalScreen *screen);
142 static void terminal_screen_icon_title_changed (VteTerminal *vte_terminal,
143 TerminalScreen *screen);
144
145 static void update_color_scheme (TerminalScreen *screen);
146
147 static gboolean terminal_screen_format_title (TerminalScreen *screen, const char *raw_title, char **old_cooked_title);
148
149 static void terminal_screen_cook_title (TerminalScreen *screen);
150 static void terminal_screen_cook_icon_title (TerminalScreen *screen);
151
152 static char* terminal_screen_check_match (TerminalScreen *screen,
153 GdkEvent *event,
154 int *flavor);
155
156 static guint signals[LAST_SIGNAL] = { 0 };
157
158 #define USERCHARS "-[:alnum:]"
159 #define USERCHARS_CLASS "[" USERCHARS "]"
160 #define PASSCHARS_CLASS "[-[:alnum:]\\Q,?;.:/!%$^*&~\"#'\\E]"
161 #define HOSTCHARS_CLASS "[-[:alnum:]]"
162 #define HOST HOSTCHARS_CLASS "+(\\." HOSTCHARS_CLASS "+)*"
163 #define PORT "(?:\\:[[:digit:]]{1,5})?"
164 #define PATHCHARS_CLASS "[-[:alnum:]\\Q_$.+!*,:;@&=?/~#%\\E]"
165 #define PATHTERM_CLASS "[^\\Q]'.:}>) \t\r\n,\"\\E]"
166 #define SCHEME "(?:news:|telnet:|nntp:|file:\\/|https?:|ftps?:|sftp:|webcal:)"
167 #define USERPASS USERCHARS_CLASS "+(?:" PASSCHARS_CLASS "+)?"
168 #define URLPATH "(?:(/"PATHCHARS_CLASS"+(?:[(]"PATHCHARS_CLASS"*[)])*"PATHCHARS_CLASS"*)*"PATHTERM_CLASS")?"
169
170 typedef struct
171 {
172 const char *pattern;
173 TerminalURLFlavour flavor;
174 guint32 flags;
175 } TerminalRegexPattern;
176
177 static const TerminalRegexPattern url_regex_patterns[] =
178 {
179 { SCHEME "//(?:" USERPASS "\\@)?" HOST PORT URLPATH, FLAVOR_AS_IS, PCRE2_CASELESS },
180 { "(?:www|ftp)" HOSTCHARS_CLASS "*\\." HOST PORT URLPATH , FLAVOR_DEFAULT_TO_HTTP, PCRE2_CASELESS },
181 { "(?:callto:|h323:|sip:)" USERCHARS_CLASS "[" USERCHARS ".]*(?:" PORT "/[a-z0-9]+)?\\@" HOST, FLAVOR_VOIP_CALL, PCRE2_CASELESS },
182 { "(?:mailto:)?" USERCHARS_CLASS "[" USERCHARS ".]*\\@" HOSTCHARS_CLASS "+\\." HOST, FLAVOR_EMAIL, PCRE2_CASELESS },
183 { "news:[[:alnum:]\\Q^_{|}~!\"#$%&'()*+,./;:=?`\\E]+", FLAVOR_AS_IS, PCRE2_CASELESS },
184 };
185
186 static VteRegex **url_regexes;
187 static TerminalURLFlavour *url_regex_flavors;
188 static guint n_url_regexes;
189
190 static void terminal_screen_url_match_remove (TerminalScreen *screen);
191
192
193 #ifdef ENABLE_SKEY
194 static const TerminalRegexPattern skey_regex_patterns[] =
195 {
196 { "s/key [[:digit:]]* [-[:alnum:]]*", FLAVOR_AS_IS, 0 },
197 { "otp-[a-z0-9]* [[:digit:]]* [-[:alnum:]]*", FLAVOR_AS_IS, 0 },
198 };
199
200 static VteRegex **skey_regexes;
201 static guint n_skey_regexes;
202
203 static void terminal_screen_skey_match_remove (TerminalScreen *screen);
204 #endif /* ENABLE_SKEY */
205
G_DEFINE_TYPE_WITH_PRIVATE(TerminalScreen,terminal_screen,VTE_TYPE_TERMINAL)206 G_DEFINE_TYPE_WITH_PRIVATE (TerminalScreen, terminal_screen, VTE_TYPE_TERMINAL)
207
208 static char *
209 cwd_of_pid (int pid)
210 {
211 #ifndef __FreeBSD__
212 static const char patterns[][18] =
213 {
214 "/proc/%d/cwd", /* Linux */
215 "/proc/%d/path/cwd", /* Solaris >= 10 */
216 };
217 #else
218 #if __FreeBSD_version > 800018 || (__FreeBSD_version < 800000 && __FreeBSD_version >= 700104)
219 struct kinfo_file *freep, *kif;
220 #ifndef HAVE_KINFO_GETFILE
221 size_t len;
222 int name[4];
223 #else
224 int cnt;
225 #endif /* HAVE_KINFO_GETFILE */
226 #endif /* __FreeBSD_version > 800018 || (__FreeBSD_version < 800000 && __FreeBSD_version >= 700104) */
227 #endif /* __FreeBSD__ */
228 guint i;
229
230 if (pid == -1)
231 return NULL;
232
233 #ifndef __FreeBSD__
234 /* Try to get the working directory using various OS-specific mechanisms */
235 for (i = 0; i < G_N_ELEMENTS (patterns); ++i)
236 {
237 char cwd_file[64];
238 char buf[PATH_MAX + 1];
239 int len;
240
241 g_snprintf (cwd_file, sizeof (cwd_file), patterns[i], pid);
242 len = readlink (cwd_file, buf, sizeof (buf) - 1);
243
244 if (len > 0 && buf[0] == '/')
245 return g_strndup (buf, len);
246
247 /* If that didn't do it, try this hack */
248 if (len <= 0)
249 {
250 char *cwd, *working_dir = NULL;
251
252 cwd = g_get_current_dir ();
253 if (cwd != NULL)
254 {
255 /* On Solaris, readlink returns an empty string, but the
256 * link can be used as a directory, including as a target
257 * of chdir().
258 */
259 if (chdir (cwd_file) == 0)
260 {
261 working_dir = g_get_current_dir ();
262 if (chdir (cwd) < 0)
263 g_warning ("Could not change working directory.");
264 }
265 g_free (cwd);
266 }
267
268 if (working_dir)
269 return working_dir;
270 }
271 }
272 #else
273 int fgpid = pid;
274 #if __FreeBSD_version > 800018 || (__FreeBSD_version < 800000 && __FreeBSD_version >= 700104)
275 #ifndef HAVE_KINFO_GETFILE
276 name[0] = CTL_KERN;
277 name[1] = KERN_PROC;
278 name[2] = KERN_PROC_FILEDESC;
279 name[3] = fgpid;
280
281 if (sysctl (name, 4, NULL, &len, NULL, 0) < 0)
282 return NULL;
283 freep = kif = g_malloc (len);
284 if (sysctl (name, 4, kif, &len, NULL, 0) < 0)
285 {
286 g_free (freep);
287 return NULL;
288 }
289 #else
290 freep = kinfo_getfile (fgpid, &cnt);
291 #endif /* HAVE_KINFO_GETFILE */
292
293 #ifndef HAVE_KINFO_GETFILE
294 for (i = 0; i < len / sizeof (*kif); i++, kif++)
295 {
296 if (kif->kf_structsize != sizeof (*kif))
297 continue;
298 #else
299 for (i = 0; i < cnt; i++)
300 {
301 kif = &freep[i];
302 #endif /* HAVE_KINFO_GETFILE */
303 if (kif->kf_fd == KF_FD_TYPE_CWD)
304 {
305 char *working_dir;
306
307 working_dir = g_strdup (kif->kf_path);
308 g_free (freep);
309 return working_dir;
310 }
311 }
312 g_free (freep);
313 #endif /* __FreeBSD_version > 800018 || (__FreeBSD_version < 800000 && __FreeBSD_version >= 700104) */
314 #endif /* __FreeBSD__ */
315
316 return NULL;
317 }
318
319 static void
320 free_tag_data (TagData *tagdata)
321 {
322 g_slice_free (TagData, tagdata);
323 }
324
325 static void
326 terminal_screen_class_enable_menu_bar_accel_notify_cb (TerminalApp *app,
327 GParamSpec *pspec,
328 TerminalScreenClass *klass)
329 {
330 static gboolean is_enabled = TRUE; /* the binding is enabled by default since GtkWidgetClass installs it */
331 gboolean enable;
332 GtkBindingSet *binding_set;
333
334 g_object_get (app, TERMINAL_APP_ENABLE_MENU_BAR_ACCEL, &enable, NULL);
335
336 /* Only remove the 'skip' entry when we have added it previously! */
337 if (enable == is_enabled)
338 return;
339
340 is_enabled = enable;
341
342 binding_set = gtk_binding_set_by_class (klass);
343 if (enable)
344 gtk_binding_entry_remove (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK);
345 else
346 gtk_binding_entry_skip (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK);
347 }
348
349 static TerminalWindow *
350 terminal_screen_get_window (TerminalScreen *screen)
351 {
352 GtkWidget *widget = GTK_WIDGET (screen);
353 GtkWidget *toplevel;
354
355 toplevel = gtk_widget_get_toplevel (widget);
356 if (!gtk_widget_is_toplevel (toplevel))
357 return NULL;
358
359 return TERMINAL_WINDOW (toplevel);
360 }
361
362 static void
363 terminal_screen_realize (GtkWidget *widget)
364 {
365 TerminalScreen *screen = TERMINAL_SCREEN (widget);
366
367 GTK_WIDGET_CLASS (terminal_screen_parent_class)->realize (widget);
368
369 terminal_screen_set_font (screen);
370 }
371
372 static void
373 terminal_screen_style_updated (GtkWidget *widget)
374 {
375 TerminalScreen *screen = TERMINAL_SCREEN (widget);
376
377 GTK_WIDGET_CLASS (terminal_screen_parent_class)->style_updated (widget);
378
379 update_color_scheme (screen);
380
381 if (gtk_widget_get_realized (widget))
382 terminal_screen_change_font (screen);
383 }
384
385 #ifdef MATE_ENABLE_DEBUG
386 static void
387 size_allocate (GtkWidget *widget,
388 GtkAllocation *allocation)
389 {
390 _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
391 "[screen %p] size-alloc %d : %d at (%d, %d)\n",
392 widget, allocation->width, allocation->height, allocation->x, allocation->y);
393 }
394 #endif
395
396 static void
397 terminal_screen_init (TerminalScreen *screen)
398 {
399 const GtkTargetEntry target_table[] =
400 {
401 { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, TARGET_TAB },
402 { "application/x-color", 0, TARGET_COLOR },
403 { "property/bgimage", 0, TARGET_BGIMAGE },
404 { "x-special/mate-reset-background", 0, TARGET_RESET_BG },
405 { "text/x-moz-url", 0, TARGET_MOZ_URL },
406 { "_NETSCAPE_URL", 0, TARGET_NETSCAPE_URL }
407 };
408 TerminalScreenPrivate *priv;
409 GtkTargetList *target_list;
410 GtkTargetEntry *targets;
411 int n_targets;
412
413 priv = screen->priv = terminal_screen_get_instance_private (screen);
414
415 vte_terminal_set_mouse_autohide (VTE_TERMINAL (screen), TRUE);
416 #if VTE_CHECK_VERSION (0, 52, 0)
417 vte_terminal_set_bold_is_bright (VTE_TERMINAL (screen), TRUE);
418 #endif
419
420 priv->child_pid = -1;
421
422 priv->font_scale = PANGO_SCALE_MEDIUM;
423
424 /* Setup DND */
425 target_list = gtk_target_list_new (NULL, 0);
426 gtk_target_list_add_uri_targets (target_list, 0);
427 gtk_target_list_add_text_targets (target_list, 0);
428 gtk_target_list_add_table (target_list, target_table, G_N_ELEMENTS (target_table));
429
430 targets = gtk_target_table_new_from_list (target_list, &n_targets);
431
432 gtk_drag_dest_set (GTK_WIDGET (screen),
433 GTK_DEST_DEFAULT_MOTION |
434 GTK_DEST_DEFAULT_HIGHLIGHT |
435 GTK_DEST_DEFAULT_DROP,
436 targets, n_targets,
437 GDK_ACTION_COPY | GDK_ACTION_MOVE);
438
439 gtk_target_table_free (targets, n_targets);
440 gtk_target_list_unref (target_list);
441
442 priv->override_title = NULL;
443 priv->user_title = FALSE;
444
445 g_signal_connect (screen, "window-title-changed",
446 G_CALLBACK (terminal_screen_window_title_changed),
447 screen);
448 g_signal_connect (screen, "icon-title-changed",
449 G_CALLBACK (terminal_screen_icon_title_changed),
450 screen);
451
452 g_signal_connect (terminal_app_get (), "notify::system-font",
453 G_CALLBACK (terminal_screen_system_font_notify_cb), screen);
454
455 priv->bg_image_callback_id = 0;
456 priv->bg_image = NULL;
457
458 #ifdef MATE_ENABLE_DEBUG
459 _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_GEOMETRY)
460 {
461 g_signal_connect_after (screen, "size-allocate", G_CALLBACK (size_allocate), NULL);
462 }
463 #endif
464 }
465
466 static void
467 terminal_screen_get_property (GObject *object,
468 guint prop_id,
469 GValue *value,
470 GParamSpec *pspec)
471 {
472 TerminalScreen *screen = TERMINAL_SCREEN (object);
473
474 switch (prop_id)
475 {
476 case PROP_PROFILE:
477 g_value_set_object (value, terminal_screen_get_profile (screen));
478 break;
479 case PROP_ICON_TITLE:
480 g_value_set_string (value, terminal_screen_get_icon_title (screen));
481 break;
482 case PROP_ICON_TITLE_SET:
483 g_value_set_boolean (value, terminal_screen_get_icon_title_set (screen));
484 break;
485 case PROP_OVERRIDE_COMMAND:
486 g_value_set_boxed (value, terminal_screen_get_override_command (screen));
487 break;
488 case PROP_INITIAL_ENVIRONMENT:
489 g_value_set_boxed (value, terminal_screen_get_initial_environment (screen));
490 break;
491 case PROP_TITLE:
492 g_value_set_string (value, terminal_screen_get_title (screen));
493 break;
494 default:
495 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
496 break;
497 }
498 }
499
500 static void
501 terminal_screen_set_property (GObject *object,
502 guint prop_id,
503 const GValue *value,
504 GParamSpec *pspec)
505 {
506 TerminalScreen *screen = TERMINAL_SCREEN (object);
507
508 switch (prop_id)
509 {
510 case PROP_PROFILE:
511 {
512 TerminalProfile *profile;
513
514 profile = g_value_get_object (value);
515 g_assert (profile != NULL);
516 terminal_screen_set_profile (screen, profile);
517 break;
518 }
519 case PROP_OVERRIDE_COMMAND:
520 terminal_screen_set_override_command (screen, g_value_get_boxed (value));
521 break;
522 case PROP_INITIAL_ENVIRONMENT:
523 terminal_screen_set_initial_environment (screen, g_value_get_boxed (value));
524 break;
525 case PROP_ICON_TITLE:
526 case PROP_ICON_TITLE_SET:
527 case PROP_TITLE:
528 /* not writable */
529 default:
530 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
531 break;
532 }
533 }
534
535 static void
536 terminal_screen_class_init (TerminalScreenClass *klass)
537 {
538 GObjectClass *object_class = G_OBJECT_CLASS (klass);
539 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
540 VteTerminalClass *terminal_class = VTE_TERMINAL_CLASS (klass);
541 TerminalApp *app;
542 guint i;
543
544 object_class->dispose = terminal_screen_dispose;
545 object_class->finalize = terminal_screen_finalize;
546 object_class->get_property = terminal_screen_get_property;
547 object_class->set_property = terminal_screen_set_property;
548
549 widget_class->realize = terminal_screen_realize;
550 widget_class->style_updated = terminal_screen_style_updated;
551 widget_class->drag_data_received = terminal_screen_drag_data_received;
552 widget_class->button_press_event = terminal_screen_button_press;
553 widget_class->popup_menu = terminal_screen_popup_menu;
554
555 terminal_class->child_exited = terminal_screen_child_exited;
556
557 signals[PROFILE_SET] =
558 g_signal_new (I_("profile-set"),
559 G_OBJECT_CLASS_TYPE (object_class),
560 G_SIGNAL_RUN_LAST,
561 G_STRUCT_OFFSET (TerminalScreenClass, profile_set),
562 NULL, NULL,
563 g_cclosure_marshal_VOID__OBJECT,
564 G_TYPE_NONE,
565 1, TERMINAL_TYPE_PROFILE);
566
567 signals[SHOW_POPUP_MENU] =
568 g_signal_new (I_("show-popup-menu"),
569 G_OBJECT_CLASS_TYPE (object_class),
570 G_SIGNAL_RUN_LAST,
571 G_STRUCT_OFFSET (TerminalScreenClass, show_popup_menu),
572 NULL, NULL,
573 g_cclosure_marshal_VOID__POINTER,
574 G_TYPE_NONE,
575 1,
576 G_TYPE_POINTER);
577
578 signals[MATCH_CLICKED] =
579 g_signal_new (I_("match-clicked"),
580 G_OBJECT_CLASS_TYPE (object_class),
581 G_SIGNAL_RUN_LAST,
582 G_STRUCT_OFFSET (TerminalScreenClass, match_clicked),
583 g_signal_accumulator_true_handled, NULL,
584 _terminal_marshal_BOOLEAN__STRING_INT_UINT,
585 G_TYPE_BOOLEAN,
586 3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT);
587
588 signals[CLOSE_SCREEN] =
589 g_signal_new (I_("close-screen"),
590 G_OBJECT_CLASS_TYPE (object_class),
591 G_SIGNAL_RUN_LAST,
592 G_STRUCT_OFFSET (TerminalScreenClass, close_screen),
593 NULL, NULL,
594 g_cclosure_marshal_VOID__VOID,
595 G_TYPE_NONE,
596 0);
597
598 g_object_class_install_property
599 (object_class,
600 PROP_PROFILE,
601 g_param_spec_string ("profile", NULL, NULL,
602 NULL,
603 G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
604
605 g_object_class_install_property
606 (object_class,
607 PROP_ICON_TITLE,
608 g_param_spec_string ("icon-title", NULL, NULL,
609 NULL,
610 G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
611
612 g_object_class_install_property
613 (object_class,
614 PROP_ICON_TITLE_SET,
615 g_param_spec_boolean ("icon-title-set", NULL, NULL,
616 FALSE,
617 G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
618
619 g_object_class_install_property
620 (object_class,
621 PROP_OVERRIDE_COMMAND,
622 g_param_spec_boxed ("override-command", NULL, NULL,
623 G_TYPE_STRV,
624 G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
625
626 g_object_class_install_property
627 (object_class,
628 PROP_TITLE,
629 g_param_spec_string ("title", NULL, NULL,
630 NULL,
631 G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
632
633 g_object_class_install_property
634 (object_class,
635 PROP_INITIAL_ENVIRONMENT,
636 g_param_spec_boxed ("initial-environment", NULL, NULL,
637 G_TYPE_STRV,
638 G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
639
640 /* Precompile the regexes */
641 n_url_regexes = G_N_ELEMENTS (url_regex_patterns);
642 url_regexes = g_new0 (VteRegex*, n_url_regexes);
643 url_regex_flavors = g_new0 (TerminalURLFlavour, n_url_regexes);
644
645 for (i = 0; i < n_url_regexes; ++i)
646 {
647 GError *error = NULL;
648
649 url_regexes[i] = vte_regex_new_for_match(url_regex_patterns[i].pattern, -1,
650 url_regex_patterns[i].flags | PCRE2_MULTILINE, &error);
651 if (error)
652 {
653 g_message ("%s", error->message);
654 g_error_free (error);
655 }
656
657 url_regex_flavors[i] = url_regex_patterns[i].flavor;
658 }
659
660 #ifdef ENABLE_SKEY
661 n_skey_regexes = G_N_ELEMENTS (skey_regex_patterns);
662 skey_regexes = g_new0 (VteRegex*, n_skey_regexes);
663
664 for (i = 0; i < n_skey_regexes; ++i)
665 {
666 GError *error = NULL;
667
668 skey_regexes[i] = vte_regex_new_for_match(skey_regex_patterns[i].pattern, -1,
669 PCRE2_MULTILINE | PCRE2_UTF | PCRE2_NO_UTF_CHECK, &error);
670 if (error)
671 {
672 g_message ("%s", error->message);
673 g_error_free (error);
674 }
675 }
676 #endif /* ENABLE_SKEY */
677
678 /* This fixes bug #329827 */
679 app = terminal_app_get ();
680 terminal_screen_class_enable_menu_bar_accel_notify_cb (app, NULL, klass);
681 g_signal_connect (app, "notify::" TERMINAL_APP_ENABLE_MENU_BAR_ACCEL,
682 G_CALLBACK (terminal_screen_class_enable_menu_bar_accel_notify_cb), klass);
683 }
684
685 static void
686 terminal_screen_dispose (GObject *object)
687 {
688 TerminalScreen *screen = TERMINAL_SCREEN (object);
689 TerminalScreenPrivate *priv = screen->priv;
690 GtkSettings *settings;
691
692 settings = gtk_widget_get_settings (GTK_WIDGET (screen));
693 g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
694 0, 0, NULL, NULL,
695 screen);
696
697 if (priv->launch_child_source_id != 0)
698 {
699 g_source_remove (priv->launch_child_source_id);
700 priv->launch_child_source_id = 0;
701 }
702
703 G_OBJECT_CLASS (terminal_screen_parent_class)->dispose (object);
704 }
705
706 static void
707 terminal_screen_finalize (GObject *object)
708 {
709 TerminalScreen *screen = TERMINAL_SCREEN (object);
710 TerminalScreenPrivate *priv = screen->priv;
711
712 g_signal_handlers_disconnect_by_func (terminal_app_get (),
713 G_CALLBACK (terminal_screen_system_font_notify_cb),
714 screen);
715
716 terminal_screen_set_profile (screen, NULL);
717
718 g_free (priv->raw_title);
719 g_free (priv->cooked_title);
720 g_free (priv->override_title);
721 g_free (priv->raw_icon_title);
722 g_free (priv->cooked_icon_title);
723 g_free (priv->initial_working_directory);
724 g_strfreev (priv->override_command);
725 g_strfreev (priv->initial_env);
726
727 g_slist_foreach (priv->match_tags, (GFunc) free_tag_data, NULL);
728 g_slist_free (priv->match_tags);
729
730 if (priv->bg_image)
731 g_object_unref (priv->bg_image);
732
733 G_OBJECT_CLASS (terminal_screen_parent_class)->finalize (object);
734 }
735
736 static gboolean
737 terminal_screen_image_draw_cb (GtkWidget *widget, cairo_t *cr, void *userdata)
738 {
739 TerminalScreen *screen = TERMINAL_SCREEN (widget);
740 TerminalScreenPrivate *priv = screen->priv;
741 GdkPixbuf *bg_image = priv->bg_image;
742 GdkRectangle target_rect;
743 GtkAllocation alloc;
744 cairo_surface_t *child_surface;
745 cairo_t *child_cr;
746
747 if (!bg_image)
748 return FALSE;
749
750 gtk_widget_get_allocation (widget, &alloc);
751
752 target_rect.x = 0;
753 target_rect.y = 0;
754 target_rect.width = alloc.width;
755 target_rect.height = alloc.height;
756
757 child_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, alloc.width, alloc.height);
758 child_cr = cairo_create (child_surface);
759
760 g_signal_handler_block (screen, priv->bg_image_callback_id);
761 gtk_widget_draw (widget, child_cr);
762 g_signal_handler_unblock (screen, priv->bg_image_callback_id);
763
764 gdk_cairo_set_source_pixbuf (cr, bg_image, 0, 0);
765 cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
766
767 gdk_cairo_rectangle (cr, &target_rect);
768 cairo_fill (cr);
769
770 cairo_set_source_surface (cr, child_surface, 0, 0);
771 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
772 cairo_paint (cr);
773
774 cairo_destroy (child_cr);
775 cairo_surface_destroy (child_surface);
776
777 return TRUE;
778 }
779
780 TerminalScreen *
781 terminal_screen_new (TerminalProfile *profile,
782 char **override_command,
783 const char *title,
784 const char *working_dir,
785 char **child_env,
786 double zoom)
787 {
788 TerminalScreen *screen;
789 TerminalScreenPrivate *priv;
790
791 g_return_val_if_fail (TERMINAL_IS_PROFILE (profile), NULL);
792
793 screen = g_object_new (TERMINAL_TYPE_SCREEN, NULL);
794 priv = screen->priv;
795
796 terminal_screen_set_profile (screen, profile);
797
798 if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_CUSTOM_DEFAULT_SIZE))
799 {
800 vte_terminal_set_size (VTE_TERMINAL (screen),
801 terminal_profile_get_property_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_COLUMNS),
802 terminal_profile_get_property_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_ROWS));
803 }
804
805 if (title)
806 terminal_screen_set_override_title (screen, title);
807
808 priv->initial_working_directory = g_strdup (working_dir);
809
810 if (override_command)
811 terminal_screen_set_override_command (screen, override_command);
812
813 if (child_env)
814 terminal_screen_set_initial_environment (screen, child_env);
815
816 terminal_screen_set_font_scale (screen, zoom);
817 terminal_screen_set_font (screen);
818
819 /* Launch the child on idle */
820 terminal_screen_launch_child_on_idle (screen);
821
822 return screen;
823 }
824
825 const char*
826 terminal_screen_get_raw_title (TerminalScreen *screen)
827 {
828 TerminalScreenPrivate *priv = screen->priv;
829
830 if (priv->raw_title)
831 return priv->raw_title;
832
833 return "";
834 }
835
836 const char*
837 terminal_screen_get_title (TerminalScreen *screen)
838 {
839 TerminalScreenPrivate *priv = screen->priv;
840
841 if (priv->cooked_title == NULL)
842 terminal_screen_cook_title (screen);
843
844 /* cooked_title may still be NULL */
845 if (priv->cooked_title != NULL)
846 return priv->cooked_title;
847 else
848 return "";
849 }
850
851 const char*
852 terminal_screen_get_icon_title (TerminalScreen *screen)
853 {
854 TerminalScreenPrivate *priv = screen->priv;
855
856 if (priv->cooked_icon_title == NULL)
857 terminal_screen_cook_icon_title (screen);
858
859 /* cooked_icon_title may still be NULL */
860 if (priv->cooked_icon_title != NULL)
861 return priv->cooked_icon_title;
862 else
863 return "";
864 }
865
866 gboolean
867 terminal_screen_get_icon_title_set (TerminalScreen *screen)
868 {
869 return screen->priv->icon_title_set;
870 }
871
872 /* Supported format specifiers:
873 * %S = static title
874 * %D = dynamic title
875 * %A = dynamic title, falling back to static title if empty
876 * %- = separator, if not at start or end of string (excluding whitespace)
877 */
878 static const char *
879 terminal_screen_get_title_format (TerminalScreen *screen)
880 {
881 TerminalScreenPrivate *priv = screen->priv;
882 static const char *formats[] =
883 {
884 "%A" /* TERMINAL_TITLE_REPLACE */,
885 "%D%-%S" /* TERMINAL_TITLE_BEFORE */,
886 "%S%-%D" /* TERMINAL_TITLE_AFTER */,
887 "%S" /* TERMINAL_TITLE_IGNORE */
888 };
889
890 return formats[terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_TITLE_MODE)];
891 }
892
893 /**
894 * terminal_screen_format_title::
895 * @screen:
896 * @raw_title: main ingredient
897 * @titleptr <inout>: pointer of the current title string
898 *
899 * Format title according @format, and stores it in <literal>*titleptr</literal>.
900 * Always ensures that *titleptr will be non-NULL.
901 *
902 * Returns: %TRUE iff the title changed
903 */
904 static gboolean
905 terminal_screen_format_title (TerminalScreen *screen,
906 const char *raw_title,
907 char **titleptr)
908 {
909 TerminalScreenPrivate *priv = screen->priv;
910 const char *format, *arg;
911 const char *static_title = NULL;
912 GString *title;
913 gboolean add_sep = FALSE;
914
915 g_assert (titleptr);
916
917 /* use --title argument if one was supplied, otherwise ask the profile */
918 if (priv->override_title)
919 static_title = priv->override_title;
920 else
921 static_title = terminal_profile_get_property_string (priv->profile, TERMINAL_PROFILE_TITLE);
922
923 //title = g_string_sized_new (strlen (static_title) + strlen (raw_title) + 3 + 1);
924 title = g_string_sized_new (128);
925
926 format = terminal_screen_get_title_format (screen);
927 for (arg = format; *arg; arg += 2)
928 {
929 const char *text_to_append = NULL;
930
931 g_assert (arg[0] == '%');
932
933 switch (arg[1])
934 {
935 case 'A':
936 text_to_append = raw_title ? raw_title : static_title;
937 break;
938 case 'D':
939 text_to_append = raw_title;
940 break;
941 case 'S':
942 text_to_append = static_title;
943 break;
944 case '-':
945 text_to_append = NULL;
946 add_sep = TRUE;
947 break;
948 default:
949 g_assert_not_reached ();
950 }
951
952 if (!text_to_append || !text_to_append[0])
953 continue;
954
955 if (add_sep && title->len > 0)
956 g_string_append (title, " - ");
957
958 g_string_append (title, text_to_append);
959 add_sep = FALSE;
960 }
961
962 if (*titleptr == NULL || strcmp (title->str, *titleptr) != 0)
963 {
964 g_free (*titleptr);
965 *titleptr = g_string_free (title, FALSE);
966 return TRUE;
967 }
968
969 g_string_free (title, TRUE);
970 return FALSE;
971 }
972
973 static void
974 terminal_screen_cook_title (TerminalScreen *screen)
975 {
976 TerminalScreenPrivate *priv = screen->priv;
977
978 if (terminal_screen_format_title (screen, priv->raw_title, &priv->cooked_title))
979 g_object_notify (G_OBJECT (screen), "title");
980 }
981
982 static void
983 terminal_screen_cook_icon_title (TerminalScreen *screen)
984 {
985 TerminalScreenPrivate *priv = screen->priv;
986
987 if (terminal_screen_format_title (screen, priv->raw_icon_title, &priv->cooked_icon_title))
988 g_object_notify (G_OBJECT (screen), "icon-title");
989 }
990
991 static void
992 terminal_screen_profile_notify_cb (TerminalProfile *profile,
993 GParamSpec *pspec,
994 TerminalScreen *screen)
995 {
996 TerminalScreenPrivate *priv = screen->priv;
997 GObject *object = G_OBJECT (screen);
998 VteTerminal *vte_terminal = VTE_TERMINAL (screen);
999 const char *prop_name;
1000 TerminalWindow *window;
1001
1002 if (pspec)
1003 prop_name = pspec->name;
1004 else
1005 prop_name = NULL;
1006
1007 g_object_freeze_notify (object);
1008
1009 if ((window = terminal_screen_get_window (screen)))
1010 {
1011 /* We need these in line for the set_size in
1012 * update_on_realize
1013 */
1014 terminal_window_update_geometry (window);
1015
1016 /* madars.vitolins@gmail.com 24/07/2014 -
1017 * update terminal window config
1018 * with the flag of copy selection to clipboard or not. */
1019 terminal_window_update_copy_selection(screen, window);
1020 }
1021
1022 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLLBAR_POSITION))
1023 _terminal_screen_update_scrollbar (screen);
1024
1025 if (!prop_name ||
1026 prop_name == I_(TERMINAL_PROFILE_TITLE_MODE) ||
1027 prop_name == I_(TERMINAL_PROFILE_TITLE))
1028 {
1029 terminal_screen_cook_title (screen);
1030 terminal_screen_cook_icon_title (screen);
1031 }
1032
1033 if (gtk_widget_get_realized (GTK_WIDGET (screen)) &&
1034 (!prop_name ||
1035 prop_name == I_(TERMINAL_PROFILE_USE_SYSTEM_FONT) ||
1036 prop_name == I_(TERMINAL_PROFILE_FONT)))
1037 terminal_screen_change_font (screen);
1038
1039 if (!prop_name ||
1040 prop_name == I_(TERMINAL_PROFILE_USE_THEME_COLORS) ||
1041 prop_name == I_(TERMINAL_PROFILE_FOREGROUND_COLOR) ||
1042 prop_name == I_(TERMINAL_PROFILE_BACKGROUND_COLOR) ||
1043 prop_name == I_(TERMINAL_PROFILE_BACKGROUND_TYPE) ||
1044 prop_name == I_(TERMINAL_PROFILE_BACKGROUND_DARKNESS) ||
1045 prop_name == I_(TERMINAL_PROFILE_BACKGROUND_IMAGE) ||
1046 prop_name == I_(TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG) ||
1047 prop_name == I_(TERMINAL_PROFILE_BOLD_COLOR) ||
1048 prop_name == I_(TERMINAL_PROFILE_PALETTE))
1049 update_color_scheme (screen);
1050
1051 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SILENT_BELL))
1052 vte_terminal_set_audible_bell (vte_terminal, !terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_SILENT_BELL));
1053 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_WORD_CHARS))
1054 vte_terminal_set_word_char_exceptions (vte_terminal,
1055 terminal_profile_get_property_string (profile, TERMINAL_PROFILE_WORD_CHARS));
1056 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE))
1057 vte_terminal_set_scroll_on_keystroke (vte_terminal,
1058 terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE));
1059 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLL_ON_OUTPUT))
1060 vte_terminal_set_scroll_on_output (vte_terminal,
1061 terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_SCROLL_ON_OUTPUT));
1062 if (!prop_name ||
1063 prop_name == I_(TERMINAL_PROFILE_SCROLLBACK_LINES) ||
1064 prop_name == I_(TERMINAL_PROFILE_SCROLLBACK_UNLIMITED))
1065 {
1066 glong lines = terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_SCROLLBACK_UNLIMITED) ?
1067 -1 : terminal_profile_get_property_int (profile, TERMINAL_PROFILE_SCROLLBACK_LINES);
1068 vte_terminal_set_scrollback_lines (vte_terminal, lines);
1069 }
1070
1071 #ifdef ENABLE_SKEY
1072 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_USE_SKEY))
1073 {
1074 if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_SKEY))
1075 {
1076 guint i;
1077
1078 for (i = 0; i < n_skey_regexes; ++i)
1079 {
1080 TagData *tag_data;
1081
1082 tag_data = g_slice_new (TagData);
1083 tag_data->flavor = FLAVOR_SKEY;
1084 tag_data->tag = vte_terminal_match_add_regex (vte_terminal, skey_regexes[i], 0);
1085 vte_terminal_match_set_cursor_type (vte_terminal, tag_data->tag, SKEY_MATCH_CURSOR);
1086
1087 priv->match_tags = g_slist_prepend (priv->match_tags, tag_data);
1088 }
1089 }
1090 else
1091 {
1092 terminal_screen_skey_match_remove (screen);
1093 }
1094 }
1095 #endif /* ENABLE_SKEY */
1096
1097 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_BACKSPACE_BINDING))
1098 vte_terminal_set_backspace_binding (vte_terminal,
1099 terminal_profile_get_property_enum (profile, TERMINAL_PROFILE_BACKSPACE_BINDING));
1100
1101 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_DELETE_BINDING))
1102 vte_terminal_set_delete_binding (vte_terminal,
1103 terminal_profile_get_property_enum (profile, TERMINAL_PROFILE_DELETE_BINDING));
1104
1105 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ALLOW_BOLD))
1106 vte_terminal_set_allow_bold (vte_terminal,
1107 terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_ALLOW_BOLD));
1108
1109 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CURSOR_BLINK_MODE))
1110 vte_terminal_set_cursor_blink_mode (vte_terminal,
1111 terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_CURSOR_BLINK_MODE));
1112
1113 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CURSOR_SHAPE))
1114 vte_terminal_set_cursor_shape (vte_terminal,
1115 terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_CURSOR_SHAPE));
1116
1117 if (!prop_name || prop_name == I_(TERMINAL_PROFILE_USE_URLS))
1118 {
1119 if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_URLS))
1120 {
1121 guint i;
1122
1123 for (i = 0; i < n_url_regexes; ++i)
1124 {
1125 TagData *tag_data;
1126
1127 tag_data = g_slice_new (TagData);
1128 tag_data->flavor = url_regex_flavors[i];
1129 tag_data->tag = vte_terminal_match_add_regex (vte_terminal, url_regexes[i], 0);
1130 vte_terminal_match_set_cursor_type (vte_terminal, tag_data->tag, URL_MATCH_CURSOR);
1131
1132 priv->match_tags = g_slist_prepend (priv->match_tags, tag_data);
1133 }
1134 }
1135 else
1136 {
1137 terminal_screen_url_match_remove (screen);
1138 }
1139 }
1140 g_object_thaw_notify (object);
1141 }
1142
1143 static void
1144 update_color_scheme (TerminalScreen *screen)
1145 {
1146 TerminalScreenPrivate *priv = screen->priv;
1147 TerminalProfile *profile = priv->profile;
1148 GdkRGBA colors[TERMINAL_PALETTE_SIZE];
1149 const GdkRGBA *fg_rgba, *bg_rgba, *bold_rgba;
1150 TerminalBackgroundType bg_type;
1151 const gchar *bg_image_file;
1152 double bg_alpha = 1.0;
1153 GdkRGBA fg, bg;
1154 GdkRGBA *c;
1155 guint n_colors;
1156 GtkStyleContext *context;
1157 GError *error = NULL;
1158
1159 context = gtk_widget_get_style_context (GTK_WIDGET (screen));
1160 gtk_style_context_save (context);
1161 gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
1162 gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg);
1163
1164 gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL,
1165 GTK_STYLE_PROPERTY_BACKGROUND_COLOR,
1166 &c, NULL);
1167 bg = *c;
1168 gdk_rgba_free (c);
1169
1170 gtk_style_context_restore (context);
1171
1172 bold_rgba = NULL;
1173
1174 if (!terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_THEME_COLORS))
1175 {
1176 fg_rgba = terminal_profile_get_property_boxed (profile, TERMINAL_PROFILE_FOREGROUND_COLOR);
1177 bg_rgba = terminal_profile_get_property_boxed (profile, TERMINAL_PROFILE_BACKGROUND_COLOR);
1178
1179 if (!terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG))
1180 bold_rgba = terminal_profile_get_property_boxed (profile, TERMINAL_PROFILE_BOLD_COLOR);
1181
1182 if (fg_rgba)
1183 fg = *fg_rgba;
1184 if (bg_rgba)
1185 bg = *bg_rgba;
1186 }
1187
1188 n_colors = G_N_ELEMENTS (colors);
1189 terminal_profile_get_palette (priv->profile, colors, &n_colors);
1190
1191 bg_type = terminal_profile_get_property_enum (profile, TERMINAL_PROFILE_BACKGROUND_TYPE);
1192 bg_image_file = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_BACKGROUND_IMAGE_FILE);
1193
1194 if (bg_type == TERMINAL_BACKGROUND_TRANSPARENT)
1195 bg_alpha = terminal_profile_get_property_double (profile, TERMINAL_PROFILE_BACKGROUND_DARKNESS);
1196 else if (bg_type == TERMINAL_BACKGROUND_IMAGE)
1197 bg_alpha = 0.0;
1198 bg.alpha = bg_alpha;
1199
1200 if (bg_type == TERMINAL_BACKGROUND_IMAGE)
1201 {
1202 if (!priv->bg_image_callback_id)
1203 priv->bg_image_callback_id = g_signal_connect (screen, "draw", G_CALLBACK (terminal_screen_image_draw_cb), NULL);
1204
1205 g_clear_object (&priv->bg_image);
1206 priv->bg_image = gdk_pixbuf_new_from_file (bg_image_file, &error);
1207
1208 if (error) {
1209 g_printerr ("Failed to load background image: %s\n", error->message);
1210 g_clear_error (&error);
1211 }
1212
1213 gtk_widget_queue_draw (GTK_WIDGET (screen));
1214 } else {
1215 if (priv->bg_image_callback_id)
1216 {
1217 g_signal_handler_disconnect (screen, priv->bg_image_callback_id);
1218 priv->bg_image_callback_id = 0;
1219 }
1220 }
1221
1222 vte_terminal_set_colors (VTE_TERMINAL (screen),
1223 &fg, &bg,
1224 colors, n_colors);
1225 if (bold_rgba)
1226 vte_terminal_set_color_bold (VTE_TERMINAL (screen),
1227 bold_rgba);
1228 }
1229
1230 void
1231 terminal_screen_set_font (TerminalScreen *screen)
1232 {
1233 TerminalScreenPrivate *priv = screen->priv;
1234 TerminalProfile *profile;
1235 PangoFontDescription *desc;
1236 int size;
1237
1238 profile = priv->profile;
1239
1240 if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_SYSTEM_FONT))
1241 g_object_get (terminal_app_get (), "system-font", &desc, NULL);
1242 else
1243 g_object_get (profile, TERMINAL_PROFILE_FONT, &desc, NULL);
1244 g_assert (desc);
1245
1246 size = pango_font_description_get_size (desc);
1247 if (pango_font_description_get_size_is_absolute (desc))
1248 pango_font_description_set_absolute_size (desc, priv->font_scale * size);
1249 else
1250 pango_font_description_set_size (desc, (int)(priv->font_scale * size));
1251
1252 vte_terminal_set_font (VTE_TERMINAL (screen), desc);
1253
1254 pango_font_description_free (desc);
1255 }
1256
1257 static void
1258 terminal_screen_system_font_notify_cb (TerminalApp *app,
1259 GParamSpec *pspec,
1260 TerminalScreen *screen)
1261 {
1262 TerminalScreenPrivate *priv = screen->priv;
1263
1264 if (!gtk_widget_get_realized (GTK_WIDGET (screen)))
1265 return;
1266
1267 if (!terminal_profile_get_property_boolean (priv->profile, TERMINAL_PROFILE_USE_SYSTEM_FONT))
1268 return;
1269
1270 terminal_screen_change_font (screen);
1271 }
1272
1273 static void
1274 terminal_screen_change_font (TerminalScreen *screen)
1275 {
1276 TerminalWindow *window;
1277
1278 terminal_screen_set_font (screen);
1279
1280 window = terminal_screen_get_window (screen);
1281 terminal_window_update_size (window, screen, TRUE);
1282 }
1283
1284 static void
1285 profile_forgotten_callback (TerminalProfile *profile,
1286 TerminalScreen *screen)
1287 {
1288 TerminalProfile *new_profile;
1289
1290 new_profile = terminal_app_get_profile_for_new_term (terminal_app_get ());
1291 g_assert (new_profile != NULL);
1292 terminal_screen_set_profile (screen, new_profile);
1293 }
1294
1295 void
1296 terminal_screen_set_profile (TerminalScreen *screen,
1297 TerminalProfile *profile)
1298 {
1299 TerminalScreenPrivate *priv = screen->priv;
1300 TerminalProfile *old_profile;
1301
1302 old_profile = priv->profile;
1303 if (profile == old_profile)
1304 return;
1305
1306 if (priv->profile_changed_id)
1307 {
1308 g_signal_handler_disconnect (G_OBJECT (priv->profile),
1309 priv->profile_changed_id);
1310 priv->profile_changed_id = 0;
1311 }
1312
1313 if (priv->profile_forgotten_id)
1314 {
1315 g_signal_handler_disconnect (G_OBJECT (priv->profile),
1316 priv->profile_forgotten_id);
1317 priv->profile_forgotten_id = 0;
1318 }
1319
1320 priv->profile = profile;
1321 if (profile)
1322 {
1323 g_object_ref (profile);
1324 priv->profile_changed_id =
1325 g_signal_connect (profile, "notify",
1326 G_CALLBACK (terminal_screen_profile_notify_cb),
1327 screen);
1328 priv->profile_forgotten_id =
1329 g_signal_connect (G_OBJECT (profile),
1330 "forgotten",
1331 G_CALLBACK (profile_forgotten_callback),
1332 screen);
1333
1334 terminal_screen_profile_notify_cb (profile, NULL, screen);
1335
1336 g_signal_emit (G_OBJECT (screen), signals[PROFILE_SET], 0, old_profile);
1337 }
1338
1339 if (old_profile)
1340 g_object_unref (old_profile);
1341
1342 g_object_notify (G_OBJECT (screen), "profile");
1343 }
1344
1345 TerminalProfile*
1346 terminal_screen_get_profile (TerminalScreen *screen)
1347 {
1348 TerminalScreenPrivate *priv = screen->priv;
1349
1350 g_assert (priv->profile != NULL);
1351 return priv->profile;
1352 }
1353
1354 void
1355 terminal_screen_set_override_command (TerminalScreen *screen,
1356 char **argv)
1357 {
1358 TerminalScreenPrivate *priv;
1359
1360 g_return_if_fail (TERMINAL_IS_SCREEN (screen));
1361
1362 priv = screen->priv;
1363 g_strfreev (priv->override_command);
1364 priv->override_command = g_strdupv (argv);
1365 }
1366
1367 const char**
1368 terminal_screen_get_override_command (TerminalScreen *screen)
1369 {
1370 g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
1371
1372 return (const char**) screen->priv->override_command;
1373 }
1374
1375 void
1376 terminal_screen_set_initial_environment (TerminalScreen *screen,
1377 char **argv)
1378 {
1379 TerminalScreenPrivate *priv;
1380
1381 g_return_if_fail (TERMINAL_IS_SCREEN (screen));
1382
1383 priv = screen->priv;
1384 g_assert (priv->initial_env == NULL);
1385 priv->initial_env = g_strdupv (argv);
1386 }
1387
1388 char**
1389 terminal_screen_get_initial_environment (TerminalScreen *screen)
1390 {
1391 g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
1392
1393 return screen->priv->initial_env;
1394 }
1395
1396 static gboolean
1397 get_child_command (TerminalScreen *screen,
1398 const char *shell_env,
1399 GSpawnFlags *spawn_flags_p,
1400 char ***argv_p,
1401 GError **err)
1402 {
1403 TerminalScreenPrivate *priv = screen->priv;
1404 TerminalProfile *profile;
1405 char **argv;
1406
1407 g_assert (spawn_flags_p != NULL && argv_p != NULL);
1408
1409 profile = priv->profile;
1410
1411 *argv_p = argv = NULL;
1412
1413 if (priv->override_command)
1414 {
1415 argv = g_strdupv (priv->override_command);
1416
1417 *spawn_flags_p |= G_SPAWN_SEARCH_PATH;
1418 }
1419 else if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_CUSTOM_COMMAND))
1420 {
1421 if (!g_shell_parse_argv (terminal_profile_get_property_string (profile, TERMINAL_PROFILE_CUSTOM_COMMAND),
1422 NULL, &argv,
1423 err))
1424 return FALSE;
1425
1426 *spawn_flags_p |= G_SPAWN_SEARCH_PATH;
1427 }
1428 else
1429 {
1430 const char *only_name;
1431 char *shell;
1432 int argc = 0;
1433
1434 shell = egg_shell (shell_env);
1435
1436 only_name = strrchr (shell, '/');
1437 if (only_name != NULL)
1438 only_name++;
1439 else
1440 only_name = shell;
1441
1442 argv = g_new (char*, 3);
1443
1444 argv[argc++] = shell;
1445
1446 if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_LOGIN_SHELL))
1447 argv[argc++] = g_strconcat ("-", only_name, NULL);
1448 else
1449 argv[argc++] = g_strdup (only_name);
1450
1451 argv[argc++] = NULL;
1452
1453 *spawn_flags_p |= G_SPAWN_FILE_AND_ARGV_ZERO;
1454 }
1455
1456 *argv_p = argv;
1457
1458 return TRUE;
1459 }
1460
1461 static char**
1462 get_child_environment (TerminalScreen *screen,
1463 char **shell)
1464 {
1465 TerminalScreenPrivate *priv = screen->priv;
1466 GtkWidget *term = GTK_WIDGET (screen);
1467 GtkWidget *window;
1468 GdkDisplay *display;
1469 char **env;
1470 char *e, *v;
1471 GHashTable *env_table;
1472 GHashTableIter iter;
1473 GPtrArray *retval;
1474 guint i;
1475 gchar **list_schemas = NULL;
1476 gboolean schema_exists;
1477
1478 window = gtk_widget_get_toplevel (term);
1479 g_assert (window != NULL);
1480 g_assert (gtk_widget_is_toplevel (window));
1481 display = gdk_window_get_display (gtk_widget_get_window (window));
1482
1483 env_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1484
1485 /* First take the factory's environment */
1486 env = g_listenv ();
1487 for (i = 0; env[i]; ++i)
1488 g_hash_table_insert (env_table, env[i], g_strdup (g_getenv (env[i])));
1489 g_free (env); /* the strings themselves are now owned by the hash table */
1490
1491 /* and then merge the child environment, if any */
1492 env = priv->initial_env;
1493 if (env)
1494 {
1495 for (i = 0; env[i]; ++i)
1496 {
1497 v = strchr (env[i], '=');
1498 if (v)
1499 g_hash_table_replace (env_table, g_strndup (env[i], v - env[i]), g_strdup (v + 1));
1500 else
1501 g_hash_table_replace (env_table, g_strdup (env[i]), NULL);
1502 }
1503 }
1504
1505 g_hash_table_remove (env_table, "COLUMNS");
1506 g_hash_table_remove (env_table, "LINES");
1507 g_hash_table_remove (env_table, "MATE_DESKTOP_ICON");
1508
1509 g_hash_table_replace (env_table, g_strdup ("TERM"), g_strdup ("xterm-256color")); /* FIXME configurable later? */
1510
1511 /* FIXME: moving the tab between windows, or the window between displays will make the next two invalid... */
1512 #ifdef GDK_WINDOWING_X11
1513 if (GDK_IS_X11_DISPLAY (display)) {
1514 g_hash_table_replace (env_table, g_strdup ("WINDOWID"), g_strdup_printf ("%ld", GDK_WINDOW_XID (gtk_widget_get_window (window))));
1515 }
1516 #endif
1517 g_hash_table_replace (env_table, g_strdup ("DISPLAY"), g_strdup (gdk_display_get_name (display)));
1518
1519 g_settings_schema_source_list_schemas (g_settings_schema_source_get_default (), TRUE, &list_schemas, NULL);
1520
1521 schema_exists = FALSE;
1522 for (i = 0; list_schemas[i] != NULL; i++) {
1523 if (g_strcmp0 (list_schemas[i], CONF_PROXY_SCHEMA) == 0)
1524 {
1525 schema_exists = TRUE;
1526 break;
1527 }
1528 }
1529
1530 g_strfreev (list_schemas);
1531
1532 if (schema_exists == TRUE) {
1533 terminal_util_add_proxy_env (env_table);
1534 }
1535
1536 retval = g_ptr_array_sized_new (g_hash_table_size (env_table));
1537 g_hash_table_iter_init (&iter, env_table);
1538 while (g_hash_table_iter_next (&iter, (gpointer *) &e, (gpointer *) &v))
1539 g_ptr_array_add (retval, g_strdup_printf ("%s=%s", e, v ? v : ""));
1540 g_ptr_array_add (retval, NULL);
1541
1542 *shell = g_strdup (g_hash_table_lookup (env_table, "SHELL"));
1543
1544 g_hash_table_destroy (env_table);
1545 return (char **) g_ptr_array_free (retval, FALSE);
1546 }
1547
1548 enum
1549 {
1550 RESPONSE_RELAUNCH,
1551 RESPONSE_EDIT_PROFILE
1552 };
1553
1554 static void
1555 info_bar_response_cb (GtkWidget *info_bar,
1556 int response,
1557 TerminalScreen *screen)
1558 {
1559 gtk_widget_grab_focus (GTK_WIDGET (screen));
1560
1561 switch (response)
1562 {
1563 case GTK_RESPONSE_CANCEL:
1564 gtk_widget_destroy (info_bar);
1565 g_signal_emit (screen, signals[CLOSE_SCREEN], 0);
1566 break;
1567 case RESPONSE_RELAUNCH:
1568 gtk_widget_destroy (info_bar);
1569 terminal_screen_launch_child_on_idle (screen);
1570 break;
1571 case RESPONSE_EDIT_PROFILE:
1572 terminal_app_edit_profile (terminal_app_get (),
1573 terminal_screen_get_profile (screen),
1574 GTK_WINDOW (terminal_screen_get_window (screen)),
1575 "custom-command-entry");
1576 break;
1577 default:
1578 gtk_widget_destroy (info_bar);
1579 break;
1580 }
1581 }
1582
1583 static void handle_error_child (TerminalScreen *screen,
1584 GError *err)
1585 {
1586 GtkWidget *info_bar;
1587
1588 info_bar = terminal_info_bar_new (GTK_MESSAGE_ERROR,
1589 _("_Profile Preferences"), RESPONSE_EDIT_PROFILE,
1590 _("_Relaunch"), RESPONSE_RELAUNCH,
1591 NULL);
1592 terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
1593 _("There was an error creating the child process for this terminal"));
1594 terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
1595 "%s", err->message);
1596 g_signal_connect (info_bar, "response",
1597 G_CALLBACK (info_bar_response_cb), screen);
1598
1599 gtk_box_pack_start (GTK_BOX (terminal_screen_container_get_from_screen (screen)),
1600 info_bar, FALSE, FALSE, 0);
1601 gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL);
1602 gtk_widget_show (info_bar);
1603 }
1604
1605 static void term_spawn_callback (GtkWidget *terminal,
1606 GPid pid,
1607 GError *error,
1608 gpointer user_data)
1609 {
1610 TerminalScreen *screen = TERMINAL_SCREEN (terminal);
1611
1612 if (error)
1613 {
1614 handle_error_child (screen, error);
1615 }
1616 else
1617 {
1618 TerminalScreenPrivate *priv = screen->priv;
1619 priv->child_pid = pid;
1620 }
1621 }
1622
1623 static gboolean
1624 terminal_screen_launch_child_cb (TerminalScreen *screen)
1625 {
1626 TerminalScreenPrivate *priv = screen->priv;
1627 VteTerminal *terminal = VTE_TERMINAL (screen);
1628 char **env, **argv;
1629 char *shell = NULL;
1630 GError *err = NULL;
1631 const char *working_dir;
1632 VtePtyFlags pty_flags = VTE_PTY_DEFAULT;
1633 GSpawnFlags spawn_flags = 0;
1634
1635 priv->launch_child_source_id = 0;
1636
1637 _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
1638 "[screen %p] now launching the child process\n",
1639 screen);
1640
1641 env = get_child_environment (screen, &shell);
1642
1643 if (priv->initial_working_directory)
1644 working_dir = priv->initial_working_directory;
1645 else
1646 working_dir = g_get_home_dir ();
1647
1648 if (!get_child_command (screen, shell, &spawn_flags, &argv, &err))
1649 {
1650 handle_error_child (screen, err);
1651
1652 g_error_free (err);
1653 g_strfreev (env);
1654 g_free (shell);
1655
1656 return FALSE;
1657 }
1658
1659 vte_terminal_spawn_async (terminal,
1660 pty_flags,
1661 working_dir,
1662 argv,
1663 env,
1664 spawn_flags,
1665 NULL,
1666 NULL,
1667 NULL,
1668 -1,
1669 NULL,
1670 (VteTerminalSpawnAsyncCallback) term_spawn_callback,
1671 NULL);
1672
1673 g_free (shell);
1674 g_strfreev (argv);
1675 g_strfreev (env);
1676
1677 return FALSE; /* don't run again */
1678 }
1679
1680 static void
1681 terminal_screen_launch_child_on_idle (TerminalScreen *screen)
1682 {
1683 TerminalScreenPrivate *priv = screen->priv;
1684
1685 if (priv->launch_child_source_id != 0)
1686 return;
1687
1688 _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
1689 "[screen %p] scheduling launching the child process on idle\n",
1690 screen);
1691
1692 priv->launch_child_source_id = g_idle_add ((GSourceFunc) terminal_screen_launch_child_cb, screen);
1693 }
1694
1695 static TerminalScreenPopupInfo *
1696 terminal_screen_popup_info_new (TerminalScreen *screen)
1697 {
1698 TerminalScreenPopupInfo *info;
1699
1700 info = g_slice_new0 (TerminalScreenPopupInfo);
1701 info->ref_count = 1;
1702 info->screen = g_object_ref (screen);
1703 info->window = terminal_screen_get_window (screen);
1704
1705 return info;
1706 }
1707
1708 TerminalScreenPopupInfo *
1709 terminal_screen_popup_info_ref (TerminalScreenPopupInfo *info)
1710 {
1711 g_return_val_if_fail (info != NULL, NULL);
1712
1713 info->ref_count++;
1714 return info;
1715 }
1716
1717 void
1718 terminal_screen_popup_info_unref (TerminalScreenPopupInfo *info)
1719 {
1720 g_return_if_fail (info != NULL);
1721
1722 if (--info->ref_count > 0)
1723 return;
1724
1725 g_object_unref (info->screen);
1726 g_free (info->string);
1727 g_slice_free (TerminalScreenPopupInfo, info);
1728 }
1729
1730 static gboolean
1731 terminal_screen_popup_menu (GtkWidget *widget)
1732 {
1733 TerminalScreen *screen = TERMINAL_SCREEN (widget);
1734 TerminalScreenPopupInfo *info;
1735
1736 info = terminal_screen_popup_info_new (screen);
1737 info->button = 0;
1738 info->timestamp = gtk_get_current_event_time ();
1739
1740 g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info);
1741 terminal_screen_popup_info_unref (info);
1742
1743 return TRUE;
1744 }
1745
1746 static gboolean
1747 terminal_screen_button_press (GtkWidget *widget,
1748 GdkEventButton *event)
1749 {
1750 TerminalScreen *screen = TERMINAL_SCREEN (widget);
1751 gboolean (* button_press_event) (GtkWidget*, GdkEventButton*) =
1752 GTK_WIDGET_CLASS (terminal_screen_parent_class)->button_press_event;
1753 char *matched_string;
1754 int matched_flavor = 0;
1755 guint state;
1756
1757 state = event->state & gtk_accelerator_get_default_mod_mask ();
1758
1759 matched_string = terminal_screen_check_match (screen, (GdkEvent*)event, &matched_flavor);
1760
1761 if (matched_string != NULL &&
1762 (event->button == 1 || event->button == 2) &&
1763 (state & GDK_CONTROL_MASK))
1764 {
1765 gboolean handled = FALSE;
1766
1767 #ifdef ENABLE_SKEY
1768 if (matched_flavor != FLAVOR_SKEY ||
1769 terminal_profile_get_property_boolean (screen->priv->profile, TERMINAL_PROFILE_USE_SKEY))
1770 #endif
1771 {
1772 g_signal_emit (screen, signals[MATCH_CLICKED], 0,
1773 matched_string,
1774 matched_flavor,
1775 state,
1776 &handled);
1777 }
1778
1779 g_free (matched_string);
1780
1781 if (handled)
1782 return TRUE; /* don't do anything else such as select with the click */
1783 }
1784
1785 if (event->button == 3 &&
1786 (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0)
1787 {
1788 TerminalScreenPopupInfo *info;
1789
1790 info = terminal_screen_popup_info_new (screen);
1791 info->button = event->button;
1792 info->state = state;
1793 info->timestamp = event->time;
1794 info->string = matched_string; /* adopted */
1795 info->flavour = matched_flavor;
1796
1797 g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info);
1798 terminal_screen_popup_info_unref (info);
1799
1800 return TRUE;
1801 }
1802
1803 /* default behavior is to let the terminal widget deal with it */
1804 if (button_press_event)
1805 return button_press_event (widget, event);
1806
1807 return FALSE;
1808 }
1809
1810 static void
1811 terminal_screen_set_dynamic_title (TerminalScreen *screen,
1812 const char *title,
1813 gboolean userset)
1814 {
1815 TerminalScreenPrivate *priv = screen->priv;
1816
1817 g_assert (TERMINAL_IS_SCREEN (screen));
1818
1819 if ((priv->user_title && !userset) ||
1820 (priv->raw_title && title &&
1821 strcmp (priv->raw_title, title) == 0))
1822 return;
1823
1824 g_free (priv->raw_title);
1825 priv->raw_title = g_strdup (title);
1826 terminal_screen_cook_title (screen);
1827 }
1828
1829 static void
1830 terminal_screen_set_dynamic_icon_title (TerminalScreen *screen,
1831 const char *icon_title,
1832 gboolean userset)
1833 {
1834 TerminalScreenPrivate *priv = screen->priv;
1835 GObject *object = G_OBJECT (screen);
1836
1837 g_assert (TERMINAL_IS_SCREEN (screen));
1838
1839 if ((priv->user_title && !userset) ||
1840 (priv->icon_title_set &&
1841 priv->raw_icon_title &&
1842 icon_title &&
1843 strcmp (priv->raw_icon_title, icon_title) == 0))
1844 return;
1845
1846 g_object_freeze_notify (object);
1847
1848 g_free (priv->raw_icon_title);
1849 priv->raw_icon_title = g_strdup (icon_title);
1850 priv->icon_title_set = TRUE;
1851
1852 g_object_notify (object, "icon-title-set");
1853 terminal_screen_cook_icon_title (screen);
1854
1855 g_object_thaw_notify (object);
1856 }
1857
1858 void
1859 terminal_screen_set_override_title (TerminalScreen *screen,
1860 const char *title)
1861 {
1862 TerminalScreenPrivate *priv = screen->priv;
1863 char *old_title;
1864
1865 old_title = priv->override_title;
1866 priv->override_title = g_strdup (title);
1867 g_free (old_title);
1868
1869 terminal_screen_set_dynamic_title (screen, title, FALSE);
1870 terminal_screen_set_dynamic_icon_title (screen, title, FALSE);
1871 }
1872
1873 const char*
1874 terminal_screen_get_dynamic_title (TerminalScreen *screen)
1875 {
1876 g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
1877
1878 return screen->priv->raw_title;
1879 }
1880
1881 const char*
1882 terminal_screen_get_dynamic_icon_title (TerminalScreen *screen)
1883 {
1884 g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
1885
1886 return screen->priv->raw_icon_title;
1887 }
1888
1889 /**
1890 * terminal_screen_get_current_dir:
1891 * @screen:
1892 *
1893 * Tries to determine the current working directory of the foreground process
1894 * in @screen's PTY, falling back to the current working directory of the
1895 * primary child.
1896 *
1897 * Returns: a newly allocated string containing the current working directory,
1898 * or %NULL on failure
1899 */
1900 char*
1901 terminal_screen_get_current_dir (TerminalScreen *screen)
1902 {
1903 TerminalScreenPrivate *priv = screen->priv;
1904 char *cwd;
1905 VtePty *pty;
1906
1907 pty = vte_terminal_get_pty (VTE_TERMINAL (screen));
1908 if (pty != NULL)
1909 {
1910 #if 0
1911 /* Get the foreground process ID */
1912 cwd = cwd_of_pid (tcgetpgrp (priv->pty_fd));
1913 if (cwd != NULL)
1914 return cwd;
1915 #endif
1916
1917 /* If that didn't work, try falling back to the primary child. See bug #575184. */
1918 cwd = cwd_of_pid (priv->child_pid);
1919 if (cwd != NULL)
1920 return cwd;
1921 }
1922
1923 return NULL;
1924 }
1925
1926 /**
1927 * terminal_screen_get_current_dir_with_fallback:
1928 * @screen:
1929 *
1930 * Like terminal_screen_get_current_dir(), but falls back to returning
1931 * @screen's initial working directory, with a further fallback to the
1932 * user's home directory.
1933 *
1934 * Returns: a newly allocated string containing the current working directory,
1935 * or %NULL on failure
1936 */
1937 char*
1938 terminal_screen_get_current_dir_with_fallback (TerminalScreen *screen)
1939 {
1940 VtePty *pty;
1941 TerminalScreenPrivate *priv = screen->priv;
1942
1943 pty = vte_terminal_get_pty (VTE_TERMINAL (screen));
1944 if (pty == NULL)
1945 return g_strdup (priv->initial_working_directory);
1946
1947 return terminal_screen_get_current_dir (screen);
1948 }
1949
1950 void
1951 terminal_screen_set_font_scale (TerminalScreen *screen,
1952 double factor)
1953 {
1954 TerminalScreenPrivate *priv = screen->priv;
1955
1956 g_return_if_fail (TERMINAL_IS_SCREEN (screen));
1957
1958 if (factor < TERMINAL_SCALE_MINIMUM)
1959 factor = TERMINAL_SCALE_MINIMUM;
1960 if (factor > TERMINAL_SCALE_MAXIMUM)
1961 factor = TERMINAL_SCALE_MAXIMUM;
1962
1963 priv->font_scale = factor;
1964
1965 if (gtk_widget_get_realized (GTK_WIDGET (screen)))
1966 {
1967 /* Update the font */
1968 terminal_screen_change_font (screen);
1969 }
1970 }
1971
1972 double
1973 terminal_screen_get_font_scale (TerminalScreen *screen)
1974 {
1975 g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), 1.0);
1976
1977 return screen->priv->font_scale;
1978 }
1979
1980 static void
1981 terminal_screen_window_title_changed (VteTerminal *vte_terminal,
1982 TerminalScreen *screen)
1983 {
1984 terminal_screen_set_dynamic_title (screen,
1985 vte_terminal_get_window_title (vte_terminal),
1986 FALSE);
1987 }
1988
1989 static void
1990 terminal_screen_icon_title_changed (VteTerminal *vte_terminal,
1991 TerminalScreen *screen)
1992 {
1993 terminal_screen_set_dynamic_icon_title (screen,
1994 vte_terminal_get_icon_title (vte_terminal),
1995 FALSE);
1996 }
1997
1998 static void
1999 terminal_screen_child_exited (VteTerminal *terminal, int status)
2000 {
2001 TerminalScreen *screen = TERMINAL_SCREEN (terminal);
2002 TerminalScreenPrivate *priv = screen->priv;
2003 TerminalExitAction action;
2004
2005 /* No need to chain up to VteTerminalClass::child_exited since it's NULL */
2006
2007 _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
2008 "[screen %p] child process exited\n",
2009 screen);
2010
2011 priv->child_pid = -1;
2012
2013 action = terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_EXIT_ACTION);
2014
2015 switch (action)
2016 {
2017 case TERMINAL_EXIT_CLOSE:
2018 if ((status != 9) || (priv->override_command != NULL))
2019 g_signal_emit (screen, signals[CLOSE_SCREEN], 0);
2020 break;
2021 case TERMINAL_EXIT_RESTART:
2022 terminal_screen_launch_child_on_idle (screen);
2023 break;
2024 case TERMINAL_EXIT_HOLD:
2025 {
2026 if ((status == 9) && (priv->override_command == NULL))
2027 break;
2028
2029 GtkWidget *info_bar;
2030
2031 info_bar = terminal_info_bar_new (GTK_MESSAGE_INFO,
2032 _("_Relaunch"), RESPONSE_RELAUNCH,
2033 NULL);
2034 if (WIFEXITED (status))
2035 {
2036 terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
2037 _("The child process exited normally with status %d."), WEXITSTATUS (status));
2038 }
2039 else if (WIFSIGNALED (status))
2040 {
2041 terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
2042 _("The child process was terminated by signal %d."), WTERMSIG (status));
2043 }
2044 else
2045 {
2046 terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
2047 _("The child process was terminated."));
2048 }
2049 g_signal_connect (info_bar, "response",
2050 G_CALLBACK (info_bar_response_cb), screen);
2051
2052 gtk_box_pack_start (GTK_BOX (terminal_screen_container_get_from_screen (screen)),
2053 info_bar, FALSE, FALSE, 0);
2054 gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), RESPONSE_RELAUNCH);
2055 gtk_widget_show (info_bar);
2056 break;
2057 }
2058
2059 default:
2060 break;
2061 }
2062 }
2063
2064 void
2065 terminal_screen_set_user_title (TerminalScreen *screen,
2066 const char *text)
2067 {
2068 TerminalScreenPrivate *priv = screen->priv;
2069
2070 /* The user set the title to nothing, let's understand that as a
2071 request to revert to dynamically setting the title again. */
2072 if (!text || !text[0])
2073 priv->user_title = FALSE;
2074 else
2075 {
2076 priv->user_title = TRUE;
2077 terminal_screen_set_dynamic_title (screen, text, TRUE);
2078 terminal_screen_set_dynamic_icon_title (screen, text, TRUE);
2079 }
2080 }
2081
2082 static void
2083 terminal_screen_drag_data_received (GtkWidget *widget,
2084 GdkDragContext *context,
2085 gint x,
2086 gint y,
2087 GtkSelectionData *selection_data,
2088 guint info,
2089 guint timestamp)
2090 {
2091 TerminalScreen *screen = TERMINAL_SCREEN (widget);
2092 TerminalScreenPrivate *priv = screen->priv;
2093 const guchar *selection_data_data;
2094 GdkAtom selection_data_target;
2095 gint selection_data_length, selection_data_format;
2096
2097 selection_data_data = gtk_selection_data_get_data (selection_data);
2098 selection_data_target = gtk_selection_data_get_target (selection_data);
2099 selection_data_length = gtk_selection_data_get_length (selection_data);
2100 selection_data_format = gtk_selection_data_get_format (selection_data);
2101
2102 #if 0
2103 {
2104 GList *tmp;
2105
2106 g_print ("info: %d\n", info);
2107 tmp = context->targets;
2108 while (tmp != NULL)
2109 {
2110 GdkAtom atom = GDK_POINTER_TO_ATOM (tmp->data);
2111
2112 g_print ("Target: %s\n", gdk_atom_name (atom));
2113
2114 tmp = tmp->next;
2115 }
2116
2117 g_print ("Chosen target: %s\n", gdk_atom_name (selection_data->target));
2118 }
2119 #endif
2120
2121 if (gtk_targets_include_uri (&selection_data_target, 1))
2122 {
2123 char **uris;
2124 char *text;
2125 gsize len;
2126
2127 uris = gtk_selection_data_get_uris (selection_data);
2128 if (!uris)
2129 return;
2130
2131 terminal_util_transform_uris_to_quoted_fuse_paths (uris);
2132
2133 text = terminal_util_concat_uris (uris, &len);
2134 vte_terminal_feed_child (VTE_TERMINAL (screen), text, len);
2135 g_free (text);
2136
2137 g_strfreev (uris);
2138 }
2139 else if (gtk_targets_include_text (&selection_data_target, 1))
2140 {
2141 char *text;
2142
2143 text = (char *) gtk_selection_data_get_text (selection_data);
2144 if (text && text[0])
2145 vte_terminal_feed_child (VTE_TERMINAL (screen), text, strlen (text));
2146 g_free (text);
2147 }
2148 else switch (info)
2149 {
2150 case TARGET_COLOR:
2151 {
2152 guint16 *data = (guint16 *)selection_data_data;
2153 GdkRGBA color;
2154
2155 /* We accept drops with the wrong format, since the KDE color
2156 * chooser incorrectly drops application/x-color with format 8.
2157 * So just check for the data length.
2158 */
2159 if (selection_data_length != 8)
2160 return;
2161
2162 color.red = (double) data[0] / 65535.;
2163 color.green = (double) data[1] / 65535.;
2164 color.blue = (double) data[2] / 65535.;
2165 color.alpha = 1.;
2166 /* FIXME: use opacity from data[3] */
2167
2168 g_object_set (priv->profile,
2169 TERMINAL_PROFILE_BACKGROUND_TYPE, TERMINAL_BACKGROUND_SOLID,
2170 TERMINAL_PROFILE_USE_THEME_COLORS, FALSE,
2171 TERMINAL_PROFILE_BACKGROUND_COLOR, &color,
2172 NULL);
2173 }
2174 break;
2175
2176 case TARGET_MOZ_URL:
2177 {
2178 char *utf8_data, *newline, *text;
2179 char *uris[2];
2180 gsize len;
2181
2182 /* MOZ_URL is in UCS-2 but in format 8. BROKEN!
2183 *
2184 * The data contains the URL, a \n, then the
2185 * title of the web page.
2186 */
2187 if (selection_data_format != 8 ||
2188 selection_data_length == 0 ||
2189 (selection_data_length % 2) != 0)
2190 return;
2191
2192 utf8_data = g_utf16_to_utf8 ((const gunichar2*) selection_data_data,
2193 selection_data_length / 2,
2194 NULL, NULL, NULL);
2195 if (!utf8_data)
2196 return;
2197
2198 newline = strchr (utf8_data, '\n');
2199 if (newline)
2200 *newline = '\0';
2201
2202 uris[0] = utf8_data;
2203 uris[1] = NULL;
2204 terminal_util_transform_uris_to_quoted_fuse_paths (uris); /* This may replace uris[0] */
2205
2206 text = terminal_util_concat_uris (uris, &len);
2207 vte_terminal_feed_child (VTE_TERMINAL (screen), text, len);
2208 g_free (text);
2209 g_free (uris[0]);
2210 }
2211 break;
2212
2213 case TARGET_NETSCAPE_URL:
2214 {
2215 char *utf8_data, *newline, *text;
2216 char *uris[2];
2217 gsize len;
2218
2219 /* The data contains the URL, a \n, then the
2220 * title of the web page.
2221 */
2222 if (selection_data_length < 0 || selection_data_format != 8)
2223 return;
2224
2225 utf8_data = g_strndup ((char *) selection_data_data, selection_data_length);
2226 newline = strchr (utf8_data, '\n');
2227 if (newline)
2228 *newline = '\0';
2229
2230 uris[0] = utf8_data;
2231 uris[1] = NULL;
2232 terminal_util_transform_uris_to_quoted_fuse_paths (uris); /* This may replace uris[0] */
2233
2234 text = terminal_util_concat_uris (uris, &len);
2235 vte_terminal_feed_child (VTE_TERMINAL (screen), text, len);
2236 g_free (text);
2237 g_free (uris[0]);
2238 }
2239 break;
2240
2241 case TARGET_BGIMAGE:
2242 {
2243 char *utf8_data;
2244 char **uris;
2245
2246 if (selection_data_length < 0 || selection_data_format != 8)
2247 return;
2248
2249 utf8_data = g_strndup ((char *) selection_data_data, selection_data_length);
2250 uris = g_uri_list_extract_uris (utf8_data);
2251 g_free (utf8_data);
2252
2253 /* FIXME: use terminal_util_transform_uris_to_quoted_fuse_paths? */
2254
2255 if (uris && uris[0])
2256 {
2257 char *filename;
2258
2259 filename = g_filename_from_uri (uris[0], NULL, NULL);
2260 if (filename)
2261 {
2262 g_object_set (priv->profile,
2263 TERMINAL_PROFILE_BACKGROUND_TYPE, TERMINAL_BACKGROUND_IMAGE,
2264 TERMINAL_PROFILE_BACKGROUND_IMAGE_FILE, filename,
2265 NULL);
2266 }
2267
2268 g_free (filename);
2269 }
2270
2271 g_strfreev (uris);
2272 }
2273 break;
2274
2275 case TARGET_RESET_BG:
2276 g_object_set (priv->profile,
2277 TERMINAL_PROFILE_BACKGROUND_TYPE, TERMINAL_BACKGROUND_SOLID,
2278 NULL);
2279 break;
2280
2281 case TARGET_TAB:
2282 {
2283 GtkWidget *container;
2284 TerminalScreen *moving_screen;
2285 TerminalWindow *source_window;
2286 TerminalWindow *dest_window;
2287 GtkWidget *dest_notebook;
2288 int page_num;
2289
2290 container = *(GtkWidget**) selection_data_data;
2291 if (!GTK_IS_WIDGET (container))
2292 return;
2293
2294 moving_screen = terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (container));
2295 g_return_if_fail (TERMINAL_IS_SCREEN (moving_screen));
2296 if (!TERMINAL_IS_SCREEN (moving_screen))
2297 return;
2298
2299 source_window = terminal_screen_get_window (moving_screen);
2300 dest_window = terminal_screen_get_window (screen);
2301 dest_notebook = terminal_window_get_notebook (dest_window);
2302 page_num = gtk_notebook_page_num (GTK_NOTEBOOK (dest_notebook),
2303 GTK_WIDGET (screen));
2304 terminal_window_move_screen (source_window, dest_window, moving_screen, page_num + 1);
2305
2306 gtk_drag_finish (context, TRUE, TRUE, timestamp);
2307 }
2308 break;
2309
2310 default:
2311 g_assert_not_reached ();
2312 }
2313 }
2314
2315 void
2316 _terminal_screen_update_scrollbar (TerminalScreen *screen)
2317 {
2318 TerminalScreenPrivate *priv = screen->priv;
2319 TerminalScreenContainer *container;
2320 GtkPolicyType policy = GTK_POLICY_ALWAYS;
2321 GtkCornerType corner = GTK_CORNER_TOP_LEFT;
2322
2323 container = terminal_screen_container_get_from_screen (screen);
2324 if (container == NULL)
2325 return;
2326
2327 switch (terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_SCROLLBAR_POSITION))
2328 {
2329 case TERMINAL_SCROLLBAR_HIDDEN:
2330 policy = GTK_POLICY_NEVER;
2331 break;
2332 case TERMINAL_SCROLLBAR_RIGHT:
2333 policy = GTK_POLICY_ALWAYS;
2334 corner = GTK_CORNER_TOP_LEFT;
2335 break;
2336 case TERMINAL_SCROLLBAR_LEFT:
2337 policy = GTK_POLICY_ALWAYS;
2338 corner = GTK_CORNER_TOP_RIGHT;
2339 break;
2340 default:
2341 g_assert_not_reached ();
2342 break;
2343 }
2344
2345 terminal_screen_container_set_placement (container, corner);
2346 terminal_screen_container_set_policy (container, GTK_POLICY_NEVER, policy);
2347 }
2348
2349 void
2350 terminal_screen_get_size (TerminalScreen *screen,
2351 int *width_chars,
2352 int *height_chars)
2353 {
2354 VteTerminal *terminal = VTE_TERMINAL (screen);
2355
2356 *width_chars = vte_terminal_get_column_count (terminal);
2357 *height_chars = vte_terminal_get_row_count (terminal);
2358 }
2359
2360 void
2361 terminal_screen_get_cell_size (TerminalScreen *screen,
2362 int *cell_width_pixels,
2363 int *cell_height_pixels)
2364 {
2365 VteTerminal *terminal = VTE_TERMINAL (screen);
2366
2367 *cell_width_pixels = vte_terminal_get_char_width (terminal);
2368 *cell_height_pixels = vte_terminal_get_char_height (terminal);
2369 }
2370
2371 #ifdef ENABLE_SKEY
2372 static void
2373 terminal_screen_skey_match_remove (TerminalScreen *screen)
2374 {
2375 TerminalScreenPrivate *priv = screen->priv;
2376 GSList *l, *next;
2377
2378 l = priv->match_tags;
2379 while (l != NULL)
2380 {
2381 TagData *tag_data = (TagData *) l->data;
2382
2383 next = l->next;
2384 if (tag_data->flavor == FLAVOR_SKEY)
2385 {
2386 vte_terminal_match_remove (VTE_TERMINAL (screen), tag_data->tag);
2387 priv->match_tags = g_slist_delete_link (priv->match_tags, l);
2388 }
2389
2390 l = next;
2391 }
2392 }
2393 #endif /* ENABLE_SKEY */
2394
2395 static void
2396 terminal_screen_url_match_remove (TerminalScreen *screen)
2397 {
2398 TerminalScreenPrivate *priv = screen->priv;
2399 GSList *l, *next;
2400
2401 l = priv->match_tags;
2402 while (l != NULL)
2403 {
2404 TagData *tag_data = (TagData *) l->data;
2405
2406 next = l->next;
2407 #ifdef ENABLE_SKEY
2408 if (tag_data->flavor != FLAVOR_SKEY)
2409 #endif
2410 {
2411 vte_terminal_match_remove (VTE_TERMINAL (screen), tag_data->tag);
2412 priv->match_tags = g_slist_delete_link (priv->match_tags, l);
2413 }
2414
2415 l = next;
2416 }
2417 }
2418
2419 static char*
2420 terminal_screen_check_match (TerminalScreen *screen,
2421 GdkEvent *event,
2422 int *flavor)
2423 {
2424 TerminalScreenPrivate *priv = screen->priv;
2425 GSList *tags;
2426 int tag;
2427 char *match;
2428
2429 match = vte_terminal_match_check_event (VTE_TERMINAL (screen), event, &tag);
2430 for (tags = priv->match_tags; tags != NULL; tags = tags->next)
2431 {
2432 TagData *tag_data = (TagData*) tags->data;
2433 if (tag_data->tag == tag)
2434 {
2435 if (flavor)
2436 *flavor = tag_data->flavor;
2437 return match;
2438 }
2439 }
2440
2441 g_free (match);
2442 return NULL;
2443 }
2444
2445 void
2446 terminal_screen_save_config (TerminalScreen *screen,
2447 GKeyFile *key_file,
2448 const char *group)
2449 {
2450 TerminalScreenPrivate *priv = screen->priv;
2451 VteTerminal *terminal = VTE_TERMINAL (screen);
2452 TerminalProfile *profile = priv->profile;
2453 const char *profile_id;
2454 char *working_directory;
2455
2456 profile_id = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME);
2457 g_key_file_set_string (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_PROFILE_ID, profile_id);
2458
2459 if (priv->override_command)
2460 terminal_util_key_file_set_argv (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_COMMAND,
2461 -1, priv->override_command);
2462
2463 if (priv->override_title)
2464 g_key_file_set_string (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_TITLE, priv->override_title);
2465
2466 /* FIXMEchpe: use the initial_working_directory instead?? */
2467 working_directory = terminal_screen_get_current_dir (screen);
2468 if (working_directory)
2469 terminal_util_key_file_set_string_escape (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_WORKING_DIRECTORY, working_directory);
2470 g_free (working_directory);
2471
2472 g_key_file_set_double (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_ZOOM, priv->font_scale);
2473
2474 g_key_file_set_integer (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_WIDTH,
2475 vte_terminal_get_column_count (terminal));
2476 g_key_file_set_integer (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_HEIGHT,
2477 vte_terminal_get_row_count (terminal));
2478 }
2479
2480 /**
2481 * terminal_screen_has_foreground_process:
2482 * @screen:
2483 *
2484 * Checks whether there's a foreground process running in
2485 * this terminal.
2486 *
2487 * Returns: %TRUE iff there's a foreground process running in @screen
2488 */
2489 gboolean
2490 terminal_screen_has_foreground_process (TerminalScreen *screen)
2491 {
2492 TerminalScreenPrivate *priv = screen->priv;
2493 VtePty *pty;
2494 int fd;
2495 int fgpid;
2496
2497 pty = vte_terminal_get_pty (VTE_TERMINAL (screen));
2498 if (pty == NULL)
2499 return FALSE;
2500
2501 fd = vte_pty_get_fd (pty);
2502 if (fd == -1)
2503 return FALSE;
2504
2505 fgpid = tcgetpgrp (fd);
2506 if (fgpid == -1 || fgpid == priv->child_pid)
2507 return FALSE;
2508
2509 return TRUE;
2510
2511 #if 0
2512 char *cmdline, *basename, *name;
2513 gsize len;
2514 char filename[64];
2515
2516 g_snprintf (filename, sizeof (filename), "/proc/%d/cmdline", fgpid);
2517 if (!g_file_get_contents (filename, &cmdline, &len, NULL))
2518 return TRUE;
2519
2520 basename = g_path_get_basename (cmdline);
2521 g_free (cmdline);
2522 if (!basename)
2523 return TRUE;
2524
2525 name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
2526 g_free (basename);
2527 if (!name)
2528 return TRUE;
2529
2530 if (process_name)
2531 *process_name = name;
2532
2533 return TRUE;
2534 #endif
2535 }
2536