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