summaryrefslogtreecommitdiff
path: root/src/terminal.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal.c')
-rw-r--r--src/terminal.c619
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;
+}