diff options
Diffstat (limited to 'src/terminal.c')
-rw-r--r-- | src/terminal.c | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/src/terminal.c b/src/terminal.c new file mode 100644 index 0000000..03a619e --- /dev/null +++ b/src/terminal.c @@ -0,0 +1,619 @@ +/* + * Copyright © 2001, 2002 Havoc Pennington + * Copyright © 2002 Red Hat, Inc. + * Copyright © 2002 Sun Microsystems + * Copyright © 2003 Mariano Suarez-Alvarez + * Copyright © 2008, 2010 Christian Persch + * + * Mate-terminal is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Mate-terminal is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <errno.h> +#include <locale.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include <gio/gio.h> + +#include <gdk/gdkx.h> + +#ifdef WITH_SMCLIENT +#include "eggsmclient.h" +#endif + +#include "terminal-accels.h" +#include "terminal-app.h" +#include "terminal-debug.h" +#include "terminal-intl.h" +#include "terminal-options.h" +#include "terminal-util.h" + +#define TERMINAL_FACTORY_SERVICE_NAME_PREFIX "org.mate.Terminal.Display" +#define TERMINAL_FACTORY_SERVICE_PATH "/org/mate/Terminal/Factory" +#define TERMINAL_FACTORY_INTERFACE_NAME "org.mate.Terminal.Factory" + +static char * +ay_to_string (GVariant *variant, + GError **error) +{ + gsize len; + const char *data; + + data = g_variant_get_fixed_array (variant, &len, sizeof (char)); + if (len == 0) + return NULL; + + /* Make sure there are no embedded NULs */ + if (memchr (data, '\0', len) != NULL) { + g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "String is shorter than claimed"); + return NULL; + } + + return g_strndup (data, len); +} + +static char ** +ay_to_strv (GVariant *variant, + int *argc) +{ + GPtrArray *argv; + const char *data, *nullbyte; + gsize data_len; + gssize len; + + data = g_variant_get_fixed_array (variant, &data_len, sizeof (char)); + if (data_len == 0 || data_len > G_MAXSSIZE) { + *argc = 0; + return NULL; + } + + argv = g_ptr_array_new (); + + len = data_len; + do { + gssize string_len; + + nullbyte = memchr (data, '\0', len); + + string_len = nullbyte ? (gssize) (nullbyte - data) : len; + g_ptr_array_add (argv, g_strndup (data, string_len)); + + len -= string_len + 1; + data += string_len + 1; + } while (len > 0); + + if (argc) + *argc = argv->len; + + /* NULL terminate */ + g_ptr_array_add (argv, NULL); + return (char **) g_ptr_array_free (argv, FALSE); +} + +static GVariant * +string_to_ay (const char *string) +{ + gsize len; + char *data; + + len = strlen (string); + data = g_strndup (string, len); + + return g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, len, TRUE, g_free, data); +} + +typedef struct { + char *factory_name; + TerminalOptions *options; + int exit_code; + char **argv; + int argc; +} OwnData; + +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) +{ + if (g_strcmp0 (method_name, "HandleArguments") == 0) { + TerminalOptions *options = NULL; + GVariant *v_wd, *v_display, *v_sid, *v_envv, *v_argv; + char *working_directory = NULL, *display_name = NULL, *startup_id = NULL; + char **envv = NULL, **argv = NULL; + int argc; + GError *error = NULL; + + g_variant_get (parameters, "(@ay@ay@ay@ay@ay)", + &v_wd, &v_display, &v_sid, &v_envv, &v_argv); + + working_directory = ay_to_string (v_wd, &error); + if (error) + goto out; + display_name = ay_to_string (v_display, &error); + if (error) + goto out; + startup_id = ay_to_string (v_sid, &error); + if (error) + goto out; + envv = ay_to_strv (v_envv, NULL); + argv = ay_to_strv (v_argv, &argc); + + _terminal_debug_print (TERMINAL_DEBUG_FACTORY, + "Factory invoked with working-dir='%s' display='%s' startup-id='%s'\n", + working_directory ? working_directory : "(null)", + display_name ? display_name : "(null)", + startup_id ? startup_id : "(null)"); + + options = terminal_options_parse (working_directory, + display_name, + startup_id, + envv, + TRUE, + TRUE, + &argc, &argv, + &error, + NULL); + + if (options != NULL) { + terminal_app_handle_options (terminal_app_get (), options, FALSE /* no resume */, &error); + terminal_options_free (options); + } + + out: + g_variant_unref (v_wd); + g_free (working_directory); + g_variant_unref (v_display); + g_free (display_name); + g_variant_unref (v_sid); + g_free (startup_id); + g_variant_unref (v_envv); + g_strfreev (envv); + g_variant_unref (v_argv); + g_strfreev (argv); + + if (error == NULL) { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("()")); + } else { + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + } + } +} + +static void +bus_acquired_cb (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + static const char dbus_introspection_xml[] = + "<node name='/org/mate/Terminal'>" + "<interface name='org.mate.Terminal.Factory'>" + "<method name='HandleArguments'>" + "<arg type='ay' name='working_directory' direction='in' />" + "<arg type='ay' name='display_name' direction='in' />" + "<arg type='ay' name='startup_id' direction='in' />" + "<arg type='ay' name='environment' direction='in' />" + "<arg type='ay' name='arguments' direction='in' />" + "</method>" + "</interface>" + "</node>"; + + static const GDBusInterfaceVTable interface_vtable = { + method_call_cb, + NULL, + NULL, + }; + + OwnData *data = (OwnData *) user_data; + GDBusNodeInfo *introspection_data; + guint registration_id; + GError *error = NULL; + + _terminal_debug_print (TERMINAL_DEBUG_FACTORY, + "Bus %s acquired\n", name); + + introspection_data = g_dbus_node_info_new_for_xml (dbus_introspection_xml, NULL); + g_assert (introspection_data != NULL); + + registration_id = g_dbus_connection_register_object (connection, + TERMINAL_FACTORY_SERVICE_PATH, + introspection_data->interfaces[0], + &interface_vtable, + NULL, NULL, + &error); + g_dbus_node_info_unref (introspection_data); + + if (registration_id == 0) { + g_printerr ("Failed to register object: %s\n", error->message); + g_error_free (error); + data->exit_code = EXIT_FAILURE; + gtk_main_quit (); + } +} + +static void +name_acquired_cb (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + OwnData *data = (OwnData *) user_data; + GError *error = NULL; + + _terminal_debug_print (TERMINAL_DEBUG_FACTORY, + "Acquired the name %s on the session bus\n", name); + + if (data->options == NULL) { + /* Name re-acquired!? */ + g_assert_not_reached (); + } + + + if (!terminal_app_handle_options (terminal_app_get (), data->options, TRUE /* do resume */, &error)) { + g_printerr ("Failed to handle options: %s\n", error->message); + g_error_free (error); + data->exit_code = EXIT_FAILURE; + gtk_main_quit (); + } + + terminal_options_free (data->options); + data->options = NULL; +} + +static void +name_lost_cb (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + OwnData *data = (OwnData *) user_data; + GError *error = NULL; + char **envv; + int envc, i; + GVariantBuilder builder; + GVariant *value; + GString *string; + char *s; + gsize len; + + _terminal_debug_print (TERMINAL_DEBUG_FACTORY, + "Lost the name %s on the session bus\n", name); + + /* Couldn't get the connection? No way to continue! */ + if (connection == NULL) { + data->exit_code = EXIT_FAILURE; + gtk_main_quit (); + return; + } + + if (data->options == NULL) { + /* Already handled */ + data->exit_code = EXIT_SUCCESS; + gtk_main_quit (); + return; + } + + _terminal_debug_print (TERMINAL_DEBUG_FACTORY, + "Forwarding arguments to existing instance\n"); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(ayayayayay)")); + + g_variant_builder_add (&builder, "@ay", string_to_ay (data->options->default_working_dir)); + g_variant_builder_add (&builder, "@ay", string_to_ay (data->options->display_name)); + g_variant_builder_add (&builder, "@ay", string_to_ay (data->options->startup_id)); + + string = g_string_new (NULL); + envv = g_listenv (); + envc = g_strv_length (envv); + for (i = 0; i < envc; ++i) + { + const char *value; + + value = g_getenv (envv[i]); + if (value == NULL) + continue; + + if (i > 0) + g_string_append_c (string, '\0'); + + g_string_append_printf (string, "%s=%s", envv[i], value); + } + + len = string->len; + s = g_string_free (string, FALSE); + g_variant_builder_add (&builder, "@ay", + g_variant_new_from_data (G_VARIANT_TYPE ("ay"), s, len, TRUE, g_free, s)); + + string = g_string_new (NULL); + + for (i = 0; i < data->argc; ++i) + { + if (i > 0) + g_string_append_c (string, '\0'); + g_string_append (string, data->argv[i]); + } + + len = string->len; + s = g_string_free (string, FALSE); + g_variant_builder_add (&builder, "@ay", + g_variant_new_from_data (G_VARIANT_TYPE ("ay"), s, len, TRUE, g_free, s)); + + value = g_dbus_connection_call_sync (connection, + data->factory_name, + TERMINAL_FACTORY_SERVICE_PATH, + TERMINAL_FACTORY_INTERFACE_NAME, + "HandleArguments", + g_variant_builder_end (&builder), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (value == NULL) { + g_printerr ("Failed to forward arguments: %s\n", error->message); + g_error_free (error); + data->exit_code = EXIT_FAILURE; + gtk_main_quit (); + } else { + g_variant_unref (value); + data->exit_code = EXIT_SUCCESS; + } + + terminal_options_free (data->options); + data->options = NULL; + + gtk_main_quit (); +} + +/* Settings storage works as follows: + * /apps/mate-terminal/global/ + * /apps/mate-terminal/profiles/Foo/ + * + * It's somewhat tricky to manage the profiles/ dir since we need to track + * the list of profiles, but mateconf doesn't have a concept of notifying that + * a directory has appeared or disappeared. + * + * Session state is stored entirely in the RestartCommand command line. + * + * The number one rule: all stored information is EITHER per-session, + * per-profile, or set from a command line option. THERE CAN BE NO + * OVERLAP. The UI and implementation totally break if you overlap + * these categories. See mate-terminal 1.x for why. + * + * Don't use this code as an example of how to use MateConf - it's hugely + * overcomplicated due to the profiles stuff. Most apps should not + * have to do scary things of this nature, and should not have + * a profiles feature. + * + */ + +/* Copied from libcaja/caja-program-choosing.c; Needed in case + * we have no DESKTOP_STARTUP_ID (with its accompanying timestamp). + */ +static Time +slowly_and_stupidly_obtain_timestamp (Display *xdisplay) +{ + Window xwindow; + XEvent event; + + { + XSetWindowAttributes attrs; + Atom atom_name; + Atom atom_type; + const char *name; + + attrs.override_redirect = True; + attrs.event_mask = PropertyChangeMask | StructureNotifyMask; + + xwindow = + XCreateWindow (xdisplay, + RootWindow (xdisplay, 0), + -100, -100, 1, 1, + 0, + CopyFromParent, + CopyFromParent, + (Visual *)CopyFromParent, + CWOverrideRedirect | CWEventMask, + &attrs); + + atom_name = XInternAtom (xdisplay, "WM_NAME", TRUE); + g_assert (atom_name != None); + atom_type = XInternAtom (xdisplay, "STRING", TRUE); + g_assert (atom_type != None); + + name = "Fake Window"; + XChangeProperty (xdisplay, + xwindow, atom_name, + atom_type, + 8, PropModeReplace, (unsigned char *)name, strlen (name)); + } + + XWindowEvent (xdisplay, + xwindow, + PropertyChangeMask, + &event); + + XDestroyWindow(xdisplay, xwindow); + + return event.xproperty.time; +} + +static char * +get_factory_name_for_display (const char *display_name) +{ + GString *name; + const char *p; + + name = g_string_sized_new (strlen (TERMINAL_FACTORY_SERVICE_NAME_PREFIX) + strlen (display_name) + 1 /* NUL */); + g_string_append (name, TERMINAL_FACTORY_SERVICE_NAME_PREFIX); + + for (p = display_name; *p; ++p) + { + if (g_ascii_isalnum (*p)) + g_string_append_c (name, *p); + else + g_string_append_c (name, '_'); + } + + _terminal_debug_print (TERMINAL_DEBUG_FACTORY, + "Factory name is \"%s\"\n", name->str); + + return g_string_free (name, FALSE); +} + +int +main (int argc, char **argv) +{ + int i; + char **argv_copy; + int argc_copy; + const char *startup_id, *display_name, *home_dir; + GdkDisplay *display; + TerminalOptions *options; + GError *error = NULL; + char *working_directory; + int ret = EXIT_SUCCESS; + + setlocale (LC_ALL, ""); + + bindtextdomain (GETTEXT_PACKAGE, TERM_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + /* MateConf uses MateCORBA2 which need GThread. See bug #565516 */ + g_thread_init (NULL); + + _terminal_debug_init (); + + /* Make a NULL-terminated copy since we may need it later */ + argv_copy = g_new (char *, argc + 1); + for (i = 0; i < argc; ++i) + argv_copy [i] = argv [i]; + argv_copy [i] = NULL; + argc_copy = argc; + + startup_id = g_getenv ("DESKTOP_STARTUP_ID"); + + working_directory = g_get_current_dir (); + + /* Now change directory to $HOME so we don't prevent unmounting, e.g. if the + * factory is started by caja-open-terminal. See bug #565328. + * On failure back to /. + */ + home_dir = g_get_home_dir (); + if (home_dir == NULL || chdir (home_dir) < 0) + (void) chdir ("/"); + + options = terminal_options_parse (working_directory, + NULL, + startup_id, + NULL, + FALSE, + FALSE, + &argc, &argv, + &error, + gtk_get_option_group (TRUE), +#ifdef WITH_SMCLIENT + egg_sm_client_get_option_group (), +#endif + NULL); + + g_free (working_directory); + + if (options == NULL) { + g_printerr (_("Failed to parse arguments: %s\n"), error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + g_set_application_name (_("Terminal")); + + /* Unset the these env variables, so they doesn't end up + * in the factory's env and thus in the terminals' envs. + */ + g_unsetenv ("DESKTOP_STARTUP_ID"); + g_unsetenv ("GIO_LAUNCHED_DESKTOP_FILE_PID"); + g_unsetenv ("GIO_LAUNCHED_DESKTOP_FILE"); + + /* Do this here so that gdk_display is initialized */ + if (options->startup_id == NULL) + { + /* Create a fake one containing a timestamp that we can use */ + Time timestamp; + + timestamp = slowly_and_stupidly_obtain_timestamp (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + options->startup_id = g_strdup_printf ("_TIME%lu", timestamp); + } + + display = gdk_display_get_default (); + display_name = gdk_display_get_name (display); + options->display_name = g_strdup (display_name); + + if (options->use_factory) { + OwnData *data; + guint owner_id; + + data = g_new (OwnData, 1); + data->factory_name = get_factory_name_for_display (display_name); + data->options = options; + data->exit_code = -1; + data->argv = argv_copy; + data->argc = argc_copy; + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + data->factory_name, + G_BUS_NAME_OWNER_FLAGS_NONE, + bus_acquired_cb, + name_acquired_cb, + name_lost_cb, + data, NULL); + + gtk_main (); + + ret = data->exit_code; + g_bus_unown_name (owner_id); + + g_free (data->factory_name); + g_free (data); + + } else { + + terminal_app_handle_options (terminal_app_get (), options, TRUE /* allow resume */, &error); + terminal_options_free (options); + + if (error == NULL) { + gtk_main (); + } else { + g_printerr ("Error handling options: %s\n", error->message); + g_error_free (error); + ret = EXIT_FAILURE; + } + } + + terminal_app_shutdown (); + + g_free (argv_copy); + + return ret; +} |