1 /*
2     roxterm - VTE/GTK terminal emulator with tabs
3     Copyright (C) 2004-2018 Tony Houghton <h@realh.co.uk>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19 
20 #include "defns.h"
21 
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <sys/fcntl.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <pwd.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 #include <vte/vte.h>
33 
34 #ifdef GDK_WINDOWING_X11
35 #include <gdk/gdkx.h>
36 #endif
37 
38 #define PCRE2_CODE_UNIT_WIDTH 8
39 #include <pcre2.h>
40 
41 #include "about.h"
42 #include "colourscheme.h"
43 #include "dlg.h"
44 #include "dragrcv.h"
45 #include "dynopts.h"
46 #include "globalopts.h"
47 #include "optsfile.h"
48 #include "optsdbus.h"
49 #include "roxterm.h"
50 #include "multitab.h"
51 #include "roxterm-regex.h"
52 #include "search.h"
53 #include "session-file.h"
54 #include "shortcuts.h"
55 #include "uri.h"
56 #include "resources.h"
57 
58 #if VTE_CHECK_VERSION(0,50,0)
59 /* EK says hyperlinks can cause segfaults in 0.50.0 */
roxterm_enable_hyperlinks()60 inline static gboolean roxterm_enable_hyperlinks()
61 {
62     return vte_get_major_version() > 0 || vte_get_minor_version() > 50 ||
63         vte_get_micro_version() >= 1;
64     /* Note no need to check minor_version == 50 in final clause because this
65      * has already been checked by #if.
66      */
67 }
68 #endif
69 
70 // -1 = don't know yet, 0 = no, 1 = yes
71 static int roxterm_can_disable_fallback_scrolling = -1;
72 static int roxterm_can_use_pixel_scrolling = -1;
73 
74 typedef enum {
75     Roxterm_ChildExitClose,
76     Roxterm_ChildExitHold,
77     Roxterm_ChildExitRespawn,
78     Roxterm_ChildExitAsk,
79 
80     Roxterm_ChildExitNotOverridden
81 } RoxtermChildExitAction;
82 
83 
84 typedef struct {
85     int tag;
86     ROXTerm_MatchType type;
87 } ROXTerm_MatchMap;
88 
89 struct ROXTermData {
90     /* We do not own references to tab or widget */
91     MultiTab *tab;
92     GtkWidget *widget;            /* VteTerminal */
93     pid_t pid;
94     /* We own a reference to colour_scheme */
95     Options *colour_scheme;
96     char *matched_url;
97     ROXTerm_MatchType match_type;
98     Options *profile;
99     char *special_command;      /* Next window/tab opened from this one should
100                                    run this command instead of default */
101     char **commandv;            /* Copied from --execute args */
102     char **actual_commandv;     /* The actual command used */
103     char *directory;            /* Copied from global_options_directory */
104     GArray *match_map;
105     gboolean no_respawn;
106     gboolean running;
107     DragReceiveData *drd;
108 
109     gboolean hold_over_uri;    /* Whether we've just received a button press
110                                over a URI which may turn into a drag or click
111                                 */
112     double hold_x, hold_y;     /* Position of event above */
113     gulong hold_handler_id;    /* Signal handler id for the above event */
114     double target_zoom_factor, current_zoom_factor;
115     int zoom_index;
116     gulong post_exit_tag;
117     const char *status_icon_name;
118     gboolean borderless;
119     gboolean maximise;
120     gulong win_state_changed_tag;
121     GtkWidget *replace_task_dialog;
122     gboolean postponed_free;
123     PangoFontDescription *pango_desc;
124     gboolean dont_lookup_dimensions;
125     char *reply;
126     int columns, rows;
127     char **env;
128     char *search_pattern;
129     guint search_flags;
130     /*int file_match_tag[2];*/
131     gboolean from_session;
132     int padding_w, padding_h;
133     gboolean is_shell;
134     gulong child_exited_tag;
135     RoxtermChildExitAction exit_action;
136     gboolean override_exit_action;
137 };
138 
139 #define PROFILE_NAME_KEY "roxterm_profile_name"
140 
141 static GList *roxterm_terms = NULL;
142 
143 static DynamicOptions *roxterm_profiles = NULL;
144 
145 static void roxterm_apply_profile(ROXTermData * roxterm, VteTerminal * vte,
146         gboolean update_geometry);
147 
roxterm_get_win(ROXTermData * roxterm)148 inline static MultiWin *roxterm_get_win(ROXTermData *roxterm)
149 {
150     return roxterm->tab ? multi_tab_get_parent(roxterm->tab) : NULL;
151 }
152 
153 /*********************** URI handling ***********************/
154 
roxterm_match_add(ROXTermData * roxterm,VteTerminal * vte,const char * match,ROXTerm_MatchType type)155 static int roxterm_match_add(ROXTermData *roxterm, VteTerminal *vte,
156         const char *match, ROXTerm_MatchType type)
157 {
158     ROXTerm_MatchMap map;
159     VteRegex *regex;
160     GError *err = NULL;
161 
162     regex = vte_regex_new_for_match(match, -1, PCRE2_MULTILINE, &err);
163     if (!regex || err)
164     {
165         g_warning("Failed to compile regex '%s': %s",
166                 match, err ? err->message : "");
167         return -1;
168     }
169     map.type = type;
170     map.tag = vte_terminal_match_add_regex(vte, regex, 0);
171     vte_terminal_match_set_cursor_name(vte, map.tag, "pointer");
172     g_array_append_val(roxterm->match_map, map);
173     return map.tag;
174 }
175 
176 /*
177 static void roxterm_match_remove(ROXTermData *roxterm, VteTerminal *vte,
178         int tag)
179 {
180     guint n;
181 
182     vte_terminal_match_remove(vte, tag);
183     for (n = 0; n < roxterm->match_map->len; ++n)
184     {
185         if (g_array_index(roxterm->match_map, ROXTerm_MatchMap, n).tag == tag)
186         {
187             g_array_remove_index_fast(roxterm->match_map, n);
188             break;
189         }
190     }
191 }
192 */
193 
roxterm_add_matches(ROXTermData * roxterm,VteTerminal * vte)194 static void roxterm_add_matches(ROXTermData *roxterm, VteTerminal *vte)
195 {
196     int n;
197 
198 #if VTE_CHECK_VERSION(0,50,0)
199     vte_terminal_set_allow_hyperlink(vte, roxterm_enable_hyperlinks());
200 #endif
201 
202     for (n = 0; roxterm_regexes[n].regex; ++n)
203     {
204         roxterm_match_add(roxterm, vte, roxterm_regexes[n].regex,
205                 roxterm_regexes[n].match_type);
206     }
207 }
208 
209 /*
210 static void roxterm_add_file_matches(ROXTermData *roxterm, VteTerminal *vte)
211 {
212     int n;
213 
214     for (n = 0; n < 2; ++n)
215     {
216         roxterm->file_match_tag[n] =
217             roxterm_match_add(roxterm, vte, file_urls[n], ROXTerm_Match_File);
218     }
219 }
220 
221 static void roxterm_remove_file_matches(ROXTermData *roxterm, VteTerminal *vte)
222 {
223     int n;
224 
225     for (n = 0; n < 2; ++n)
226     {
227         roxterm_match_remove(roxterm, vte, roxterm->file_match_tag[n]);
228         roxterm->file_match_tag[n] = -1;
229     }
230 }
231 */
232 
roxterm_get_match_type(ROXTermData * roxterm,int tag)233 static ROXTerm_MatchType roxterm_get_match_type(ROXTermData *roxterm, int tag)
234 {
235     guint n;
236 
237     for (n = 0; n < roxterm->match_map->len; ++n)
238     {
239         ROXTerm_MatchMap *m = &g_array_index(roxterm->match_map,
240                 ROXTerm_MatchMap, n);
241 
242         if (m->tag == tag)
243             return m->type;
244     }
245     return ROXTerm_Match_Invalid;
246 }
247 
248 /********************* End URI handling *********************/
249 
roxterm_get_profiles(void)250 inline static DynamicOptions *roxterm_get_profiles(void)
251 {
252     if (!roxterm_profiles)
253         roxterm_profiles = dynamic_options_get("Profiles");
254     return roxterm_profiles;
255 }
256 
257 /* Foreground (and palette?) colour change doesn't cause a redraw so force it */
roxterm_force_redraw(ROXTermData * roxterm)258 inline static void roxterm_force_redraw(ROXTermData *roxterm)
259 {
260     if (roxterm->widget)
261         gtk_widget_queue_draw(roxterm->widget);
262 }
263 
roxterm_get_cwd(ROXTermData * roxterm)264 char *roxterm_get_cwd(ROXTermData *roxterm)
265 {
266     char *pidfile = NULL;
267     char *target = NULL;
268     GError *error = NULL;
269 
270     if (roxterm->pid < 0)
271         return NULL;
272 
273     pidfile = g_strdup_printf("/proc/%d/cwd", roxterm->pid);
274     target = g_file_read_link(pidfile, &error);
275     /* According to a comment in gnome-terminal readlink()
276      * returns a NULL/empty string in Solaris but we can still use the
277      * /proc link if it exists */
278     if (!target || !target[0])
279     {
280         g_free(target);
281         target = NULL;
282         if (!error && g_file_test(pidfile, G_FILE_TEST_EXISTS))
283             target = pidfile;
284         else
285             g_free(pidfile);
286     }
287     else
288     {
289         g_free(pidfile);
290     }
291     if (error)
292         g_error_free(error);
293     return target;
294 }
295 
roxterm_strv_copy(char ** src)296 static char **roxterm_strv_copy(char **src)
297 {
298     int n;
299     char **dest;
300 
301     for (n = 0; src[n]; ++n);
302     dest = g_new(char *, n + 1);
303     for (n = 0; src[n]; ++n)
304         dest[n] = g_strdup(src[n]);
305     dest[n] = 0;
306     return dest;
307 }
308 
roxterm_data_clone(ROXTermData * old_gt)309 static ROXTermData *roxterm_data_clone(ROXTermData *old_gt)
310 {
311     ROXTermData *new_gt = g_new(ROXTermData, 1);
312 
313     *new_gt = *old_gt;
314     new_gt->status_icon_name = NULL;
315     new_gt->widget = NULL;
316     new_gt->tab = NULL;
317     new_gt->running = FALSE;
318     new_gt->postponed_free = FALSE;
319     new_gt->dont_lookup_dimensions = FALSE;
320     new_gt->actual_commandv = NULL;
321     new_gt->env = roxterm_strv_copy(old_gt->env);
322     new_gt->child_exited_tag = 0;
323     new_gt->post_exit_tag = 0;
324     new_gt->win_state_changed_tag = 0;
325 
326     if (old_gt->colour_scheme)
327     {
328         new_gt->colour_scheme = colour_scheme_lookup_and_ref
329             (options_get_leafname(old_gt->colour_scheme));
330     }
331     if (old_gt->profile)
332     {
333         new_gt->profile = dynamic_options_lookup_and_ref(roxterm_profiles,
334             options_get_leafname(old_gt->profile), "roxterm profile");
335     }
336     /* special_command should be transferred to new data and removed from old
337      * one */
338     old_gt->special_command = NULL;
339 
340     /* Only inherit commandv from templates, not from running terminals */
341     if (!old_gt->running && old_gt->commandv)
342     {
343         new_gt->commandv = global_options_copy_strv(old_gt->commandv);
344     }
345     else
346     {
347         new_gt->commandv = NULL;
348     }
349     new_gt->directory = roxterm_get_cwd(old_gt);
350     if (!new_gt->directory && old_gt->directory)
351     {
352         new_gt->directory = g_strdup(old_gt->directory);
353     }
354 
355     new_gt->match_map = g_array_new(FALSE, FALSE, sizeof(ROXTerm_MatchMap));
356     new_gt->matched_url = NULL;
357 
358     if (old_gt->pango_desc)
359     {
360         new_gt->pango_desc = pango_font_description_copy(old_gt->pango_desc);
361     }
362     if (old_gt->widget && gtk_widget_get_realized(old_gt->widget))
363     {
364         VteTerminal *vte = VTE_TERMINAL(old_gt->widget);
365 
366         new_gt->columns = vte_terminal_get_column_count(vte);
367         new_gt->rows = vte_terminal_get_row_count(vte);
368     }
369     /*new_gt->file_match_tag[0] = new_gt->file_match_tag[1] = -1;*/
370     if (new_gt->override_exit_action)
371         new_gt->override_exit_action = FALSE;
372     else
373         new_gt->exit_action = Roxterm_ChildExitNotOverridden;
374 
375     return new_gt;
376 }
377 
roxterm_hash_env(char ** env)378 static GHashTable *roxterm_hash_env(char **env)
379 {
380     int n;
381     GHashTable *env_table = g_hash_table_new_full(g_str_hash, g_str_equal,
382             g_free, g_free);
383 
384     for (n = 0; env[n]; ++n)
385     {
386         const char *eq = strchr(env[n], '=');
387 
388         g_hash_table_replace(env_table, eq ?
389                 g_strndup(env[n], eq - env[n]) : g_strdup(env[n]),
390                 eq ? g_strdup(eq + 1) : NULL);
391     }
392     return env_table;
393 }
394 
roxterm_envv_from_hash(GHashTable * hash)395 static char **roxterm_envv_from_hash(GHashTable *hash)
396 {
397     GHashTableIter it;
398     gsize len = g_hash_table_size(hash);
399     char **env = g_new(char *, len + 1);
400     char *key, *val;
401     int n = 0;
402 
403     g_hash_table_iter_init(&it, hash);
404     while (g_hash_table_iter_next(&it, (gpointer *) &key, (gpointer *) &val))
405     {
406         env[n++] = g_strdup_printf("%s=%s", key, val);
407     }
408     env[n] = NULL;
409     return env;
410 }
411 
roxterm_get_environment(ROXTermData * roxterm,const char * term)412 static char **roxterm_get_environment(ROXTermData *roxterm, const char *term)
413 {
414     GHashTable *env = roxterm_hash_env(roxterm->env);
415     char **envv;
416 
417     if (term)
418         g_hash_table_replace(env, g_strdup("TERM"), g_strdup(term));
419     else
420         g_hash_table_remove(env, "TERM");
421     g_hash_table_replace(env, g_strdup("ROXTERM_ID"),
422             g_strdup_printf("%p", roxterm));
423     g_hash_table_replace(env, g_strdup("ROXTERM_NUM"),
424             g_strdup_printf("%d", g_list_length(roxterm_terms)));
425     g_hash_table_replace(env, g_strdup("ROXTERM_PID"),
426             g_strdup_printf("%d", (int) getpid()));
427 
428 #ifdef GDK_WINDOWING_X11
429     if (GDK_IS_X11_DISPLAY(gdk_display_get_default()))
430     {
431         MultiWin *mwin = roxterm_get_win(roxterm);
432         if (mwin)
433         {
434             GtkWidget *widget = multi_win_get_widget(mwin);
435             if (widget && gtk_widget_is_toplevel(widget))
436             {
437                 Window xid = gdk_x11_window_get_xid(
438                                 gtk_widget_get_window(widget));
439                 g_hash_table_replace(env, g_strdup("WINDOWID"),
440                         g_strdup_printf("%ld", xid));
441             }
442 
443         }
444     }
445 #endif
446 
447     g_hash_table_remove(env, "COLORTERM");
448     g_hash_table_remove(env, "COLUMNS");
449     g_hash_table_remove(env, "LINES");
450     g_hash_table_remove(env, "VTE_VERSION");
451     /* gnome-terminal also removes GNOME_DESKTOP_ICON, probably best not to do
452      * the same without knowing why. */
453     /* g_hash_table_remove(env, "GNOME_DESKTOP_ICON"); */
454 
455     envv = roxterm_envv_from_hash(env);
456     g_hash_table_unref(env);
457     return envv;
458 }
459 
roxterm_get_toplevel(ROXTermData * roxterm)460 static GtkWindow *roxterm_get_toplevel(ROXTermData *roxterm)
461 {
462     MultiWin *w;
463 
464     if (roxterm && (w = roxterm_get_win(roxterm)) != NULL)
465     {
466         GtkWidget *tl = multi_win_get_widget(w);
467         if (tl && gtk_widget_is_toplevel(tl))
468             return GTK_WINDOW(tl);
469     }
470     return NULL;
471 }
472 
get_default_command(ROXTermData * roxterm)473 static char *get_default_command(ROXTermData *roxterm)
474 {
475     char *command = NULL;
476     struct passwd *pinfo = getpwuid(getuid());
477 
478     if (pinfo)
479     {
480         command = g_strdup(pinfo->pw_shell);
481     }
482     else
483     {
484         dlg_warning(roxterm_get_toplevel(roxterm),
485                 _("Unable to look up login details: %s"), strerror(errno));
486     }
487     if (!command || !command[0])
488         command = g_strdup("/bin/sh");
489     return command;
490 }
491 
roxterm_show_status(ROXTermData * roxterm,const char * name)492 static void roxterm_show_status(ROXTermData *roxterm, const char *name)
493 {
494     if (name && !strcmp(name, "dialog-information") &&
495             roxterm->status_icon_name &&
496             (!strcmp(roxterm->status_icon_name, "dialog-warning") ||
497             !strcmp(roxterm->status_icon_name, "dialog_error")))
498 
499     {
500         return;
501     }
502     roxterm->status_icon_name = name;
503     if (roxterm->tab && options_lookup_int_with_default(roxterm->profile,
504             "show_tab_status", FALSE))
505     {
506         multi_tab_set_status_icon_name(roxterm->tab, name);
507     }
508 }
509 
roxterm_command_failed(ROXTermData * roxterm)510 static gboolean roxterm_command_failed(ROXTermData *roxterm)
511 {
512     MultiWin *win = roxterm_get_win(roxterm);
513     GtkWidget *w = win ? multi_win_get_widget(win) : NULL;
514 
515     dlg_critical(w ? GTK_WINDOW(w) : NULL, "%s", roxterm->reply);
516     g_free(roxterm->reply);
517     multi_tab_delete(roxterm->tab);
518     return FALSE;
519 }
520 
521 /* Returns NULL if cwd is apparently inaccessible */
check_cwd(const char * cwd)522 static const char *check_cwd(const char *cwd)
523 {
524     return (cwd && g_file_test(cwd, G_FILE_TEST_IS_DIR) &&
525             g_file_test(cwd, G_FILE_TEST_IS_EXECUTABLE)) ?
526             cwd : NULL;
527 }
528 
529 /* Returns home dir if cwd is inaccessible. If home is also inaccessible
530  * returns NULL. */
roxterm_check_cwd(const char * cwd)531 static const char *roxterm_check_cwd(const char *cwd)
532 {
533     return check_cwd(cwd) ? cwd : check_cwd(g_get_home_dir());
534 }
535 
roxterm_report_launch_error_async(ROXTermData * roxterm,const char * msg,GError * error)536 static void roxterm_report_launch_error_async(ROXTermData *roxterm,
537         const char *msg, GError *error)
538 {
539     roxterm->reply = g_strdup_printf("%s: %s",
540             msg, error ? error->message : "?");
541     g_idle_add((GSourceFunc) roxterm_command_failed, roxterm);
542 }
543 
544 /* Mustn't free this error: https://bugzilla.gnome.org/show_bug.cgi?id=793675 */
roxterm_fork_callback(VteTerminal * vte,GPid pid,GError * error,gpointer user_data)545 static void roxterm_fork_callback(VteTerminal *vte,
546         GPid pid, GError *error, gpointer user_data)
547 {
548     ROXTermData *roxterm = user_data;
549     (void) vte;
550 
551     roxterm->pid = pid;
552     if (!vte)
553         roxterm->widget = NULL;
554     if (pid == -1)
555     {
556         roxterm_report_launch_error_async(roxterm,
557                 _("Failed to run command"), error);
558     }
559 }
560 
roxterm_fork_command(ROXTermData * roxterm,VteTerminal * vte,char ** argv,char ** envv,const char * working_directory,gboolean login)561 static void roxterm_fork_command(ROXTermData *roxterm, VteTerminal *vte,
562         char **argv, char **envv,
563         const char *working_directory,
564         gboolean login)
565 {
566     char *filename = argv[0];
567     char **new_argv = NULL;
568 
569     working_directory = roxterm_check_cwd(working_directory);
570     if (login)
571     {
572         /* If login_shell, make sure argv[0] is the
573          * shell base name (== leaf name) prepended by "-" */
574         guint old_len;
575         char *leaf = strrchr(filename, G_DIR_SEPARATOR);
576 
577         if (leaf)
578             ++leaf;
579         else
580             leaf = argv[0];
581 
582         /* The NULL terminator is not considered by g_strv_length() */
583         old_len = g_strv_length(argv);
584         new_argv = g_new(char *, old_len + 2);
585 
586         new_argv[0] = filename;
587         new_argv[1] = g_strconcat("-", leaf, NULL);
588         memcpy(new_argv + 2, argv + 1, sizeof(char *) * old_len);
589         argv = new_argv;
590     }
591 
592     /* VTE_SPAWN_NO_PARENT_ENVV is undocumented; looking at VTE's source it
593      * appears to prevent our process' env from being merged into the envv we're
594      * passing in here, which is behaviour we want.
595      */
596     vte_terminal_spawn_async(vte, VTE_PTY_DEFAULT,
597             working_directory, argv, envv,
598             (login ? G_SPAWN_FILE_AND_ARGV_ZERO : G_SPAWN_SEARCH_PATH) |
599             VTE_SPAWN_NO_PARENT_ENVV,
600             NULL, NULL, NULL,
601             -1, NULL,
602             roxterm_fork_callback, roxterm);
603     if (new_argv)
604     {
605         g_free(new_argv[1]);
606         g_free(new_argv);
607     }
608 }
609 
roxterm_run_command(ROXTermData * roxterm,VteTerminal * vte)610 static void roxterm_run_command(ROXTermData *roxterm, VteTerminal *vte)
611 {
612     char **commandv = NULL;
613     char *command = NULL;
614     gboolean special = FALSE; /* special_command and -e override login_shell */
615     gboolean login = FALSE;
616     const char *term = options_lookup_string(roxterm->profile, "term");
617     char **env;
618 
619     if (term && !term[0])
620         term = NULL;
621     env = roxterm_get_environment(roxterm, term);
622     roxterm->running = TRUE;
623     roxterm_show_status(roxterm, "window-close");
624     roxterm->is_shell = FALSE;
625     if (roxterm->actual_commandv)
626     {
627         special = TRUE;
628     }
629     else if (roxterm->special_command)
630     {
631         special = TRUE;
632         /* Use special_command, currently single string */
633         command = roxterm->special_command;
634         special = TRUE;
635     }
636     else if (roxterm->commandv)
637     {
638         /* Either restoring a session or -e was given */
639         if (!roxterm->from_session)
640         {
641             special = TRUE;
642             /* If commandv is a single string it might be a command + args
643              * separated by spaces, so parse it. See
644              * <http://sourceforge.net/p/roxterm/bugs/87/>.
645              */
646             if (!roxterm->commandv[1])
647             {
648                 command = roxterm->commandv[0];
649                 roxterm->commandv[0] = NULL;
650                 g_strfreev(roxterm->commandv);
651                 roxterm->commandv = NULL;
652             }
653         }
654     }
655     else
656     {
657         /* Either use custom command from option or default (as single string)
658          */
659         if (options_lookup_int_with_default(roxterm->profile,
660                     "use_ssh", FALSE))
661         {
662             const char *host = options_lookup_string_with_default(
663                     roxterm->profile, "ssh_address", "localhost");
664             const char *user = options_lookup_string(roxterm->profile,
665                     "ssh_user");
666             const char *ssh_opts = options_lookup_string(roxterm->profile,
667                     "ssh_options");
668             int port = options_lookup_int_with_default(roxterm->profile,
669                     "ssh_port", 22);
670 
671             command = g_strdup_printf("ssh%s%s %s -p %d %s",
672                     (user && user[0]) ? " -l " : "",
673                     (user && user[0]) ? user : "",
674                     (ssh_opts && ssh_opts[0]) ? ssh_opts : "",
675                     port, host);
676         }
677         else if (options_lookup_int_with_default(roxterm->profile,
678                     "use_custom_command", FALSE))
679         {
680             command = options_lookup_string(roxterm->profile, "command");
681         }
682         if (!command || !command[0])
683         {
684             command = get_default_command(roxterm);
685             roxterm->is_shell = TRUE;
686         }
687         else
688         {
689             /* Using custom command disables login_shell */
690             special = TRUE;
691         }
692     }
693     if (roxterm->actual_commandv)
694     {
695         commandv = roxterm->actual_commandv;
696     }
697     if (roxterm->commandv && !roxterm->special_command)
698     {
699         /* We're using roxterm->commandv, point commandv to it */
700         commandv = roxterm->commandv;
701     }
702     else if (!roxterm->actual_commandv)
703     {
704         /* Parse our own commandv from single string command */
705         int argc;
706         GError *error = NULL;
707 
708         commandv = NULL;
709         if (!g_shell_parse_argv(command, &argc, &commandv, &error))
710         {
711             char *msg = g_strdup_printf(_("Unable to parse command '%s'"),
712                     command);
713             roxterm_report_launch_error_async(roxterm, msg, error);
714             g_free(msg);
715             if (error)
716                 g_error_free(error);
717             if (commandv)
718                 g_strfreev(commandv);
719             commandv = NULL;
720             /*
721             commandv = g_new(char *, 2);
722             commandv[0] = get_default_command(roxterm);
723             commandv[1] = NULL;
724             */
725         }
726     }
727     if (!special && options_lookup_int_with_default(roxterm->profile,
728             "login_shell", 0))
729     {
730         login = TRUE;
731     }
732 
733     if (commandv && commandv[0])
734     {
735         roxterm_fork_command(roxterm, vte, commandv, env,
736                 roxterm->directory, login);
737     }
738 
739     roxterm->special_command = NULL;
740     /* If special_command was set, command points to the same string */
741     g_free(command);
742     roxterm->actual_commandv = commandv;
743     g_strfreev(env);
744 }
745 
roxterm_lookup_uri_handler(ROXTermData * roxterm,const char * tag)746 static char *roxterm_lookup_uri_handler(ROXTermData *roxterm, const char *tag)
747 {
748     char *filename = options_lookup_string(roxterm->profile, tag);
749 
750     if (g_file_test(filename, G_FILE_TEST_IS_DIR))
751     {
752         char *apprun = g_build_filename(filename, "AppRun", NULL);
753 
754         if (g_file_test(apprun, G_FILE_TEST_IS_EXECUTABLE))
755         {
756             g_free(filename);
757             filename = apprun;
758         }
759         else
760         {
761             g_free(apprun);
762         }
763     }
764     return filename;
765 }
766 
767 /* Converts quotes to % sequences to fix debian bug #696917 */
preparse_url(const char * url)768 char *preparse_url(const char *url)
769 {
770     GString *s = g_string_new("");
771     size_t n;
772     char c;
773     for (n = 0; (c = url[n]) != 0; ++n)
774     {
775         switch (c)
776         {
777             case '"':
778                 s = g_string_append(s, "%22");
779                 break;
780             case '\'':
781                 s = g_string_append(s, "%27");
782                 break;
783             default:
784                 s = g_string_append_c(s, c);
785                 break;
786         }
787     }
788     return g_string_free(s, FALSE);
789 }
790 
roxterm_launch_ssh(ROXTermData * roxterm,const char * uri)791 static void roxterm_launch_ssh(ROXTermData *roxterm, const char *uri)
792 {
793     char *ssh_o = roxterm_lookup_uri_handler(roxterm, "ssh");
794     char *ssh = uri_get_ssh_command(uri, ssh_o);
795 
796     if (ssh_o)
797         g_free(ssh_o);
798     if (ssh)
799     {
800         roxterm_spawn(roxterm, ssh, options_lookup_int_with_default
801             (roxterm->profile, "ssh_spawn_type", ROXTerm_SpawnNewTab));
802         g_free(ssh);
803     }
804 }
805 
roxterm_get_modified_uri(ROXTermData * roxterm)806 static char *roxterm_get_modified_uri(ROXTermData *roxterm)
807 {
808     const char *m = roxterm->matched_url;
809 
810     switch (roxterm->match_type)
811     {
812         case ROXTerm_Match_MailTo:
813             if (!g_str_has_prefix(m, "mailto:"))
814                 return g_strdup_printf("mailto:%s", m);
815             break;
816         case ROXTerm_Match_URINoScheme:
817             if (g_str_has_prefix(m, "ftp"))
818                 return g_strdup_printf("ftp://%s", m);
819             else
820                 return g_strdup_printf("http://%s", m);
821         case ROXTerm_Match_SSH_Host:
822             if (!g_str_has_prefix(m, "ssh:"))
823                 return g_strdup_printf("ssh://%s", m);
824             break;
825         default:
826             break;
827     }
828     return NULL;
829 }
830 
roxterm_launch_uri(ROXTermData * roxterm,const char * uri,guint32 timestamp)831 static void roxterm_launch_uri(ROXTermData *roxterm,
832         const char *uri, guint32 timestamp)
833 {
834     GError *error = NULL;
835     gboolean result;
836 
837 #if GTK_CHECK_VERSION(3,22,0)
838     result = gtk_show_uri_on_window(roxterm_get_toplevel(roxterm),
839             uri, timestamp, &error);
840 #else
841     result = gtk_show_uri(gtk_window_get_screen(roxterm_get_toplevel(roxterm)),
842             uri, timestamp, &error);
843 #endif
844     if (!result)
845     {
846         dlg_warning(roxterm_get_toplevel(roxterm),
847                 _("Unable to open URI %s: %s"), uri, error->message);
848         g_error_free(error);
849     }
850 }
851 
roxterm_launch_matched_uri(ROXTermData * roxterm,guint32 timestamp)852 static void roxterm_launch_matched_uri(ROXTermData *roxterm, guint32 timestamp)
853 {
854     const char *m = roxterm->matched_url;
855     char *mod = NULL;
856 
857     if (roxterm->match_type == ROXTerm_Match_SSH_Host)
858     {
859         roxterm_launch_ssh(roxterm, roxterm->matched_url);
860         return;
861     }
862     mod = roxterm_get_modified_uri(roxterm);
863     roxterm_launch_uri(roxterm, mod ? mod : m, timestamp);
864     g_free(mod);
865 }
866 
roxterm_data_delete(ROXTermData * roxterm)867 static void roxterm_data_delete(ROXTermData *roxterm)
868 {
869     /* This doesn't delete widgets because they're deleted when removed from
870      * the parent */
871     GtkWindow *gwin;
872 
873     g_return_if_fail(roxterm);
874 
875     /* Fix for https://github.com/realh/roxterm/issues/197 */
876     if (roxterm->widget && roxterm->child_exited_tag)
877     {
878         g_signal_handler_disconnect(roxterm->widget, roxterm->child_exited_tag);
879     }
880     else if (roxterm->child_exited_tag)
881     {
882         g_warning("child_exited_tag == %ld but widget is NULL for %p",
883                 roxterm->child_exited_tag, roxterm);
884     }
885     gwin = roxterm_get_toplevel(roxterm);
886     if (gwin && roxterm->win_state_changed_tag)
887     {
888         g_signal_handler_disconnect(gwin, roxterm->win_state_changed_tag);
889     }
890     if (roxterm->post_exit_tag)
891         g_source_remove(roxterm->post_exit_tag);
892     if (roxterm->colour_scheme)
893     {
894         UNREF_LOG(colour_scheme_unref(roxterm->colour_scheme));
895     }
896     if (roxterm->profile)
897     {
898         UNREF_LOG(dynamic_options_unref(roxterm_profiles,
899                 options_get_leafname(roxterm->profile)));
900     }
901     drag_receive_data_delete(roxterm->drd);
902     if (roxterm->actual_commandv != roxterm->commandv)
903         g_strfreev(roxterm->actual_commandv);
904     if (roxterm->commandv)
905         g_strfreev(roxterm->commandv);
906     g_free(roxterm->directory);
907     g_strfreev(roxterm->env);
908     if (roxterm->pango_desc)
909         pango_font_description_free(roxterm->pango_desc);
910     if (roxterm->replace_task_dialog)
911     {
912         roxterm->postponed_free = TRUE;
913         gtk_widget_destroy(roxterm->replace_task_dialog);
914     }
915     else
916     {
917         g_free(roxterm);
918     }
919 }
920 
921 typedef enum {
922     ROXTerm_DontShowURIMenuItems,
923     ROXTerm_ShowWebURIMenuItems,
924     ROXTerm_ShowMailURIMenuItems,
925     ROXTerm_ShowFileURIMenuItems,
926     ROXTerm_ShowSSHHostMenuItems,
927     ROXTerm_ShowVOIPURIMenuItems,
928 } ROXTerm_URIMenuItemsShowType;
929 
930 static void
set_show_uri_menu_items(MenuTree * tree,ROXTerm_URIMenuItemsShowType show_type)931 set_show_uri_menu_items(MenuTree *tree, ROXTerm_URIMenuItemsShowType show_type)
932 {
933     g_return_if_fail(tree);
934     menutree_set_show_item(tree, MENUTREE_OPEN_IN_BROWSER,
935         show_type == ROXTerm_ShowWebURIMenuItems);
936     menutree_set_show_item(tree, MENUTREE_OPEN_IN_MAILER,
937         show_type == ROXTerm_ShowMailURIMenuItems);
938     menutree_set_show_item(tree, MENUTREE_OPEN_IN_FILER,
939         show_type == ROXTerm_ShowFileURIMenuItems);
940     menutree_set_show_item(tree, MENUTREE_COPY_URI,
941         show_type != ROXTerm_DontShowURIMenuItems);
942     menutree_set_show_item(tree, MENUTREE_SSH_HOST,
943         show_type == ROXTerm_ShowSSHHostMenuItems);
944     menutree_set_show_item(tree, MENUTREE_VOIP_CALL,
945         show_type == ROXTerm_ShowVOIPURIMenuItems);
946     menutree_set_show_item(tree, MENUTREE_URI_SEPARATOR,
947         show_type != ROXTerm_DontShowURIMenuItems);
948 }
949 
950 static void
roxterm_set_show_uri_menu_items(ROXTermData * roxterm,ROXTerm_URIMenuItemsShowType show_type)951 roxterm_set_show_uri_menu_items(ROXTermData * roxterm,
952     ROXTerm_URIMenuItemsShowType show_type)
953 {
954     MultiWin *win = roxterm_get_win(roxterm);
955 
956     set_show_uri_menu_items(multi_win_get_popup_menu(win), show_type);
957     set_show_uri_menu_items(multi_win_get_short_popup_menu(win), show_type);
958 }
959 
roxterm_get_config_saturation(ROXTermData * roxterm)960 static double roxterm_get_config_saturation(ROXTermData *roxterm)
961 {
962     double saturation = options_lookup_double_with_default(roxterm->profile,
963             "saturation", 1.0);
964 
965     if (saturation < 0 || saturation > 1)
966     {
967         g_warning(_("Saturation option %f out of range"), saturation);
968         saturation = CLAMP(saturation, 0, 1);
969     }
970     return saturation;
971 }
972 
973 static void
roxterm_update_cursor_colour(ROXTermData * roxterm,VteTerminal * vte)974 roxterm_update_cursor_colour(ROXTermData * roxterm, VteTerminal * vte)
975 {
976     vte_terminal_set_color_cursor(vte,
977             colour_scheme_get_cursor_colour(roxterm->colour_scheme, TRUE));
978 }
979 
980 static void
roxterm_update_cursorfg_colour(ROXTermData * roxterm,VteTerminal * vte)981 roxterm_update_cursorfg_colour(ROXTermData * roxterm, VteTerminal * vte)
982 {
983     vte_terminal_set_color_cursor_foreground(vte,
984             colour_scheme_get_cursorfg_colour(roxterm->colour_scheme, TRUE));
985 }
986 
987 static void
roxterm_update_bold_colour(ROXTermData * roxterm,VteTerminal * vte)988 roxterm_update_bold_colour(ROXTermData * roxterm, VteTerminal * vte)
989 {
990     vte_terminal_set_color_bold(vte,
991             colour_scheme_get_bold_colour(roxterm->colour_scheme, TRUE));
992 }
993 
extrapolate_chroma(guint16 bg,guint16 fg,double factor)994 guint16 extrapolate_chroma(guint16 bg, guint16 fg, double factor)
995 {
996     gint32 ext = (gint32) bg +
997         (gint32) ((double) ((gint32) fg - (gint32) bg) * factor);
998 
999     return (guint16) CLAMP(ext, 0, 0xffff);
1000 }
1001 
extrapolate_colours(const GdkRGBA * bg,const GdkRGBA * fg,double factor)1002 static const GdkRGBA *extrapolate_colours(const GdkRGBA *bg,
1003         const GdkRGBA *fg, double factor)
1004 {
1005     static GdkRGBA ext;
1006 
1007 #define EXTRAPOLATE(c) ext. c = \
1008     extrapolate_chroma(bg ? bg->  c : 0, fg ? fg->  c : 0xffff, factor)
1009     EXTRAPOLATE(red);
1010     EXTRAPOLATE(green);
1011     EXTRAPOLATE(blue);
1012     return &ext;
1013 }
1014 
roxterm_get_background_colour_with_transparency(ROXTermData * roxterm)1015 static const GdkRGBA *roxterm_get_background_colour_with_transparency(
1016         ROXTermData * roxterm)
1017 {
1018     const GdkRGBA *bgro =
1019             colour_scheme_get_background_colour(roxterm->colour_scheme, TRUE);
1020     static GdkRGBA background;
1021     float alpha = roxterm_get_config_saturation(roxterm);
1022 
1023     if (bgro)
1024     {
1025         background = *bgro;
1026     }
1027     else if (alpha < 1.0f)
1028     {
1029         bgro = colour_scheme_get_background_colour(roxterm->colour_scheme,
1030                 FALSE);
1031         background = *bgro;
1032         bgro = NULL;
1033     }
1034     background.alpha = alpha;
1035     return (alpha < 1.0f) ? &background : bgro;
1036 }
1037 
1038 static void
roxterm_apply_colour_scheme(ROXTermData * roxterm,VteTerminal * vte)1039 roxterm_apply_colour_scheme(ROXTermData *roxterm, VteTerminal *vte)
1040 {
1041     gboolean bold = FALSE;
1042     const GdkRGBA *bd = NULL;
1043     int ncolours = 0;
1044     const GdkRGBA *palette = NULL;
1045     const GdkRGBA *foreground =
1046             colour_scheme_get_foreground_colour(roxterm->colour_scheme, TRUE);
1047     const GdkRGBA *background =
1048             roxterm_get_background_colour_with_transparency(roxterm);
1049 
1050     vte_terminal_set_default_colors(vte);
1051     ncolours = colour_scheme_get_palette_size(roxterm->colour_scheme);
1052     if (ncolours)
1053         palette = colour_scheme_get_palette(roxterm->colour_scheme);
1054     vte_terminal_set_colors(vte, foreground, background,
1055             palette, ncolours);
1056     if (!ncolours && foreground && background)
1057     {
1058         vte_terminal_set_color_bold(vte,
1059                 extrapolate_colours(background, foreground, 1.2));
1060         bold = TRUE;
1061     }
1062     bd = colour_scheme_get_bold_colour(roxterm->colour_scheme, TRUE);
1063     if (bd || !bold)
1064         vte_terminal_set_color_bold(vte, bd);
1065     roxterm_update_cursor_colour(roxterm, vte);
1066     roxterm_update_cursorfg_colour(roxterm, vte);
1067     roxterm_force_redraw(roxterm);
1068 }
1069 
roxterm_is_parented(ROXTermData * roxterm)1070 static gboolean roxterm_is_parented(ROXTermData *roxterm)
1071 {
1072     GtkWindow *top = roxterm_get_toplevel(roxterm);
1073     return top && gtk_widget_get_realized(GTK_WIDGET(top)) &&
1074         gtk_widget_get_realized(roxterm->widget);
1075 }
1076 
1077 static void
roxterm_set_vte_size(ROXTermData * roxterm,VteTerminal * vte,int columns,int rows)1078 roxterm_set_vte_size(ROXTermData *roxterm, VteTerminal *vte,
1079         int columns, int rows)
1080 {
1081     MultiWin *win = roxterm_get_win(roxterm);
1082 
1083     vte_terminal_set_size(vte, columns, rows);
1084     if (roxterm_is_parented(roxterm) &&
1085             multi_win_get_current_tab(win) == roxterm->tab)
1086     {
1087         multi_win_apply_new_geometry(win, columns, rows, roxterm->tab);
1088     }
1089 }
1090 
1091 static void
roxterm_get_padding(ROXTermData * roxterm,int * width,int * height)1092 roxterm_get_padding(ROXTermData *roxterm, int *width, int *height)
1093 {
1094     GtkBorder padding;
1095 
1096     gtk_style_context_get_padding(gtk_widget_get_style_context(roxterm->widget),
1097             gtk_widget_get_state_flags(roxterm->widget), &padding);
1098     /* The +2 is a fudge otherwise the window shrinks by one cell each time
1099      * it's maximized and then unmaximized.
1100      */
1101     *width = padding.left + padding.right + 2;
1102     *height = padding.top + padding.bottom + 2;
1103 }
1104 
roxterm_geometry_func(ROXTermData * roxterm,GdkGeometry * geom,GdkWindowHints * hints)1105 static void roxterm_geometry_func(ROXTermData *roxterm,
1106         GdkGeometry *geom, GdkWindowHints *hints)
1107 {
1108     VteTerminal *vte = VTE_TERMINAL(roxterm->widget);
1109 
1110     roxterm_get_padding(roxterm, &geom->base_width, &geom->base_height);
1111     /* The following results include spacing (cell-*-scale) */
1112     geom->width_inc = vte_terminal_get_char_width(vte);
1113     geom->height_inc = vte_terminal_get_char_height(vte);
1114     geom->min_width = geom->base_width + 4 * geom->width_inc;
1115     geom->min_height = geom->base_height + 4 * geom->height_inc;
1116     if (hints)
1117         *hints = GDK_HINT_RESIZE_INC | GDK_HINT_BASE_SIZE | GDK_HINT_MIN_SIZE;
1118 }
1119 
roxterm_size_func(ROXTermData * roxterm,gboolean pixels,int * pwidth,int * pheight)1120 static void roxterm_size_func(ROXTermData *roxterm, gboolean pixels,
1121         int *pwidth, int *pheight)
1122 {
1123     VteTerminal *vte = VTE_TERMINAL(roxterm->widget);
1124 
1125     if (!pixels || *pwidth == -1)
1126         *pwidth = vte_terminal_get_column_count(vte);
1127     if (!pixels || *pheight == -1)
1128         *pheight = vte_terminal_get_row_count(vte);
1129     if (pixels)
1130     {
1131         int px, py;
1132 
1133         roxterm_get_padding(roxterm, &px, &py);
1134         /* The following results include spacing (cell-*-scale) */
1135         *pwidth = *pwidth * vte_terminal_get_char_width(vte) + px;
1136         *pheight = *pheight * vte_terminal_get_char_height(vte) + py;
1137     }
1138 }
1139 
roxterm_default_size_func(ROXTermData * roxterm,int * pwidth,int * pheight)1140 static void roxterm_default_size_func(ROXTermData *roxterm,
1141         int *pwidth, int *pheight)
1142 {
1143     *pwidth = options_lookup_int_with_default(roxterm->profile, "width", 80);
1144     *pheight = options_lookup_int_with_default(roxterm->profile, "height", 24);
1145 }
1146 
roxterm_update_geometry(ROXTermData * roxterm,VteTerminal * vte)1147 static void roxterm_update_geometry(ROXTermData * roxterm, VteTerminal * vte)
1148 {
1149     int columns, rows;
1150     (void) vte;
1151 
1152     columns = vte_terminal_get_column_count(vte);
1153     rows = vte_terminal_get_row_count(vte);
1154     roxterm_set_vte_size(roxterm, vte, columns, rows);
1155 }
1156 
roxterm_update_size(ROXTermData * roxterm,VteTerminal * vte)1157 static void roxterm_update_size(ROXTermData * roxterm, VteTerminal * vte)
1158 {
1159     int w, h;
1160 
1161     roxterm_default_size_func(roxterm, &w, &h);
1162     roxterm_set_vte_size(roxterm, vte, w, h);
1163     roxterm_update_geometry(roxterm, vte);
1164 }
1165 
resize_pango_for_zoom(PangoFontDescription * pango_desc,double zf)1166 static void resize_pango_for_zoom(PangoFontDescription *pango_desc,
1167         double zf)
1168 {
1169     int size;
1170     gboolean abs = FALSE;
1171 
1172     if (zf == 1.0)
1173         return;
1174     size = pango_font_description_get_size(pango_desc);
1175     if (!size)
1176         size = 10 * PANGO_SCALE;
1177     else
1178         abs = pango_font_description_get_size_is_absolute(pango_desc);
1179     size = (int) ((double) size * zf);
1180     if (abs)
1181         pango_font_description_set_absolute_size(pango_desc, size);
1182     else
1183         pango_font_description_set_size(pango_desc, size);
1184 }
1185 
1186 /* The spacing apply functions don't need to explicitly resize the window
1187  * because VTE triggers the apropriate signal.
1188  */
1189 static void
roxterm_apply_vspacing(ROXTermData * roxterm,VteTerminal * vte)1190 roxterm_apply_vspacing(ROXTermData *roxterm, VteTerminal *vte)
1191 {
1192     double spacing = (double) options_lookup_int_with_default(roxterm->profile,
1193             "vspacing", 0) / 100.0;
1194 
1195     vte_terminal_set_cell_height_scale(vte, CLAMP(spacing, 0.0, 1.0) + 1.0);
1196 }
1197 
1198 static void
roxterm_apply_hspacing(ROXTermData * roxterm,VteTerminal * vte)1199 roxterm_apply_hspacing(ROXTermData *roxterm, VteTerminal *vte)
1200 {
1201     double spacing = (double) options_lookup_int_with_default(roxterm->profile,
1202             "hspacing", 0) / 100.0;
1203 
1204     vte_terminal_set_cell_width_scale(vte, CLAMP(spacing, 0.0, 1.0) + 1.0);
1205 }
1206 
1207 static void
roxterm_apply_profile_font(ROXTermData * roxterm,VteTerminal * vte,gboolean update_geometry)1208 roxterm_apply_profile_font(ROXTermData *roxterm, VteTerminal *vte,
1209     gboolean update_geometry)
1210 {
1211     char *fdesc;
1212     PangoFontDescription *pango_desc = NULL;
1213     int w = vte_terminal_get_column_count(vte);
1214     int h = vte_terminal_get_row_count(vte);
1215 
1216     if (roxterm->pango_desc)
1217     {
1218         pango_font_description_free(roxterm->pango_desc);
1219         roxterm->pango_desc = NULL;
1220     }
1221     fdesc = options_lookup_string(roxterm->profile, "font");
1222     double zf = roxterm->target_zoom_factor;
1223 
1224     if (fdesc && fdesc[0])
1225     {
1226         pango_desc = pango_font_description_from_string(fdesc);
1227         if (!pango_desc)
1228             g_warning(_("Couldn't create a font from '%s'"), fdesc);
1229     }
1230     if (!pango_desc)
1231     {
1232         if (zf == roxterm->current_zoom_factor)
1233         {
1234             if (fdesc)
1235                 g_free(fdesc);
1236             return;
1237         }
1238         pango_desc = pango_font_description_copy(
1239                 vte_terminal_get_font(vte));
1240         zf /= roxterm->current_zoom_factor;
1241     }
1242     if (!pango_desc)
1243     {
1244         g_free(fdesc);
1245         g_return_if_fail(pango_desc != NULL);
1246     }
1247     resize_pango_for_zoom(pango_desc, zf);
1248     vte_terminal_set_font(vte, pango_desc);
1249     roxterm->pango_desc = pango_desc;
1250     g_free(fdesc);
1251     roxterm->current_zoom_factor = roxterm->target_zoom_factor;
1252     roxterm_apply_vspacing(roxterm, vte);
1253     roxterm_apply_hspacing(roxterm, vte);
1254     if (update_geometry)
1255     {
1256         roxterm_set_vte_size(roxterm, vte, w, h);
1257     }
1258 }
1259 
1260 static void
roxterm_update_font(ROXTermData * roxterm,VteTerminal * vte,gboolean update_geometry)1261 roxterm_update_font(ROXTermData *roxterm, VteTerminal *vte,
1262     gboolean update_geometry)
1263 {
1264     PangoFontDescription *pango_desc = NULL;
1265     double zf;
1266     int w, h;
1267 
1268     if (!roxterm->pango_desc)
1269     {
1270         roxterm_apply_profile_font(roxterm, vte, update_geometry);
1271         return;
1272     }
1273     w = vte_terminal_get_column_count(vte);
1274     h = vte_terminal_get_row_count(vte);
1275     zf = roxterm->target_zoom_factor /
1276             (roxterm->current_zoom_factor ?
1277                     roxterm->current_zoom_factor : 1);
1278     pango_desc = roxterm->pango_desc;
1279     resize_pango_for_zoom(pango_desc, zf);
1280     vte_terminal_set_font(vte, pango_desc);
1281     roxterm->current_zoom_factor = roxterm->target_zoom_factor;
1282     roxterm_apply_vspacing(roxterm, vte);
1283     roxterm_apply_hspacing(roxterm, vte);
1284     if (update_geometry)
1285     {
1286         roxterm_set_vte_size(roxterm, vte, w, h);
1287     }
1288 }
1289 
roxterm_set_zoom_factor(ROXTermData * roxterm,double factor,int index)1290 static void roxterm_set_zoom_factor(ROXTermData *roxterm, double factor,
1291         int index)
1292 {
1293     roxterm->zoom_index = index;
1294     roxterm->target_zoom_factor = factor;
1295     roxterm_update_font(roxterm, VTE_TERMINAL(roxterm->widget), TRUE);
1296 }
1297 
1298 /* Change a terminal's font etc so it matches size with another */
roxterm_match_text_size(ROXTermData * roxterm,ROXTermData * other)1299 static void roxterm_match_text_size(ROXTermData *roxterm, ROXTermData *other)
1300 {
1301     VteTerminal *vte;
1302     VteTerminal *other_vte;
1303     const PangoFontDescription *fd;
1304     int width, height;
1305 
1306     if (roxterm == other)
1307         return;
1308     vte = VTE_TERMINAL(roxterm->widget);
1309     other_vte = VTE_TERMINAL(other->widget);
1310     width = vte_terminal_get_column_count(other_vte);
1311     height = vte_terminal_get_row_count(other_vte);
1312     fd = vte_terminal_get_font(VTE_TERMINAL(other->widget));
1313     roxterm->target_zoom_factor = other->current_zoom_factor;
1314     roxterm->current_zoom_factor = other->current_zoom_factor;
1315     roxterm->zoom_index = other->zoom_index;
1316     if (!pango_font_description_equal(
1317             vte_terminal_get_font(VTE_TERMINAL(roxterm->widget)), fd))
1318     {
1319         vte_terminal_set_font(vte, fd);
1320     }
1321     roxterm_set_vte_size(roxterm, VTE_TERMINAL(roxterm->widget),
1322             width, height);
1323 }
1324 
roxterm_about_uri_hook(GtkAboutDialog * about,gchar * link,gpointer data)1325 static gboolean roxterm_about_uri_hook(GtkAboutDialog *about,
1326         gchar *link, gpointer data)
1327 {
1328     (void) about;
1329     roxterm_launch_uri(data, link, gtk_get_current_event_time());
1330     return TRUE;
1331 }
1332 
roxterm_popup_handler(GtkWidget * widget,ROXTermData * roxterm)1333 static gboolean roxterm_popup_handler(GtkWidget * widget, ROXTermData * roxterm)
1334 {
1335     (void) widget;
1336     roxterm_set_show_uri_menu_items(roxterm, ROXTerm_DontShowURIMenuItems);
1337     multi_tab_popup_menu_at_pointer(roxterm->tab);
1338     return TRUE;
1339 }
1340 
1341 /* Checks whether a button event position is over a matched expression; if so
1342  * roxterm->matched_url is set and the result is TRUE */
roxterm_check_match(ROXTermData * roxterm,VteTerminal * vte,GdkEvent * event)1343 static gboolean roxterm_check_match(ROXTermData *roxterm, VteTerminal *vte,
1344         GdkEvent *event)
1345 {
1346     int tag;
1347 #if VTE_CHECK_VERSION(0,50,0)
1348     char *hyper = roxterm_enable_hyperlinks() ?
1349         vte_terminal_hyperlink_check_event(vte, event) : NULL;
1350 #endif
1351 
1352     g_free(roxterm->matched_url);
1353     /* If we're over a hyperlink we should also have a pattern match; be lazy
1354      * and use that to work out the type of link */
1355     roxterm->matched_url = vte_terminal_match_check_event(vte, event, &tag);
1356     if (roxterm->matched_url)
1357     {
1358         roxterm->match_type = roxterm_get_match_type(roxterm, tag);
1359     }
1360 #if VTE_CHECK_VERSION(0,50,0)
1361     else if (hyper)
1362     {
1363         roxterm->match_type = ROXTerm_Match_FullURI;
1364     }
1365     if (hyper)
1366     {
1367         g_free(roxterm->matched_url);
1368         roxterm->matched_url = hyper;
1369     }
1370 #endif
1371     return roxterm->matched_url != NULL;
1372 }
1373 
roxterm_clear_hold_over_uri(ROXTermData * roxterm)1374 static void roxterm_clear_hold_over_uri(ROXTermData *roxterm)
1375 {
1376     if (roxterm->hold_over_uri)
1377     {
1378         g_signal_handler_disconnect(roxterm->widget, roxterm->hold_handler_id);
1379         roxterm->hold_over_uri = FALSE;
1380     }
1381 }
1382 
roxterm_clear_drag_url(ROXTermData * roxterm)1383 inline static void roxterm_clear_drag_url(ROXTermData *roxterm)
1384 {
1385     roxterm_clear_hold_over_uri(roxterm);
1386 }
1387 
roxterm_get_uri_drag_target_list(void)1388 static GtkTargetList *roxterm_get_uri_drag_target_list(void)
1389 {
1390     static const GtkTargetEntry target_table[] = {
1391         { (char *) "text/uri-list", 0, ROXTERM_DRAG_TARGET_URI_LIST },
1392         { (char *) "UTF8_STRING", 0, ROXTERM_DRAG_TARGET_UTF8_STRING }
1393     };
1394 
1395     return gtk_target_list_new(target_table, G_N_ELEMENTS(target_table));
1396 }
1397 
roxterm_uri_drag_ended(GtkWidget * widget,GdkDragContext * drag_context,ROXTermData * roxterm)1398 static void roxterm_uri_drag_ended(GtkWidget *widget,
1399         GdkDragContext *drag_context, ROXTermData *roxterm)
1400 {
1401     (void) widget;
1402     (void) drag_context;
1403     roxterm_clear_drag_url(roxterm);
1404 }
1405 
roxterm_uri_drag_data_get(GtkWidget * widget,GdkDragContext * drag_context,GtkSelectionData * selection,guint info,guint time,ROXTermData * roxterm)1406 static void roxterm_uri_drag_data_get(GtkWidget *widget,
1407         GdkDragContext *drag_context, GtkSelectionData *selection,
1408         guint info, guint time, ROXTermData *roxterm)
1409 {
1410     char *uri_list[2];
1411     (void) widget;
1412     (void) drag_context;
1413     (void) time;
1414 
1415     g_return_if_fail(roxterm->matched_url);
1416 
1417     if (info == ROXTERM_DRAG_TARGET_URI_LIST)
1418     {
1419         uri_list[0] = roxterm_get_modified_uri(roxterm);
1420         if (!uri_list[0])
1421             uri_list[0] = g_strdup(roxterm->matched_url);
1422         uri_list[1] = NULL;
1423         gtk_selection_data_set_uris(selection, uri_list);
1424         g_free(uri_list[0]);
1425     }
1426     else
1427     {
1428         gtk_selection_data_set_text(selection, roxterm->matched_url, -1);
1429     }
1430 }
1431 
1432 /* Starts drag & drop process if user tries to drag a URL */
roxterm_motion_handler(GtkWidget * widget,GdkEventButton * event,ROXTermData * roxterm)1433 static gboolean roxterm_motion_handler(GtkWidget *widget, GdkEventButton *event,
1434         ROXTermData *roxterm)
1435 {
1436     GtkTargetList *target_list;
1437 
1438     if (!gtk_drag_check_threshold(widget, roxterm->hold_x,
1439                 roxterm->hold_y, event->x, event->y)
1440             || !roxterm->hold_over_uri)
1441     {
1442         return FALSE;
1443     }
1444 
1445     roxterm_clear_hold_over_uri(roxterm);
1446     g_return_val_if_fail(roxterm->matched_url, FALSE);
1447 
1448 
1449     target_list = roxterm_get_uri_drag_target_list();
1450     gtk_drag_begin_with_coordinates(roxterm->widget, target_list,
1451             GDK_ACTION_COPY, 1, (GdkEvent *) event, event->x, event->y);
1452 
1453     return TRUE;
1454 }
1455 
roxterm_click_handler(GtkWidget * widget,GdkEventButton * event,ROXTermData * roxterm)1456 static gboolean roxterm_click_handler(GtkWidget *widget,
1457         GdkEventButton *event, ROXTermData *roxterm)
1458 {
1459     VteTerminal *vte = VTE_TERMINAL(roxterm->widget);
1460 
1461     (void) widget;
1462 
1463     roxterm_clear_drag_url(roxterm);
1464     roxterm_check_match(roxterm, vte, (GdkEvent *) event);
1465     if (event->button == 3)
1466     {
1467         ROXTerm_URIMenuItemsShowType show_type = ROXTerm_DontShowURIMenuItems;
1468 
1469         /* If user right-clicks with no modifiers give the child app a chance
1470          * to handle the event first.
1471          */
1472         if (!(event->state & (GDK_SHIFT_MASK |
1473                 GDK_CONTROL_MASK | GDK_MOD1_MASK)))
1474         {
1475             if (GTK_WIDGET_CLASS(VTE_TERMINAL_GET_CLASS(widget))->
1476                     button_press_event(widget, event))
1477             {
1478                 return TRUE;
1479             }
1480         }
1481 
1482         if (roxterm->matched_url)
1483         {
1484             switch (roxterm->match_type)
1485             {
1486                 case ROXTerm_Match_SSH_Host:
1487                     show_type = ROXTerm_ShowSSHHostMenuItems;
1488                     break;
1489                 case ROXTerm_Match_MailTo:
1490                     show_type = ROXTerm_ShowMailURIMenuItems;
1491                     break;
1492                 case ROXTerm_Match_VOIP:
1493                     show_type = ROXTerm_ShowVOIPURIMenuItems;
1494                     break;
1495                 case ROXTerm_Match_File:
1496                     show_type = ROXTerm_ShowFileURIMenuItems;
1497                     break;
1498                 default:
1499                     show_type = ROXTerm_ShowWebURIMenuItems;
1500                     break;
1501                 }
1502         }
1503         roxterm_set_show_uri_menu_items(roxterm, show_type);
1504         multi_tab_popup_menu_at_pointer(roxterm->tab);
1505         return TRUE;
1506     }
1507     else if ((event->state & GDK_CONTROL_MASK) && event->button == 1
1508              && event->type == GDK_BUTTON_PRESS && roxterm->matched_url)
1509     {
1510         roxterm->hold_over_uri = TRUE;
1511         roxterm->hold_x = event->x;
1512         roxterm->hold_y = event->y;
1513         roxterm->hold_handler_id = g_signal_connect(roxterm->widget,
1514                 "motion-notify-event",
1515                 G_CALLBACK(roxterm_motion_handler), roxterm);
1516         return TRUE;
1517     }
1518     return FALSE;
1519 }
1520 
roxterm_release_handler(GtkWidget * widget,GdkEventButton * event,ROXTermData * roxterm)1521 static gboolean roxterm_release_handler(GtkWidget *widget,
1522         GdkEventButton *event, ROXTermData *roxterm)
1523 {
1524     gboolean result = FALSE;
1525     (void) widget;
1526 
1527     if ((event->state & GDK_CONTROL_MASK) && event->button == 1
1528             && roxterm->hold_over_uri && roxterm->matched_url)
1529     {
1530         roxterm_launch_matched_uri(roxterm, event->time);
1531         result = TRUE;
1532     }
1533     roxterm_clear_hold_over_uri(roxterm);
1534     return result;
1535 }
1536 
roxterm_window_title_handler(VteTerminal * vte,ROXTermData * roxterm)1537 static void roxterm_window_title_handler(VteTerminal *vte,
1538         ROXTermData * roxterm)
1539 {
1540     const char *t = vte_terminal_get_window_title(vte);
1541 
1542     multi_tab_set_window_title(roxterm->tab, t ? t : _("ROXTerm"));
1543 }
1544 
1545 /* data is cast to char const **pname - indirect pointer to name to check for -
1546  * if a match is found, *pname is set to NULL */
check_if_name_matches_property(GtkWidget * widget,gpointer data)1547 static void check_if_name_matches_property(GtkWidget *widget, gpointer data)
1548 {
1549     const char *profile_name = g_object_get_data(G_OBJECT(widget),
1550             PROFILE_NAME_KEY);
1551     char const **pname = data;
1552     gboolean check = FALSE;
1553 
1554     g_return_if_fail(profile_name);
1555     /* pname may have been NULLed by an earlier iteration */
1556     if (!*pname)
1557         return;
1558     if (!strcmp(profile_name, *pname))
1559     {
1560         check = TRUE;
1561         *pname = NULL;
1562     }
1563     gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), check);
1564 }
1565 
check_preferences_submenu(MenuTree * tree,MenuTreeID id,const char * current_name)1566 static void check_preferences_submenu(MenuTree *tree, MenuTreeID id,
1567         const char *current_name)
1568 {
1569     GtkMenu *submenu = menutree_submenu_from_id(tree, id);
1570     const char *name = current_name;
1571 
1572     gtk_container_foreach(GTK_CONTAINER(submenu),
1573             check_if_name_matches_property, &name);
1574     if (name)
1575     {
1576         /* name hasn't been NULLed, no match, try "ghost" */
1577         char *ghost_name = g_strdup_printf("(%s)", current_name);
1578 
1579         name = ghost_name;
1580         gtk_container_foreach(GTK_CONTAINER(submenu),
1581                 check_if_name_matches_property, &name);
1582         g_free(ghost_name);
1583         if (name)
1584         {
1585             g_critical(_("No menu item matches profile/scheme '%s'"),
1586                     current_name);
1587         }
1588     }
1589 }
1590 
check_preferences_submenu_pair(ROXTermData * roxterm,MenuTreeID id,const char * current_name)1591 static void check_preferences_submenu_pair(ROXTermData *roxterm, MenuTreeID id,
1592         const char *current_name)
1593 {
1594     MultiWin *win = roxterm_get_win(roxterm);
1595     MenuTree *tree = multi_win_get_popup_menu(win);
1596 
1597     multi_win_set_ignore_toggles(win, TRUE);
1598     if (tree)
1599     {
1600         check_preferences_submenu(tree, id, current_name);
1601     }
1602     tree = multi_win_get_menu_bar(win);
1603     if (tree)
1604     {
1605         check_preferences_submenu(tree, id, current_name);
1606     }
1607     multi_win_set_ignore_toggles(win, FALSE);
1608 }
1609 
roxterm_shade_mtree_search_items(MenuTree * mtree,gboolean shade)1610 inline static void roxterm_shade_mtree_search_items(MenuTree *mtree,
1611         gboolean shade)
1612 {
1613     if (mtree)
1614     {
1615         menutree_shade(mtree, MENUTREE_SEARCH_FIND_NEXT, shade);
1616         menutree_shade(mtree, MENUTREE_SEARCH_FIND_PREVIOUS, shade);
1617     }
1618 }
1619 
roxterm_shade_search_menu_items(ROXTermData * roxterm)1620 static void roxterm_shade_search_menu_items(ROXTermData *roxterm)
1621 {
1622     MultiWin *win = roxterm_get_win(roxterm);
1623     gboolean shade = vte_terminal_search_get_regex(
1624             VTE_TERMINAL(roxterm->widget)) == NULL;
1625 
1626     roxterm_shade_mtree_search_items(multi_win_get_menu_bar(win), shade);
1627     roxterm_shade_mtree_search_items(multi_win_get_popup_menu(win), shade);
1628 }
1629 
roxterm_tab_selection_handler(ROXTermData * roxterm,MultiTab * tab)1630 static void roxterm_tab_selection_handler(ROXTermData * roxterm, MultiTab * tab)
1631 {
1632     MultiWin *win = roxterm_get_win(roxterm);
1633     (void) tab;
1634 
1635     roxterm->status_icon_name = NULL;
1636     check_preferences_submenu_pair(roxterm,
1637             MENUTREE_PREFERENCES_SELECT_PROFILE,
1638             options_get_leafname(roxterm->profile));
1639     check_preferences_submenu_pair(roxterm,
1640             MENUTREE_PREFERENCES_SELECT_COLOUR_SCHEME,
1641             options_get_leafname(roxterm->colour_scheme));
1642     check_preferences_submenu_pair(roxterm,
1643             MENUTREE_PREFERENCES_SELECT_SHORTCUTS,
1644             options_get_leafname(multi_win_get_shortcut_scheme(win)));
1645     roxterm_shade_search_menu_items(roxterm);
1646 
1647     multi_win_set_ignore_toggles(win, TRUE);
1648     multi_win_set_ignore_toggles(win, FALSE);
1649 }
1650 
run_child_when_idle(ROXTermData * roxterm)1651 static gboolean run_child_when_idle(ROXTermData *roxterm)
1652 {
1653     if (!roxterm->running)
1654         roxterm_run_command(roxterm, VTE_TERMINAL(roxterm->widget));
1655     return FALSE;
1656 }
1657 
roxterm_launch_uri_action(MultiWin * win)1658 static void roxterm_launch_uri_action(MultiWin * win)
1659 {
1660     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1661 
1662     g_return_if_fail(roxterm);
1663     if (roxterm->matched_url)
1664         roxterm_launch_matched_uri(roxterm, gtk_get_current_event_time());
1665 }
1666 
roxterm_copy_url_action(MultiWin * win)1667 static void roxterm_copy_url_action(MultiWin * win)
1668 {
1669     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1670 
1671     g_return_if_fail(roxterm);
1672     if (roxterm->matched_url)
1673     {
1674         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY),
1675             roxterm->matched_url, -1);
1676         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
1677             roxterm->matched_url, -1);
1678     }
1679 }
1680 
roxterm_select_all_action(MultiWin * win)1681 static void roxterm_select_all_action(MultiWin * win)
1682 {
1683     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1684 
1685     g_return_if_fail(roxterm);
1686     vte_terminal_select_all(VTE_TERMINAL(roxterm->widget));
1687 }
1688 
roxterm_copy_clipboard_action(MultiWin * win)1689 static void roxterm_copy_clipboard_action(MultiWin * win)
1690 {
1691     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1692 
1693     g_return_if_fail(roxterm);
1694 #if VTE_CHECK_VERSION(0,50,0)
1695     vte_terminal_copy_clipboard_format(VTE_TERMINAL(roxterm->widget),
1696             VTE_FORMAT_TEXT);
1697 #else
1698     vte_terminal_copy_clipboard(VTE_TERMINAL(roxterm->widget));
1699 #endif
1700 }
1701 
roxterm_paste_clipboard_action(MultiWin * win)1702 static void roxterm_paste_clipboard_action(MultiWin * win)
1703 {
1704     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1705 
1706     g_return_if_fail(roxterm);
1707     vte_terminal_paste_clipboard(VTE_TERMINAL(roxterm->widget));
1708 }
1709 
roxterm_copy_and_paste_action(MultiWin * win)1710 static void roxterm_copy_and_paste_action(MultiWin * win)
1711 {
1712     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1713 
1714     g_return_if_fail(roxterm);
1715 #if VTE_CHECK_VERSION(0,50,0)
1716     vte_terminal_copy_clipboard_format(VTE_TERMINAL(roxterm->widget),
1717             VTE_FORMAT_TEXT);
1718 #else
1719     vte_terminal_copy_clipboard(VTE_TERMINAL(roxterm->widget));
1720 #endif
1721     vte_terminal_paste_clipboard(VTE_TERMINAL(roxterm->widget));
1722 }
1723 
roxterm_reset_action(MultiWin * win)1724 static void roxterm_reset_action(MultiWin * win)
1725 {
1726     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1727 
1728     g_return_if_fail(roxterm);
1729     vte_terminal_reset(VTE_TERMINAL(roxterm->widget), TRUE, FALSE);
1730 }
1731 
roxterm_reset_and_clear_action(MultiWin * win)1732 static void roxterm_reset_and_clear_action(MultiWin * win)
1733 {
1734     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1735 
1736     g_return_if_fail(roxterm);
1737     vte_terminal_reset(VTE_TERMINAL(roxterm->widget), TRUE, TRUE);
1738 }
1739 
roxterm_respawn_action(MultiWin * win)1740 static void roxterm_respawn_action(MultiWin * win)
1741 {
1742     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1743     int response;
1744     GtkWidget *w = NULL;
1745 
1746     if (roxterm->post_exit_tag)
1747     {
1748         g_source_remove(roxterm->post_exit_tag);
1749     }
1750     if (roxterm->running)
1751     {
1752         w = roxterm->replace_task_dialog =
1753                 dlg_ok_cancel(GTK_WINDOW(multi_win_get_widget(win)),
1754                     _("Kill current task?"),
1755                     _("The current task is still running; "
1756                     "to restart it the current instance must be killed. "
1757                     "Do you want to kill the current task to "
1758                     "replace it with a new instance?"));
1759         response = gtk_dialog_run(GTK_DIALOG(w));
1760     }
1761     else
1762     {
1763         response = GTK_RESPONSE_OK;
1764     }
1765     switch (response)
1766     {
1767         case GTK_RESPONSE_NONE:
1768         case GTK_RESPONSE_DELETE_EVENT:
1769             /* Don't destroy */
1770             break;
1771         case GTK_RESPONSE_OK:
1772             roxterm_run_command(roxterm, VTE_TERMINAL(roxterm->widget));
1773             /* Fall through */
1774         default:
1775             if (w)
1776                 gtk_widget_destroy(w);
1777     }
1778     if (roxterm->postponed_free)
1779         g_free(roxterm);
1780     else
1781         roxterm->replace_task_dialog = NULL;
1782 }
1783 
roxterm_edit_profile_action(MultiWin * win)1784 static void roxterm_edit_profile_action(MultiWin * win)
1785 {
1786     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1787 
1788     g_return_if_fail(roxterm);
1789     optsdbus_send_edit_profile_message(options_get_leafname(roxterm->profile));
1790 }
1791 
roxterm_edit_colour_scheme_action(MultiWin * win)1792 static void roxterm_edit_colour_scheme_action(MultiWin * win)
1793 {
1794     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1795 
1796     g_return_if_fail(roxterm);
1797     optsdbus_send_edit_colour_scheme_message(
1798             options_get_leafname(roxterm->colour_scheme));
1799 }
1800 
roxterm_edit_shortcuts_scheme_action(MultiWin * win)1801 static void roxterm_edit_shortcuts_scheme_action(MultiWin * win)
1802 {
1803     shortcuts_edit(GTK_WINDOW(multi_win_get_widget(win)),
1804             multi_win_get_shortcuts_scheme_name(win));
1805 }
1806 
1807 
roxterm_open_search_action(MultiWin * win)1808 static void roxterm_open_search_action(MultiWin *win)
1809 {
1810     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1811 
1812     g_return_if_fail(roxterm);
1813     search_open_dialog(roxterm);
1814 }
1815 
roxterm_find_next_action(MultiWin * win)1816 static void roxterm_find_next_action(MultiWin *win)
1817 {
1818     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1819 
1820     g_return_if_fail(roxterm);
1821     vte_terminal_search_find_next(VTE_TERMINAL(roxterm->widget));
1822 }
1823 
roxterm_find_prev_action(MultiWin * win)1824 static void roxterm_find_prev_action(MultiWin *win)
1825 {
1826     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1827 
1828     g_return_if_fail(roxterm);
1829     vte_terminal_search_find_previous(VTE_TERMINAL(roxterm->widget));
1830 }
1831 
roxterm_show_about(MultiWin * win)1832 static void roxterm_show_about(MultiWin * win)
1833 {
1834     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1835 
1836     g_return_if_fail(roxterm);
1837     about_dialog_show(GTK_WINDOW(multi_win_get_widget(win)),
1838             roxterm_about_uri_hook,
1839             roxterm);
1840 }
1841 
roxterm_get_help_filename(const char * base_dir,const char * lang)1842 static char *roxterm_get_help_filename(const char *base_dir, const char *lang)
1843 {
1844     char *filename = g_build_filename(base_dir, lang, "guide.html", NULL);
1845 
1846     if (!g_file_test(filename, G_FILE_TEST_EXISTS))
1847     {
1848         g_free(filename);
1849         filename = NULL;
1850     }
1851     return filename;
1852 }
1853 
roxterm_get_help_uri(const char * base_dir,const char * lang)1854 static char *roxterm_get_help_uri(const char *base_dir, const char *lang)
1855 {
1856     char *short_lang = NULL;
1857     char *sep;
1858     char *filename;
1859     char *uri;
1860 
1861     filename = roxterm_get_help_filename(base_dir, lang);
1862     if (!filename)
1863     {
1864         short_lang = g_strdup(lang);
1865         sep = strchr(short_lang, '.');
1866         if (sep)
1867         {
1868             *sep = 0;
1869             filename = roxterm_get_help_filename(base_dir, short_lang);
1870         }
1871         if (!filename)
1872         {
1873             sep = strchr(short_lang, '_');
1874             filename = roxterm_get_help_filename(base_dir, short_lang);
1875         }
1876         g_free(short_lang);
1877         if (!filename)
1878         {
1879             filename = roxterm_get_help_filename(base_dir, "en");
1880         }
1881     }
1882     uri = g_strconcat("file://", filename, NULL);
1883     g_free(filename);
1884     return uri;
1885 }
1886 
roxterm_show_manual(MultiWin * win)1887 static void roxterm_show_manual(MultiWin * win)
1888 {
1889     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
1890     char *uri;
1891     char *dir = NULL;
1892     const char *lang;
1893 
1894     g_return_if_fail(roxterm);
1895 
1896     if (global_options_appdir)
1897     {
1898         dir = g_build_filename(global_options_appdir, "Help", NULL);
1899     }
1900     else
1901     {
1902         dir = g_build_filename(HTML_DIR, NULL);
1903     }
1904 
1905     lang = g_getenv("LANG");
1906     if (!lang || !lang[0])
1907         lang = "en";
1908     uri = roxterm_get_help_uri(dir, lang);
1909 
1910     roxterm_launch_uri(roxterm, uri, gtk_get_current_event_time());
1911     g_free(uri);
1912     g_free(dir);
1913 }
1914 
roxterm_open_config_manager(void * ignored)1915 static void roxterm_open_config_manager(void *ignored)
1916 {
1917     (void) ignored;
1918     optsdbus_send_edit_opts_message("Configlet", NULL);
1919 }
1920 
1921 /* The "style-updated" signal gets heavily spammed, including when GTK is
1922  * playing silly buggers with size allocation, so we don't have much choice
1923  * but to ignore it altogether.
1924  */
1925 /*
1926 static void
1927 roxterm_style_change_handler(GtkWidget * widget, ROXTermData * roxterm)
1928 {
1929     (void) widget;
1930     if (gtk_widget_get_allocated_width(roxterm->widget) > 1 &&
1931         gtk_widget_get_allocated_height(roxterm->widget) > 1)
1932     {
1933         roxterm_update_geometry(roxterm, VTE_TERMINAL(roxterm->widget));
1934     }
1935 }
1936 */
1937 
1938 static void
roxterm_char_size_changed(GtkSettings * settings,guint arg1,guint arg2,ROXTermData * roxterm)1939 roxterm_char_size_changed(GtkSettings * settings, guint arg1, guint arg2,
1940     ROXTermData * roxterm)
1941 {
1942     (void) settings;
1943     (void) arg1;
1944     (void) arg2;
1945     roxterm_update_geometry(roxterm, VTE_TERMINAL(roxterm->widget));
1946 }
1947 
roxterm_hide_menutree(GtkMenuItem * item,gpointer handle)1948 static void roxterm_hide_menutree(GtkMenuItem *item, gpointer handle)
1949 {
1950     GtkWidget *submenu = gtk_menu_item_get_submenu(item);
1951     (void) handle;
1952 
1953     if (submenu)
1954     {
1955         gtk_widget_hide(submenu);
1956         gtk_container_foreach(GTK_CONTAINER(submenu),
1957                 (GtkCallback) roxterm_hide_menutree, NULL);
1958     }
1959 }
1960 
1961 static RoxtermChildExitAction
roxterm_get_child_exit_action(ROXTermData * roxterm)1962 roxterm_get_child_exit_action(ROXTermData *roxterm)
1963 {
1964     RoxtermChildExitAction action = roxterm->exit_action;
1965     if (action == Roxterm_ChildExitNotOverridden)
1966     {
1967         action = options_lookup_int_with_default(roxterm->profile,
1968                 "exit_action", Roxterm_ChildExitClose);
1969     }
1970     return action;
1971 }
1972 
roxterm_post_child_exit(ROXTermData * roxterm)1973 static gboolean roxterm_post_child_exit(ROXTermData *roxterm)
1974 {
1975     MultiWin *win = roxterm_get_win(roxterm);
1976     RoxtermChildExitAction action = roxterm_get_child_exit_action(roxterm);
1977     if (action == Roxterm_ChildExitNotOverridden)
1978     {
1979         action = options_lookup_int_with_default(roxterm->profile,
1980                 "exit_action", Roxterm_ChildExitClose);
1981     }
1982     if (action == Roxterm_ChildExitAsk)
1983     {
1984         GtkWidget *dialog = gtk_message_dialog_new(
1985                 roxterm_get_toplevel(roxterm),
1986                 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1987                 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1988                 _("The command in this terminal has terminated. "
1989                 "What do you want to do with the terminal now?"));
1990         GtkWidget *respawn;
1991 
1992         gtk_dialog_add_button(GTK_DIALOG(dialog),
1993                 _("Close"), Roxterm_ChildExitClose);
1994         gtk_dialog_add_button(GTK_DIALOG(dialog),
1995                 _("Leave open"), Roxterm_ChildExitHold);
1996         respawn = gtk_dialog_add_button(GTK_DIALOG(dialog),
1997                 _("Rerun command"), Roxterm_ChildExitRespawn);
1998         if (roxterm->no_respawn)
1999             gtk_widget_set_sensitive(respawn, FALSE);
2000         gtk_widget_show_all(dialog);
2001         action = gtk_dialog_run(GTK_DIALOG(dialog));
2002         gtk_widget_destroy(dialog);
2003     }
2004     roxterm->post_exit_tag = 0;
2005     if (action == Roxterm_ChildExitRespawn && roxterm->no_respawn)
2006         action = Roxterm_ChildExitClose;
2007     switch (action)
2008     {
2009         case Roxterm_ChildExitClose:
2010             gtk_container_foreach(
2011                     GTK_CONTAINER(
2012                             multi_win_get_menu_bar(win)->top_level),
2013                     (GtkCallback) roxterm_hide_menutree, NULL);
2014             gtk_widget_hide(multi_win_get_popup_menu(win)->top_level);
2015             gtk_widget_hide(multi_win_get_short_popup_menu(win)->top_level);
2016             multi_tab_delete(roxterm->tab);
2017             break;
2018         case Roxterm_ChildExitHold:
2019             roxterm_show_status(roxterm, "dialog-error");
2020             break;
2021         case Roxterm_ChildExitRespawn:
2022             roxterm_run_command(roxterm, VTE_TERMINAL(roxterm->widget));
2023             break;
2024         default:
2025             break;
2026     }
2027     return FALSE;
2028 }
2029 
roxterm_child_exited(VteTerminal * vte,int status,ROXTermData * roxterm)2030 static void roxterm_child_exited(VteTerminal *vte, int status,
2031         ROXTermData *roxterm)
2032 {
2033     if (!g_list_find(roxterm_terms, roxterm))
2034     {
2035         g_warning("roxterm_child_exited for widget %p: data %p not listed",
2036                 vte, roxterm);
2037         return;
2038     }
2039     double delay = 0;
2040 
2041     (void) status;
2042 
2043     roxterm->running = FALSE;
2044     roxterm_show_status(roxterm, "dialog-error");
2045     RoxtermChildExitAction action = roxterm_get_child_exit_action(roxterm);
2046     if (action != Roxterm_ChildExitAsk &&
2047         ((delay = options_lookup_double(roxterm->profile, "exit_pause")) != 0))
2048     {
2049         vte_terminal_feed(vte, _("\n.\n"), -1);
2050         roxterm->post_exit_tag = g_timeout_add((guint) (delay * 1000.0),
2051                 (GSourceFunc) roxterm_post_child_exit, roxterm);
2052     }
2053     else
2054     {
2055         roxterm_post_child_exit(roxterm);
2056     }
2057 }
2058 
roxterm_from_menutree(MenuTree * mtree)2059 inline static ROXTermData *roxterm_from_menutree(MenuTree *mtree)
2060 {
2061     return (ROXTermData *) multi_win_get_user_data_for_current_tab(
2062                      (MultiWin *) mtree->user_data);
2063 
2064 }
2065 
roxterm_change_profile(ROXTermData * roxterm,Options * profile)2066 static void roxterm_change_profile(ROXTermData *roxterm, Options *profile)
2067 {
2068     if (roxterm->profile != profile)
2069     {
2070         if (roxterm->profile)
2071         {
2072             dynamic_options_unref(roxterm_profiles,
2073                     options_get_leafname(roxterm->profile));
2074         }
2075         roxterm->profile = profile;
2076         options_ref(roxterm->profile);
2077         /* Force profile's font */
2078         if (roxterm->pango_desc)
2079         {
2080             pango_font_description_free(roxterm->pango_desc);
2081             roxterm->pango_desc = NULL;
2082         }
2083         roxterm_apply_profile(roxterm, VTE_TERMINAL(roxterm->widget), FALSE);
2084         roxterm_update_size(roxterm, VTE_TERMINAL(roxterm->widget));
2085     }
2086 }
2087 
roxterm_change_colour_scheme(ROXTermData * roxterm,Options * colour_scheme)2088 static void roxterm_change_colour_scheme(ROXTermData *roxterm,
2089         Options *colour_scheme)
2090 {
2091     if (roxterm->colour_scheme != colour_scheme)
2092     {
2093         if (roxterm->colour_scheme)
2094             colour_scheme_unref(roxterm->colour_scheme);
2095         roxterm->colour_scheme = colour_scheme;
2096         options_ref(colour_scheme);
2097         roxterm_apply_colour_scheme(roxterm, VTE_TERMINAL(roxterm->widget));
2098     }
2099 }
2100 
roxterm_change_colour_scheme_by_name(ROXTermData * roxterm,const char * name)2101 static void roxterm_change_colour_scheme_by_name(ROXTermData *roxterm,
2102         const char *name)
2103 {
2104     Options *scheme = colour_scheme_lookup_and_ref(name);
2105     if (scheme)
2106     {
2107         roxterm_change_colour_scheme(roxterm, scheme);
2108         colour_scheme_unref(scheme);
2109     }
2110     else
2111     {
2112         dlg_warning(roxterm_get_toplevel(roxterm),
2113                 _("Unknown colour scheme '%s'"), name);
2114     }
2115 }
2116 
match_text_size_foreach_tab(MultiTab * tab,void * data)2117 static void match_text_size_foreach_tab(MultiTab *tab, void *data)
2118 {
2119     ROXTermData *roxterm = multi_tab_get_user_data(tab);
2120 
2121     if (roxterm != data)
2122         roxterm_match_text_size(roxterm, data);
2123 }
2124 
roxterm_new_term_with_profile(GtkMenuItem * mitem,MenuTree * mtree,gboolean just_tab)2125 static void roxterm_new_term_with_profile(GtkMenuItem *mitem,
2126         MenuTree *mtree, gboolean just_tab)
2127 {
2128     ROXTermData *roxterm = roxterm_from_menutree(mtree);
2129     MultiWin *win;
2130     const char *profile_name;
2131     Options *old_profile;
2132     Options *new_profile;
2133 
2134     if (!roxterm)
2135         return;
2136 
2137     win = roxterm_get_win(roxterm);
2138     profile_name = g_object_get_data(G_OBJECT(mitem), PROFILE_NAME_KEY);
2139     if (!profile_name)
2140     {
2141         g_critical(_("Menu item has no '%s' data"), PROFILE_NAME_KEY);
2142         return;
2143     }
2144     old_profile = roxterm->profile;
2145     new_profile = dynamic_options_lookup_and_ref(roxterm_get_profiles(),
2146             profile_name, "roxterm profile");
2147     if (!new_profile)
2148     {
2149         dlg_warning(roxterm_get_toplevel(roxterm),
2150                 _("Profile '%s' not found"), profile_name);
2151         return;
2152     }
2153     roxterm->profile = new_profile;
2154     if (just_tab)
2155     {
2156         multi_tab_new(win, roxterm);
2157     }
2158     else
2159     {
2160         multi_win_clone(win, roxterm,
2161                 options_lookup_int_with_default(new_profile,
2162                         "always_show_tabs", TRUE));
2163     }
2164     dynamic_options_unref(roxterm_profiles, profile_name);
2165     roxterm->profile = old_profile;
2166     /* All tabs in the same window must have same size */
2167     /*
2168     if (just_tab && strcmp(profile_name, options_get_leafname(old_profile)))
2169     {
2170         multi_win_foreach_tab(win, match_text_size_foreach_tab,
2171                 multi_tab_get_user_data(tab));
2172     }
2173     */
2174 }
2175 
roxterm_new_window_with_profile(GtkMenuItem * mitem,MenuTree * mtree)2176 static void roxterm_new_window_with_profile(GtkMenuItem *mitem, MenuTree *mtree)
2177 {
2178     roxterm_new_term_with_profile(mitem, mtree, FALSE);
2179 }
2180 
roxterm_new_tab_with_profile(GtkMenuItem * mitem,MenuTree * mtree)2181 static void roxterm_new_tab_with_profile(GtkMenuItem *mitem, MenuTree *mtree)
2182 {
2183     roxterm_new_term_with_profile(mitem, mtree, TRUE);
2184 }
2185 
roxterm_profile_selected(GtkCheckMenuItem * mitem,MenuTree * mtree)2186 static void roxterm_profile_selected(GtkCheckMenuItem *mitem, MenuTree *mtree)
2187 {
2188     ROXTermData *roxterm = roxterm_from_menutree(mtree);
2189     const char *profile_name;
2190     MultiWin *win = roxterm ? roxterm_get_win(roxterm) : NULL;
2191 
2192     if (!roxterm || multi_win_get_ignore_toggles(win))
2193         return;
2194 
2195     profile_name = g_object_get_data(G_OBJECT(mitem), PROFILE_NAME_KEY);
2196     if (!profile_name)
2197     {
2198         g_critical(_("Menu item has no '%s' data"), PROFILE_NAME_KEY);
2199         return;
2200     }
2201     if (!gtk_check_menu_item_get_active(mitem))
2202     {
2203         check_preferences_submenu_pair(roxterm,
2204                 MENUTREE_PREFERENCES_SELECT_PROFILE, profile_name);
2205     }
2206 
2207     if (strcmp(profile_name, options_get_leafname(roxterm->profile)))
2208     {
2209         Options *profile = dynamic_options_lookup_and_ref(roxterm_profiles,
2210             profile_name, "roxterm profile");
2211 
2212         if (profile)
2213         {
2214             roxterm_change_profile(roxterm, profile);
2215             /* All tabs in the same window must have same size */
2216             multi_win_foreach_tab(win, match_text_size_foreach_tab,
2217                     roxterm);
2218             /* Have one more ref than we need, so decrease it */
2219             options_unref(profile);
2220         }
2221         else
2222         {
2223             dlg_warning(roxterm_get_toplevel(roxterm),
2224                     _("Profile '%s' not found"), profile_name);
2225         }
2226     }
2227 }
2228 
roxterm_colour_scheme_selected(GtkCheckMenuItem * mitem,MenuTree * mtree)2229 static void roxterm_colour_scheme_selected(GtkCheckMenuItem *mitem,
2230         MenuTree *mtree)
2231 {
2232     ROXTermData *roxterm = roxterm_from_menutree(mtree);
2233     const char *scheme_name;
2234 
2235     if (!roxterm || multi_win_get_ignore_toggles(roxterm_get_win(roxterm)))
2236         return;
2237 
2238     scheme_name = g_object_get_data(G_OBJECT(mitem), PROFILE_NAME_KEY);
2239     if (!scheme_name)
2240     {
2241         g_critical(_("Menu item has no '%s' data"), PROFILE_NAME_KEY);
2242         return;
2243     }
2244     if (!gtk_check_menu_item_get_active(mitem))
2245     {
2246         check_preferences_submenu_pair(roxterm,
2247                 MENUTREE_PREFERENCES_SELECT_COLOUR_SCHEME, scheme_name);
2248     }
2249 
2250     if (strcmp(scheme_name, options_get_leafname(roxterm->colour_scheme)))
2251     {
2252         Options *colour_scheme = colour_scheme_lookup_and_ref(scheme_name);
2253 
2254         if (colour_scheme)
2255         {
2256             roxterm_change_colour_scheme(roxterm, colour_scheme);
2257             options_unref(colour_scheme);
2258         }
2259         else
2260         {
2261             dlg_warning(roxterm_get_toplevel(roxterm),
2262                     _("Colour scheme '%s' not found"), scheme_name);
2263         }
2264     }
2265 }
2266 
roxterm_shortcuts_selected(GtkCheckMenuItem * mitem,MenuTree * mtree)2267 static void roxterm_shortcuts_selected(GtkCheckMenuItem *mitem,
2268         MenuTree *mtree)
2269 {
2270     ROXTermData *roxterm = roxterm_from_menutree(mtree);
2271     MultiWin *win = roxterm ? roxterm_get_win(roxterm) : NULL;
2272     char *scheme_name;
2273     Options *shortcuts;
2274 
2275     if (!roxterm || multi_win_get_ignore_toggles(win))
2276         return;
2277 
2278     scheme_name = g_object_get_data(G_OBJECT(mitem), PROFILE_NAME_KEY);
2279     if (!scheme_name)
2280     {
2281         g_critical(_("Menu item has no '%s' data"), PROFILE_NAME_KEY);
2282         return;
2283     }
2284     if (!gtk_check_menu_item_get_active(mitem))
2285     {
2286         check_preferences_submenu_pair(roxterm,
2287                 MENUTREE_PREFERENCES_SELECT_SHORTCUTS, scheme_name);
2288     }
2289     shortcuts = shortcuts_open(scheme_name, TRUE);
2290     multi_win_set_shortcut_scheme(win, shortcuts);
2291     shortcuts_unref(shortcuts);
2292 }
2293 
roxterm_text_changed_handler(VteTerminal * vte,ROXTermData * roxterm)2294 static void roxterm_text_changed_handler(VteTerminal *vte, ROXTermData *roxterm)
2295 {
2296     (void) vte;
2297     if (roxterm->tab != multi_win_get_current_tab(roxterm_get_win(roxterm)))
2298     {
2299         roxterm_show_status(roxterm, "dialog-information");
2300     }
2301 }
2302 
roxterm_bell_handler(VteTerminal * vte,ROXTermData * roxterm)2303 static void roxterm_bell_handler(VteTerminal *vte, ROXTermData *roxterm)
2304 {
2305     MultiWin *win = roxterm_get_win(roxterm);
2306     (void) vte;
2307 
2308     if (roxterm->tab != multi_win_get_current_tab(win))
2309     {
2310         roxterm_show_status(roxterm, "dialog-warning");
2311     }
2312     if (options_lookup_int_with_default(roxterm->profile,
2313             "bell_highlights_tab", TRUE))
2314     {
2315         GtkWindow *gwin = GTK_WINDOW(multi_win_get_widget(win));
2316 
2317         if (roxterm->tab != multi_win_get_current_tab(win))
2318             multi_tab_draw_attention(roxterm->tab);
2319         if (!gtk_window_is_active(gwin))
2320             gtk_window_set_urgency_hint(gwin, TRUE);
2321     }
2322 }
2323 
2324 /* Ignore keys which are shortcuts, otherwise they get sent to terminal
2325  * when menu item is shaded.
2326  */
roxterm_key_press_handler(GtkWidget * widget,GdkEventKey * event,ROXTermData * roxterm)2327 static gboolean roxterm_key_press_handler(GtkWidget *widget,
2328         GdkEventKey *event, ROXTermData *roxterm)
2329 {
2330     Options *shortcuts = multi_win_get_shortcut_scheme(
2331             roxterm_get_win(roxterm));
2332     (void) widget;
2333 
2334     if (!event->is_modifier &&
2335             shortcuts_key_is_shortcut(shortcuts, event->keyval,
2336                     event->state & GDK_MODIFIER_MASK))
2337     {
2338         return TRUE;
2339     }
2340     return FALSE;
2341 }
2342 
roxterm_resize_window_handler(VteTerminal * vte,guint columns,guint rows,ROXTermData * roxterm)2343 static void roxterm_resize_window_handler(VteTerminal *vte,
2344         guint columns, guint rows, ROXTermData *roxterm)
2345 {
2346     MultiWin *win = roxterm_get_win(roxterm);
2347 
2348     /* Can't compute size if not realized */
2349     if (!gtk_widget_get_realized(roxterm->widget))
2350         return;
2351     /* Ignore if maximised or full screen */
2352     if (win && (multi_win_is_maximised(win) || multi_win_is_fullscreen(win)))
2353         return;
2354 
2355     /* May already be desired size */
2356     // The following code was broken, needs to convert to pixels
2357     //GtkAllocation alloc;
2358     //gtk_widget_get_allocation(roxterm->widget, &alloc);
2359     //if (alloc.width == (int) width && alloc.height == (int) height)
2360     //    return;
2361 
2362     roxterm_set_vte_size(roxterm, vte, columns, rows);
2363 }
2364 
create_radio_menu_item(MenuTree * mtree,const char * name,GSList ** group,GCallback handler)2365 static GtkWidget *create_radio_menu_item(MenuTree *mtree,
2366         const char *name, GSList **group, GCallback handler)
2367 {
2368     GtkWidget *mitem;
2369 
2370     mitem = gtk_radio_menu_item_new_with_label(*group, name);
2371     gtk_widget_show(mitem);
2372     *group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(mitem));
2373     if (handler)
2374     {
2375         g_object_set_data_full(G_OBJECT(mitem), PROFILE_NAME_KEY,
2376                 g_strdup(name), g_free);
2377         g_signal_connect(mitem, "activate", handler, mtree);
2378     }
2379     return mitem;
2380 }
2381 
radio_menu_from_strv(char ** items,GCallback handler,MenuTree * mtree)2382 static GtkMenu *radio_menu_from_strv(char **items,
2383         GCallback handler, MenuTree *mtree)
2384 {
2385     int n;
2386     GtkMenu *menu;
2387     GSList *group = NULL;
2388 
2389     if (!items || !items[0])
2390         return NULL;
2391 
2392     menu = GTK_MENU(gtk_menu_new());
2393 
2394     for (n = 0; items[n]; ++n)
2395     {
2396         GtkWidget *mitem = create_radio_menu_item(mtree, items[n],
2397                 &group, handler);
2398 
2399         gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
2400         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mitem),
2401                 FALSE);
2402     }
2403     return menu;
2404 }
2405 
roxterm_add_pair_of_pref_submenus(MultiWin * win,DynamicOptions * family,MenuTreeID id,GCallback handler)2406 static void roxterm_add_pair_of_pref_submenus(MultiWin *win,
2407         DynamicOptions *family, MenuTreeID id, GCallback handler)
2408 {
2409     char **items = dynamic_options_list_sorted(family);
2410     GtkMenu *submenu;
2411     MenuTree *menutree;
2412 
2413     g_return_if_fail(items);
2414     multi_win_set_ignore_toggles(win, TRUE);
2415     menutree = multi_win_get_menu_bar(win);
2416     if (menutree)
2417     {
2418         submenu = radio_menu_from_strv(items, handler, menutree);
2419         gtk_menu_item_set_submenu(
2420                 GTK_MENU_ITEM(menutree_get_widget_for_id(menutree, id)),
2421                 GTK_WIDGET(submenu));
2422     }
2423     menutree = multi_win_get_popup_menu(win);
2424     if (menutree)
2425     {
2426         submenu = radio_menu_from_strv(items, handler, menutree);
2427         gtk_menu_item_set_submenu(
2428                 GTK_MENU_ITEM(menutree_get_widget_for_id(menutree, id)),
2429                 GTK_WIDGET(submenu));
2430     }
2431     multi_win_set_ignore_toggles(win, FALSE);
2432     g_strfreev(items);
2433 }
2434 
build_new_term_with_profile_submenu(MenuTree * mtree,GCallback callback,GtkMenuShell * mshell,char ** items)2435 static void build_new_term_with_profile_submenu(MenuTree *mtree,
2436         GCallback callback, GtkMenuShell *mshell, char **items)
2437 {
2438     int n;
2439 
2440     for (n = 0; items[n]; ++n)
2441     {
2442         GtkWidget *mitem = gtk_menu_item_new_with_label(items[n]);
2443 
2444         gtk_widget_show(mitem);
2445         if (callback)
2446         {
2447             g_object_set_data_full(G_OBJECT(mitem), PROFILE_NAME_KEY,
2448                     g_strdup(items[n]), g_free);
2449             g_signal_connect(mitem, "activate", callback, mtree);
2450         }
2451         gtk_menu_shell_append(mshell, mitem);
2452     }
2453 }
2454 
rebuild_new_term_with_profile_submenu(MenuTree * mtree,GCallback callback,GtkMenuShell * mshell,char ** items)2455 static void rebuild_new_term_with_profile_submenu(MenuTree *mtree,
2456         GCallback callback, GtkMenuShell *mshell, char **items)
2457 {
2458     GList *children = gtk_container_get_children(GTK_CONTAINER(mshell));
2459     GList *child;
2460     int n;
2461 
2462     for (n = 0, child = children; child; child = g_list_next(child), ++n)
2463     {
2464         /* First two items are header and separator */
2465         if (n >= 2)
2466         {
2467             gtk_container_remove(GTK_CONTAINER(mshell),
2468                     GTK_WIDGET(child->data));
2469         }
2470     }
2471     build_new_term_with_profile_submenu(mtree, callback, mshell, items);
2472 }
2473 
roxterm_build_pair_of_profile_submenus(MultiWin * win)2474 static void roxterm_build_pair_of_profile_submenus(MultiWin *win)
2475 {
2476     char **items = dynamic_options_list_sorted(dynamic_options_get("Profiles"));
2477     MenuTree *menutree;
2478 
2479     g_return_if_fail(items);
2480     multi_win_set_ignore_toggles(win, TRUE);
2481     menutree = multi_win_get_menu_bar(win);
2482     if (menutree)
2483     {
2484         build_new_term_with_profile_submenu(menutree,
2485             G_CALLBACK(roxterm_new_window_with_profile),
2486             GTK_MENU_SHELL(menutree->new_win_profiles_menu), items);
2487         build_new_term_with_profile_submenu(menutree,
2488             G_CALLBACK(roxterm_new_tab_with_profile),
2489             GTK_MENU_SHELL(menutree->new_tab_profiles_menu), items);
2490     }
2491     menutree = multi_win_get_popup_menu(win);
2492     if (menutree)
2493     {
2494         build_new_term_with_profile_submenu(menutree,
2495             G_CALLBACK(roxterm_new_window_with_profile),
2496             GTK_MENU_SHELL(menutree->new_win_profiles_menu), items);
2497         build_new_term_with_profile_submenu(menutree,
2498             G_CALLBACK(roxterm_new_tab_with_profile),
2499             GTK_MENU_SHELL(menutree->new_tab_profiles_menu), items);
2500     }
2501     multi_win_set_ignore_toggles(win, FALSE);
2502     g_strfreev(items);
2503 }
2504 
roxterm_add_all_pref_submenus(MultiWin * win)2505 static void roxterm_add_all_pref_submenus(MultiWin *win)
2506 {
2507     roxterm_add_pair_of_pref_submenus(win, dynamic_options_get("Profiles"),
2508             MENUTREE_PREFERENCES_SELECT_PROFILE,
2509             G_CALLBACK(roxterm_profile_selected));
2510     roxterm_build_pair_of_profile_submenus(win);
2511     roxterm_add_pair_of_pref_submenus(win, dynamic_options_get("Colours"),
2512             MENUTREE_PREFERENCES_SELECT_COLOUR_SCHEME,
2513             G_CALLBACK(roxterm_colour_scheme_selected));
2514     roxterm_add_pair_of_pref_submenus(win, dynamic_options_get("Shortcuts"),
2515             MENUTREE_PREFERENCES_SELECT_SHORTCUTS,
2516             G_CALLBACK(roxterm_shortcuts_selected));
2517 }
2518 
roxterm_connect_menu_signals(MultiWin * win)2519 static void roxterm_connect_menu_signals(MultiWin * win)
2520 {
2521     multi_win_menu_connect_swapped(win, MENUTREE_EDIT_SELECT_ALL,
2522         G_CALLBACK(roxterm_select_all_action), win, NULL, NULL, NULL);
2523     multi_win_menu_connect_swapped(win, MENUTREE_EDIT_COPY,
2524         G_CALLBACK(roxterm_copy_clipboard_action), win, NULL, NULL, NULL);
2525     multi_win_menu_connect_swapped(win, MENUTREE_EDIT_PASTE,
2526         G_CALLBACK(roxterm_paste_clipboard_action), win, NULL, NULL, NULL);
2527     multi_win_menu_connect_swapped(win, MENUTREE_EDIT_COPY_AND_PASTE,
2528         G_CALLBACK(roxterm_copy_and_paste_action), win, NULL, NULL, NULL);
2529     multi_win_menu_connect_swapped(win, MENUTREE_EDIT_RESET,
2530         G_CALLBACK(roxterm_reset_action), win, NULL, NULL, NULL);
2531     multi_win_menu_connect_swapped(win, MENUTREE_EDIT_RESET_AND_CLEAR,
2532         G_CALLBACK(roxterm_reset_and_clear_action), win, NULL, NULL, NULL);
2533     multi_win_menu_connect_swapped(win, MENUTREE_EDIT_RESPAWN,
2534         G_CALLBACK(roxterm_respawn_action), win, NULL, NULL, NULL);
2535 
2536     multi_win_menu_connect_swapped(win,
2537             MENUTREE_PREFERENCES_EDIT_CURRENT_PROFILE,
2538         G_CALLBACK(roxterm_edit_profile_action), win, NULL, NULL, NULL);
2539     multi_win_menu_connect_swapped(win,
2540             MENUTREE_PREFERENCES_EDIT_CURRENT_COLOUR_SCHEME,
2541         G_CALLBACK(roxterm_edit_colour_scheme_action), win, NULL, NULL, NULL);
2542     multi_win_menu_connect_swapped(win,
2543             MENUTREE_PREFERENCES_EDIT_CURRENT_SHORTCUTS_SCHEME,
2544         G_CALLBACK(roxterm_edit_shortcuts_scheme_action),
2545         win, NULL, NULL, NULL);
2546 
2547     multi_win_menu_connect_swapped(win, MENUTREE_HELP_ABOUT,
2548         G_CALLBACK(roxterm_show_about), win, NULL, NULL, NULL);
2549     multi_win_menu_connect_swapped(win, MENUTREE_HELP_SHOW_MANUAL,
2550         G_CALLBACK(roxterm_show_manual), win, NULL, NULL, NULL);
2551 
2552     multi_win_menu_connect(win, MENUTREE_PREFERENCES_CONFIG_MANAGER,
2553         G_CALLBACK(roxterm_open_config_manager), NULL, NULL, NULL, NULL);
2554 
2555     multi_win_menu_connect_swapped(win, MENUTREE_OPEN_IN_BROWSER,
2556         G_CALLBACK(roxterm_launch_uri_action), win, NULL, NULL, NULL);
2557     multi_win_menu_connect_swapped(win, MENUTREE_OPEN_IN_MAILER,
2558         G_CALLBACK(roxterm_launch_uri_action), win, NULL, NULL, NULL);
2559     multi_win_menu_connect_swapped(win, MENUTREE_OPEN_IN_FILER,
2560         G_CALLBACK(roxterm_launch_uri_action), win, NULL, NULL, NULL);
2561     multi_win_menu_connect_swapped(win, MENUTREE_COPY_URI,
2562         G_CALLBACK(roxterm_copy_url_action), win, NULL, NULL, NULL);
2563     multi_win_menu_connect_swapped(win, MENUTREE_SSH_HOST,
2564         G_CALLBACK(roxterm_launch_uri_action), win, NULL, NULL, NULL);
2565     multi_win_menu_connect_swapped(win, MENUTREE_SEARCH_FIND,
2566         G_CALLBACK(roxterm_open_search_action), win, NULL, NULL, NULL);
2567     multi_win_menu_connect_swapped(win, MENUTREE_SEARCH_FIND_NEXT,
2568         G_CALLBACK(roxterm_find_next_action), win, NULL, NULL, NULL);
2569     multi_win_menu_connect_swapped(win, MENUTREE_SEARCH_FIND_PREVIOUS,
2570         G_CALLBACK(roxterm_find_prev_action), win, NULL, NULL, NULL);
2571 
2572     roxterm_add_all_pref_submenus(win);
2573 }
2574 
roxterm_composited_changed_handler(VteTerminal * vte,ROXTermData * roxterm)2575 static void roxterm_composited_changed_handler(VteTerminal *vte,
2576         ROXTermData *roxterm)
2577 {
2578     roxterm_apply_colour_scheme(roxterm, vte);
2579 }
2580 
roxterm_connect_misc_signals(ROXTermData * roxterm)2581 static void roxterm_connect_misc_signals(ROXTermData * roxterm)
2582 {
2583     roxterm->child_exited_tag = g_signal_connect(roxterm->widget,
2584             "child-exited", G_CALLBACK(roxterm_child_exited), roxterm);
2585     g_signal_connect(roxterm->widget, "popup-menu",
2586             G_CALLBACK(roxterm_popup_handler), roxterm);
2587     g_signal_connect(roxterm->widget, "button-press-event",
2588             G_CALLBACK (roxterm_click_handler), roxterm);
2589     g_signal_connect(roxterm->widget, "button-release-event",
2590             G_CALLBACK (roxterm_release_handler), roxterm);
2591     g_signal_connect(roxterm->widget, "window-title-changed",
2592         G_CALLBACK(roxterm_window_title_handler), roxterm);
2593     //g_signal_connect(roxterm->widget, "style-updated",
2594         //G_CALLBACK(roxterm_style_change_handler), roxterm);
2595     g_signal_connect(roxterm->widget, "char-size-changed",
2596         G_CALLBACK(roxterm_char_size_changed), roxterm);
2597     g_signal_connect(roxterm->widget, "drag-end",
2598         G_CALLBACK(roxterm_uri_drag_ended), roxterm);
2599     g_signal_connect(roxterm->widget, "drag-data-get",
2600         G_CALLBACK(roxterm_uri_drag_data_get), roxterm);
2601     g_signal_connect(roxterm->widget, "bell",
2602             G_CALLBACK(roxterm_bell_handler), roxterm);
2603     /* None of these seem to get raised on text output */
2604     /*
2605     g_signal_connect(roxterm->widget, "text-modified",
2606             G_CALLBACK(roxterm_text_changed_handler), roxterm);
2607     g_signal_connect(roxterm->widget, "text-inserted",
2608             G_CALLBACK(roxterm_text_changed_handler), roxterm);
2609     g_signal_connect(roxterm->widget, "text-deleted",
2610             G_CALLBACK(roxterm_text_changed_handler), roxterm);
2611     */
2612     g_signal_connect(roxterm->widget, "cursor-moved",
2613             G_CALLBACK(roxterm_text_changed_handler), roxterm);
2614     g_signal_connect(roxterm->widget, "resize-window",
2615             G_CALLBACK(roxterm_resize_window_handler), roxterm);
2616     g_signal_connect(roxterm->widget, "composited-changed",
2617             G_CALLBACK(roxterm_composited_changed_handler), roxterm);
2618     g_signal_connect(roxterm->widget, "key-press-event",
2619             G_CALLBACK(roxterm_key_press_handler), roxterm);
2620 }
2621 
2622 inline static void
roxterm_set_word_chars(ROXTermData * roxterm,VteTerminal * vte)2623 roxterm_set_word_chars(ROXTermData * roxterm, VteTerminal * vte)
2624 {
2625     char *wchars = options_lookup_string_with_default
2626         (roxterm->profile, "word_chars", "-,./?%&#:_=+@~");
2627 
2628     vte_terminal_set_word_char_exceptions(vte, wchars);
2629     if (wchars)
2630         g_free(wchars);
2631 }
2632 
2633 inline static void
roxterm_update_audible_bell(ROXTermData * roxterm,VteTerminal * vte)2634 roxterm_update_audible_bell(ROXTermData * roxterm, VteTerminal * vte)
2635 {
2636     vte_terminal_set_audible_bell(vte, options_lookup_int
2637         (roxterm->profile, "audible_bell") != 0);
2638 }
2639 
2640 static void
roxterm_update_cursor_blink_mode(ROXTermData * roxterm,VteTerminal * vte)2641 roxterm_update_cursor_blink_mode(ROXTermData * roxterm, VteTerminal * vte)
2642 {
2643     int o = options_lookup_int(roxterm->profile, "cursor_blink_mode");
2644 
2645     if (o == -1)
2646     {
2647         o = options_lookup_int(roxterm->profile, "cursor_blinks") + 1;
2648         if (o)
2649             o ^= 3;
2650     }
2651     vte_terminal_set_cursor_blink_mode(vte, o);
2652 }
2653 
2654 inline static void
roxterm_update_cursor_shape(ROXTermData * roxterm,VteTerminal * vte)2655 roxterm_update_cursor_shape(ROXTermData * roxterm, VteTerminal * vte)
2656 {
2657     vte_terminal_set_cursor_shape(vte, options_lookup_int_with_default
2658         (roxterm->profile, "cursor_shape", 0));
2659 }
2660 
2661 inline static void
roxterm_update_mouse_autohide(ROXTermData * roxterm,VteTerminal * vte)2662 roxterm_update_mouse_autohide(ROXTermData * roxterm, VteTerminal * vte)
2663 {
2664     vte_terminal_set_mouse_autohide(vte, options_lookup_int(roxterm->profile,
2665             "mouse_autohide") != 0);
2666 }
2667 
roxterm_set_scrollback_lines(ROXTermData * roxterm,VteTerminal * vte)2668 static void roxterm_set_scrollback_lines(ROXTermData * roxterm,
2669         VteTerminal * vte)
2670 {
2671     int lines = options_lookup_int_with_default(roxterm->profile,
2672                 "limit_scrollback", 0) ?
2673             options_lookup_int_with_default(roxterm->profile,
2674                     "scrollback_lines", 1000) :
2675             -1;
2676     vte_terminal_set_scrollback_lines(vte, lines);
2677 }
2678 
roxterm_set_scroll_on_output(ROXTermData * roxterm,VteTerminal * vte)2679 static void roxterm_set_scroll_on_output(ROXTermData * roxterm,
2680         VteTerminal * vte)
2681 {
2682     vte_terminal_set_scroll_on_output(vte, options_lookup_int_with_default
2683         (roxterm->profile, "scroll_on_output", 0));
2684 }
2685 
roxterm_set_scroll_on_keystroke(ROXTermData * roxterm,VteTerminal * vte)2686 static void roxterm_set_scroll_on_keystroke(ROXTermData * roxterm,
2687         VteTerminal * vte)
2688 {
2689     vte_terminal_set_scroll_on_keystroke(vte, options_lookup_int_with_default
2690         (roxterm->profile, "scroll_on_keystroke", 0));
2691 }
2692 
roxterm_set_backspace_binding(ROXTermData * roxterm,VteTerminal * vte)2693 static void roxterm_set_backspace_binding(ROXTermData * roxterm,
2694         VteTerminal * vte)
2695 {
2696     vte_terminal_set_backspace_binding(vte, (VteEraseBinding)
2697         options_lookup_int_with_default(roxterm->profile,
2698             "backspace_binding", VTE_ERASE_AUTO));
2699 }
2700 
roxterm_set_delete_binding(ROXTermData * roxterm,VteTerminal * vte)2701 static void roxterm_set_delete_binding(ROXTermData * roxterm,
2702         VteTerminal * vte)
2703 {
2704     vte_terminal_set_delete_binding(vte, (VteEraseBinding)
2705         options_lookup_int_with_default(roxterm->profile,
2706             "delete_binding", VTE_ERASE_AUTO));
2707 }
2708 
roxterm_apply_wrap_switch_tab(ROXTermData * roxterm)2709 inline static void roxterm_apply_wrap_switch_tab(ROXTermData *roxterm)
2710 {
2711     multi_win_set_wrap_switch_tab(roxterm_get_win(roxterm),
2712         options_lookup_int_with_default(roxterm->profile,
2713             "wrap_switch_tab", FALSE));
2714 }
2715 
roxterm_apply_always_show_tabs(ROXTermData * roxterm)2716 inline static void roxterm_apply_always_show_tabs(ROXTermData *roxterm)
2717 {
2718     multi_win_set_always_show_tabs(roxterm_get_win(roxterm),
2719         options_lookup_int_with_default(roxterm->profile,
2720             "always_show_tabs", TRUE));
2721 }
2722 
roxterm_apply_disable_menu_access(ROXTermData * roxterm)2723 static void roxterm_apply_disable_menu_access(ROXTermData *roxterm)
2724 {
2725     static char *orig_menu_access = NULL;
2726     static gboolean disabled = FALSE;
2727     gboolean disable = options_lookup_int_with_default(roxterm->profile,
2728             "disable_menu_access", FALSE);
2729     GtkSettings *settings;
2730     GtkBindingSet *binding_set;
2731 
2732     if (disable == disabled)
2733         return;
2734     settings = gtk_settings_get_default();
2735     if (!orig_menu_access)
2736     {
2737         g_object_get(settings, "gtk-menu-bar-accel", &orig_menu_access, NULL);
2738     }
2739     disabled = disable;
2740     g_type_class_unref(g_type_class_ref(GTK_TYPE_MENU_BAR));
2741     g_object_set(settings, "gtk-menu-bar-accel",
2742             disable ? NULL: orig_menu_access,
2743             NULL);
2744     binding_set = gtk_binding_set_by_class(
2745             VTE_TERMINAL_GET_CLASS(roxterm->widget));
2746     if (disable)
2747     {
2748         gtk_binding_entry_skip(binding_set, GDK_KEY_F10, GDK_SHIFT_MASK);
2749     }
2750     else
2751     {
2752         gtk_binding_entry_remove(binding_set, GDK_KEY_F10, GDK_SHIFT_MASK);
2753     }
2754 }
2755 
roxterm_apply_title_template(ROXTermData * roxterm)2756 static void roxterm_apply_title_template(ROXTermData *roxterm)
2757 {
2758     char *win_title = global_options_lookup_string("title");
2759     gboolean custom_win_title;
2760     MultiWin *win = roxterm_get_win(roxterm);
2761 
2762     if (win_title)
2763     {
2764         custom_win_title = TRUE;
2765     }
2766     else
2767     {
2768         custom_win_title = FALSE;
2769         win_title = options_lookup_string_with_default(roxterm->profile,
2770                     "win_title", "%s");
2771     }
2772     multi_win_set_title_template(win, win_title);
2773     if (custom_win_title)
2774     {
2775         global_options_reset_string("title");
2776         multi_win_set_title_template_locked(win, TRUE);
2777     }
2778     g_free(win_title);
2779 }
2780 
roxterm_apply_show_tab_status(ROXTermData * roxterm)2781 static void roxterm_apply_show_tab_status(ROXTermData *roxterm)
2782 {
2783     if (roxterm->tab)
2784     {
2785         const char *name;
2786 
2787         if (options_lookup_int_with_default(roxterm->profile,
2788                 "show_tab_status", FALSE))
2789         {
2790             name = roxterm->status_icon_name;
2791         }
2792         else
2793         {
2794             name = NULL;
2795         }
2796         multi_tab_set_status_icon_name(roxterm->tab, name);
2797     }
2798 }
2799 
2800 /*
2801 static void roxterm_apply_match_files(ROXTermData *roxterm, VteTerminal *vte)
2802 {
2803     if (options_lookup_int_with_default(roxterm->profile,
2804             "match_plain_files", FALSE))
2805     {
2806         if (roxterm->file_match_tag[0] == -1)
2807             roxterm_add_file_matches(roxterm, vte);
2808     }
2809     else
2810     {
2811         if (roxterm->file_match_tag[0] != -1)
2812             roxterm_remove_file_matches(roxterm, vte);
2813     }
2814 
2815 }
2816 */
2817 
roxterm_apply_middle_click_tab(ROXTermData * roxterm)2818 inline static void roxterm_apply_middle_click_tab(ROXTermData *roxterm)
2819 {
2820     multi_tab_set_middle_click_tab_action(roxterm->tab,
2821             options_lookup_int_with_default(roxterm->profile,
2822                     "middle_click_tab", 0));
2823 }
2824 
roxterm_apply_colour_scheme_from_profile(ROXTermData * roxterm)2825 static void roxterm_apply_colour_scheme_from_profile(ROXTermData *roxterm)
2826 {
2827     char *scheme = options_lookup_string(roxterm->profile, "colour_scheme");
2828     if (scheme && scheme[0])
2829     {
2830         roxterm_change_colour_scheme_by_name(roxterm, scheme);
2831     }
2832     g_free(scheme);
2833 }
2834 
roxterm_apply_show_add_tab_btn(ROXTermData * roxterm)2835 static void roxterm_apply_show_add_tab_btn(ROXTermData *roxterm)
2836 {
2837     MultiWin *win = roxterm_get_win(roxterm);
2838     if (win)
2839     {
2840         multi_win_set_show_add_tab_button(win,
2841                 options_lookup_int_with_default(roxterm->profile,
2842                         "show_add_tab_btn", 1));
2843     }
2844 }
2845 
2846 static void
roxterm_apply_bold_is_bright(ROXTermData * roxterm,VteTerminal * vte)2847 roxterm_apply_bold_is_bright(ROXTermData *roxterm, VteTerminal *vte)
2848 {
2849     vte_terminal_set_bold_is_bright(vte,
2850             options_lookup_int_with_default(roxterm->profile, "bold_is_bright",
2851                 FALSE));
2852 }
2853 
2854 static void
roxterm_apply_text_blink_mode(ROXTermData * roxterm,VteTerminal * vte)2855 roxterm_apply_text_blink_mode(ROXTermData *roxterm, VteTerminal *vte)
2856 {
2857     static VteTextBlinkMode modes[] = { VTE_TEXT_BLINK_NEVER,
2858         VTE_TEXT_BLINK_FOCUSED, VTE_TEXT_BLINK_UNFOCUSED,
2859         VTE_TEXT_BLINK_ALWAYS };
2860     int i = options_lookup_int_with_default(roxterm->profile,
2861             "text_blink_mode", 0);
2862     if (i < 0 || i >= (int) G_N_ELEMENTS(modes))
2863     {
2864         g_warning("Value %d out of range for 'text_blink_mode' option", i);
2865         i = 0;
2866     }
2867     vte_terminal_set_text_blink_mode(vte, modes[i]);
2868 }
2869 
roxterm_apply_kinetic_scroling(ROXTermData * roxterm)2870 static void roxterm_apply_kinetic_scroling(ROXTermData *roxterm)
2871 {
2872     if (roxterm_can_disable_fallback_scrolling == -1)
2873     {
2874         roxterm_can_disable_fallback_scrolling = g_object_class_find_property(
2875                 G_OBJECT_GET_CLASS(roxterm->widget),
2876                 "enable-fallback-scrolling") ? 1 : 0;
2877         g_debug("VTE %s enable-fallback-scrolling",
2878                 roxterm_can_disable_fallback_scrolling ?
2879                 "supports" : "doesn't support");
2880     }
2881     gboolean kinetic = options_lookup_int_with_default(roxterm->profile,
2882                 "kinetic_scrolling", TRUE);
2883     if (roxterm_can_disable_fallback_scrolling == 1)
2884     {
2885         g_object_set(roxterm->widget, "enable-fallback-scrolling",
2886                 !kinetic, NULL);
2887         if (roxterm_can_use_pixel_scrolling == -1)
2888         {
2889             roxterm_can_use_pixel_scrolling = g_object_class_find_property(
2890                     G_OBJECT_GET_CLASS(roxterm->widget),
2891                     "scroll-unit-is-pixels") ? 1 : 0;
2892             g_debug("VTE %s scroll-unit-is-pixels",
2893                     roxterm_can_use_pixel_scrolling ?
2894                     "supports" : "doesn't support");
2895         }
2896         gboolean pixel = options_lookup_int_with_default(roxterm->profile,
2897                     "pixel_scrolling", kinetic);
2898         if (roxterm_can_use_pixel_scrolling == 1)
2899         {
2900             g_object_set(roxterm->widget, "scroll-unit-is-pixels",
2901                     pixel, NULL);
2902         }
2903     }
2904 }
2905 
roxterm_apply_profile(ROXTermData * roxterm,VteTerminal * vte,gboolean update_geometry)2906 static void roxterm_apply_profile(ROXTermData *roxterm, VteTerminal *vte,
2907         gboolean update_geometry)
2908 {
2909     roxterm_set_word_chars(roxterm, vte);
2910     roxterm_update_audible_bell(roxterm, vte);
2911     roxterm_update_cursor_blink_mode(roxterm, vte);
2912     roxterm_update_cursor_shape(roxterm, vte);
2913 
2914     roxterm_apply_colour_scheme(roxterm, vte);
2915 
2916     roxterm_update_font(roxterm, vte, update_geometry);
2917 
2918     roxterm_apply_bold_is_bright(roxterm, vte);
2919     roxterm_apply_text_blink_mode(roxterm, vte);
2920 
2921     roxterm_set_scrollback_lines(roxterm, vte);
2922     roxterm_set_scroll_on_output(roxterm, vte);
2923     roxterm_set_scroll_on_keystroke(roxterm, vte);
2924     roxterm_apply_kinetic_scroling(roxterm);
2925 
2926     roxterm_set_backspace_binding(roxterm, vte);
2927     roxterm_set_delete_binding(roxterm, vte);
2928 
2929     roxterm_update_mouse_autohide(roxterm, vte);
2930 
2931     roxterm_apply_wrap_switch_tab(roxterm);
2932 
2933     roxterm_apply_disable_menu_access(roxterm);
2934 
2935     roxterm_apply_title_template(roxterm);
2936     roxterm_apply_show_tab_status(roxterm);
2937     /*roxterm_apply_match_files(roxterm, vte);*/
2938     roxterm_apply_middle_click_tab(roxterm);
2939 
2940     roxterm_apply_colour_scheme_from_profile(roxterm);
2941 
2942     roxterm_apply_show_add_tab_btn(roxterm);
2943 }
2944 
2945 static gboolean
roxterm_drag_data_received(GtkWidget * widget,const char * text,gulong len,gpointer data)2946 roxterm_drag_data_received(GtkWidget *widget,
2947         const char *text, gulong len, gpointer data)
2948 {
2949     char *sep = strstr(text, "\r\n");
2950     char *rejoined = NULL;
2951     (void) data;
2952 
2953     if (sep)
2954     {
2955         char **uris = g_strsplit(text, "\r\n", 0);
2956         int n;
2957         char *tmp;
2958 
2959         for (n = 0; uris[n]; ++n)
2960         {
2961             /* Last uri is usually a blank line so skip */
2962             if (!uris[n][0])
2963             {
2964                 continue;
2965             }
2966             /* escape ' chars */
2967             if (strchr(uris[n], '\''))
2968             {
2969                 char **split = g_strsplit(uris[n], "'", 0);
2970 
2971                 tmp = g_strjoinv("'\\''", split);
2972                 g_free(uris[n]);
2973                 uris[n] = tmp;
2974             }
2975             tmp = g_strdup_printf("%s%s%s", rejoined ? rejoined : "",
2976                     rejoined ? "' '" : "'", uris[n]);
2977             g_free(rejoined);
2978             rejoined = tmp;
2979         }
2980         if (rejoined && rejoined[0])
2981         {
2982             tmp = g_strdup_printf("%s' ", rejoined);
2983             g_free(rejoined);
2984             rejoined = tmp;
2985         }
2986         text = rejoined;
2987         if (text)
2988             len = strlen(text);
2989         g_strfreev(uris);
2990     }
2991     if (text)
2992         vte_terminal_feed_child(VTE_TERMINAL(widget), text, len);
2993     if (rejoined)
2994         g_free(rejoined);
2995     return TRUE;
2996 }
2997 
roxterm_window_state_changed(GtkWidget * win,GdkEventWindowState * event,ROXTermData * roxterm)2998 static gboolean roxterm_window_state_changed(GtkWidget *win,
2999         GdkEventWindowState *event, ROXTermData *roxterm)
3000 {
3001     (void) win;
3002     roxterm->maximise =
3003             (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0;
3004     return FALSE;
3005 }
3006 
3007 inline static void
roxterm_attach_state_changed_handler(ROXTermData * roxterm)3008 roxterm_attach_state_changed_handler(ROXTermData *roxterm)
3009 {
3010     roxterm->win_state_changed_tag =
3011             g_signal_connect(roxterm_get_toplevel(roxterm),
3012             "window-state-event",
3013             G_CALLBACK(roxterm_window_state_changed), roxterm);
3014 }
3015 
3016 static void
roxterm_tab_received(GtkWidget * rcvd_widget,ROXTermData * roxterm)3017 roxterm_tab_received(GtkWidget *rcvd_widget, ROXTermData *roxterm)
3018 {
3019     MultiTab *tab = multi_tab_get_from_widget(rcvd_widget);
3020 
3021     if (tab != roxterm->tab)
3022     {
3023         MultiWin *win = roxterm_get_win(roxterm);
3024         int page_num = multi_tab_get_page_num(roxterm->tab);
3025 
3026         if (multi_tab_get_parent(tab) == win)
3027             multi_tab_move_to_position(tab, page_num, TRUE);
3028         else
3029             multi_tab_move_to_new_window(win, tab, page_num);
3030     }
3031 }
3032 
roxterm_get_vte_hadjustment(VteTerminal * vte)3033 inline static GtkAdjustment *roxterm_get_vte_hadjustment(VteTerminal *vte)
3034 {
3035     return gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(vte));
3036 }
3037 
roxterm_get_vte_vadjustment(VteTerminal * vte)3038 inline static GtkAdjustment *roxterm_get_vte_vadjustment(VteTerminal *vte)
3039 {
3040     return gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte));
3041 }
3042 
roxterm_multi_tab_filler(MultiWin * win,MultiTab * tab,ROXTermData * roxterm_template,ROXTermData ** roxterm_out,GtkWidget ** vte_widget,GtkAdjustment ** adjustment)3043 static GtkWidget *roxterm_multi_tab_filler(MultiWin * win, MultiTab * tab,
3044     ROXTermData * roxterm_template, ROXTermData ** roxterm_out,
3045     GtkWidget ** vte_widget, GtkAdjustment **adjustment)
3046 {
3047     VteTerminal *vte;
3048     const char *title_orig;
3049     ROXTermData *roxterm = roxterm_data_clone(roxterm_template);
3050     int hide_menu_bar;
3051     MultiWinScrollBar_Position scrollbar_pos;
3052     char *tab_name;
3053     gboolean custom_tab_name = FALSE;
3054     MultiWin *template_win = roxterm_get_win(roxterm_template);
3055     GtkWidget *viewport = NULL;
3056 
3057     roxterm_terms = g_list_append(roxterm_terms, roxterm);
3058 
3059     if (template_win)
3060     {
3061         hide_menu_bar = !multi_win_get_show_menu_bar(template_win);
3062     }
3063     else
3064     {
3065         hide_menu_bar = global_options_lookup_int("hide_menubar");
3066         if (hide_menu_bar == -1)
3067         {
3068             hide_menu_bar = options_lookup_int(roxterm_template->profile,
3069                 "hide_menubar") == 1;
3070         }
3071     }
3072     multi_win_set_show_menu_bar(win, !hide_menu_bar);
3073 
3074     roxterm->tab = tab;
3075     *roxterm_out = roxterm;
3076 
3077     roxterm->widget = vte_terminal_new();
3078     vte_terminal_set_size(VTE_TERMINAL(roxterm->widget),
3079             roxterm->columns, roxterm->rows);
3080     gtk_widget_grab_focus(roxterm->widget);
3081     vte = VTE_TERMINAL(roxterm->widget);
3082     if (vte_widget)
3083         *vte_widget = roxterm->widget;
3084     if (adjustment)
3085         *adjustment = roxterm_get_vte_vadjustment(vte);
3086 
3087     scrollbar_pos = multi_win_set_scroll_bar_position(win,
3088     options_lookup_int_with_default(roxterm_template->profile,
3089         "scrollbar_pos", MultiWinScrollBar_Right));
3090     viewport = gtk_scrolled_window_new(roxterm_get_vte_hadjustment(vte),
3091                     roxterm_get_vte_vadjustment(vte));
3092     GtkScrolledWindow *sw = GTK_SCROLLED_WINDOW(viewport);
3093     gtk_scrolled_window_set_policy(sw, GTK_POLICY_NEVER,
3094             scrollbar_pos ? GTK_POLICY_ALWAYS : GTK_POLICY_EXTERNAL);
3095     gtk_scrolled_window_set_propagate_natural_width(sw, TRUE);
3096     gtk_scrolled_window_set_propagate_natural_height(sw, TRUE);
3097     gtk_scrolled_window_set_overlay_scrolling(sw,
3098         options_lookup_int_with_default(roxterm_template->profile,
3099             "overlay_scrollbar", TRUE));
3100     gtk_scrolled_window_set_placement(sw,
3101             (scrollbar_pos == MultiWinScrollBar_Left) ?
3102             GTK_CORNER_BOTTOM_RIGHT : GTK_CORNER_BOTTOM_LEFT);
3103     gtk_container_add(GTK_CONTAINER(viewport), roxterm->widget);
3104     gtk_widget_show_all(viewport);
3105 
3106     roxterm_add_matches(roxterm, vte);
3107 
3108     roxterm_apply_profile(roxterm, vte, FALSE);
3109     tab_name = global_options_lookup_string("tab-name");
3110     if (tab_name)
3111     {
3112         custom_tab_name = TRUE;
3113     }
3114     else
3115     {
3116         custom_tab_name = FALSE;
3117         tab_name = options_lookup_string_with_default(roxterm->profile,
3118                 "title_string", "%t. %s");
3119     }
3120     multi_tab_set_window_title_template(tab, tab_name);
3121     multi_tab_set_title_template_locked(tab, custom_tab_name);
3122     if (custom_tab_name)
3123     {
3124         global_options_reset_string("tab-name");
3125     }
3126     g_free(tab_name);
3127     title_orig = vte_terminal_get_window_title(vte);
3128     multi_tab_set_window_title(tab, title_orig ? title_orig : _("ROXTerm"));
3129 
3130     //g_debug("call roxterm_set_vte_size from line %d", __LINE__);
3131     //roxterm_set_vte_size(roxterm, vte, roxterm->columns, roxterm->rows);
3132 
3133     multi_tab_connect_tab_selection_handler(win,
3134         (MultiTabSelectionHandler) roxterm_tab_selection_handler);
3135     roxterm->drd = drag_receive_setup_dest_widget(roxterm->widget,
3136             roxterm_drag_data_received,
3137             (DragReceiveTabHandler) roxterm_tab_received,
3138             roxterm);
3139 
3140     roxterm_attach_state_changed_handler(roxterm);
3141 
3142     g_idle_add((GSourceFunc) run_child_when_idle, roxterm);
3143 
3144     return viewport ? viewport : roxterm->widget;
3145 }
3146 
roxterm_get_current_vte_ptr(MultiWin * win)3147 VteTerminal *roxterm_get_current_vte_ptr(MultiWin *win) {
3148     ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
3149     VteTerminal *vte = VTE_TERMINAL(roxterm->widget);
3150     return vte;
3151 }
3152 
roxterm_multi_tab_destructor(ROXTermData * roxterm)3153 static void roxterm_multi_tab_destructor(ROXTermData * roxterm)
3154 {
3155     roxterm_terms = g_list_remove(roxterm_terms, roxterm);
3156     roxterm_data_delete(roxterm);
3157 }
3158 
3159 /* Returns FALSE if the value hasn't really changed */
3160 static gboolean
roxterm_update_option(Options * opts,const char * key,OptsDBusOptType opt_type,OptsDBusValue val)3161 roxterm_update_option(Options * opts, const char *key,
3162     OptsDBusOptType opt_type, OptsDBusValue val)
3163 {
3164     gboolean result = TRUE;
3165     char *oldval;
3166 
3167     switch (opt_type)
3168     {
3169         case OptsDBus_StringOpt:
3170             oldval = options_lookup_string(opts, key);
3171             if (g_strcmp0(oldval, val.s))
3172                 options_set_string(opts, key, val.s);
3173             else
3174                 result = FALSE;
3175             if (oldval)
3176                 g_free(oldval);
3177             return result;
3178         case OptsDBus_IntOpt:
3179             if (options_lookup_int(opts, key) == val.i)
3180                 return FALSE;
3181             options_set_int(opts, key, val.i);
3182             return TRUE;
3183         case OptsDBus_FloatOpt:
3184             if (options_lookup_double(opts, key) == val.f)
3185                 return FALSE;
3186             options_set_double(opts, key, val.f);
3187             return TRUE;
3188         default:
3189             g_critical(_("Unknown option type (%d)"), opt_type);
3190     }
3191     return FALSE;
3192 }
3193 
roxterm_get_show_tab_close_button(ROXTermData * roxterm)3194 static gboolean roxterm_get_show_tab_close_button(ROXTermData *roxterm)
3195 {
3196     return options_lookup_int_with_default(roxterm->profile,
3197             "tab_close_btn", TRUE);
3198 }
3199 
roxterm_get_new_tab_adjacent(ROXTermData * roxterm)3200 static gboolean roxterm_get_new_tab_adjacent(ROXTermData *roxterm)
3201 {
3202     return options_lookup_int_with_default(roxterm->profile,
3203             "new_tabs_adjacent", FALSE);
3204 }
3205 
roxterm_reflect_profile_change(Options * profile,const char * key)3206 static void roxterm_reflect_profile_change(Options * profile, const char *key)
3207 {
3208     GList *link;
3209 
3210     for (link = roxterm_terms; link; link = g_list_next(link))
3211     {
3212         ROXTermData *roxterm = link->data;
3213         VteTerminal *vte;
3214         MultiWin *win = roxterm_get_win(roxterm);
3215         gboolean apply_to_win = FALSE;
3216 
3217         if (roxterm->profile != profile || roxterm->profile->deleted)
3218             continue;
3219 
3220         vte = VTE_TERMINAL(roxterm->widget);
3221         if (!strcmp(key, "font"))
3222         {
3223             roxterm_apply_profile_font(roxterm, vte, TRUE);
3224             apply_to_win = TRUE;
3225         }
3226         else if (!strcmp(key, "vspacing"))
3227         {
3228             roxterm_apply_vspacing(roxterm, vte);
3229             apply_to_win = TRUE;
3230         }
3231         else if (!strcmp(key, "hspacing"))
3232         {
3233             roxterm_apply_hspacing(roxterm, vte);
3234             apply_to_win = TRUE;
3235         }
3236         else if (!strcmp(key, "bold_is_bright"))
3237         {
3238             roxterm_apply_bold_is_bright(roxterm, vte);
3239         }
3240         else if (!strcmp(key, "text_blink_mode"))
3241         {
3242             roxterm_apply_text_blink_mode(roxterm, vte);
3243         }
3244         else if (!strcmp(key, "hide_menubar") &&
3245             multi_win_get_current_tab(win) == roxterm->tab)
3246         {
3247             multi_win_set_show_menu_bar(win,
3248                 !options_lookup_int(roxterm->profile, "hide_menubar"));
3249         }
3250         else if (!strcmp(key, "audible_bell"))
3251         {
3252             roxterm_update_audible_bell(roxterm, vte);
3253         }
3254         else if (!strcmp(key, "cursor_blink_mode"))
3255         {
3256             roxterm_update_cursor_blink_mode(roxterm, vte);
3257         }
3258         else if (!strcmp(key, "cursor_shape"))
3259         {
3260             roxterm_update_cursor_shape(roxterm, vte);
3261         }
3262         else if (!strcmp(key, "mouse_autohide"))
3263         {
3264             roxterm_update_mouse_autohide(roxterm, vte);
3265         }
3266         else if (!strcmp(key, "word_chars"))
3267         {
3268             roxterm_set_word_chars(roxterm, vte);
3269         }
3270         else if (!strcmp(key, "width") || !strcmp(key, "height"))
3271         {
3272             roxterm_update_size(roxterm, vte);
3273             apply_to_win = TRUE;
3274         }
3275         else if (!strcmp(key, "maximise"))
3276         {
3277             roxterm->maximise = options_lookup_int(roxterm->profile,
3278                     "maximise");
3279             if (roxterm->maximise)
3280             {
3281                 gtk_window_maximize(roxterm_get_toplevel(roxterm));
3282             }
3283             else
3284             {
3285                 gtk_window_unmaximize(roxterm_get_toplevel(roxterm));
3286             }
3287             apply_to_win = TRUE;
3288         }
3289         else if (!strcmp(key, "full_screen"))
3290         {
3291             int fs = options_lookup_int(roxterm->profile, "full_screen");
3292             multi_win_set_fullscreen(win, fs);
3293             apply_to_win = TRUE;
3294         }
3295         else if (!strcmp(key, "borderless"))
3296         {
3297             int fs = options_lookup_int(roxterm->profile, "borderless");
3298             multi_win_set_borderless(win, fs);
3299             apply_to_win = TRUE;
3300         }
3301         else if (!strcmp(key, "saturation"))
3302         {
3303             roxterm_apply_colour_scheme(roxterm, vte);
3304         }
3305         else if (!strcmp(key, "scrollback_lines") ||
3306                 !strcmp(key, "limit_scrollback"))
3307         {
3308             roxterm_set_scrollback_lines(roxterm, vte);
3309         }
3310         else if (!strcmp(key, "scroll_on_output"))
3311         {
3312             roxterm_set_scroll_on_output(roxterm, vte);
3313         }
3314         else if (!strcmp(key, "scroll_on_keystroke"))
3315         {
3316             roxterm_set_scroll_on_keystroke(roxterm, vte);
3317         }
3318         else if (!strcmp(key, "kinetic_scrolling"))
3319         {
3320             roxterm_apply_kinetic_scroling(roxterm);
3321         }
3322         else if (!strcmp(key, "backspace_binding"))
3323         {
3324             roxterm_set_backspace_binding(roxterm, vte);
3325         }
3326         else if (!strcmp(key, "delete_binding"))
3327         {
3328             roxterm_set_delete_binding(roxterm, vte);
3329         }
3330         else if (!strcmp(key, "wrap_switch_tab"))
3331         {
3332             roxterm_apply_wrap_switch_tab(roxterm);
3333         }
3334         else if (!strcmp(key, "always_show_tabs"))
3335         {
3336             roxterm_apply_always_show_tabs(roxterm);
3337         }
3338         else if (!strcmp(key, "show_add_tab_btn"))
3339         {
3340             roxterm_apply_show_add_tab_btn(roxterm);
3341         }
3342         else if (!strcmp(key, "disable_menu_access"))
3343         {
3344             roxterm_apply_disable_menu_access(roxterm);
3345         }
3346         else if (!strcmp(key, "disable_menu_shortcuts"))
3347         {
3348             gboolean disable = options_lookup_int(roxterm->profile,
3349                     "disable_menu_shortcuts");
3350             MenuTree *mtree = multi_win_get_menu_bar(win);
3351 
3352             menutree_disable_shortcuts(mtree, disable);
3353         }
3354         else if (!strcmp(key, "disable_tab_menu_shortcuts"))
3355         {
3356             gboolean disable = options_lookup_int(roxterm->profile,
3357                     "disable_tab_menu_shortcuts");
3358             MenuTree *mtree = multi_win_get_popup_menu(win);
3359 
3360             menutree_disable_tab_shortcuts(mtree, disable);
3361             mtree = multi_win_get_menu_bar(win);
3362             menutree_disable_tab_shortcuts(mtree, disable);
3363         }
3364         else if (!strcmp(key, "title_string"))
3365         {
3366             multi_tab_set_window_title_template(roxterm->tab,
3367                     options_lookup_string_with_default(roxterm->profile,
3368                             "title_string", "%t. %s"));
3369         }
3370         else if (!strcmp(key, "win_title"))
3371         {
3372             multi_win_set_title_template(win,
3373                     options_lookup_string_with_default(roxterm->profile,
3374                             "win_title", "%s"));
3375         }
3376         else if (!strcmp(key, "tab_close_btn"))
3377         {
3378             if (roxterm_get_show_tab_close_button(roxterm))
3379                 multi_tab_add_close_button(roxterm->tab);
3380             else
3381                 multi_tab_remove_close_button(roxterm->tab);
3382         }
3383         else if (!strcmp(key, "show_tab_status"))
3384         {
3385             roxterm_apply_show_tab_status(roxterm);
3386         }
3387         /*
3388         else if (!strcmp(key, "match_plain_files"))
3389         {
3390             roxterm_apply_match_files(roxterm, vte);
3391         }
3392         */
3393         else if (!strcmp(key, "middle_click_tab"))
3394         {
3395             roxterm_apply_middle_click_tab(roxterm);
3396         }
3397         else if (!strcmp(key, "colour_scheme"))
3398         {
3399             roxterm_apply_colour_scheme_from_profile(roxterm);
3400         }
3401         if (apply_to_win)
3402         {
3403             multi_win_foreach_tab(win, match_text_size_foreach_tab, roxterm);
3404         }
3405     }
3406 }
3407 
roxterm_update_colour_option(Options * scheme,const char * key,const char * value)3408 static gboolean roxterm_update_colour_option(Options *scheme, const char *key,
3409         const char *value)
3410 {
3411     void (*setter)(Options *, const char *) = NULL;
3412     GdkRGBA *old_colour;
3413     GdkRGBA *pnew_colour = NULL;
3414     GdkRGBA  new_colour;
3415 
3416     if (value)
3417     {
3418         g_return_val_if_fail(gdk_rgba_parse(&new_colour, value), FALSE);
3419         pnew_colour = &new_colour;
3420     }
3421     if (!strcmp(key, "foreground"))
3422     {
3423         old_colour = colour_scheme_get_foreground_colour(scheme, TRUE);
3424         setter = colour_scheme_set_foreground_colour;
3425     }
3426     else if (!strcmp(key, "background"))
3427     {
3428         old_colour = colour_scheme_get_background_colour(scheme, TRUE);
3429         setter = colour_scheme_set_background_colour;
3430     }
3431     else if (!strcmp(key, "cursor"))
3432     {
3433         old_colour = colour_scheme_get_cursor_colour(scheme, TRUE);
3434         setter = colour_scheme_set_cursor_colour;
3435     }
3436     else if (!strcmp(key, "cursorfg"))
3437     {
3438         old_colour = colour_scheme_get_cursorfg_colour(scheme, TRUE);
3439         setter = colour_scheme_set_cursorfg_colour;
3440     }
3441     else if (!strcmp(key, "bold"))
3442     {
3443         old_colour = colour_scheme_get_bold_colour(scheme, TRUE);
3444         setter = colour_scheme_set_bold_colour;
3445     }
3446     else
3447     {
3448         old_colour = colour_scheme_get_palette(scheme) + atoi(key);
3449     }
3450     if (!old_colour && !pnew_colour)
3451         return FALSE;
3452     if (old_colour && pnew_colour && gdk_rgba_equal(old_colour, pnew_colour))
3453         return FALSE;
3454     if (setter)
3455         setter(scheme, value);
3456     else
3457         colour_scheme_set_palette_entry(scheme, atoi(key), value);
3458     return TRUE;
3459 }
3460 
roxterm_update_palette_size(Options * scheme,int size)3461 static gboolean roxterm_update_palette_size(Options *scheme, int size)
3462 {
3463     if (colour_scheme_get_palette_size(scheme) == size)
3464         return FALSE;
3465     colour_scheme_set_palette_size(scheme, size);
3466     return TRUE;
3467 }
3468 
roxterm_reflect_colour_change(Options * scheme,const char * key)3469 static void roxterm_reflect_colour_change(Options *scheme, const char *key)
3470 {
3471     GList *link;
3472 
3473     for (link = roxterm_terms; link; link = g_list_next(link))
3474     {
3475         ROXTermData *roxterm = link->data;
3476         VteTerminal *vte;
3477 
3478         if (roxterm->colour_scheme != scheme || roxterm->colour_scheme->deleted)
3479             continue;
3480 
3481         vte = VTE_TERMINAL(roxterm->widget);
3482 
3483         if (!strcmp(key, "cursor"))
3484             roxterm_update_cursor_colour(roxterm, vte);
3485         else if (!strcmp(key, "cursorfg"))
3486             roxterm_update_cursorfg_colour(roxterm, vte);
3487         else if (!strcmp(key, "bold"))
3488             roxterm_update_bold_colour(roxterm, vte);
3489         else
3490             roxterm_apply_colour_scheme(roxterm, vte);
3491     }
3492 }
3493 
3494 static void
roxterm_opt_signal_handler(const char * profile_name,const char * key,OptsDBusOptType opt_type,OptsDBusValue val)3495 roxterm_opt_signal_handler(const char *profile_name, const char *key,
3496     OptsDBusOptType opt_type, OptsDBusValue val)
3497 {
3498     if (!strncmp(profile_name, "Profiles/", 9))
3499     {
3500         const char *short_profile_name = profile_name + 9;
3501         Options *profile = dynamic_options_lookup_and_ref(roxterm_profiles,
3502             short_profile_name, "roxterm profile");
3503 
3504         if (roxterm_update_option(profile, key, opt_type, val))
3505             roxterm_reflect_profile_change(profile, key);
3506         dynamic_options_unref(roxterm_profiles, short_profile_name);
3507     }
3508     else if (!strncmp(profile_name, "Colours/", 8))
3509     {
3510         const char *scheme_name = profile_name + 8;
3511         Options *scheme = colour_scheme_lookup_and_ref(scheme_name);
3512         gboolean changed = FALSE;
3513 
3514         if (!strcmp(key, "palette_size"))
3515             changed = roxterm_update_palette_size(scheme, val.i);
3516         else
3517             changed = roxterm_update_colour_option(scheme, key, val.s);
3518         if (changed)
3519             roxterm_reflect_colour_change(scheme, key);
3520         colour_scheme_unref(scheme);
3521     }
3522     else if (!strcmp(profile_name, "Global") &&
3523             (!strcmp(key, "warn_close") ||
3524             !strcmp(key, "only_warn_running") ||
3525             !strcmp(key, "prefer_dark_theme")))
3526     {
3527         options_set_int(global_options, key, val.i);
3528         if (!strcmp(key, "prefer_dark_theme"))
3529             global_options_apply_dark_theme();
3530     }
3531     else
3532     {
3533         g_critical(_("Profile/key '%s/%s' not understood"), profile_name, key);
3534     }
3535 }
3536 
3537 /* data is cast to char const **pname; if the deleted item is currently
3538  * selected *pname is changed to NULL */
delete_name_from_menu(GtkWidget * widget,gpointer data)3539 static void delete_name_from_menu(GtkWidget *widget, gpointer data)
3540 {
3541     const char *profile_name = g_object_get_data(G_OBJECT(widget),
3542             PROFILE_NAME_KEY);
3543     char const **pname = data;
3544 
3545     g_return_if_fail(profile_name);
3546     /* pname may have been NULLed by an earlier call */
3547     if (!*pname)
3548         return;
3549     if (!strcmp(profile_name, *pname))
3550     {
3551         if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
3552             *pname = NULL;
3553         gtk_widget_destroy(widget);
3554     }
3555 }
3556 
3557 /* Casts data to GtkWidget ** and sets target to found widget if it finds an
3558  * entry with no PROFILE_NAME_KEY */
find_unhandled_menu_entry(GtkWidget * widget,gpointer data)3559 static void find_unhandled_menu_entry(GtkWidget *widget, gpointer data)
3560 {
3561     if (!g_object_get_data(G_OBJECT(widget), PROFILE_NAME_KEY))
3562         *((GtkWidget **) data) = widget;
3563 }
3564 
append_name_to_menu(MenuTree * mtree,GtkMenu * menu,const char * name,GCallback handler)3565 static GtkWidget *append_name_to_menu(MenuTree *mtree, GtkMenu *menu,
3566         const char *name, GCallback handler)
3567 {
3568     GSList *group = NULL;
3569     GtkWidget *mitem;
3570 
3571     gtk_container_foreach(GTK_CONTAINER(menu),
3572             menutree_find_radio_group, &group);
3573     mitem = create_radio_menu_item(mtree, name, &group, handler);
3574     gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
3575     return mitem;
3576 }
3577 
3578 /* Sets *pname to NULL if it's currently selected */
remove_name_from_menu(GtkMenu * menu,char const ** pname)3579 inline static void remove_name_from_menu(GtkMenu *menu, char const **pname)
3580 {
3581     gtk_container_foreach(GTK_CONTAINER(menu),
3582             delete_name_from_menu, (gpointer) pname);
3583 }
3584 
stuff_changed_do_menu(MenuTree * mtree,const char * what_happened,const char * family_name,const char * current_name,const char * new_name)3585 static void stuff_changed_do_menu(MenuTree *mtree,
3586         const char *what_happened, const char *family_name,
3587         const char *current_name, const char *new_name)
3588 {
3589     MenuTreeID id;
3590     GtkMenu *menu;
3591     GCallback handler = NULL;
3592 
3593     if (!strcmp(family_name, "Profiles"))
3594     {
3595         char **items = dynamic_options_list_sorted(
3596                 dynamic_options_get("Profiles"));
3597 
3598         char *s = (char *) "Current profiles:";
3599         for (char **it = items; *it; ++it)
3600         {
3601             s = g_strdup_printf("%s %s", s, *it);
3602         }
3603 
3604         id = MENUTREE_PREFERENCES_SELECT_PROFILE;
3605         handler = G_CALLBACK(roxterm_profile_selected);
3606 
3607         menu = menutree_submenu_from_id(mtree,
3608                 MENUTREE_FILE_NEW_WINDOW_WITH_PROFILE);
3609         rebuild_new_term_with_profile_submenu(mtree,
3610             G_CALLBACK(roxterm_new_window_with_profile),
3611             GTK_MENU_SHELL(mtree->new_win_profiles_menu), items);
3612         rebuild_new_term_with_profile_submenu(mtree,
3613             G_CALLBACK(roxterm_new_tab_with_profile),
3614             GTK_MENU_SHELL(mtree->new_tab_profiles_menu), items);
3615         g_strfreev(items);
3616     }
3617     else if (!strcmp(family_name, "Colours"))
3618     {
3619         id = MENUTREE_PREFERENCES_SELECT_COLOUR_SCHEME;
3620         handler = G_CALLBACK(roxterm_colour_scheme_selected);
3621     }
3622     else if (!strcmp(family_name, "Shortcuts"))
3623     {
3624         id = MENUTREE_PREFERENCES_SELECT_SHORTCUTS;
3625         handler = G_CALLBACK(roxterm_shortcuts_selected);
3626     }
3627     else
3628     {
3629         g_warning(_("Changed options family '%s' not known"), family_name);
3630         return;
3631     }
3632     g_return_if_fail((menu = menutree_submenu_from_id(mtree, id)) != NULL);
3633 
3634     if (!strcmp(what_happened, OPTSDBUS_DELETED))
3635     {
3636         GtkWidget *unhandled_mitem = NULL;
3637 
3638         remove_name_from_menu(menu, &current_name);
3639         gtk_container_foreach(GTK_CONTAINER(menu),
3640                 find_unhandled_menu_entry, &unhandled_mitem);
3641         if (!unhandled_mitem)
3642         {
3643             unhandled_mitem = append_name_to_menu(mtree, menu,
3644                     _("[Deleted]"), NULL);
3645             gtk_widget_set_sensitive(unhandled_mitem, FALSE);
3646         }
3647         /* If deleted item was selected, select unhandled item in its place
3648          */
3649         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(unhandled_mitem),
3650                 current_name == NULL);
3651     }
3652     else if (!strcmp(what_happened, OPTSDBUS_ADDED))
3653     {
3654         append_name_to_menu(mtree, menu, current_name, handler);
3655     }
3656     else if (!strcmp(what_happened, OPTSDBUS_RENAMED))
3657     {
3658         GtkWidget *mitem;
3659 
3660         remove_name_from_menu(menu, &current_name);
3661         mitem = append_name_to_menu(mtree, menu, new_name, handler);
3662         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mitem),
3663                 current_name == NULL);
3664     }
3665     else
3666     {
3667         g_warning(_("Options family change action '%s' not known"),
3668                 what_happened);
3669     }
3670 }
3671 
roxterm_stuff_changed_handler(const char * what_happened,const char * family_name,const char * current_name,const char * new_name)3672 void roxterm_stuff_changed_handler(const char *what_happened,
3673         const char *family_name, const char *current_name,
3674         const char *new_name)
3675 {
3676     GList *link;
3677     DynamicOptions *dynopts = NULL;
3678     Options *options = NULL;
3679 
3680     if (!strcmp(what_happened, OPTSDBUS_CHANGED) &&
3681             strcmp(family_name, "Shortcuts"))
3682     {
3683         for (link = roxterm_terms; link; link = g_list_next(link))
3684         {
3685             ROXTermData *roxterm = link->data;
3686 
3687             if (!strcmp(family_name, "Profiles"))
3688             {
3689                 if (!strcmp(options_get_leafname(roxterm->profile),
3690                         current_name))
3691                 {
3692                     roxterm_apply_profile(roxterm,
3693                             VTE_TERMINAL(roxterm->widget), TRUE);
3694                 }
3695             }
3696             else if (!strcmp(family_name, "Colours"))
3697             {
3698                 if (!strcmp(options_get_leafname(roxterm->colour_scheme),
3699                         current_name))
3700                 {
3701                     roxterm_apply_colour_scheme(roxterm,
3702                             VTE_TERMINAL(roxterm->widget));
3703                 }
3704             }
3705         }
3706         return;
3707     }
3708 
3709     if (!strcmp(what_happened, OPTSDBUS_DELETED))
3710     {
3711         if (options)
3712         {
3713             dynamic_options_forget(dynopts, current_name);
3714             options->deleted = TRUE;
3715         }
3716     }
3717     else if (!strcmp(what_happened, OPTSDBUS_RENAMED))
3718     {
3719         if (options)
3720         {
3721             options_change_leafname(options, new_name);
3722             dynamic_options_rename(dynopts, current_name, new_name);
3723         }
3724     }
3725 
3726     for (link = multi_win_all; link; link = g_list_next(link))
3727     {
3728         MultiWin *win = (MultiWin *) link->data;
3729 
3730         if (strcmp(what_happened, OPTSDBUS_CHANGED))
3731         {
3732             MenuTree *mtree = multi_win_get_menu_bar(win);
3733 
3734             multi_win_set_ignore_toggles(win, TRUE);
3735             if (mtree)
3736             {
3737                 stuff_changed_do_menu(mtree, what_happened, family_name,
3738                         current_name, new_name);
3739             }
3740             mtree = multi_win_get_popup_menu(win);
3741             if (mtree)
3742             {
3743                 stuff_changed_do_menu(mtree, what_happened, family_name,
3744                         current_name, new_name);
3745             }
3746             multi_win_set_ignore_toggles(win, FALSE);
3747         }
3748         else if (!strcmp(family_name, "Shortcuts"))
3749         {
3750             Options *shortcuts = shortcuts_open(current_name, TRUE);
3751             multi_win_set_shortcut_scheme(win, shortcuts);
3752             shortcuts_unref(shortcuts);
3753         }
3754     }
3755 }
3756 
roxterm_verify_id(ROXTermData * roxterm)3757 static gboolean roxterm_verify_id(ROXTermData *roxterm)
3758 {
3759     GList *link;
3760 
3761     for (link = roxterm_terms; link; link = g_list_next(link))
3762     {
3763         if (link->data == roxterm)
3764             return TRUE;
3765     }
3766     g_warning(_("Invalid ROXTERM_ID %p in D-Bus message "
3767             "(this is expected if you used roxterm's --separate option)"),
3768             roxterm);
3769     return FALSE;
3770 }
3771 
roxterm_set_profile_handler(ROXTermData * roxterm,const char * name)3772 static void roxterm_set_profile_handler(ROXTermData *roxterm, const char *name)
3773 {
3774     if (!roxterm_verify_id(roxterm))
3775         return;
3776 
3777     Options *profile = dynamic_options_lookup_and_ref(roxterm_profiles, name,
3778             "roxterm profile");
3779 
3780     if (profile)
3781     {
3782         roxterm_change_profile(roxterm, profile);
3783         multi_win_foreach_tab(roxterm_get_win(roxterm),
3784                 match_text_size_foreach_tab, roxterm);
3785         options_unref(profile);
3786     }
3787     else
3788     {
3789         dlg_warning(roxterm_get_toplevel(roxterm),
3790                 _("Unknown profile '%s' in D-Bus signal"), name);
3791     }
3792 }
3793 
roxterm_set_colour_scheme_handler(ROXTermData * roxterm,const char * name)3794 static void roxterm_set_colour_scheme_handler(ROXTermData *roxterm,
3795         const char *name)
3796 {
3797     if (!roxterm_verify_id(roxterm))
3798         return;
3799     roxterm_change_colour_scheme_by_name(roxterm, name);
3800 }
3801 
roxterm_set_shortcut_scheme_handler(ROXTermData * roxterm,const char * name)3802 static void roxterm_set_shortcut_scheme_handler(ROXTermData *roxterm,
3803         const char *name)
3804 {
3805     if (!roxterm_verify_id(roxterm))
3806         return;
3807 
3808     Options *shortcuts = shortcuts_open(name, TRUE);
3809     multi_win_set_shortcut_scheme(roxterm_get_win(roxterm), shortcuts);
3810     shortcuts_unref(shortcuts);
3811 }
3812 
get_profile_tab_pos(Options * profile)3813 static GtkPositionType get_profile_tab_pos(Options *profile)
3814 {
3815     switch (options_lookup_int_with_default(profile, "tab_pos", 0))
3816     {
3817         case 0:
3818             return GTK_POS_TOP;
3819         case 1:
3820             return GTK_POS_BOTTOM;
3821         case 2:
3822             return GTK_POS_LEFT;
3823         case 3:
3824             return GTK_POS_RIGHT;
3825         case 4:
3826             return -1;
3827         default:
3828             return GTK_POS_TOP;
3829     }
3830 }
3831 
roxterm_get_tab_pos(const ROXTermData * roxterm)3832 inline static GtkPositionType roxterm_get_tab_pos(const ROXTermData *roxterm)
3833 {
3834     return get_profile_tab_pos(roxterm->profile);
3835 }
3836 
roxterm_get_always_show_tabs(const ROXTermData * roxterm)3837 static gboolean roxterm_get_always_show_tabs(const ROXTermData *roxterm)
3838 {
3839     return (gboolean) options_lookup_int_with_default(roxterm->profile,
3840             "always_show_tabs", TRUE);
3841 }
3842 
3843 /* Takes over ownership of profile and non-const strings;
3844  * on exit size_on_cli indicates whether dimensions were given in *geom;
3845  * geom is freed and set to NULL if it's invalid
3846  */
roxterm_data_new(double zoom_factor,const char * directory,char * profile_name,Options * profile,gboolean maximise,const char * colour_scheme_name,char ** geom,gboolean * size_on_cli,char ** env)3847 static ROXTermData *roxterm_data_new(double zoom_factor, const char *directory,
3848         char *profile_name, Options *profile, gboolean maximise,
3849         const char *colour_scheme_name,
3850         char **geom, gboolean *size_on_cli, char **env)
3851 {
3852     ROXTermData *roxterm = g_new0(ROXTermData, 1);
3853     int width, height, x, y;
3854     (void) profile_name;
3855 
3856     roxterm->status_icon_name = NULL;
3857     roxterm->target_zoom_factor = zoom_factor;
3858     roxterm->current_zoom_factor = 1.0;
3859     if (!roxterm->target_zoom_factor)
3860     {
3861         roxterm->target_zoom_factor = 1.0;
3862         roxterm->zoom_index = -1;
3863     }
3864     else
3865     {
3866         roxterm->zoom_index = multi_win_get_nearest_index_for_zoom(
3867                 roxterm->target_zoom_factor);
3868     }
3869     roxterm->directory = directory ? g_strdup(directory) : g_get_current_dir();
3870     roxterm->profile = profile;
3871     if (size_on_cli)
3872         *size_on_cli = FALSE;
3873     if (*geom)
3874     {
3875         if (multi_win_parse_geometry(*geom, &width, &height, &x, &y, NULL))
3876         {
3877             roxterm->columns = width;
3878             roxterm->rows = height;
3879             if (size_on_cli)
3880                 *size_on_cli = TRUE;
3881             //g_debug("geom '%s' parsed to %dx%d", *geom, width, height);
3882         }
3883         else
3884         {
3885             dlg_warning(NULL, _("Invalid geometry specification"));
3886             *geom = NULL;
3887         }
3888     }
3889     if (!*geom)
3890     {
3891         roxterm_default_size_func(roxterm, &roxterm->columns, &roxterm->rows);
3892         //g_debug("Using default size %dx%d", roxterm->columns, roxterm->rows);
3893     }
3894     roxterm->maximise = maximise;
3895     roxterm->borderless = options_lookup_int_with_default(profile,
3896                                         "borderless", 0);
3897     if (colour_scheme_name)
3898     {
3899         roxterm->colour_scheme = colour_scheme_lookup_and_ref
3900             (colour_scheme_name);
3901     }
3902     roxterm->pid = -1;
3903     roxterm->env = global_options_copy_strv(env);
3904     /*roxterm->file_match_tag[0] = roxterm->file_match_tag[1] = -1;*/
3905     roxterm->exit_action = Roxterm_ChildExitNotOverridden;
3906     return roxterm;
3907 }
3908 
roxterm_launch(char ** env)3909 void roxterm_launch(char **env)
3910 {
3911     GtkPositionType tab_pos;
3912     gboolean always_show_tabs;
3913     char *shortcut_scheme;
3914     Options *shortcuts;
3915     char *geom = options_lookup_string(global_options, "geometry");
3916     gboolean size_on_cli = FALSE;
3917     char *profile_name =
3918             global_options_lookup_string_with_default("profile", "Default");
3919     char *colour_scheme_name =
3920             global_options_lookup_string_with_default("colour_scheme", "GTK");
3921     Options *profile = dynamic_options_lookup_and_ref(roxterm_get_profiles(),
3922             profile_name, "roxterm profile");
3923     MultiWin *win = NULL;
3924     ROXTermData *roxterm = roxterm_data_new(
3925             global_options_lookup_double("zoom"),
3926             global_options_directory,
3927             profile_name, profile,
3928             global_options_maximise ||
3929                     options_lookup_int_with_default(profile,
3930                             "maximise", 0),
3931             colour_scheme_name,
3932             &geom, &size_on_cli, env);
3933     int show_add_tab_btn;
3934 
3935     if (!size_on_cli)
3936     {
3937         roxterm_default_size_func(roxterm, &roxterm->columns, &roxterm->rows);
3938     }
3939     if (global_options_commandv)
3940     {
3941         roxterm->commandv = global_options_copy_strv(global_options_commandv);
3942     }
3943     tab_pos = roxterm_get_tab_pos(roxterm);
3944     always_show_tabs = roxterm_get_always_show_tabs(roxterm);
3945     shortcut_scheme = global_options_lookup_string_with_default
3946         ("shortcut_scheme", "Default");
3947     shortcuts = shortcuts_open(shortcut_scheme, FALSE);
3948     if (global_options_tab)
3949     {
3950         ROXTermData *partner;
3951         char *wtitle = global_options_lookup_string("title");
3952         MultiWin *best_inactive = NULL;
3953         GList *link;
3954 
3955         win = NULL;
3956         for (link = multi_win_all; link; link = g_list_next(link))
3957         {
3958             GtkWidget *w;
3959             gboolean title_ok;
3960 
3961             win = link->data;
3962             w = multi_win_get_widget(win);
3963             title_ok = !wtitle ||
3964                     !g_strcmp0(multi_win_get_title_template(win), wtitle);
3965             if (gtk_window_is_active(GTK_WINDOW(w)) && title_ok)
3966             {
3967                 break;
3968             }
3969             if (title_ok && !best_inactive)
3970                 best_inactive = win;
3971             win = NULL;
3972         }
3973         if (!win)
3974         {
3975             if (best_inactive)
3976                 win = best_inactive;
3977         }
3978         partner = win ? multi_win_get_user_data_for_current_tab(win) : NULL;
3979         if (partner)
3980         {
3981             GtkWidget *gwin = multi_win_get_widget(win);
3982 
3983             roxterm->dont_lookup_dimensions = TRUE;
3984             roxterm->target_zoom_factor = partner->target_zoom_factor;
3985             roxterm->zoom_index = partner->zoom_index;
3986             if (partner->pango_desc)
3987             {
3988                 roxterm->pango_desc =
3989                         pango_font_description_copy(partner->pango_desc);
3990                 roxterm->current_zoom_factor = partner->current_zoom_factor;
3991             }
3992             else
3993             {
3994                 roxterm->current_zoom_factor = 1.0;
3995             }
3996             /* roxterm_data_clone needs to see a widget to get geometry
3997              * correct */
3998             roxterm->widget = partner->widget;
3999             //g_debug("Assigned partner tab %p to roxterm %p",
4000             //        partner->tab, roxterm);
4001             roxterm->tab = partner->tab;
4002             multi_tab_new(win, roxterm);
4003             if (gtk_widget_get_visible(gwin))
4004                 gtk_window_present(GTK_WINDOW(gwin));
4005         }
4006         else
4007         {
4008             global_options_tab = FALSE;
4009         }
4010     }
4011     if (0 <= global_options_atexit && global_options_atexit <= 3)
4012     {
4013         roxterm->exit_action = global_options_atexit;
4014         roxterm->override_exit_action = TRUE;
4015         global_options_atexit = -1;
4016     }
4017 
4018     gboolean borderless = roxterm->borderless | global_options_borderless;
4019 
4020     show_add_tab_btn = options_lookup_int_with_default(roxterm->profile,
4021             "show_add_tab_btn", 1);
4022     if (global_options_tab)
4023     {
4024         global_options_tab = FALSE;
4025     }
4026     else if (global_options_fullscreen ||
4027             options_lookup_int_with_default(roxterm->profile, "full_screen", 0))
4028     {
4029         global_options_fullscreen = FALSE;
4030         win = multi_win_new_fullscreen(shortcuts,
4031                 roxterm->zoom_index, roxterm,
4032                 tab_pos, borderless, always_show_tabs, show_add_tab_btn);
4033     }
4034     else if (roxterm->maximise)
4035     {
4036         global_options_maximise = FALSE;
4037         win = multi_win_new_maximised(shortcuts,
4038                 roxterm->zoom_index, roxterm,
4039                 tab_pos, borderless, always_show_tabs, show_add_tab_btn);
4040     }
4041     else
4042     {
4043         if (!size_on_cli)
4044         {
4045             if (geom)
4046             {
4047                 char *old_geom = geom;
4048 
4049                 geom = g_strdup_printf("%dx%d%s",
4050                         roxterm->columns, roxterm->rows, old_geom);
4051                 g_free(old_geom);
4052             }
4053             else
4054             {
4055                 geom = g_strdup_printf("%dx%d",
4056                         roxterm->columns, roxterm->rows);
4057             }
4058         }
4059         win = multi_win_new_with_geom(shortcuts,
4060                 roxterm->zoom_index, roxterm, geom,
4061                 tab_pos, borderless, always_show_tabs, show_add_tab_btn);
4062     }
4063     g_free(geom);
4064 
4065     roxterm_data_delete(roxterm);
4066     g_free(shortcut_scheme);
4067     g_free(colour_scheme_name);
4068     g_free(profile_name);
4069 
4070     //g_debug("call roxterm_force_resize_now from %s:%d", __FILE__, __LINE__);
4071     //roxterm_force_resize_now(win);
4072 }
4073 
roxterm_tab_to_new_window(MultiWin * win,MultiTab * tab,MultiWin * old_win)4074 static void roxterm_tab_to_new_window(MultiWin *win, MultiTab *tab,
4075         MultiWin *old_win)
4076 {
4077     ROXTermData *roxterm = multi_tab_get_user_data(tab);
4078     ROXTermData *match_roxterm;
4079     MultiTab *match_tab = multi_win_get_current_tab(win);
4080     GtkWindow *old_gwin = old_win ?
4081             GTK_WINDOW(multi_win_get_widget(old_win)) : NULL;
4082 
4083     if (old_gwin)
4084     {
4085         g_signal_handler_disconnect(old_gwin, roxterm->win_state_changed_tag);
4086     }
4087     roxterm_attach_state_changed_handler(roxterm);
4088     if (!match_tab)
4089         return;
4090     match_roxterm = multi_tab_get_user_data(match_tab);
4091     if (roxterm == match_roxterm)
4092     {
4093         return;
4094     }
4095     roxterm_match_text_size(roxterm, match_roxterm);
4096 }
4097 
roxterm_get_disable_menu_shortcuts(ROXTermData * roxterm,gboolean * general,gboolean * tabs)4098 static void roxterm_get_disable_menu_shortcuts(ROXTermData *roxterm,
4099     gboolean *general, gboolean *tabs)
4100 {
4101     if (general)
4102     {
4103         *general = options_lookup_int_with_default(roxterm->profile,
4104             "disable_menu_shortcuts", FALSE);
4105     }
4106     if (tabs)
4107     {
4108         *tabs = options_lookup_int_with_default(roxterm->profile,
4109             "disable_tab_menu_shortcuts", FALSE);
4110     }
4111 }
4112 
4113 typedef struct {
4114     guint ntabs;
4115     int warn;
4116     gboolean only_running;
4117 } DontShowData;
4118 
dont_show_again_toggled(GtkToggleButton * button,DontShowData * d)4119 static void dont_show_again_toggled(GtkToggleButton *button, DontShowData *d)
4120 {
4121     int val = 0;
4122 
4123     if (gtk_toggle_button_get_active(button))
4124     {
4125         if (!d->ntabs)
4126             val = 2;
4127         else if (d->ntabs > 1)
4128             val = 0;
4129         else
4130             val = 1;
4131     }
4132     else
4133     {
4134         val = d->warn;
4135     }
4136     options_set_int(global_options, "warn_close", val);
4137     options_file_save(global_options->kf, "Global");
4138 }
4139 
only_running_toggled(GtkToggleButton * button,DontShowData * d)4140 static void only_running_toggled(GtkToggleButton *button, DontShowData *d)
4141 {
4142     (void) d;
4143     options_set_int(global_options, "only_warn_running",
4144             gtk_toggle_button_get_active(button));
4145     options_file_save(global_options->kf, "Global");
4146 }
4147 
check_pid_has_children(pid_t pid)4148 static gboolean check_pid_has_children(pid_t pid)
4149 {
4150     int fd = 0;
4151     ssize_t count = -1;
4152     char *buf = g_strdup_printf("/proc/%d/task/%d/children", pid, pid);
4153     size_t l = strlen(buf);
4154 
4155     fd = open(buf, O_RDONLY);
4156     if (fd == -1)
4157     {
4158         g_warning("Failed to open %s\n", buf);
4159         g_free(buf);
4160     }
4161     else
4162     {
4163         memset(buf, 0, l);
4164         count = read(fd, buf, l);
4165         if (count == -1)
4166             g_warning("Failed to read %s\n", buf);
4167         close(fd);
4168     }
4169     return count > 0;
4170 }
4171 
roxterm_check_is_running(ROXTermData * roxterm)4172 static gboolean roxterm_check_is_running(ROXTermData *roxterm)
4173 {
4174     if (roxterm && roxterm->running)
4175     {
4176         return roxterm->is_shell ? check_pid_has_children(roxterm->pid) : TRUE;
4177     }
4178     return FALSE;
4179 }
4180 
check_each_tab_running(MultiTab * tab,void * data)4181 static void check_each_tab_running(MultiTab *tab, void *data)
4182 {
4183     if (roxterm_check_is_running((ROXTermData *) multi_tab_get_user_data(tab)))
4184         *((gboolean *) data) = TRUE;
4185 }
4186 
roxterm_delete_handler(GtkWindow * gtkwin,GdkEvent * event,gpointer data)4187 static gboolean roxterm_delete_handler(GtkWindow *gtkwin, GdkEvent *event,
4188         gpointer data)
4189 {
4190     GtkWidget *dialog;
4191     GtkWidget *noshow;
4192     GtkWidget *only_running;
4193     gboolean response;
4194     DontShowData d;
4195     const char *msg;
4196     MultiWin *win = event ? data : NULL;
4197     ROXTermData *roxterm = event ? NULL : data;
4198     GtkWidget *ca_box;
4199     gboolean running = FALSE;
4200 
4201 
4202     d.warn = global_options_lookup_int_with_default("warn_close", 3);
4203     d.only_running = global_options_lookup_int_with_default("only_warn_running",
4204             FALSE);
4205     d.ntabs = win ? multi_win_get_ntabs(win) : 0;
4206     if ((!win && d.warn < 3) || !d.warn || (d.warn == 1 && d.ntabs <= 1))
4207         return FALSE;
4208     if (d.only_running)
4209     {
4210         if (win)
4211         {
4212             multi_win_foreach_tab(win, check_each_tab_running, &running);
4213             if (!running)
4214                 return FALSE;
4215         }
4216         else
4217         {
4218             if (!roxterm_check_is_running(roxterm))
4219                 return FALSE;
4220         }
4221     }
4222 
4223     switch (d.ntabs)
4224     {
4225         case 0:
4226             msg = _("Closing this ROXTerm tab may cause loss of data. "
4227                     "Are you sure you want to continue?");
4228             break;
4229         case 1:
4230             msg = _("You are about to close a ROXTerm window; this may cause "
4231                     "loss of data. Are you sure you want to continue?");
4232             break;
4233         default:
4234             msg = _("You are about to close a window containing multiple "
4235                     "ROXTerm tabs; this may cause loss of data. Are you sure "
4236                     "you want to continue?");
4237             break;
4238     }
4239     dialog = gtk_message_dialog_new(gtkwin,
4240             GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
4241             GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO,
4242             "%s", msg);
4243     gtk_window_set_title(GTK_WINDOW(dialog), _("ROXTerm: Confirm close"));
4244     gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_NO);
4245 
4246     ca_box = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
4247 
4248     noshow = gtk_check_button_new_with_mnemonic(_("_Don't show this again"));
4249     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(noshow), FALSE);
4250     g_signal_connect(noshow, "toggled",
4251             G_CALLBACK(dont_show_again_toggled), &d);
4252     gtk_box_pack_start(GTK_BOX(ca_box), noshow, FALSE, FALSE, 0);
4253     gtk_widget_show(noshow);
4254 
4255     only_running = gtk_check_button_new_with_mnemonic(
4256             _("Only warn if tasks are still _running"));
4257     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(only_running),
4258             d.only_running);
4259     g_signal_connect(only_running, "toggled",
4260             G_CALLBACK(only_running_toggled), &d);
4261     gtk_box_pack_start(GTK_BOX(ca_box), only_running, FALSE, FALSE, 0);
4262     gtk_widget_show(only_running);
4263 
4264     response = gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_YES;
4265     gtk_widget_destroy(dialog);
4266     return response;
4267 }
4268 
roxterm_init(void)4269 void roxterm_init(void)
4270 {
4271     resources_access_icon();
4272     gtk_window_set_default_icon_name("roxterm");
4273 
4274     optsdbus_listen_for_opt_signals(roxterm_opt_signal_handler);
4275     optsdbus_listen_for_stuff_changed_signals(roxterm_stuff_changed_handler);
4276     optsdbus_listen_for_set_profile_signals(
4277             (OptsDBusSetProfileHandler) roxterm_set_profile_handler);
4278     optsdbus_listen_for_set_colour_scheme_signals(
4279             (OptsDBusSetProfileHandler) roxterm_set_colour_scheme_handler);
4280     optsdbus_listen_for_set_shortcut_scheme_signals(
4281             (OptsDBusSetProfileHandler) roxterm_set_shortcut_scheme_handler);
4282 
4283     multi_tab_init((MultiTabFiller) roxterm_multi_tab_filler,
4284         (MultiTabDestructor) roxterm_multi_tab_destructor,
4285         roxterm_connect_menu_signals,
4286         (MultiWinGeometryFunc) roxterm_geometry_func,
4287         (MultiWinSizeFunc) roxterm_size_func,
4288         (MultiWinDefaultSizeFunc) roxterm_default_size_func,
4289         roxterm_tab_to_new_window,
4290         (MultiWinZoomHandler) roxterm_set_zoom_factor,
4291         (MultiWinGetDisableMenuShortcuts) roxterm_get_disable_menu_shortcuts,
4292         (MultiWinGetTabPos) roxterm_get_tab_pos,
4293         (MultiWinDeleteHandler) roxterm_delete_handler,
4294         (MultiTabGetShowCloseButton) roxterm_get_show_tab_close_button,
4295         (MultiTabGetNewTabAdjacent) roxterm_get_new_tab_adjacent,
4296         (MultiTabConnectMiscSignals) roxterm_connect_misc_signals
4297         );
4298 }
4299 
roxterm_spawn_command_line(const gchar * command_line,const char * cwd,char ** env,GError ** error)4300 gboolean roxterm_spawn_command_line(const gchar *command_line,
4301         const char *cwd, char **env, GError **error)
4302 {
4303     char **env2 = NULL;
4304     int argc;
4305     char **argv = NULL;
4306     gboolean result;
4307 
4308     cwd = roxterm_check_cwd(cwd);
4309     result = g_shell_parse_argv(command_line, &argc, &argv, error);
4310     if (result)
4311     {
4312         result = g_spawn_async(cwd, argv, env, G_SPAWN_SEARCH_PATH,
4313                 NULL, NULL, NULL, error);
4314     }
4315     if (env2)
4316         g_strfreev(env2);
4317     if (argv)
4318         g_strfreev(argv);
4319     return result;
4320 }
4321 
roxterm_spawn(ROXTermData * roxterm,const char * command,ROXTerm_SpawnType spawn_type)4322 void roxterm_spawn(ROXTermData *roxterm, const char *command,
4323     ROXTerm_SpawnType spawn_type)
4324 {
4325     GError *error = NULL;
4326     GtkPositionType tab_pos;
4327     char *cwd;
4328     MultiWin *win = roxterm_get_win(roxterm);
4329     MultiTab *tab;
4330 
4331     tab_pos = roxterm_get_tab_pos(roxterm);
4332     switch (spawn_type)
4333     {
4334         case ROXTerm_SpawnNewWindow:
4335             roxterm->special_command = g_strdup(command);
4336             roxterm->no_respawn = TRUE;
4337             multi_win_new(multi_win_get_shortcut_scheme(win),
4338                     roxterm->zoom_index, roxterm, tab_pos,
4339                     roxterm->borderless,
4340                     multi_win_get_always_show_tabs(win),
4341                     options_lookup_int_with_default(roxterm->profile,
4342                             "show_add_tab_btn", 1));
4343             break;
4344         case ROXTerm_SpawnNewTab:
4345             roxterm->special_command = g_strdup(command);
4346             roxterm->no_respawn = TRUE;
4347             tab = multi_tab_new(win, roxterm);
4348             break;
4349         default:
4350             cwd = roxterm_get_cwd(roxterm);
4351             roxterm_spawn_command_line(command, cwd, roxterm->env, &error);
4352             if (error)
4353             {
4354                 dlg_warning(roxterm_get_toplevel(roxterm),
4355                         _("Unable to spawn command %s: %s"),
4356                     command, error->message);
4357             }
4358             g_free(cwd);
4359             break;
4360     }
4361 }
4362 
roxterm_get_vte_terminal(ROXTermData * roxterm)4363 VteTerminal *roxterm_get_vte_terminal(ROXTermData *roxterm)
4364 {
4365     return VTE_TERMINAL(roxterm->widget);
4366 }
4367 
get_options_leafname(Options * opts)4368 static const char *get_options_leafname(Options *opts)
4369 {
4370     char *slash = strrchr(opts->name, G_DIR_SEPARATOR);
4371 
4372     return slash ? slash + 1 : opts->name;
4373 }
4374 
roxterm_get_profile_name(ROXTermData * roxterm)4375 const char *roxterm_get_profile_name(ROXTermData *roxterm)
4376 {
4377     return get_options_leafname(roxterm->profile);
4378 }
4379 
roxterm_get_colour_scheme_name(ROXTermData * roxterm)4380 const char *roxterm_get_colour_scheme_name(ROXTermData *roxterm)
4381 {
4382     return get_options_leafname(roxterm->colour_scheme);
4383 }
4384 
roxterm_get_shortcuts_scheme_name(ROXTermData * roxterm)4385 const char *roxterm_get_shortcuts_scheme_name(ROXTermData *roxterm)
4386 {
4387     return get_options_leafname(roxterm->profile);
4388 }
4389 
roxterm_get_nonfs_dimensions(ROXTermData * roxterm,int * cols,int * rows)4390 void roxterm_get_nonfs_dimensions(ROXTermData *roxterm, int *cols, int *rows)
4391 {
4392     /* FIXME: This gets current dimensions whether we're full-screen/maximised
4393      * or not, but this is for session management so presumably manager will
4394      * remember size anyway.
4395      */
4396     VteTerminal *vte = VTE_TERMINAL(roxterm->widget);
4397 
4398     *cols = vte_terminal_get_column_count(vte);
4399     *rows = vte_terminal_get_row_count(vte);
4400 }
4401 
roxterm_get_zoom_factor(ROXTermData * roxterm)4402 double roxterm_get_zoom_factor(ROXTermData *roxterm)
4403 {
4404     return roxterm->current_zoom_factor;
4405 }
4406 
roxterm_get_actual_commandv(ROXTermData * roxterm)4407 char const * const *roxterm_get_actual_commandv(ROXTermData *roxterm)
4408 {
4409     return (char const * const *) roxterm->actual_commandv;
4410 }
4411 
4412 typedef struct {
4413     const char *client_id;
4414     gboolean session_tag_open;
4415     MultiWin *win;
4416     MultiTab *tab;
4417     ROXTermData *roxterm;
4418     gboolean borderless;
4419     gboolean maximised;
4420     gboolean fullscreen;
4421     char *geom;
4422     //int width, height;
4423     PangoFontDescription *fdesc;
4424     char *window_title;
4425     char *window_title_template;
4426     double zoom_factor;
4427     char *title;
4428     char *role;
4429     char **commandv;
4430     int argc;
4431     int argn;
4432     char *tab_name;
4433     char *tab_title_template;
4434     char *tab_title;
4435     gboolean win_title_template_locked;
4436     gboolean tab_title_template_locked;
4437     gboolean current;
4438     MultiTab *active_tab;
4439 } _ROXTermParseContext;
4440 
parse_open_win(_ROXTermParseContext * rctx,const char ** attribute_names,const char ** attribute_values,GError ** error)4441 static void parse_open_win(_ROXTermParseContext *rctx,
4442         const char **attribute_names, const char **attribute_values,
4443         GError **error)
4444 {
4445     int n;
4446     const char *geom = NULL;
4447     const char *role = NULL;
4448     const char *font = NULL;
4449     const char *shortcuts_name = NULL;
4450     const char *title_template = NULL;
4451     const char *title = NULL;
4452     gboolean show_mbar = TRUE;
4453     gboolean show_tabs = FALSE;
4454     gboolean show_add_tab_btn = TRUE;
4455     gboolean disable_menu_shortcuts = FALSE;
4456     gboolean disable_tab_shortcuts = FALSE;
4457     GtkPositionType tab_pos = GTK_POS_TOP;
4458     MultiWin *win;
4459     GtkWindow *gwin;
4460     Options *shortcuts;
4461 
4462     rctx->fullscreen = FALSE;
4463     rctx->maximised = FALSE;
4464     rctx->borderless = FALSE;
4465     rctx->zoom_factor = 1.0;
4466     rctx->active_tab = NULL;
4467     rctx->win_title_template_locked = FALSE;
4468     for (n = 0; attribute_names[n]; ++n)
4469     {
4470         const char *a = attribute_names[n];
4471         const char *v = attribute_values[n];
4472 
4473         if (!strcmp(a, "geometry"))
4474         {
4475             geom = g_strdup(v);
4476             //sscanf(v, "%dx%d+", &rctx->width, &rctx->height);
4477         }
4478         else if (!strcmp(a, "title_template"))
4479             title_template = v;
4480         else if (!strcmp(a, "font"))
4481             font = v;
4482         else if (!strcmp(a, "title"))
4483             title = v;
4484         else if (!strcmp(a, "role"))
4485             role = v;
4486         else if (!strcmp(a, "shortcut_scheme"))
4487             shortcuts_name = v;
4488         else if (!strcmp(a, "show_menubar"))
4489             show_mbar = (strcmp(v, "0") != 0);
4490         else if (!strcmp(a, "always_show_tabs"))
4491             show_tabs = strcmp(v, "0");
4492         else if (!strcmp(a, "tab_pos"))
4493             tab_pos = atoi(v);
4494         else if (!strcmp(a, "show_add_tab_btn"))
4495             show_add_tab_btn = strcmp(v, "0");
4496         else if (!strcmp(a, "disable_menu_shortcuts"))
4497             disable_menu_shortcuts = (strcmp(v, "0") != 0);
4498         else if (!strcmp(a, "disable_tab_shortcuts"))
4499             disable_tab_shortcuts = (strcmp(v, "0") != 0);
4500         else if (!strcmp(a, "maximised"))
4501             rctx->maximised = (strcmp(v, "0") != 0);
4502         else if (!strcmp(a, "fullscreen"))
4503             rctx->fullscreen = (strcmp(v, "0") != 0);
4504         else if (!strcmp(a, "borderless"))
4505             rctx->borderless = (strcmp(v, "0") != 0);
4506         else if (!strcmp(a, "zoom"))
4507             rctx->zoom_factor = atof(v);
4508         else if (!strcmp(a, "title_template_locked"))
4509             rctx->win_title_template_locked = atoi(v);
4510         else
4511         {
4512             *error = g_error_new(G_MARKUP_ERROR,
4513                     G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
4514                     _("Unknown <window> attribute '%s'"), a);
4515             return;
4516         }
4517     }
4518     SLOG("Opening window with title %s", title);
4519 
4520     shortcuts = shortcuts_open(shortcuts_name, FALSE);
4521     rctx->win = win = multi_win_new_blank(shortcuts,
4522             multi_win_get_nearest_index_for_zoom(rctx->zoom_factor),
4523             disable_menu_shortcuts, disable_tab_shortcuts, tab_pos, show_tabs,
4524             show_add_tab_btn);
4525     shortcuts_unref(shortcuts);
4526     /* Set role and title before and after adding tabs because docs are quite
4527      * vague about how these are used for restoring the session.
4528      */
4529     gwin = GTK_WINDOW(multi_win_get_widget(rctx->win));
4530     if (role && role[0])
4531     {
4532         rctx->role = g_strdup(role);
4533         gtk_window_set_role(gwin, role);
4534     }
4535     if (title_template && title_template[0])
4536     {
4537         rctx->window_title_template = g_strdup(title_template);
4538         multi_win_set_title_template(win, title_template);
4539     }
4540     if (rctx->window_title)
4541     {
4542         multi_win_set_title(win, rctx->window_title);
4543     }
4544     if (geom && geom[0])
4545     {
4546         rctx->geom = g_strdup(geom);
4547     }
4548     if (font && font[0])
4549     {
4550         rctx->fdesc = pango_font_description_from_string(font);
4551         resize_pango_for_zoom(rctx->fdesc, rctx->zoom_factor);
4552     }
4553     multi_win_set_borderless(win, rctx->borderless);
4554     multi_win_set_show_menu_bar(win, show_mbar);
4555     multi_win_set_always_show_tabs(win, show_tabs);
4556     if (title && title[0])
4557         rctx->window_title = g_strdup(title);
4558 }
4559 
close_win_tag(_ROXTermParseContext * rctx)4560 static void close_win_tag(_ROXTermParseContext *rctx)
4561 {
4562     if (!multi_win_get_num_tabs(rctx->win))
4563     {
4564         g_warning(_("Session contains window with no valid tabs"));
4565         multi_win_delete(rctx->win);
4566     }
4567     else
4568     {
4569         GtkWindow *gwin = GTK_WINDOW(multi_win_get_widget(rctx->win));
4570         gtk_widget_realize(GTK_WIDGET(gwin));
4571 
4572         if (rctx->borderless)
4573         {
4574             multi_win_set_borderless(rctx->win, TRUE);
4575         }
4576         if (rctx->geom)
4577         {
4578             /* Need to show children before parsing geom */
4579             gtk_widget_realize(gtk_bin_get_child(GTK_BIN(gwin)));
4580             gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(gwin)));
4581             multi_win_set_initial_geometry(rctx->win, rctx->geom,
4582                     rctx->active_tab);
4583         }
4584         if (rctx->fullscreen)
4585         {
4586             multi_win_set_fullscreen(rctx->win, TRUE);
4587         }
4588         else if (rctx->maximised)
4589         {
4590             gtk_window_maximize(gwin);
4591         }
4592         if (rctx->window_title_template)
4593         {
4594             multi_win_set_title_template(rctx->win,
4595                     rctx->window_title_template);
4596         }
4597         if (rctx->role)
4598             gtk_window_set_role(gwin, rctx->role);
4599         if (rctx->window_title)
4600             multi_win_set_title(rctx->win, rctx->window_title);
4601         multi_win_set_title_template_locked(rctx->win,
4602                 rctx->win_title_template_locked);
4603         multi_win_show(rctx->win);
4604         if (rctx->active_tab)
4605         {
4606             multi_win_select_tab(rctx->win, rctx->active_tab);
4607             /*
4608             roxterm_tab_selection_handler(
4609                     multi_tab_get_user_data(rctx->active_tab),
4610                     rctx->active_tab);
4611             */
4612         }
4613     }
4614     rctx->win = NULL;
4615     g_free(rctx->role);
4616     rctx->role = NULL;
4617     g_free(rctx->window_title);
4618     rctx->window_title = NULL;
4619     g_free(rctx->window_title_template);
4620     rctx->window_title_template = NULL;
4621     if (rctx->fdesc)
4622     {
4623         pango_font_description_free(rctx->fdesc);
4624         rctx->fdesc = NULL;
4625     }
4626     g_free(rctx->geom);
4627     rctx->geom = NULL;
4628 }
4629 
4630 extern char **environ;
4631 
parse_open_tab(_ROXTermParseContext * rctx,const char ** attribute_names,const char ** attribute_values)4632 static void parse_open_tab(_ROXTermParseContext *rctx,
4633         const char **attribute_names, const char **attribute_values)
4634 {
4635     const char *profile_name = "Default";
4636     const char *colours_name = "GTK";
4637     const char *cwd = NULL;
4638     int n;
4639     ROXTermData *roxterm;
4640     Options *profile;
4641 
4642     rctx->current = FALSE;
4643     rctx->tab_title_template_locked = FALSE;
4644     for (n = 0; attribute_names[n]; ++n)
4645     {
4646         const char *a = attribute_names[n];
4647         const char *v = attribute_values[n];
4648 
4649         if (!strcmp(a, "profile"))
4650             profile_name = v;
4651         else if (!strcmp(a, "colour_scheme"))
4652             colours_name = v;
4653         else if (!strcmp(a, "cwd"))
4654             cwd = v;
4655         else if (!strcmp(a, "title_template"))
4656             rctx->tab_title_template = g_strdup(v);
4657         else if (!strcmp(a, "window_title"))
4658             rctx->tab_title = g_strdup(v);
4659         else if (!strcmp(a, "current"))
4660             rctx->current = (strcmp(v, "0") != 0);
4661         else if (!strcmp(a, "title_template_locked"))
4662             rctx->tab_title_template_locked = atoi(v);
4663         /* Ignore unknown tags, probably caused by deprecated settings
4664         else
4665         {
4666             *error = g_error_new(G_MARKUP_ERROR,
4667                     G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
4668                     _("Unknown <tab> attribute '%s'"), a);
4669             return;
4670         }
4671         */
4672     }
4673 
4674     profile = dynamic_options_lookup_and_ref(roxterm_get_profiles(),
4675             profile_name, "roxterm profile");
4676     roxterm = roxterm_data_new(rctx->zoom_factor, cwd,
4677             g_strdup(profile_name), profile,
4678             rctx->maximised, colours_name,
4679             &rctx->geom, NULL, environ);
4680     roxterm->from_session = TRUE;
4681     roxterm->dont_lookup_dimensions = TRUE;
4682     if (rctx->fdesc)
4683         roxterm->pango_desc = pango_font_description_copy(rctx->fdesc);
4684     rctx->roxterm = roxterm;
4685 }
4686 
close_tab_tag(_ROXTermParseContext * rctx)4687 static void close_tab_tag(_ROXTermParseContext *rctx)
4688 {
4689     ROXTermData *roxterm = rctx->roxterm;
4690 
4691     if (rctx->commandv)
4692     {
4693         if (rctx->commandv[0])
4694         {
4695             roxterm->commandv = rctx->commandv;
4696         }
4697         else
4698         {
4699             g_warning(_("<command> with no arguments"));
4700         }
4701     }
4702     rctx->commandv = NULL;
4703     rctx->tab = multi_tab_new(rctx->win, roxterm);
4704     if (rctx->current)
4705         rctx->active_tab = rctx->tab;
4706     if (rctx->tab_title_template && rctx->tab_title_template[0])
4707     {
4708         multi_tab_set_window_title_template(rctx->tab,
4709                 rctx->tab_title_template);
4710     }
4711     if (rctx->tab_title && rctx->tab_title[0])
4712         multi_tab_set_window_title(rctx->tab, rctx->tab_title);
4713     multi_tab_set_title_template_locked(rctx->tab,
4714             rctx->tab_title_template_locked);
4715     g_free(rctx->tab_title_template);
4716     rctx->tab_title_template = NULL;
4717     g_free(rctx->tab_title);
4718     rctx->tab_title = NULL;
4719     roxterm_data_delete(roxterm);
4720     /*
4721     roxterm = multi_tab_get_user_data(rctx->tab);
4722     VteTerminal *vte = VTE_TERMINAL(roxterm->widget);
4723     roxterm_set_vte_size(roxterm, vte, rctx->width, rctx->height);
4724     */
4725 }
4726 
parse_open_command(_ROXTermParseContext * rctx,const char ** attribute_names,const char ** attribute_values,GError ** error)4727 static void parse_open_command(_ROXTermParseContext *rctx,
4728         const char **attribute_names, const char **attribute_values,
4729         GError **error)
4730 {
4731     int n;
4732 
4733     rctx->argn = 0;
4734     for (n = 0; attribute_names[n]; ++n)
4735     {
4736         const char *a = attribute_names[n];
4737         const char *v = attribute_values[n];
4738 
4739         if (!strcmp(a, "argc"))
4740             rctx->argc = atoi(v);
4741         else
4742         {
4743             *error = g_error_new(G_MARKUP_ERROR,
4744                     G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
4745                     _("Unknown <command> attribute '%s'"), a);
4746             return;
4747         }
4748     }
4749     if (rctx->argc)
4750     {
4751         rctx->commandv = g_new0(char *, rctx->argc + 1);
4752     }
4753     else
4754     {
4755         *error = g_error_new(G_MARKUP_ERROR,
4756                 G_MARKUP_ERROR_MISSING_ATTRIBUTE,
4757                 _("<command> missing argc attribute"));
4758     }
4759 }
4760 
parse_open_arg(_ROXTermParseContext * rctx,const char ** attribute_names,const char ** attribute_values,GError ** error)4761 static void parse_open_arg(_ROXTermParseContext *rctx,
4762         const char **attribute_names, const char **attribute_values,
4763         GError **error)
4764 {
4765     int n;
4766     const char *arg = NULL;
4767 
4768     if (rctx->argn >= rctx->argc)
4769     {
4770         *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4771                 _("Too many <arg> elements for <command argc='%d>"),
4772                 rctx->argc);
4773         return;
4774     }
4775     for (n = 0; attribute_names[n]; ++n)
4776     {
4777         const char *a = attribute_names[n];
4778         const char *v = attribute_values[n];
4779 
4780         if (!strcmp(a, "s"))
4781         {
4782             arg = v;
4783         }
4784         else
4785         {
4786             *error = g_error_new(G_MARKUP_ERROR,
4787                     G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
4788                     _("Unknown <arg> attribute '%s'"), a);
4789             return;
4790         }
4791     }
4792     if (arg)
4793     {
4794         rctx->commandv[(rctx->argn)++] = g_strdup(arg);
4795     }
4796     else
4797     {
4798         *error = g_error_new(G_MARKUP_ERROR,
4799                 G_MARKUP_ERROR_MISSING_ATTRIBUTE,
4800                 _("<arg> missing s attribute"));
4801     }
4802 }
4803 
parse_roxterm_session_tag(_ROXTermParseContext * rctx,const char ** attribute_names,const char ** attribute_values,GError ** error)4804 static void parse_roxterm_session_tag(_ROXTermParseContext *rctx,
4805         const char **attribute_names, const char **attribute_values,
4806         GError **error)
4807 {
4808     int n;
4809     (void) rctx;
4810     (void) attribute_values;
4811 
4812     for (n = 0; attribute_names[n]; ++n)
4813     {
4814         const char *a = attribute_names[n];
4815 
4816         if (strcmp(a, "id"))
4817         {
4818             *error = g_error_new(G_MARKUP_ERROR,
4819                     G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
4820                     _("Unknown <roxterm_session> attribute '%s'"), a);
4821             return;
4822         }
4823     }
4824 }
4825 
parse_start_element(GMarkupParseContext * context,const char * element_name,const char ** attribute_names,const char ** attribute_values,gpointer handle,GError ** error)4826 static void parse_start_element(GMarkupParseContext *context,
4827         const char *element_name,
4828         const char **attribute_names, const char **attribute_values,
4829         gpointer handle, GError **error)
4830 {
4831     _ROXTermParseContext *rctx = handle;
4832     (void) context;
4833 
4834     if (!strcmp(element_name, "roxterm_session"))
4835     {
4836         if (rctx->win)
4837         {
4838             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4839                     _("<roxterm_session> tag nested within <window>"));
4840         }
4841         else
4842         {
4843             rctx->session_tag_open = TRUE;
4844             parse_roxterm_session_tag(rctx,
4845                     attribute_names, attribute_values, error);
4846         }
4847     }
4848     else if (!strcmp(element_name, "window"))
4849     {
4850         if (!rctx->session_tag_open)
4851         {
4852             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4853                     _("<window> tag not within <roxterm_session>"));
4854         }
4855         else if (rctx->win)
4856         {
4857             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4858                     _("<window> tag nested within another <window>"));
4859         }
4860         else
4861         {
4862             parse_open_win(rctx, attribute_names, attribute_values, error);
4863         }
4864     }
4865     else if (!strcmp(element_name, "tab"))
4866     {
4867         if (!rctx->win)
4868         {
4869             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4870                     _("<tab> tag not within <window>"));
4871         }
4872         else if (rctx->roxterm)
4873         {
4874             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4875                     _("<tab> tag nested within another <tab>"));
4876         }
4877         else
4878         {
4879             parse_open_tab(rctx, attribute_names, attribute_values);
4880         }
4881     }
4882     else if (!strcmp(element_name, "command"))
4883     {
4884         if (!rctx->roxterm)
4885         {
4886             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4887                     _("<command> tag not within <tab>"));
4888         }
4889         else if (rctx->commandv)
4890         {
4891             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4892                     _("<command> tag nested within another <command>"));
4893         }
4894         else
4895         {
4896             parse_open_command(rctx, attribute_names, attribute_values, error);
4897         }
4898     }
4899     else if (!strcmp(element_name, "arg"))
4900     {
4901         if (!rctx->commandv)
4902         {
4903             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4904                     _("<arg> tag not within <command>"));
4905         }
4906         else
4907         {
4908             parse_open_arg(rctx, attribute_names, attribute_values, error);
4909         }
4910     }
4911     else
4912     {
4913         *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
4914                 _("Unknown element <%s>"), element_name);
4915     }
4916 }
4917 
parse_end_element(GMarkupParseContext * context,const char * element_name,gpointer handle,GError ** error)4918 static void parse_end_element(GMarkupParseContext *context,
4919         const char *element_name,
4920         gpointer handle, GError **error)
4921 {
4922     _ROXTermParseContext *rctx = handle;
4923     (void) context;
4924 
4925     if (!strcmp(element_name, "roxterm_session"))
4926     {
4927         if (!rctx->session_tag_open)
4928         {
4929             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4930                     _("Unmatched </roxterm_session>"));
4931         }
4932         else if (rctx->win)
4933         {
4934             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4935                     _("Attempt to close <roxterm_session> without "
4936                     "closing <window>"));
4937         }
4938         else
4939         {
4940             rctx->session_tag_open = FALSE;
4941         }
4942     }
4943     else if (!strcmp(element_name, "window"))
4944     {
4945         if (!rctx->win)
4946         {
4947             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4948                     _("Unmatched </window>"));
4949         }
4950         else if (rctx->roxterm)
4951         {
4952             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4953                     _("Attempt to close <window> without closing <tab>"));
4954         }
4955         else
4956         {
4957             close_win_tag(rctx);
4958         }
4959     }
4960     else if (!strcmp(element_name, "tab"))
4961     {
4962         if (!rctx->roxterm)
4963         {
4964             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4965                     _("Unmatched </tab>"));
4966         }
4967         else
4968         {
4969             close_tab_tag(rctx);
4970             rctx->tab = NULL;
4971             rctx->roxterm = NULL;
4972         }
4973     }
4974     else if (!strcmp(element_name, "command"))
4975     {
4976         if (!rctx->commandv)
4977         {
4978             *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
4979                     _("Unmatched </command>"));
4980         }
4981     }
4982     else if (strcmp(element_name, "arg"))
4983     {
4984         *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
4985                 _("Unknown closing element </%s>"), element_name);
4986     }
4987 }
4988 
parse_error(GMarkupParseContext * context,GError * error,gpointer handle)4989 static void parse_error(GMarkupParseContext *context, GError *error,
4990         gpointer handle)
4991 {
4992     _ROXTermParseContext *rctx = handle;
4993     (void) context;
4994 
4995     g_warning(_("Unable to parse data for session '%s': %s"),
4996             rctx->client_id, error->message);
4997 }
4998 
roxterm_load_session(const char * xml,gssize len,const char * client_id)4999 gboolean roxterm_load_session(const char *xml, gssize len,
5000         const char *client_id)
5001 {
5002     GMarkupParser pvtbl = { parse_start_element, parse_end_element,
5003             NULL, NULL, parse_error };
5004     _ROXTermParseContext *rctx = g_new0(_ROXTermParseContext, 1);
5005     GMarkupParseContext *pctx = g_markup_parse_context_new(&pvtbl,
5006             G_MARKUP_PREFIX_ERROR_POSITION, rctx, NULL);
5007     gboolean result;
5008     GError *error = NULL;
5009 
5010     rctx->client_id = client_id;
5011     result = g_markup_parse_context_parse(pctx, xml, len, &error);
5012     if (!error)
5013         result = g_markup_parse_context_end_parse(pctx, &error) & result;
5014     if (error)
5015     {
5016         g_critical("Error in session %s: %s", client_id, error->message);
5017         g_error_free(error);
5018         error = NULL;
5019     }
5020     SLOG("Session loaded, result %d", result);
5021     return result;
5022 }
5023 
roxterm_get_multi_win(ROXTermData * roxterm)5024 MultiWin *roxterm_get_multi_win(ROXTermData *roxterm)
5025 {
5026     return roxterm_get_win(roxterm);
5027 }
5028 
roxterm_get_vte(ROXTermData * roxterm)5029 VteTerminal *roxterm_get_vte(ROXTermData *roxterm)
5030 {
5031     return VTE_TERMINAL(roxterm->widget);
5032 }
5033 
roxterm_set_search(ROXTermData * roxterm,const char * pattern,guint flags,GError ** error)5034 gboolean roxterm_set_search(ROXTermData *roxterm,
5035         const char *pattern, guint flags, GError **error)
5036 {
5037     VteRegex *regex = NULL;
5038     char *cooked_pattern = NULL;
5039 
5040     roxterm->search_flags = flags;
5041 
5042     if (roxterm->search_pattern)
5043     {
5044         g_free(roxterm->search_pattern);
5045         roxterm->search_pattern = NULL;
5046     }
5047 
5048     if (pattern && pattern[0])
5049     {
5050         roxterm->search_pattern = g_strdup(pattern);
5051         if (!(flags & ROXTERM_SEARCH_AS_REGEX))
5052         {
5053             cooked_pattern = g_regex_escape_string(pattern, -1);
5054             pattern = cooked_pattern;
5055         }
5056         if (flags & ROXTERM_SEARCH_ENTIRE_WORD)
5057         {
5058             char *tmp = cooked_pattern;
5059 
5060             cooked_pattern = g_strdup_printf("\\<%s\\>", pattern);
5061             pattern = cooked_pattern;
5062             g_free(tmp);
5063         }
5064 
5065         regex = vte_regex_new_for_search(pattern, -1,
5066                 ((flags & ROXTERM_SEARCH_MATCH_CASE) ? 0 : PCRE2_CASELESS) |
5067                 PCRE2_NOTEMPTY,
5068                 error);
5069         g_free(cooked_pattern);
5070         if (!regex)
5071             return FALSE;
5072     }
5073 
5074     vte_terminal_search_set_regex(VTE_TERMINAL(roxterm->widget), regex, 0);
5075     vte_terminal_search_set_wrap_around(VTE_TERMINAL(roxterm->widget),
5076             flags & ROXTERM_SEARCH_WRAP);
5077     roxterm_shade_search_menu_items(roxterm);
5078     return TRUE;
5079 }
5080 
roxterm_get_search_pattern(ROXTermData * roxterm)5081 const char *roxterm_get_search_pattern(ROXTermData *roxterm)
5082 {
5083     return roxterm->search_pattern;
5084 }
5085 
roxterm_get_search_flags(ROXTermData * roxterm)5086 guint roxterm_get_search_flags(ROXTermData *roxterm)
5087 {
5088     return roxterm->search_flags;
5089 }
5090 
5091 /* vi:set sw=4 ts=4 et cindent cino= */
5092