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