diff options
Diffstat (limited to 'src/terminal-gdbus.cc')
-rw-r--r-- | src/terminal-gdbus.cc | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/src/terminal-gdbus.cc b/src/terminal-gdbus.cc new file mode 100644 index 0000000..55f3391 --- /dev/null +++ b/src/terminal-gdbus.cc @@ -0,0 +1,573 @@ +/* + * Copyright © 2011, 2012 Christian Persch + * + * This program 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. + * + * This program 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 "terminal-gdbus.hh" + +#include <gio/gio.h> +#include <gio/gunixfdlist.h> + +#include "terminal-app.hh" +#include "terminal-debug.hh" +#include "terminal-defines.hh" +#include "terminal-mdi-container.hh" +#include "terminal-util.hh" +#include "terminal-window.hh" +#include "terminal-libgsystem.hh" + +/* ------------------------------------------------------------------------- */ + +#define TERMINAL_RECEIVER_IMPL_GET_PRIVATE(impl)(G_TYPE_INSTANCE_GET_PRIVATE ((impl), TERMINAL_TYPE_RECEIVER_IMPL, TerminalReceiverImplPrivate)) + +struct _TerminalReceiverImplPrivate { + TerminalScreen *screen; /* unowned! */ +}; + +enum { + PROP_0, + PROP_SCREEN +}; + +/* helper functions */ + +static void +child_exited_cb (VteTerminal *terminal, + int exit_code, + TerminalReceiver *receiver) +{ + terminal_receiver_emit_child_exited (receiver, exit_code); +} + +static void +terminal_receiver_impl_set_screen (TerminalReceiverImpl *impl, + TerminalScreen *screen) +{ + TerminalReceiverImplPrivate *priv; + + g_return_if_fail (TERMINAL_IS_RECEIVER_IMPL (impl)); + g_return_if_fail (screen == nullptr || TERMINAL_IS_SCREEN (screen)); + + priv = impl->priv; + if (priv->screen == screen) + return; + + if (priv->screen) { + g_signal_handlers_disconnect_matched (priv->screen, + G_SIGNAL_MATCH_DATA, + 0, 0, nullptr, nullptr, impl); + } + + priv->screen = screen; + if (screen) { + g_signal_connect (screen, "child-exited", + G_CALLBACK (child_exited_cb), + impl); + } + + g_object_notify (G_OBJECT (impl), "screen"); +} + +/* Class implementation */ + +namespace { + +typedef struct { + TerminalReceiver *receiver; + GDBusMethodInvocation *invocation; +} ExecData; + +} // anon namespace + +static void +exec_data_free (ExecData *data) +{ + g_object_unref (data->receiver); + g_object_unref (data->invocation); + g_free (data); +} + +static void +exec_cb (TerminalScreen *screen, /* unused, may be %nullptr */ + GError *error, /* set on error, %nullptr on success */ + ExecData *data) +{ + /* Note: these calls transfer the ref */ + g_object_ref (data->invocation); + if (error) { + g_dbus_method_invocation_return_gerror (data->invocation, error); + } else { + terminal_receiver_complete_exec (data->receiver, data->invocation, nullptr /* outfdlist */); + } +} + +static gboolean +terminal_receiver_impl_exec (TerminalReceiver *receiver, + GDBusMethodInvocation *invocation, + GUnixFDList *fd_list, + GVariant *options, + GVariant *arguments) +{ + TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (receiver); + TerminalReceiverImplPrivate *priv = impl->priv; + const char *working_directory; + gboolean shell; + gsize exec_argc; + gs_free char **exec_argv = nullptr; /* container needs to be freed, strings not owned */ + gs_free char **envv = nullptr; /* container needs to be freed, strings not owned */ + gs_unref_variant GVariant *fd_array = nullptr; + + if (priv->screen == nullptr) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Terminal already closed"); + return TRUE; /* handled */ + } + + if (!g_variant_lookup (options, "cwd", "^&ay", &working_directory)) + working_directory = nullptr; + if (!g_variant_lookup (options, "shell", "b", &shell)) + shell = FALSE; + if (!g_variant_lookup (options, "environ", "^a&ay", &envv)) + envv = nullptr; + if (!g_variant_lookup (options, "fd-set", "@a(ih)", &fd_array)) + fd_array = nullptr; + + /* Check environment */ + if (!terminal_util_check_envv((const char * const*)envv)) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Malformed environment"); + return TRUE; /* handled */ + } + + /* Check FD passing */ + if ((fd_list != nullptr) ^ (fd_array != nullptr)) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Must pass both fd-set options and a FD list"); + return TRUE; /* handled */ + } + if (fd_list != nullptr && fd_array != nullptr) { + const int *fd_array_data; + gsize fd_array_data_len, i; + int n_fds; + + fd_array_data = reinterpret_cast<int const*> + (g_variant_get_fixed_array (fd_array, &fd_array_data_len, 2 * sizeof (int))); + n_fds = g_unix_fd_list_get_length (fd_list); + for (i = 0; i < fd_array_data_len; i++) { + const int fd = fd_array_data[2 * i]; + const int idx = fd_array_data[2 * i + 1]; + + if (fd == -1) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Passing of invalid FD %d not supported", fd); + return TRUE; /* handled */ + } + if (fd == STDIN_FILENO || + fd == STDOUT_FILENO || + fd == STDERR_FILENO) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Passing of std%s not supported", + fd == STDIN_FILENO ? "in" : fd == STDOUT_FILENO ? "out" : "err"); + return TRUE; /* handled */ + } + if (idx < 0 || idx >= n_fds) { + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Handle out of range"); + return TRUE; /* handled */ + } + } + } + + if (working_directory != nullptr) + _terminal_debug_print (TERMINAL_DEBUG_SERVER, + "CWD is '%s'\n", working_directory); + + exec_argv = (char **) g_variant_get_bytestring_array (arguments, &exec_argc); + + ExecData *exec_data = g_new (ExecData, 1); + exec_data->receiver = (TerminalReceiver*)g_object_ref (receiver); + /* We want to transfer the ownership of @invocation to ExecData here, but + * we have to temporarily ref it so that in the error case below (where + * terminal_screen_exec() frees the exec data via the supplied callback, + * the g_dbus_method_invocation_take_error() calll still can take ownership + * of the invocation's ref passed to this function (terminal_receiver_impl_exec()). + */ + exec_data->invocation = (GDBusMethodInvocation*)g_object_ref (invocation); + + GError *err = nullptr; + if (!terminal_screen_exec (priv->screen, + exec_argc > 0 ? exec_argv : nullptr, + envv, + shell, + working_directory, + fd_list, fd_array, + (TerminalScreenExecCallback) exec_cb, + exec_data /* adopted */, + (GDestroyNotify) exec_data_free, + nullptr /* cancellable */, + &err)) { + /* Transfers ownership of @invocation */ + g_dbus_method_invocation_take_error (invocation, err); + } + + /* Now we can remove that extra ref again. */ + g_object_unref (invocation); + + return TRUE; /* handled */ +} + +static void +terminal_receiver_impl_iface_init (TerminalReceiverIface *iface) +{ + iface->handle_exec = terminal_receiver_impl_exec; +} + +G_DEFINE_TYPE_WITH_CODE (TerminalReceiverImpl, terminal_receiver_impl, TERMINAL_TYPE_RECEIVER_SKELETON, + G_IMPLEMENT_INTERFACE (TERMINAL_TYPE_RECEIVER, terminal_receiver_impl_iface_init)) + +static void +terminal_receiver_impl_init (TerminalReceiverImpl *impl) +{ + impl->priv = TERMINAL_RECEIVER_IMPL_GET_PRIVATE (impl); +} + +static void +terminal_receiver_impl_dispose (GObject *object) +{ + TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (object); + + terminal_receiver_impl_set_screen (impl, nullptr); + + G_OBJECT_CLASS (terminal_receiver_impl_parent_class)->dispose (object); +} + +static void +terminal_receiver_impl_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (object); + + switch (prop_id) { + case PROP_SCREEN: + g_value_set_object (value, terminal_receiver_impl_get_screen (impl)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +terminal_receiver_impl_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (object); + + switch (prop_id) { + case PROP_SCREEN: + terminal_receiver_impl_set_screen (impl, (TerminalScreen*)g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +terminal_receiver_impl_class_init (TerminalReceiverImplClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = terminal_receiver_impl_dispose; + gobject_class->get_property = terminal_receiver_impl_get_property; + gobject_class->set_property = terminal_receiver_impl_set_property; + + g_object_class_install_property + (gobject_class, + PROP_SCREEN, + g_param_spec_object ("screen", nullptr, nullptr, + TERMINAL_TYPE_SCREEN, + GParamFlags(G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS))); + + g_type_class_add_private (gobject_class, sizeof (TerminalReceiverImplPrivate)); +} + +/* public API */ + +/** + * terminal_receiver_impl_new: + * @screen: a #TerminalScreen + * + * Returns: a new #TerminalReceiverImpl for @screen + */ +TerminalReceiverImpl * +terminal_receiver_impl_new (TerminalScreen *screen) +{ + return reinterpret_cast<TerminalReceiverImpl*> + (g_object_new (TERMINAL_TYPE_RECEIVER_IMPL, + "screen", screen, + nullptr)); +} + +/** + * terminal_receiver_impl_get_screen: + * @impl: a #TerminalReceiverImpl + * + * Returns: (transfer none): the impl's #TerminalScreen, or %nullptr + */ +TerminalScreen * +terminal_receiver_impl_get_screen (TerminalReceiverImpl *impl) +{ + g_return_val_if_fail (TERMINAL_IS_RECEIVER_IMPL (impl), nullptr); + + return impl->priv->screen; +} + +/** + * terminal_receiver_impl_unget_screen: + * @impl: a #TerminalReceiverImpl + * + * Unsets the impls #TerminalScreen. + */ +void +terminal_receiver_impl_unset_screen (TerminalReceiverImpl *impl) +{ + g_return_if_fail (TERMINAL_IS_RECEIVER_IMPL (impl)); + + terminal_receiver_impl_set_screen (impl, nullptr); +} + +/* --------------------------------------------------------------------------- + * TerminalFactoryImpl + * --------------------------------------------------------------------------- + */ + +struct _TerminalFactoryImplPrivate { + gpointer dummy; +}; + +static gboolean +terminal_factory_impl_create_instance (TerminalFactory *factory, + GDBusMethodInvocation *invocation, + GVariant *options) +{ + TerminalApp *app = terminal_app_get (); + + /* If a parent screen is specified, use that to fill in missing information */ + TerminalScreen *parent_screen = nullptr; + const char *parent_screen_object_path; + if (g_variant_lookup (options, "parent-screen", "&o", &parent_screen_object_path)) { + parent_screen = terminal_app_get_screen_by_object_path (app, parent_screen_object_path); + if (parent_screen == nullptr) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "Failed to get screen from object path %s", + parent_screen_object_path); + return TRUE; + } + } + + /* Try getting a parent window, first by parent screen then by window ID; + * if that fails, create a new window. + */ + TerminalWindow *window = nullptr; + gboolean have_new_window = FALSE; + const char *window_from_screen_object_path; + if (g_variant_lookup (options, "window-from-screen", "&o", &window_from_screen_object_path)) { + TerminalScreen *window_screen = + terminal_app_get_screen_by_object_path (app, window_from_screen_object_path); + if (window_screen == nullptr) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "Failed to get screen from object path %s", + parent_screen_object_path); + return TRUE; + } + + GtkWidget *win = gtk_widget_get_toplevel (GTK_WIDGET (window_screen)); + if (TERMINAL_IS_WINDOW (win)) + window = TERMINAL_WINDOW (win); + } + + /* Support old client */ + guint window_id; + if (window == nullptr && g_variant_lookup (options, "window-id", "u", &window_id)) { + GtkWindow *win = gtk_application_get_window_by_id (GTK_APPLICATION (app), window_id); + + if (!TERMINAL_IS_WINDOW (win)) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "Nonexisting window %u referenced", + window_id); + return TRUE; + } + + window = TERMINAL_WINDOW (win); + } + + /* Still no parent window? Create a new one */ + if (window == nullptr) { + const char *startup_id, *role, *activation_token; + gboolean start_maximized, start_fullscreen; + + window = terminal_window_new (G_APPLICATION (app)); + have_new_window = TRUE; + + if (g_variant_lookup (options, "activation-token", "&s", &activation_token)) + gtk_window_set_startup_id (GTK_WINDOW (window), activation_token); + else if (g_variant_lookup (options, "desktop-startup-id", "^&ay", &startup_id)) + gtk_window_set_startup_id (GTK_WINDOW (window), startup_id); + + /* Overwrite the default, unique window role set in terminal_window_init */ + if (g_variant_lookup (options, "role", "&s", &role)) + gtk_window_set_role (GTK_WINDOW (window), role); + + gboolean show_menubar; + if (g_variant_lookup (options, "show-menubar", "b", &show_menubar)) + terminal_window_set_menubar_visible (window, show_menubar); + + if (g_variant_lookup (options, "fullscreen-window", "b", &start_fullscreen) && + start_fullscreen) { + gtk_window_fullscreen (GTK_WINDOW (window)); + } + if (g_variant_lookup (options, "maximize-window", "b", &start_maximized) && + start_maximized) { + gtk_window_maximize (GTK_WINDOW (window)); + } + + have_new_window = TRUE; + } + + g_assert_nonnull (window); + + const char *title; + if (!g_variant_lookup (options, "title", "&s", &title)) + title = nullptr; + + double zoom; + if (!g_variant_lookup (options, "zoom", "d", &zoom)) { + if (parent_screen != nullptr) + zoom = vte_terminal_get_font_scale (VTE_TERMINAL (parent_screen)); + else + zoom = 1.0; + } + + /* Look up the profile */ + gs_unref_object GSettings *profile = nullptr; + const char *profile_uuid; + if (!g_variant_lookup (options, "profile", "&s", &profile_uuid)) + profile_uuid = nullptr; + + if (profile_uuid == nullptr && parent_screen != nullptr) { + profile = terminal_screen_ref_profile (parent_screen); + } else { + GError *err = nullptr; + profile = terminal_profiles_list_ref_profile_by_uuid (terminal_app_get_profiles_list (app), + profile_uuid /* default if nullptr */, + &err); + if (profile == nullptr) { + g_dbus_method_invocation_return_gerror (invocation, err); + g_error_free (err); + return TRUE; + } + } + + g_assert_nonnull (profile); + + /* Now we can create the new screen */ + TerminalScreen *screen = terminal_screen_new (profile, title, zoom); + terminal_window_add_screen (window, screen, -1); + + /* Apply window properties */ + gboolean active; + if (g_variant_lookup (options, "active", "b", &active) && + active) { + terminal_window_switch_screen (window, screen); + gtk_widget_grab_focus (GTK_WIDGET (screen)); + } + + if (have_new_window) { + const char *geometry; + + if (g_variant_lookup (options, "geometry", "&s", &geometry) && + !terminal_window_parse_geometry (window, geometry)) + _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, + "Invalid geometry string \"%s\"", geometry); + } + + gboolean present_window; + gboolean present_window_set = g_variant_lookup (options, "present-window", "b", &present_window); + + if (have_new_window || (present_window_set && present_window)) + gtk_window_present (GTK_WINDOW (window)); + + gs_free char *object_path = terminal_app_dup_screen_object_path (app, screen); + terminal_factory_complete_create_instance (factory, invocation, object_path); + + return TRUE; /* handled */ +} + +static void +terminal_factory_impl_iface_init (TerminalFactoryIface *iface) +{ + iface->handle_create_instance = terminal_factory_impl_create_instance; +} + +G_DEFINE_TYPE_WITH_CODE (TerminalFactoryImpl, terminal_factory_impl, TERMINAL_TYPE_FACTORY_SKELETON, + G_IMPLEMENT_INTERFACE (TERMINAL_TYPE_FACTORY, terminal_factory_impl_iface_init)) + +static void +terminal_factory_impl_init (TerminalFactoryImpl *impl) +{ + impl->priv = G_TYPE_INSTANCE_GET_PRIVATE (impl, TERMINAL_TYPE_FACTORY_IMPL, TerminalFactoryImplPrivate); +} + +static void +terminal_factory_impl_class_init (TerminalFactoryImplClass *klass) +{ + /* g_type_class_add_private (klass, sizeof (TerminalFactoryImplPrivate)); */ +} + +/** + * terminal_factory_impl_new: + * + * Returns: (transfer full): a new #TerminalFactoryImpl + */ +TerminalFactory * +terminal_factory_impl_new (void) +{ + return reinterpret_cast<TerminalFactory*> + (g_object_new (TERMINAL_TYPE_FACTORY_IMPL, nullptr)); +} |