/*
* 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 .
*/
#include "config.h"
#include "terminal-gdbus.hh"
#include
#include
#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
(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
(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
(g_object_new (TERMINAL_TYPE_FACTORY_IMPL, nullptr));
}