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, 2010 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 <errno.h>
26 #include <locale.h>
27 #include <stdlib.h>
28 #include <time.h>
29 #include <unistd.h>
30 
31 #include <glib.h>
32 #include <glib/gstdio.h>
33 #include <gio/gio.h>
34 
35 #include <gdk/gdk.h>
36 #include <gdk/gdkx.h>
37 
38 #ifdef HAVE_SMCLIENT
39 #include "eggsmclient.h"
40 #endif /* HAVE_SMCLIENT */
41 
42 #include "terminal-accels.h"
43 #include "terminal-app.h"
44 #include "terminal-debug.h"
45 #include "terminal-intl.h"
46 #include "terminal-options.h"
47 #include "terminal-util.h"
48 
49 #define TERMINAL_FACTORY_SERVICE_NAME_PREFIX  "org.mate.Terminal.Display"
50 #define TERMINAL_FACTORY_SERVICE_PATH         "/org/mate/Terminal/Factory"
51 #define TERMINAL_FACTORY_INTERFACE_NAME       "org.mate.Terminal.Factory"
52 
53 static char *
ay_to_string(GVariant * variant,GError ** error)54 ay_to_string (GVariant *variant,
55               GError **error)
56 {
57 	gsize len;
58 	const char *data;
59 
60 	data = g_variant_get_fixed_array (variant, &len, sizeof (char));
61 	if (len == 0)
62 		return NULL;
63 
64 	/* Make sure there are no embedded NULs */
65 	if (memchr (data, '\0', len) != NULL)
66 	{
67 		g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
68 		                     "String is shorter than claimed");
69 		return NULL;
70 	}
71 
72 	return g_strndup (data, len);
73 }
74 
75 static char **
ay_to_strv(GVariant * variant,int * argc)76 ay_to_strv (GVariant *variant,
77             int *argc)
78 {
79 	GPtrArray *argv;
80 	const char *data, *nullbyte;
81 	gsize data_len;
82 	gssize len;
83 
84 	data = g_variant_get_fixed_array (variant, &data_len, sizeof (char));
85 	if (data_len == 0 || data_len > G_MAXSSIZE)
86 	{
87 		if (argc != NULL)
88 			*argc = 0;
89 		return NULL;
90 	}
91 
92 	argv = g_ptr_array_new ();
93 
94 	len = data_len;
95 	do
96 	{
97 		gssize string_len;
98 
99 		nullbyte = memchr (data, '\0', len);
100 
101 		string_len = nullbyte ? (gssize) (nullbyte - data) : len;
102 		g_ptr_array_add (argv, g_strndup (data, string_len));
103 
104 		len -= string_len + 1;
105 		data += string_len + 1;
106 	}
107 	while (len > 0);
108 
109 	if (argc)
110 		*argc = argv->len;
111 
112 	/* NULL terminate */
113 	g_ptr_array_add (argv, NULL);
114 	return (char **) g_ptr_array_free (argv, FALSE);
115 }
116 
117 static GVariant *
string_to_ay(const char * string)118 string_to_ay (const char *string)
119 {
120 	gsize len;
121 	char *data;
122 
123 	len = strlen (string);
124 	data = g_strndup (string, len);
125 
126 	return g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, len, TRUE, g_free, data);
127 }
128 
129 typedef struct
130 {
131 	char *factory_name;
132 	TerminalOptions *options;
133 	int exit_code;
134 	char **argv;
135 	int argc;
136 } OwnData;
137 
138 static void
method_call_cb(GDBusConnection * connection,const char * sender,const char * object_path,const char * interface_name,const char * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)139 method_call_cb (GDBusConnection *connection,
140                 const char *sender,
141                 const char *object_path,
142                 const char *interface_name,
143                 const char *method_name,
144                 GVariant *parameters,
145                 GDBusMethodInvocation *invocation,
146                 gpointer user_data)
147 {
148 	if (g_strcmp0 (method_name, "HandleArguments") == 0)
149 	{
150 		TerminalOptions *options = NULL;
151 		GVariant *v_wd, *v_display, *v_sid, *v_envv, *v_argv;
152 		char *working_directory = NULL, *display_name = NULL, *startup_id = NULL;
153 		int initial_workspace = -1;
154 		char **envv = NULL, **argv = NULL;
155 		int argc;
156 		GError *error = NULL;
157 
158 		g_variant_get (parameters, "(@ay@ay@ay@ayi@ay)",
159 		               &v_wd, &v_display, &v_sid, &v_envv, &initial_workspace, &v_argv);
160 
161 		working_directory = ay_to_string (v_wd, &error);
162 		if (error)
163 			goto out;
164 		display_name = ay_to_string (v_display, &error);
165 		if (error)
166 			goto out;
167 		startup_id = ay_to_string (v_sid, &error);
168 		if (error)
169 			goto out;
170 		envv = ay_to_strv (v_envv, NULL);
171 		argv = ay_to_strv (v_argv, &argc);
172 
173 		_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
174 		                       "Factory invoked with working-dir='%s' display='%s' startup-id='%s'"
175 		                       "workspace='%d'\n",
176 		                       working_directory ? working_directory : "(null)",
177 		                       display_name ? display_name : "(null)",
178 		                       startup_id ? startup_id : "(null)",
179 		                       initial_workspace);
180 
181 		options = terminal_options_parse (working_directory,
182 		                                  display_name,
183 		                                  startup_id,
184 		                                  envv,
185 		                                  TRUE,
186 		                                  TRUE,
187 		                                  &argc, &argv,
188 		                                  &error,
189 		                                  NULL);
190 
191 		if (options != NULL)
192 		{
193 			options->initial_workspace = initial_workspace;
194 
195 			terminal_app_handle_options (terminal_app_get (), options, FALSE /* no resume */, &error);
196 			terminal_options_free (options);
197 		}
198 
199 out:
200 		g_variant_unref (v_wd);
201 		g_free (working_directory);
202 		g_variant_unref (v_display);
203 		g_free (display_name);
204 		g_variant_unref (v_sid);
205 		g_free (startup_id);
206 		g_variant_unref (v_envv);
207 		g_strfreev (envv);
208 		g_variant_unref (v_argv);
209 		g_strfreev (argv);
210 
211 		if (error == NULL)
212 		{
213 			g_dbus_method_invocation_return_value (invocation, g_variant_new ("()"));
214 		}
215 		else
216 		{
217 			g_dbus_method_invocation_return_gerror (invocation, error);
218 			g_error_free (error);
219 		}
220 	}
221 }
222 
223 static void
bus_acquired_cb(GDBusConnection * connection,const char * name,gpointer user_data)224 bus_acquired_cb (GDBusConnection *connection,
225                  const char *name,
226                  gpointer user_data)
227 {
228 	static const char dbus_introspection_xml[] =
229 	    "<node name='/org/mate/Terminal'>"
230 	    "<interface name='org.mate.Terminal.Factory'>"
231 	    "<method name='HandleArguments'>"
232 	    "<arg type='ay' name='working_directory' direction='in' />"
233 	    "<arg type='ay' name='display_name' direction='in' />"
234 	    "<arg type='ay' name='startup_id' direction='in' />"
235 	    "<arg type='ay' name='environment' direction='in' />"
236 	    "<arg type='i' name='workspace' direction='in' />"
237 	    "<arg type='ay' name='arguments' direction='in' />"
238 	    "</method>"
239 	    "</interface>"
240 	    "</node>";
241 
242 	static const GDBusInterfaceVTable interface_vtable =
243 	{
244 		method_call_cb,
245 		NULL,
246 		NULL,
247 	};
248 
249 	OwnData *data = (OwnData *) user_data;
250 	GDBusNodeInfo *introspection_data;
251 	guint registration_id;
252 	GError *error = NULL;
253 
254 	_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
255 	                       "Bus %s acquired\n", name);
256 
257 	introspection_data = g_dbus_node_info_new_for_xml (dbus_introspection_xml, NULL);
258 	g_assert (introspection_data != NULL);
259 
260 	registration_id = g_dbus_connection_register_object (connection,
261 	                  TERMINAL_FACTORY_SERVICE_PATH,
262 	                  introspection_data->interfaces[0],
263 	                  &interface_vtable,
264 	                  NULL, NULL,
265 	                  &error);
266 	g_dbus_node_info_unref (introspection_data);
267 
268 	if (registration_id == 0)
269 	{
270 		g_printerr ("Failed to register object: %s\n", error->message);
271 		g_error_free (error);
272 		data->exit_code = EXIT_FAILURE;
273 		gtk_main_quit ();
274 	}
275 }
276 
277 static void
name_acquired_cb(GDBusConnection * connection,const char * name,gpointer user_data)278 name_acquired_cb (GDBusConnection *connection,
279                   const char *name,
280                   gpointer user_data)
281 {
282 	OwnData *data = (OwnData *) user_data;
283 	GError *error = NULL;
284 
285 	_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
286 	                       "Acquired the name %s on the session bus\n", name);
287 
288 	if (data->options == NULL)
289 	{
290 		/* Name re-acquired!? */
291 		g_assert_not_reached ();
292 	}
293 
294 
295 	if (!terminal_app_handle_options (terminal_app_get (), data->options, TRUE /* do resume */, &error))
296 	{
297 		g_printerr ("Failed to handle options: %s\n", error->message);
298 		g_error_free (error);
299 		data->exit_code = EXIT_FAILURE;
300 		gtk_main_quit ();
301 	}
302 
303 	terminal_options_free (data->options);
304 	data->options = NULL;
305 }
306 
307 static void
name_lost_cb(GDBusConnection * connection,const char * name,gpointer user_data)308 name_lost_cb (GDBusConnection *connection,
309               const char *name,
310               gpointer user_data)
311 {
312 	OwnData *data = (OwnData *) user_data;
313 	GError *error = NULL;
314 	char **envv;
315 	int i;
316 	GVariantBuilder builder;
317 	GVariant *value;
318 	GString *string;
319 	char *s;
320 	gsize len;
321 
322 	_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
323 	                       "Lost the name %s on the session bus\n", name);
324 
325 	/* Couldn't get the connection? No way to continue! */
326 	if (connection == NULL)
327 	{
328 		data->exit_code = EXIT_FAILURE;
329 		gtk_main_quit ();
330 		return;
331 	}
332 
333 	if (data->options == NULL)
334 	{
335 		/* Already handled */
336 		data->exit_code = EXIT_SUCCESS;
337 		gtk_main_quit ();
338 		return;
339 	}
340 
341 	_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
342 	                       "Forwarding arguments to existing instance\n");
343 
344 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("(ayayayayiay)"));
345 
346 	g_variant_builder_add (&builder, "@ay", string_to_ay (data->options->default_working_dir));
347 	g_variant_builder_add (&builder, "@ay", string_to_ay (data->options->display_name));
348 	g_variant_builder_add (&builder, "@ay", string_to_ay (data->options->startup_id));
349 
350 	string = g_string_new (NULL);
351 	envv = g_get_environ ();
352 	for (i = 0; envv[i]; ++i)
353 	{
354 		if (i > 0)
355 			g_string_append_c (string, '\0');
356 
357 		g_string_append (string, envv[i]);
358 	}
359 	g_strfreev (envv);
360 
361 	len = string->len;
362 	s = g_string_free (string, FALSE);
363 	g_variant_builder_add (&builder, "@ay",
364 	                       g_variant_new_from_data (G_VARIANT_TYPE ("ay"), s, len, TRUE, g_free, s));
365 
366 	g_variant_builder_add (&builder, "@i", g_variant_new_int32 (data->options->initial_workspace));
367 
368 	string = g_string_new (NULL);
369 
370 	for (i = 0; i < data->argc; ++i)
371 	{
372 		if (i > 0)
373 			g_string_append_c (string, '\0');
374 		g_string_append (string, data->argv[i]);
375 	}
376 
377 	len = string->len;
378 	s = g_string_free (string, FALSE);
379 	g_variant_builder_add (&builder, "@ay",
380 	                       g_variant_new_from_data (G_VARIANT_TYPE ("ay"), s, len, TRUE, g_free, s));
381 
382 	value = g_dbus_connection_call_sync (connection,
383 	                                     data->factory_name,
384 	                                     TERMINAL_FACTORY_SERVICE_PATH,
385 	                                     TERMINAL_FACTORY_INTERFACE_NAME,
386 	                                     "HandleArguments",
387 	                                     g_variant_builder_end (&builder),
388 	                                     G_VARIANT_TYPE ("()"),
389 	                                     G_DBUS_CALL_FLAGS_NONE,
390 	                                     -1,
391 	                                     NULL,
392 	                                     &error);
393 	if (value == NULL)
394 	{
395 		g_printerr ("Failed to forward arguments: %s\n", error->message);
396 		g_error_free (error);
397 		data->exit_code = EXIT_FAILURE;
398 		gtk_main_quit ();
399 	}
400 	else
401 	{
402 		g_variant_unref (value);
403 		data->exit_code = EXIT_SUCCESS;
404 	}
405 
406 	terminal_options_free (data->options);
407 	data->options = NULL;
408 
409 	gtk_main_quit ();
410 }
411 
412 static char *
get_factory_name_for_display(const char * display_name)413 get_factory_name_for_display (const char *display_name)
414 {
415 	GString *name;
416 	const char *p;
417 
418 	name = g_string_sized_new (strlen (TERMINAL_FACTORY_SERVICE_NAME_PREFIX) + strlen (display_name) + 1 /* NUL */);
419 	g_string_append (name, TERMINAL_FACTORY_SERVICE_NAME_PREFIX);
420 
421 	for (p = display_name; *p; ++p)
422 	{
423 		if (g_ascii_isalnum (*p))
424 			g_string_append_c (name, *p);
425 		else
426 			g_string_append_c (name, '_');
427 	}
428 
429 	_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
430 	                       "Factory name is \"%s\"\n", name->str);
431 
432 	return g_string_free (name, FALSE);
433 }
434 
435 static int
get_initial_workspace(void)436 get_initial_workspace (void)
437 {
438   int ret = -1;
439   GdkWindow *window;
440   guchar *data = NULL;
441   GdkAtom atom;
442   GdkAtom cardinal_atom;
443 
444   window = gdk_get_default_root_window();
445 
446   atom = gdk_atom_intern_static_string ("_NET_CURRENT_DESKTOP");
447   cardinal_atom = gdk_atom_intern_static_string ("CARDINAL");
448 
449   if (gdk_property_get (window, atom, cardinal_atom, 0, 8, FALSE, NULL, NULL, NULL, &data)) {
450 	  ret = *(int *)data;
451 	  g_free (data);
452   }
453   return ret;
454 }
455 
456 int
main(int argc,char ** argv)457 main (int argc, char **argv)
458 {
459 	int i;
460 	char **argv_copy;
461 	int argc_copy;
462 	const char *startup_id, *home_dir;
463 	TerminalOptions *options;
464 	GError *error = NULL;
465 	char *working_directory;
466 	int ret = EXIT_SUCCESS;
467 
468 	setlocale (LC_ALL, "");
469 
470 	bindtextdomain (GETTEXT_PACKAGE, TERM_LOCALEDIR);
471 	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
472 	textdomain (GETTEXT_PACKAGE);
473 
474 	_terminal_debug_init ();
475 
476 	/* Make a NULL-terminated copy since we may need it later */
477 	argv_copy = g_new (char *, argc + 1);
478 	for (i = 0; i < argc; ++i)
479 		argv_copy [i] = argv [i];
480 	argv_copy [i] = NULL;
481 	argc_copy = argc;
482 
483 	startup_id = g_getenv ("DESKTOP_STARTUP_ID");
484 
485 	working_directory = g_get_current_dir ();
486 
487 	/* Now change directory to $HOME so we don't prevent unmounting, e.g. if the
488 	 * factory is started by caja-open-terminal. See bug #565328.
489 	 * On failure back to /.
490 	 */
491 	home_dir = g_get_home_dir ();
492 	if (home_dir == NULL || chdir (home_dir) < 0)
493 		if (chdir ("/") < 0)
494 			g_warning ("Could not change working directory.");
495 
496 	options = terminal_options_parse (working_directory,
497 	                                  NULL,
498 	                                  startup_id,
499 	                                  NULL,
500 	                                  FALSE,
501 	                                  FALSE,
502 	                                  &argc, &argv,
503 	                                  &error,
504 #ifdef HAVE_SMCLIENT
505 	                                  gtk_get_option_group (TRUE),
506 	                                  egg_sm_client_get_option_group (),
507 #endif /* HAVE_SMCLIENT */
508 	                                  NULL);
509 	g_free (working_directory);
510 
511 	if (options == NULL)
512 	{
513 		g_printerr (_("Failed to parse arguments: %s\n"), error->message);
514 		g_error_free (error);
515 		exit (EXIT_FAILURE);
516 	}
517 
518 	g_set_application_name (_("Terminal"));
519 
520 	/* Unset the these env variables, so they doesn't end up
521 	 * in the factory's env and thus in the terminals' envs.
522 	 */
523 	g_unsetenv ("DESKTOP_STARTUP_ID");
524 	g_unsetenv ("GIO_LAUNCHED_DESKTOP_FILE_PID");
525 	g_unsetenv ("GIO_LAUNCHED_DESKTOP_FILE");
526 
527 	if (options->startup_id == NULL)
528 	{
529 		options->startup_id = g_strdup_printf ("_TIME%lu", g_get_monotonic_time () / 1000);
530 	}
531 
532 	gdk_init (&argc, &argv);
533 	const char *display_name = gdk_display_get_name (gdk_display_get_default ());
534 	options->display_name = g_strdup (display_name);
535 
536 	if (options->use_factory)
537 	{
538 		OwnData *data;
539 		guint owner_id;
540 
541 		data = g_new (OwnData, 1);
542 		data->factory_name = get_factory_name_for_display (display_name);
543 		data->options = options;
544 		data->exit_code = EXIT_SUCCESS;
545 		data->argv = argv_copy;
546 		data->argc = argc_copy;
547 
548 		gtk_init(&argc, &argv);
549 		options->initial_workspace = get_initial_workspace ();
550 
551 		owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
552 		                           data->factory_name,
553 		                           G_BUS_NAME_OWNER_FLAGS_NONE,
554 		                           bus_acquired_cb,
555 		                           name_acquired_cb,
556 		                           name_lost_cb,
557 		                           data, NULL);
558 
559 		gtk_main ();
560 
561 		ret = data->exit_code;
562 		g_bus_unown_name (owner_id);
563 
564 		g_free (data->factory_name);
565 		g_free (data);
566 
567 	}
568 	else
569 	{
570 		gtk_init(&argc, &argv);
571 		terminal_app_handle_options (terminal_app_get (), options, TRUE /* allow resume */, &error);
572 		terminal_options_free (options);
573 
574 		if (error == NULL)
575 		{
576 			gtk_main ();
577 		}
578 		else
579 		{
580 			g_printerr ("Error handling options: %s\n", error->message);
581 			g_error_free (error);
582 			ret = EXIT_FAILURE;
583 		}
584 	}
585 
586 	terminal_app_shutdown ();
587 
588 	g_free (argv_copy);
589 
590 	return ret;
591 }
592