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, ¤t_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, ¤t_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