1 /*
2 * Copyright © 2001, 2002 Havoc Pennington
3 * Copyright © 2002 Red Hat, Inc.
4 * Copyright © 2002 Sun Microsystems
5 * Copyright © 2003 Mariano Suarez-Alvarez
6 * Copyright © 2008 Christian Persch
7 * Copyright (C) 2012-2021 MATE Developers
8 *
9 * Mate-terminal is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * Mate-terminal is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include <config.h>
24
25 #include <string.h>
26 #include <stdlib.h>
27 #include <time.h>
28 #include <unistd.h>
29 #include <sys/types.h>
30
31 #include <glib.h>
32
33 #include <gio/gio.h>
34 #include <gtk/gtk.h>
35
36 #ifdef GDK_WINDOWING_X11
37 #include <gdk/gdkx.h>
38 #include <X11/Xatom.h>
39 #endif
40
41 #include "terminal-accels.h"
42 #include "terminal-app.h"
43 #include "terminal-intl.h"
44 #include "terminal-util.h"
45 #include "terminal-window.h"
46
47 void
terminal_util_set_unique_role(GtkWindow * window,const char * prefix)48 terminal_util_set_unique_role (GtkWindow *window, const char *prefix)
49 {
50 char *role;
51
52 role = g_strdup_printf ("%s-%d-%d-%d", prefix, getpid (), g_random_int (), (int) time (NULL));
53 gtk_window_set_role (window, role);
54 g_free (role);
55 }
56
57 /**
58 * terminal_util_show_error_dialog:
59 * @transient_parent: parent of the future dialog window;
60 * @weap_ptr: pointer to a #Widget pointer, to control the population.
61 * @error: a #GError, or %NULL
62 * @message_format: printf() style format string
63 *
64 * Create a #GtkMessageDialog window with the message, and present it, handling its buttons.
65 * If @weap_ptr is not #NULL, only create the dialog if <literal>*weap_ptr</literal> is #NULL
66 * (and in that * case, set @weap_ptr to be a weak pointer to the new dialog), otherwise just
67 * present <literal>*weak_ptr</literal>. Note that in this last case, the message <emph>will</emph>
68 * be changed.
69 */
70 void
terminal_util_show_error_dialog(GtkWindow * transient_parent,GtkWidget ** weak_ptr,GError * error,const char * message_format,...)71 terminal_util_show_error_dialog (GtkWindow *transient_parent,
72 GtkWidget **weak_ptr,
73 GError *error,
74 const char *message_format,
75 ...)
76 {
77 char *message;
78 va_list args;
79
80 if (message_format)
81 {
82 va_start (args, message_format);
83 message = g_strdup_vprintf (message_format, args);
84 va_end (args);
85 }
86 else message = NULL;
87
88 if (weak_ptr == NULL || *weak_ptr == NULL)
89 {
90 GtkWidget *dialog;
91 dialog = gtk_message_dialog_new (transient_parent,
92 GTK_DIALOG_DESTROY_WITH_PARENT,
93 GTK_MESSAGE_ERROR,
94 GTK_BUTTONS_OK,
95 message ? "%s" : NULL,
96 message);
97
98 if (error != NULL)
99 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
100 "%s", error->message);
101
102 g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (gtk_widget_destroy), NULL);
103
104 if (weak_ptr != NULL)
105 {
106 *weak_ptr = dialog;
107 g_object_add_weak_pointer (G_OBJECT (dialog), (void**)weak_ptr);
108 }
109
110 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
111
112 gtk_widget_show_all (dialog);
113 }
114 else
115 {
116 g_return_if_fail (GTK_IS_MESSAGE_DIALOG (*weak_ptr));
117
118 /* Sucks that there's no direct accessor for "text" property */
119 g_object_set (G_OBJECT (*weak_ptr), "text", message, NULL);
120
121 gtk_window_present (GTK_WINDOW (*weak_ptr));
122 }
123
124 g_free (message);
125 }
126
127 void
terminal_util_show_help(const char * topic,GtkWindow * parent)128 terminal_util_show_help (const char *topic,
129 GtkWindow *parent)
130 {
131 GError *error = NULL;
132 char *url;
133
134 if (topic)
135 {
136 url = g_strdup_printf ("help:mate-terminal/%s", topic);
137 }
138 else
139 {
140 url = g_strdup ("help:mate-terminal");
141 }
142
143 if (!gtk_show_uri_on_window (GTK_WINDOW (parent), url, gtk_get_current_event_time (), &error))
144 {
145 terminal_util_show_error_dialog (GTK_WINDOW (parent), NULL, error,
146 _("There was an error displaying help"));
147 g_error_free (error);
148 }
149
150 g_free (url);
151 }
152
153 /* sets accessible name and description for the widget */
154
155 void
terminal_util_set_atk_name_description(GtkWidget * widget,const char * name,const char * desc)156 terminal_util_set_atk_name_description (GtkWidget *widget,
157 const char *name,
158 const char *desc)
159 {
160 AtkObject *obj;
161
162 obj = gtk_widget_get_accessible (widget);
163
164 if (obj == NULL)
165 {
166 g_warning ("%s: for some reason widget has no GtkAccessible",
167 G_STRFUNC);
168 return;
169 }
170
171
172 if (!GTK_IS_ACCESSIBLE (obj))
173 return; /* This means GAIL is not loaded so we have the NoOp accessible */
174
175 g_return_if_fail (GTK_IS_ACCESSIBLE (obj));
176 if (desc)
177 atk_object_set_description (obj, desc);
178 if (name)
179 atk_object_set_name (obj, name);
180 }
181
182 void
terminal_util_open_url(GtkWidget * parent,const char * orig_url,TerminalURLFlavour flavor,guint32 user_time)183 terminal_util_open_url (GtkWidget *parent,
184 const char *orig_url,
185 TerminalURLFlavour flavor,
186 guint32 user_time)
187 {
188 GError *error = NULL;
189 char *uri;
190
191 g_return_if_fail (orig_url != NULL);
192
193 switch (flavor)
194 {
195 case FLAVOR_DEFAULT_TO_HTTP:
196 uri = g_strdup_printf ("http:%s", orig_url);
197 break;
198 case FLAVOR_EMAIL:
199 if (g_ascii_strncasecmp ("mailto:", orig_url, 7) != 0)
200 uri = g_strdup_printf ("mailto:%s", orig_url);
201 else
202 uri = g_strdup (orig_url);
203 break;
204 case FLAVOR_VOIP_CALL:
205 case FLAVOR_AS_IS:
206 uri = g_strdup (orig_url);
207 break;
208 case FLAVOR_SKEY:
209 /* shouldn't get this */
210 default:
211 uri = NULL;
212 g_assert_not_reached ();
213 }
214
215 if (!gtk_show_uri_on_window (GTK_WINDOW (parent), uri, user_time, &error))
216 {
217 terminal_util_show_error_dialog (GTK_WINDOW (parent), NULL, error,
218 _("Could not open the address “%s”"),
219 uri);
220
221 g_error_free (error);
222 }
223
224 g_free (uri);
225 }
226
227 /**
228 * terminal_util_resolve_relative_path:
229 * @path:
230 * @relative_path:
231 *
232 * Returns: a newly allocate string
233 */
234 char *
terminal_util_resolve_relative_path(const char * path,const char * relative_path)235 terminal_util_resolve_relative_path (const char *path,
236 const char *relative_path)
237 {
238 GFile *file, *resolved_file;
239 char *resolved_path = NULL;
240
241 g_return_val_if_fail (relative_path != NULL, NULL);
242
243 if (path == NULL)
244 return g_strdup (relative_path);
245
246 file = g_file_new_for_path (path);
247 resolved_file = g_file_resolve_relative_path (file, relative_path);
248 g_object_unref (file);
249
250 if (resolved_file == NULL)
251 return NULL;
252
253 resolved_path = g_file_get_path (resolved_file);
254 g_object_unref (resolved_file);
255
256 return resolved_path;
257 }
258
259 /**
260 * terminal_util_transform_uris_to_quoted_fuse_paths:
261 * @uris:
262 *
263 * Transforms those URIs in @uris to shell-quoted paths that point to
264 * GIO fuse paths.
265 */
266 void
terminal_util_transform_uris_to_quoted_fuse_paths(char ** uris)267 terminal_util_transform_uris_to_quoted_fuse_paths (char **uris)
268 {
269 guint i;
270
271 if (!uris)
272 return;
273
274 for (i = 0; uris[i]; ++i)
275 {
276 GFile *file;
277 char *path;
278
279 file = g_file_new_for_uri (uris[i]);
280
281 if ((path = g_file_get_path (file)))
282 {
283 char *quoted;
284
285 quoted = g_shell_quote (path);
286 g_free (uris[i]);
287 g_free (path);
288
289 uris[i] = quoted;
290 }
291
292 g_object_unref (file);
293 }
294 }
295
296 char *
terminal_util_concat_uris(char ** uris,gsize * length)297 terminal_util_concat_uris (char **uris,
298 gsize *length)
299 {
300 GString *string;
301 gsize len;
302 guint i;
303
304 len = 0;
305 for (i = 0; uris[i]; ++i)
306 len += strlen (uris[i]) + 1;
307
308 if (length)
309 *length = len;
310
311 string = g_string_sized_new (len + 1);
312 for (i = 0; uris[i]; ++i)
313 {
314 g_string_append (string, uris[i]);
315 g_string_append_c (string, ' ');
316 }
317
318 return g_string_free (string, FALSE);
319 }
320
321 char *
terminal_util_get_licence_text(void)322 terminal_util_get_licence_text (void)
323 {
324 const gchar *license[] =
325 {
326 N_("MATE Terminal is free software; you can redistribute it and/or modify "
327 "it under the terms of the GNU General Public License as published by "
328 "the Free Software Foundation; either version 3 of the License, or "
329 "(at your option) any later version."),
330 N_("MATE Terminal is distributed in the hope that it will be useful, "
331 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
332 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
333 "GNU General Public License for more details."),
334 N_("You should have received a copy of the GNU General Public License "
335 "along with MATE Terminal; if not, write to the Free Software Foundation, "
336 "Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA")
337 };
338
339 return g_strjoin ("\n\n", _(license[0]), _(license[1]), _(license[2]), NULL);
340 }
341
342 gboolean
terminal_util_load_builder_resource(const char * path,const char * object_name,...)343 terminal_util_load_builder_resource (const char *path,
344 const char *object_name,
345 ...)
346 {
347 GtkBuilder *builder;
348 GError *error = NULL;
349 va_list args;
350
351 builder = gtk_builder_new ();
352 gtk_builder_add_from_resource (builder, path, &error);
353 g_assert_no_error (error);
354
355 va_start (args, object_name);
356
357 while (object_name)
358 {
359 GObject **objectptr;
360
361 objectptr = va_arg (args, GObject**);
362 *objectptr = gtk_builder_get_object (builder, object_name);
363 if (!*objectptr)
364 {
365 g_warning ("Failed to fetch object \"%s\"\n", object_name);
366 break;
367 }
368
369 object_name = va_arg (args, const char*);
370 }
371
372 va_end (args);
373
374 g_object_unref (builder);
375 return object_name == NULL;
376 }
377
378 gboolean
terminal_util_dialog_response_on_delete(GtkWindow * widget)379 terminal_util_dialog_response_on_delete (GtkWindow *widget)
380 {
381 gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_DELETE_EVENT);
382 return TRUE;
383 }
384
385 /* Like g_key_file_set_string, but escapes characters so that
386 * the stored string is ASCII. Use when the input string may not
387 * be UTF-8.
388 */
389 void
terminal_util_key_file_set_string_escape(GKeyFile * key_file,const char * group,const char * key,const char * string)390 terminal_util_key_file_set_string_escape (GKeyFile *key_file,
391 const char *group,
392 const char *key,
393 const char *string)
394 {
395 char *escaped;
396
397 /* FIXMEchpe: be more intelligent and only escape characters that aren't UTF-8 */
398 escaped = g_strescape (string, NULL);
399 g_key_file_set_string (key_file, group, key, escaped);
400 g_free (escaped);
401 }
402
403 char *
terminal_util_key_file_get_string_unescape(GKeyFile * key_file,const char * group,const char * key,GError ** error)404 terminal_util_key_file_get_string_unescape (GKeyFile *key_file,
405 const char *group,
406 const char *key,
407 GError **error)
408 {
409 char *escaped, *unescaped;
410
411 escaped = g_key_file_get_string (key_file, group, key, error);
412 if (!escaped)
413 return NULL;
414
415 unescaped = g_strcompress (escaped);
416 g_free (escaped);
417
418 return unescaped;
419 }
420
421 void
terminal_util_key_file_set_argv(GKeyFile * key_file,const char * group,const char * key,int argc,char ** argv)422 terminal_util_key_file_set_argv (GKeyFile *key_file,
423 const char *group,
424 const char *key,
425 int argc,
426 char **argv)
427 {
428 char **quoted_argv;
429 char *flat;
430 int i;
431
432 if (argc < 0)
433 argc = g_strv_length (argv);
434
435 quoted_argv = g_new (char*, argc + 1);
436 for (i = 0; i < argc; ++i)
437 quoted_argv[i] = g_shell_quote (argv[i]);
438 quoted_argv[argc] = NULL;
439
440 flat = g_strjoinv (" ", quoted_argv);
441 terminal_util_key_file_set_string_escape (key_file, group, key, flat);
442
443 g_free (flat);
444 g_strfreev (quoted_argv);
445 }
446
447 char **
terminal_util_key_file_get_argv(GKeyFile * key_file,const char * group,const char * key,int * argc,GError ** error)448 terminal_util_key_file_get_argv (GKeyFile *key_file,
449 const char *group,
450 const char *key,
451 int *argc,
452 GError **error)
453 {
454 char **argv;
455 char *flat;
456 gboolean retval;
457
458 flat = terminal_util_key_file_get_string_unescape (key_file, group, key, error);
459 if (!flat)
460 return NULL;
461
462 retval = g_shell_parse_argv (flat, argc, &argv, error);
463 g_free (flat);
464
465 if (retval)
466 return argv;
467
468 return NULL;
469 }
470
471 /* Proxy stuff */
472
473 static char *
gsettings_get_string(GSettings * settings,const char * key)474 gsettings_get_string (GSettings *settings,
475 const char *key)
476 {
477 char *value;
478 value = g_settings_get_string (settings, key);
479 if (G_UNLIKELY (value && *value == '\0'))
480 {
481 g_free (value);
482 value = NULL;
483 }
484 return value;
485 }
486
487 /*
488 * set_proxy_env:
489 * @env_table: a #GHashTable
490 * @key: the env var name
491 * @value: the env var value
492 *
493 * Adds @value for @key to @env_table, taking care to never overwrite an
494 * existing value for @key. @value is consumed.
495 */
496 static void
set_proxy_env(GHashTable * env_table,const char * key,char * value)497 set_proxy_env (GHashTable *env_table,
498 const char *key,
499 char *value)
500 {
501 char *key1 = NULL, *key2 = NULL;
502 char *value1 = NULL, *value2 = NULL;
503
504 if (!value)
505 return;
506
507 if (g_hash_table_lookup (env_table, key) == NULL)
508 key1 = g_strdup (key);
509
510 key2 = g_ascii_strup (key, -1);
511 if (g_hash_table_lookup (env_table, key) != NULL)
512 {
513 g_free (key2);
514 key2 = NULL;
515 }
516
517 if (key1 && key2)
518 {
519 value1 = value;
520 value2 = g_strdup (value);
521 }
522 else if (key1)
523 value1 = value;
524 else if (key2)
525 value2 = value;
526 else
527 g_free (value);
528
529 if (key1)
530 g_hash_table_replace (env_table, key1, value1);
531 if (key2)
532 g_hash_table_replace (env_table, key2, value2);
533 }
534
535 static void
setup_http_proxy_env(GHashTable * env_table,GSettings * settings_http)536 setup_http_proxy_env (GHashTable *env_table,
537 GSettings *settings_http)
538 {
539 gchar *host;
540 gint port;
541
542 host = gsettings_get_string (settings_http, "host");
543 port = g_settings_get_int (settings_http, "port");
544 if (host && port)
545 {
546
547 GString *buf = g_string_sized_new (64);
548 g_string_append (buf, "http://");
549
550 if (g_settings_get_boolean (settings_http, "use-authentication"))
551 {
552 char *user, *password;
553 user = gsettings_get_string (settings_http, "authentication-user");
554 if (user)
555 {
556 g_string_append_uri_escaped (buf, user, NULL, TRUE);
557 password = gsettings_get_string (settings_http, "authentication-password");
558 if (password)
559 {
560 g_string_append_c (buf, ':');
561 g_string_append_uri_escaped (buf, password, NULL, TRUE);
562 g_free (password);
563 }
564 g_free (user);
565 g_string_append_c (buf, '@');
566 }
567 }
568 g_string_append_printf (buf, "%s:%d/", host, port);
569 set_proxy_env (env_table, "http_proxy", g_string_free (buf, FALSE));
570 }
571 g_free (host);
572
573 }
574
575 static void
setup_ignore_host_env(GHashTable * env_table,GSettings * settings)576 setup_ignore_host_env (GHashTable *env_table,
577 GSettings *settings)
578 {
579 gchar **ignore = g_settings_get_strv (settings, "ignore-hosts");
580 if (ignore == NULL)
581 return;
582
583 GString *buf = g_string_sized_new (64);
584 int i;
585
586 for (i = 0; ignore[i] != NULL; ++i)
587 {
588 if (buf->len)
589 g_string_append_c (buf, ',');
590 g_string_append (buf, ignore[i]);
591 }
592
593 set_proxy_env (env_table, "no_proxy", g_string_free (buf, FALSE));
594
595 g_strfreev(ignore);
596 }
597
598 static void
setup_https_proxy_env(GHashTable * env_table,GSettings * settings_https)599 setup_https_proxy_env (GHashTable *env_table,
600 GSettings *settings_https)
601 {
602 gchar *host;
603 gint port;
604
605 host = gsettings_get_string (settings_https, "host");
606 port = g_settings_get_int (settings_https, "port");
607 if (host && port)
608 {
609 char *proxy;
610 /* Even though it's https, the proxy scheme is 'http'. See bug #624440. */
611 proxy = g_strdup_printf ("http://%s:%d/", host, port);
612 set_proxy_env (env_table, "https_proxy", proxy);
613 }
614 g_free (host);
615 }
616
617 static void
setup_ftp_proxy_env(GHashTable * env_table,GSettings * settings_ftp)618 setup_ftp_proxy_env (GHashTable *env_table,
619 GSettings *settings_ftp)
620 {
621 gchar *host;
622 gint port;
623
624 host = gsettings_get_string (settings_ftp, "host");
625 port = g_settings_get_int (settings_ftp, "port");
626 if (host && port)
627 {
628 char *proxy;
629 /* Even though it's ftp, the proxy scheme is 'http'. See bug #624440. */
630 proxy = g_strdup_printf ("http://%s:%d/", host, port);
631 set_proxy_env (env_table, "ftp_proxy", proxy);
632 }
633 g_free (host);
634 }
635
636 static void
setup_socks_proxy_env(GHashTable * env_table,GSettings * settings_socks)637 setup_socks_proxy_env (GHashTable *env_table,
638 GSettings *settings_socks)
639 {
640 gchar *host;
641 gint port;
642
643 host = gsettings_get_string (settings_socks, "host");
644 port = g_settings_get_int (settings_socks, "port");
645 if (host && port)
646 {
647 char *proxy;
648 proxy = g_strdup_printf ("socks://%s:%d/", host, port);
649 set_proxy_env (env_table, "all_proxy", proxy);
650 }
651 g_free (host);
652 }
653
654 static void
setup_autoconfig_proxy_env(GHashTable * env_table,GSettings * settings)655 setup_autoconfig_proxy_env (GHashTable *env_table,
656 GSettings *settings)
657 {
658 /* XXX Not sure what to do with this. See bug #596688.
659 gchar *url;
660
661 url = gsettings_get_string (settings, "autoconfig-url");
662 if (url)
663 {
664 char *proxy;
665 proxy = g_strdup_printf ("pac+%s", url);
666 set_proxy_env (env_table, "http_proxy", proxy);
667 }
668 g_free (url);
669 */
670 }
671
672 /**
673 * terminal_util_add_proxy_env:
674 * @env_table: a #GHashTable
675 *
676 * Adds the proxy env variables to @env_table.
677 */
678 void
terminal_util_add_proxy_env(GHashTable * env_table)679 terminal_util_add_proxy_env (GHashTable *env_table)
680 {
681 char *proxymode;
682 GSettings *settings = g_settings_new (CONF_PROXY_SCHEMA);
683 GSettings *settings_http = g_settings_new (CONF_HTTP_PROXY_SCHEMA);
684 GSettings *settings_https = g_settings_new (CONF_HTTPS_PROXY_SCHEMA);
685 GSettings *settings_ftp = g_settings_new (CONF_FTP_PROXY_SCHEMA);
686 GSettings *settings_socks = g_settings_new (CONF_SOCKS_PROXY_SCHEMA);
687
688 /* If mode is not manual, nothing to set */
689 proxymode = gsettings_get_string (settings, "mode");
690 if (proxymode && 0 == strcmp (proxymode, "manual"))
691 {
692 setup_http_proxy_env (env_table, settings_http);
693 setup_ignore_host_env (env_table, settings);
694 setup_https_proxy_env (env_table, settings_https);
695 setup_ftp_proxy_env (env_table, settings_ftp);
696 setup_socks_proxy_env (env_table, settings_socks);
697 }
698 else if (proxymode && 0 == strcmp (proxymode, "auto"))
699 {
700 setup_autoconfig_proxy_env (env_table, settings);
701 }
702
703 g_free (proxymode);
704 g_object_unref (settings);
705 g_object_unref (settings_http);
706 g_object_unref (settings_https);
707 g_object_unref (settings_ftp);
708 g_object_unref (settings_socks);
709 }
710
711 /* Bidirectional object/widget binding */
712
713 typedef struct
714 {
715 GObject *object;
716 const char *object_prop;
717 GtkWidget *widget;
718 gulong object_notify_id;
719 gulong widget_notify_id;
720 PropertyChangeFlags flags;
721 } PropertyChange;
722
723 static void
property_change_free(PropertyChange * change)724 property_change_free (PropertyChange *change)
725 {
726 g_signal_handler_disconnect (change->object, change->object_notify_id);
727
728 g_slice_free (PropertyChange, change);
729 }
730
731 static gboolean
transform_boolean(gboolean input,PropertyChangeFlags flags)732 transform_boolean (gboolean input,
733 PropertyChangeFlags flags)
734 {
735 if (flags & FLAG_INVERT_BOOL)
736 input = !input;
737
738 return input;
739 }
740
741 static void
object_change_notify_cb(PropertyChange * change)742 object_change_notify_cb (PropertyChange *change)
743 {
744 GObject *object = change->object;
745 const char *object_prop = change->object_prop;
746 GtkWidget *widget = change->widget;
747
748 g_signal_handler_block (widget, change->widget_notify_id);
749
750 if (GTK_IS_RADIO_BUTTON (widget))
751 {
752 int ovalue, rvalue;
753
754 g_object_get (object, object_prop, &ovalue, NULL);
755 rvalue = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "enum-value"));
756 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), ovalue == rvalue);
757 }
758 else if (GTK_IS_TOGGLE_BUTTON (widget))
759 {
760 gboolean enabled;
761
762 g_object_get (object, object_prop, &enabled, NULL);
763 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
764 transform_boolean (enabled, change->flags));
765 }
766 else if (GTK_IS_SPIN_BUTTON (widget))
767 {
768 int value;
769
770 g_object_get (object, object_prop, &value, NULL);
771 gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value);
772 }
773 else if (GTK_IS_ENTRY (widget))
774 {
775 char *text;
776
777 g_object_get (object, object_prop, &text, NULL);
778 gtk_entry_set_text (GTK_ENTRY (widget), text ? text : "");
779 g_free (text);
780 }
781 else if (GTK_IS_COMBO_BOX (widget))
782 {
783 int value;
784
785 g_object_get (object, object_prop, &value, NULL);
786 gtk_combo_box_set_active (GTK_COMBO_BOX (widget), value);
787 }
788 else if (GTK_IS_RANGE (widget))
789 {
790 double value;
791
792 g_object_get (object, object_prop, &value, NULL);
793 gtk_range_set_value (GTK_RANGE (widget), value);
794 }
795 else if (GTK_IS_COLOR_CHOOSER (widget))
796 {
797 GdkRGBA *color;
798 GdkRGBA old_color;
799
800 g_object_get (object, object_prop, &color, NULL);
801 gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (widget), &old_color);
802
803 if (color && !gdk_rgba_equal (color, &old_color))
804 gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (widget), color);
805 if (color)
806 gdk_rgba_free (color);
807 }
808 else if (GTK_IS_FONT_BUTTON (widget))
809 {
810 PangoFontDescription *font_desc;
811 char *font;
812
813 g_object_get (object, object_prop, &font_desc, NULL);
814 if (!font_desc)
815 goto out;
816
817 font = pango_font_description_to_string (font_desc);
818 gtk_font_button_set_font_name (GTK_FONT_BUTTON (widget), font);
819 g_free (font);
820 pango_font_description_free (font_desc);
821 }
822 else if (GTK_IS_FILE_CHOOSER (widget))
823 {
824 char *name = NULL, *filename = NULL;
825
826 g_object_get (object, object_prop, &name, NULL);
827 if (name)
828 filename = g_filename_from_utf8 (name, -1, NULL, NULL, NULL);
829
830 if (filename)
831 gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (widget), filename);
832 else
833 gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (widget));
834 g_free (filename);
835 g_free (name);
836 }
837
838 out:
839 g_signal_handler_unblock (widget, change->widget_notify_id);
840 }
841
842 static void
widget_change_notify_cb(PropertyChange * change)843 widget_change_notify_cb (PropertyChange *change)
844 {
845 GObject *object = change->object;
846 const char *object_prop = change->object_prop;
847 GtkWidget *widget = change->widget;
848
849 g_signal_handler_block (change->object, change->object_notify_id);
850
851 if (GTK_IS_RADIO_BUTTON (widget))
852 {
853 gboolean active;
854 int value;
855
856 active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
857 if (!active)
858 goto out;
859
860 value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "enum-value"));
861 g_object_set (object, object_prop, value, NULL);
862 }
863 else if (GTK_IS_TOGGLE_BUTTON (widget))
864 {
865 gboolean enabled;
866
867 enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
868 g_object_set (object, object_prop, transform_boolean (enabled, change->flags), NULL);
869 }
870 else if (GTK_IS_SPIN_BUTTON (widget))
871 {
872 int value;
873
874 value = (int) gtk_spin_button_get_value (GTK_SPIN_BUTTON (widget));
875 g_object_set (object, object_prop, value, NULL);
876 }
877 else if (GTK_IS_ENTRY (widget))
878 {
879 const char *text;
880
881 text = gtk_entry_get_text (GTK_ENTRY (widget));
882 g_object_set (object, object_prop, text, NULL);
883 }
884 else if (GTK_IS_COMBO_BOX (widget))
885 {
886 int value;
887
888 value = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
889 g_object_set (object, object_prop, value, NULL);
890 }
891 else if (GTK_IS_COLOR_CHOOSER (widget))
892 {
893 GdkRGBA color;
894
895 gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (widget), &color);
896 g_object_set (object, object_prop, &color, NULL);
897 }
898 else if (GTK_IS_FONT_BUTTON (widget))
899 {
900 PangoFontDescription *font_desc;
901 const char *font;
902
903 font = gtk_font_button_get_font_name (GTK_FONT_BUTTON (widget));
904 font_desc = pango_font_description_from_string (font);
905 g_object_set (object, object_prop, font_desc, NULL);
906 pango_font_description_free (font_desc);
907 }
908 else if (GTK_IS_RANGE (widget))
909 {
910 double value;
911
912 value = gtk_range_get_value (GTK_RANGE (widget));
913 g_object_set (object, object_prop, value, NULL);
914 }
915 else if (GTK_IS_FILE_CHOOSER (widget))
916 {
917 char *filename, *name = NULL;
918
919 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
920 if (filename)
921 name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
922
923 g_object_set (object, object_prop, name, NULL);
924 g_free (filename);
925 g_free (name);
926 }
927
928 out:
929 g_signal_handler_unblock (change->object, change->object_notify_id);
930 }
931
932 void
terminal_util_bind_object_property_to_widget(GObject * object,const char * object_prop,GtkWidget * widget,PropertyChangeFlags flags)933 terminal_util_bind_object_property_to_widget (GObject *object,
934 const char *object_prop,
935 GtkWidget *widget,
936 PropertyChangeFlags flags)
937 {
938 PropertyChange *change;
939 const char *signal_name;
940 char notify_signal_name[64];
941
942 change = g_slice_new0 (PropertyChange);
943
944 change->widget = widget;
945 g_assert (g_object_get_data (G_OBJECT (widget), "GT:PCD") == NULL);
946 g_object_set_data_full (G_OBJECT (widget), "GT:PCD", change, (GDestroyNotify) property_change_free);
947
948 if (GTK_IS_TOGGLE_BUTTON (widget))
949 signal_name = "notify::active";
950 else if (GTK_IS_SPIN_BUTTON (widget))
951 signal_name = "notify::value";
952 else if (GTK_IS_ENTRY (widget))
953 signal_name = "notify::text";
954 else if (GTK_IS_COMBO_BOX (widget))
955 signal_name = "notify::active";
956 else if (GTK_IS_COLOR_CHOOSER (widget))
957 signal_name = "notify::color";
958 else if (GTK_IS_FONT_BUTTON (widget))
959 signal_name = "notify::font-name";
960 else if (GTK_IS_RANGE (widget))
961 signal_name = "value-changed";
962 else if (GTK_IS_FILE_CHOOSER_BUTTON (widget))
963 signal_name = "file-set";
964 else if (GTK_IS_FILE_CHOOSER (widget))
965 signal_name = "selection-changed";
966 else
967 g_assert_not_reached ();
968
969 change->widget_notify_id = g_signal_connect_swapped (widget, signal_name, G_CALLBACK (widget_change_notify_cb), change);
970
971 change->object = object;
972 change->flags = flags;
973 change->object_prop = object_prop;
974
975 g_snprintf (notify_signal_name, sizeof (notify_signal_name), "notify::%s", object_prop);
976 object_change_notify_cb (change);
977 change->object_notify_id = g_signal_connect_swapped (object, notify_signal_name, G_CALLBACK (object_change_notify_cb), change);
978 }
979
980 #ifdef GDK_WINDOWING_X11
981
982 /* Asks the window manager to turn off the "demands attention" state on the window.
983 *
984 * This only works for windows that are currently window managed; if the window
985 * is unmapped (in the withdrawn state) it would be necessary to change _NET_WM_STATE
986 * directly.
987 */
988 void
terminal_util_x11_clear_demands_attention(GdkWindow * window)989 terminal_util_x11_clear_demands_attention (GdkWindow *window)
990 {
991
992 GdkScreen *screen = gdk_window_get_screen (window);
993 GdkDisplay *display = gdk_screen_get_display (screen);
994 XClientMessageEvent xclient;
995
996 memset (&xclient, 0, sizeof (xclient));
997 xclient.type = ClientMessage;
998 xclient.serial = 0;
999 xclient.send_event = True;
1000 xclient.window = GDK_WINDOW_XID (window);
1001 xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE");
1002 xclient.format = 32;
1003
1004 xclient.data.l[0] = 0; /* _NET_WM_STATE_REMOVE */
1005 xclient.data.l[1] = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE_DEMANDS_ATTENTION");
1006 xclient.data.l[2] = 0;
1007 xclient.data.l[3] = 0;
1008 xclient.data.l[4] = 0;
1009
1010 XSendEvent (GDK_DISPLAY_XDISPLAY (display),
1011 GDK_WINDOW_XID (gdk_screen_get_root_window (screen)),
1012 False,
1013 SubstructureRedirectMask | SubstructureNotifyMask,
1014 (XEvent *)&xclient);
1015 }
1016
1017 #endif /* GDK_WINDOWING_X11 */
1018