mutter/src/backends/meta-remote-desktop-session.c
Jonas Ådahl 688280f82d remote-desktop/session: Handle unable to transform coordinate gracefully
There may be a race between the ability to turn stream relative input
coordinates and turning them into screen coordinates, due to the future
scenario where the entity backing a stream is created and managed ad-hoc
depending on PipeWire stream negotiations.

When an input event is sent during this time, drop it.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1698>
2021-03-12 15:09:45 +00:00

1635 lines
56 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2015-2017 Red Hat Inc.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
*/
#include "config.h"
#include "backends/meta-remote-desktop-session.h"
#include <fcntl.h>
#include <gio/gunixfdlist.h>
#include <gio/gunixoutputstream.h>
#include <glib-unix.h>
#include <linux/input.h>
#include <stdlib.h>
#include <unistd.h>
#include <xkbcommon/xkbcommon.h>
#include "backends/meta-dbus-session-watcher.h"
#include "backends/meta-screen-cast-session.h"
#include "backends/meta-remote-access-controller-private.h"
#include "backends/x11/meta-backend-x11.h"
#include "cogl/cogl.h"
#include "core/display-private.h"
#include "core/meta-selection-private.h"
#include "core/meta-selection-source-remote.h"
#include "meta/meta-backend.h"
#include "meta-dbus-remote-desktop.h"
#define META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/org/gnome/Mutter/RemoteDesktop/Session"
#define TRANSFER_REQUEST_CLEANUP_TIMEOUT_MS (s2ms (15))
typedef enum _MetaRemoteDesktopNotifyAxisFlags
{
META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_NONE = 0,
META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_FINISH = 1 << 0,
META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_WHEEL = 1 << 1,
META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_FINGER = 1 << 2,
META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_CONTINUOUS = 1 << 3,
} MetaRemoteDesktopNotifyAxisFlags;
typedef struct _SelectionReadData
{
MetaRemoteDesktopSession *session;
GOutputStream *stream;
GCancellable *cancellable;
} SelectionReadData;
struct _MetaRemoteDesktopSession
{
MetaDBusRemoteDesktopSessionSkeleton parent;
GDBusConnection *connection;
char *peer_name;
char *session_id;
char *object_path;
MetaScreenCastSession *screen_cast_session;
gulong screen_cast_session_closed_handler_id;
guint started : 1;
ClutterVirtualInputDevice *virtual_pointer;
ClutterVirtualInputDevice *virtual_keyboard;
ClutterVirtualInputDevice *virtual_touchscreen;
MetaRemoteDesktopSessionHandle *handle;
gboolean is_clipboard_enabled;
gulong owner_changed_handler_id;
SelectionReadData *read_data;
unsigned int transfer_serial;
MetaSelectionSourceRemote *current_source;
GHashTable *transfer_requests;
guint transfer_request_timeout_id;
};
static void
meta_remote_desktop_session_init_iface (MetaDBusRemoteDesktopSessionIface *iface);
static void
meta_dbus_session_init_iface (MetaDbusSessionInterface *iface);
G_DEFINE_TYPE_WITH_CODE (MetaRemoteDesktopSession,
meta_remote_desktop_session,
META_DBUS_TYPE_REMOTE_DESKTOP_SESSION_SKELETON,
G_IMPLEMENT_INTERFACE (META_DBUS_TYPE_REMOTE_DESKTOP_SESSION,
meta_remote_desktop_session_init_iface)
G_IMPLEMENT_INTERFACE (META_TYPE_DBUS_SESSION,
meta_dbus_session_init_iface))
struct _MetaRemoteDesktopSessionHandle
{
MetaRemoteAccessHandle parent;
MetaRemoteDesktopSession *session;
};
G_DEFINE_TYPE (MetaRemoteDesktopSessionHandle,
meta_remote_desktop_session_handle,
META_TYPE_REMOTE_ACCESS_HANDLE)
static MetaRemoteDesktopSessionHandle *
meta_remote_desktop_session_handle_new (MetaRemoteDesktopSession *session);
static gboolean
meta_remote_desktop_session_is_running (MetaRemoteDesktopSession *session)
{
return !!session->virtual_pointer;
}
static void
init_remote_access_handle (MetaRemoteDesktopSession *session)
{
MetaBackend *backend = meta_get_backend ();
MetaRemoteAccessController *remote_access_controller;
MetaRemoteAccessHandle *remote_access_handle;
session->handle = meta_remote_desktop_session_handle_new (session);
remote_access_controller = meta_backend_get_remote_access_controller (backend);
remote_access_handle = META_REMOTE_ACCESS_HANDLE (session->handle);
meta_remote_access_controller_notify_new_handle (remote_access_controller,
remote_access_handle);
}
static gboolean
meta_remote_desktop_session_start (MetaRemoteDesktopSession *session,
GError **error)
{
ClutterBackend *backend = clutter_get_default_backend ();
ClutterSeat *seat = clutter_backend_get_default_seat (backend);
g_assert (!session->started);
if (session->screen_cast_session)
{
if (!meta_screen_cast_session_start (session->screen_cast_session, error))
return FALSE;
}
session->virtual_pointer =
clutter_seat_create_virtual_device (seat, CLUTTER_POINTER_DEVICE);
session->virtual_keyboard =
clutter_seat_create_virtual_device (seat, CLUTTER_KEYBOARD_DEVICE);
session->virtual_touchscreen =
clutter_seat_create_virtual_device (seat, CLUTTER_TOUCHSCREEN_DEVICE);
init_remote_access_handle (session);
session->started = TRUE;
return TRUE;
}
void
meta_remote_desktop_session_close (MetaRemoteDesktopSession *session)
{
MetaDBusRemoteDesktopSession *skeleton =
META_DBUS_REMOTE_DESKTOP_SESSION (session);
session->started = FALSE;
if (session->screen_cast_session)
{
g_clear_signal_handler (&session->screen_cast_session_closed_handler_id,
session->screen_cast_session);
meta_screen_cast_session_close (session->screen_cast_session);
session->screen_cast_session = NULL;
}
g_clear_object (&session->virtual_pointer);
g_clear_object (&session->virtual_keyboard);
g_clear_object (&session->virtual_touchscreen);
meta_dbus_session_notify_closed (META_DBUS_SESSION (session));
meta_dbus_remote_desktop_session_emit_closed (skeleton);
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (session));
if (session->handle)
{
MetaRemoteAccessHandle *remote_access_handle =
META_REMOTE_ACCESS_HANDLE (session->handle);
meta_remote_access_handle_notify_stopped (remote_access_handle);
}
g_object_unref (session);
}
char *
meta_remote_desktop_session_get_object_path (MetaRemoteDesktopSession *session)
{
return session->object_path;
}
char *
meta_remote_desktop_session_get_session_id (MetaRemoteDesktopSession *session)
{
return session->session_id;
}
static void
on_screen_cast_session_closed (MetaScreenCastSession *screen_cast_session,
MetaRemoteDesktopSession *session)
{
session->screen_cast_session = NULL;
meta_remote_desktop_session_close (session);
}
gboolean
meta_remote_desktop_session_register_screen_cast (MetaRemoteDesktopSession *session,
MetaScreenCastSession *screen_cast_session,
GError **error)
{
if (session->screen_cast_session)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Remote desktop session already have an associated "
"screen cast session");
return FALSE;
}
session->screen_cast_session = screen_cast_session;
session->screen_cast_session_closed_handler_id =
g_signal_connect (screen_cast_session, "session-closed",
G_CALLBACK (on_screen_cast_session_closed),
session);
return TRUE;
}
MetaRemoteDesktopSession *
meta_remote_desktop_session_new (MetaRemoteDesktop *remote_desktop,
const char *peer_name,
GError **error)
{
MetaBackend *backend = meta_remote_desktop_get_backend (remote_desktop);
ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
ClutterSeat *seat = clutter_backend_get_default_seat (clutter_backend);
ClutterKeymap *keymap = clutter_seat_get_keymap (seat);
GDBusInterfaceSkeleton *interface_skeleton;
MetaRemoteDesktopSession *session;
session = g_object_new (META_TYPE_REMOTE_DESKTOP_SESSION, NULL);
session->peer_name = g_strdup (peer_name);
interface_skeleton = G_DBUS_INTERFACE_SKELETON (session);
session->connection = meta_remote_desktop_get_connection (remote_desktop);
if (!g_dbus_interface_skeleton_export (interface_skeleton,
session->connection,
session->object_path,
error))
{
g_object_unref (session);
return NULL;
}
g_object_bind_property (keymap, "caps-lock-state",
session, "caps-lock-state",
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
g_object_bind_property (keymap, "num-lock-state",
session, "num-lock-state",
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
return session;
}
static gboolean
check_permission (MetaRemoteDesktopSession *session,
GDBusMethodInvocation *invocation)
{
return g_strcmp0 (session->peer_name,
g_dbus_method_invocation_get_sender (invocation)) == 0;
}
static gboolean
meta_remote_desktop_session_check_can_notify (MetaRemoteDesktopSession *session,
GDBusMethodInvocation *invocation)
{
if (!session->started)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Session not started");
return FALSE;
}
if (!check_permission (session, invocation))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Permission denied");
return FALSE;
}
return TRUE;
}
static gboolean
handle_start (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
GError *error = NULL;
if (session->started)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Already started");
return TRUE;
}
if (!check_permission (session, invocation))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Permission denied");
return TRUE;
}
if (!meta_remote_desktop_session_start (session, &error))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed to start remote desktop: %s",
error->message);
g_error_free (error);
meta_remote_desktop_session_close (session);
return TRUE;
}
meta_dbus_remote_desktop_session_complete_start (skeleton, invocation);
return TRUE;
}
static gboolean
handle_stop (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
if (!session->started)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Session not started");
return TRUE;
}
if (!check_permission (session, invocation))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Permission denied");
return TRUE;
}
meta_remote_desktop_session_close (session);
meta_dbus_remote_desktop_session_complete_stop (skeleton, invocation);
return TRUE;
}
static gboolean
handle_notify_keyboard_keycode (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
unsigned int keycode,
gboolean pressed)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
ClutterKeyState state;
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
if (pressed)
state = CLUTTER_KEY_STATE_PRESSED;
else
state = CLUTTER_KEY_STATE_RELEASED;
clutter_virtual_input_device_notify_key (session->virtual_keyboard,
CLUTTER_CURRENT_TIME,
keycode,
state);
meta_dbus_remote_desktop_session_complete_notify_keyboard_keycode (skeleton,
invocation);
return TRUE;
}
static gboolean
handle_notify_keyboard_keysym (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
unsigned int keysym,
gboolean pressed)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
ClutterKeyState state;
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
if (pressed)
state = CLUTTER_KEY_STATE_PRESSED;
else
state = CLUTTER_KEY_STATE_RELEASED;
clutter_virtual_input_device_notify_keyval (session->virtual_keyboard,
CLUTTER_CURRENT_TIME,
keysym,
state);
meta_dbus_remote_desktop_session_complete_notify_keyboard_keysym (skeleton,
invocation);
return TRUE;
}
/* Translation taken from the clutter evdev backend. */
static int
translate_to_clutter_button (int button)
{
switch (button)
{
case BTN_LEFT:
return CLUTTER_BUTTON_PRIMARY;
case BTN_RIGHT:
return CLUTTER_BUTTON_SECONDARY;
case BTN_MIDDLE:
return CLUTTER_BUTTON_MIDDLE;
default:
/*
* For compatibility reasons, all additional buttons go after the old
* 4-7 scroll ones.
*/
return button - (BTN_LEFT - 1) + 4;
}
}
static gboolean
handle_notify_pointer_button (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
int button_code,
gboolean pressed)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
uint32_t button;
ClutterButtonState state;
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
button = translate_to_clutter_button (button_code);
if (pressed)
state = CLUTTER_BUTTON_STATE_PRESSED;
else
state = CLUTTER_BUTTON_STATE_RELEASED;
clutter_virtual_input_device_notify_button (session->virtual_pointer,
CLUTTER_CURRENT_TIME,
button,
state);
meta_dbus_remote_desktop_session_complete_notify_pointer_button (skeleton,
invocation);
return TRUE;
}
static gboolean
clutter_scroll_source_from_axis_flags (MetaRemoteDesktopNotifyAxisFlags axis_flags,
ClutterScrollSource *scroll_source)
{
MetaRemoteDesktopNotifyAxisFlags scroll_mask;
scroll_mask = META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_WHEEL |
META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_FINGER |
META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_CONTINUOUS;
switch (axis_flags & scroll_mask)
{
case META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_WHEEL:
*scroll_source = CLUTTER_SCROLL_SOURCE_WHEEL;
return TRUE;
case META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_NONE:
case META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_FINGER:
*scroll_source = CLUTTER_SCROLL_SOURCE_FINGER;
return TRUE;
case META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_SOURCE_CONTINUOUS:
*scroll_source = CLUTTER_SCROLL_SOURCE_CONTINUOUS;
return TRUE;
}
return FALSE;
}
static gboolean
handle_notify_pointer_axis (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
double dx,
double dy,
uint32_t flags)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
ClutterScrollFinishFlags finish_flags = CLUTTER_SCROLL_FINISHED_NONE;
ClutterScrollSource scroll_source;
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
if (!clutter_scroll_source_from_axis_flags (flags, &scroll_source))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Invalid scroll source");
return TRUE;
}
if (flags & META_REMOTE_DESKTOP_NOTIFY_AXIS_FLAGS_FINISH)
{
finish_flags |= (CLUTTER_SCROLL_FINISHED_HORIZONTAL |
CLUTTER_SCROLL_FINISHED_VERTICAL);
}
clutter_virtual_input_device_notify_scroll_continuous (session->virtual_pointer,
CLUTTER_CURRENT_TIME,
dx, dy,
scroll_source,
finish_flags);
meta_dbus_remote_desktop_session_complete_notify_pointer_axis (skeleton,
invocation);
return TRUE;
}
static ClutterScrollDirection
discrete_steps_to_scroll_direction (unsigned int axis,
int steps)
{
if (axis == 0 && steps < 0)
return CLUTTER_SCROLL_UP;
if (axis == 0 && steps > 0)
return CLUTTER_SCROLL_DOWN;
if (axis == 1 && steps < 0)
return CLUTTER_SCROLL_LEFT;
if (axis == 1 && steps > 0)
return CLUTTER_SCROLL_RIGHT;
g_assert_not_reached ();
return 0;
}
static gboolean
handle_notify_pointer_axis_discrete (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
unsigned int axis,
int steps)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
ClutterScrollDirection direction;
int step_count;
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
if (axis > 1)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Invalid axis value");
return TRUE;
}
if (steps == 0)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Invalid axis steps value");
return TRUE;
}
/*
* We don't have the actual scroll source, but only know they should be
* considered as discrete steps. The device that produces such scroll events
* is the scroll wheel, so pretend that is the scroll source.
*/
direction = discrete_steps_to_scroll_direction (axis, steps);
for (step_count = 0; step_count < abs (steps); step_count++)
clutter_virtual_input_device_notify_discrete_scroll (session->virtual_pointer,
CLUTTER_CURRENT_TIME,
direction,
CLUTTER_SCROLL_SOURCE_WHEEL);
meta_dbus_remote_desktop_session_complete_notify_pointer_axis_discrete (skeleton,
invocation);
return TRUE;
}
static gboolean
handle_notify_pointer_motion_relative (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
double dx,
double dy)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
clutter_virtual_input_device_notify_relative_motion (session->virtual_pointer,
CLUTTER_CURRENT_TIME,
dx, dy);
meta_dbus_remote_desktop_session_complete_notify_pointer_motion_relative (skeleton,
invocation);
return TRUE;
}
static gboolean
handle_notify_pointer_motion_absolute (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
const char *stream_path,
double x,
double y)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
MetaScreenCastStream *stream;
double abs_x, abs_y;
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
if (!session->screen_cast_session)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"No screen cast active");
return TRUE;
}
stream = meta_screen_cast_session_get_stream (session->screen_cast_session,
stream_path);
if (!stream)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Unknown stream");
return TRUE;
}
if (meta_screen_cast_stream_transform_position (stream, x, y, &abs_x, &abs_y))
{
clutter_virtual_input_device_notify_absolute_motion (session->virtual_pointer,
CLUTTER_CURRENT_TIME,
abs_x, abs_y);
}
else
{
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Dropping early absolute pointer motion (%f, %f)", x, y);
}
meta_dbus_remote_desktop_session_complete_notify_pointer_motion_absolute (skeleton,
invocation);
return TRUE;
}
static gboolean
handle_notify_touch_down (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
const char *stream_path,
unsigned int slot,
double x,
double y)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
MetaScreenCastStream *stream;
double abs_x, abs_y;
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
if (slot > CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Touch slot out of range");
return TRUE;
}
if (!session->screen_cast_session)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"No screen cast active");
return TRUE;
}
stream = meta_screen_cast_session_get_stream (session->screen_cast_session,
stream_path);
if (!stream)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Unknown stream");
return TRUE;
}
if (meta_screen_cast_stream_transform_position (stream, x, y, &abs_x, &abs_y))
{
clutter_virtual_input_device_notify_touch_down (session->virtual_touchscreen,
CLUTTER_CURRENT_TIME,
slot,
abs_x, abs_y);
}
else
{
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Dropping early touch down (%f, %f)", x, y);
}
meta_dbus_remote_desktop_session_complete_notify_touch_down (skeleton,
invocation);
return TRUE;
}
static gboolean
handle_notify_touch_motion (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
const char *stream_path,
unsigned int slot,
double x,
double y)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
MetaScreenCastStream *stream;
double abs_x, abs_y;
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
if (slot > CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Touch slot out of range");
return TRUE;
}
if (!session->screen_cast_session)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"No screen cast active");
return TRUE;
}
stream = meta_screen_cast_session_get_stream (session->screen_cast_session,
stream_path);
if (!stream)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Unknown stream");
return TRUE;
}
if (meta_screen_cast_stream_transform_position (stream, x, y, &abs_x, &abs_y))
{
clutter_virtual_input_device_notify_touch_motion (session->virtual_touchscreen,
CLUTTER_CURRENT_TIME,
slot,
abs_x, abs_y);
}
else
{
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Dropping early touch motion (%f, %f)", x, y);
}
meta_dbus_remote_desktop_session_complete_notify_touch_motion (skeleton,
invocation);
return TRUE;
}
static gboolean
handle_notify_touch_up (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
unsigned int slot)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
if (!meta_remote_desktop_session_check_can_notify (session, invocation))
return TRUE;
if (slot > CLUTTER_VIRTUAL_INPUT_DEVICE_MAX_TOUCH_SLOTS)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Touch slot out of range");
return TRUE;
}
clutter_virtual_input_device_notify_touch_up (session->virtual_touchscreen,
CLUTTER_CURRENT_TIME,
slot);
meta_dbus_remote_desktop_session_complete_notify_touch_up (skeleton,
invocation);
return TRUE;
}
static MetaSelectionSourceRemote *
create_remote_desktop_source (MetaRemoteDesktopSession *session,
GVariant *mime_types_variant,
GError **error)
{
GVariantIter iter;
char *mime_type;
GList *mime_types = NULL;
g_variant_iter_init (&iter, mime_types_variant);
if (g_variant_iter_n_children (&iter) == 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
"No mime types in mime types list");
return NULL;
}
while (g_variant_iter_next (&iter, "s", &mime_type))
mime_types = g_list_prepend (mime_types, mime_type);
mime_types = g_list_reverse (mime_types);
return meta_selection_source_remote_new (session, mime_types);
}
static const char *
mime_types_to_string (char **formats,
char *buf,
int buf_len)
{
g_autofree char *mime_types_string = NULL;
int len;
if (!formats)
return "N\\A";
mime_types_string = g_strjoinv (",", formats);
len = strlen (mime_types_string);
strncpy (buf, mime_types_string, buf_len - 1);
if (len >= buf_len - 1)
buf[buf_len - 2] = '*';
buf[buf_len - 1] = '\0';
return buf;
}
static gboolean
is_own_source (MetaRemoteDesktopSession *session,
MetaSelectionSource *source)
{
return source && source == META_SELECTION_SOURCE (session->current_source);
}
static GVariant *
generate_owner_changed_variant (char **mime_types_array,
gboolean is_own_source)
{
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
if (mime_types_array)
{
g_variant_builder_add (&builder, "{sv}", "mime-types",
g_variant_new ("(^as)", mime_types_array));
g_variant_builder_add (&builder, "{sv}", "session-is-owner",
g_variant_new_boolean (is_own_source));
}
return g_variant_builder_end (&builder);
}
static void
emit_owner_changed (MetaRemoteDesktopSession *session,
MetaSelectionSource *owner)
{
char log_buf[255];
g_autofree char **mime_types_array = NULL;
GList *l;
int i;
GVariant *options_variant;
const char *object_path;
if (owner)
{
GList *mime_types;
mime_types = meta_selection_source_get_mimetypes (owner);
mime_types_array = g_new0 (char *, g_list_length (mime_types) + 1);
for (l = meta_selection_source_get_mimetypes (owner), i = 0;
l;
l = l->next, i++)
mime_types_array[i] = l->data;
}
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Clipboard owner changed, owner: %p (%s, is own? %s), mime types: [%s], "
"notifying %s",
owner,
owner ? g_type_name_from_instance ((GTypeInstance *) owner)
: "NULL",
is_own_source (session, owner) ? "yes" : "no",
mime_types_to_string (mime_types_array, log_buf,
G_N_ELEMENTS (log_buf)),
session->peer_name);
options_variant =
generate_owner_changed_variant (mime_types_array,
is_own_source (session, owner));
object_path = g_dbus_interface_skeleton_get_object_path (
G_DBUS_INTERFACE_SKELETON (session));
g_dbus_connection_emit_signal (session->connection,
NULL,
object_path,
"org.gnome.Mutter.RemoteDesktop.Session",
"SelectionOwnerChanged",
g_variant_new ("(@a{sv})", options_variant),
NULL);
}
static void
on_selection_owner_changed (MetaSelection *selection,
MetaSelectionType selection_type,
MetaSelectionSource *owner,
MetaRemoteDesktopSession *session)
{
if (selection_type != META_SELECTION_CLIPBOARD)
return;
emit_owner_changed (session, owner);
}
static gboolean
handle_enable_clipboard (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
GVariant *arg_options)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
GVariant *mime_types_variant;
g_autoptr (GError) error = NULL;
MetaDisplay *display = meta_get_display ();
MetaSelection *selection = meta_display_get_selection (display);
g_autoptr (MetaSelectionSourceRemote) source_remote = NULL;
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Enable clipboard for %s",
g_dbus_method_invocation_get_sender (invocation));
if (session->is_clipboard_enabled)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Already enabled");
return TRUE;
}
mime_types_variant = g_variant_lookup_value (arg_options,
"mime-types",
G_VARIANT_TYPE_STRING_ARRAY);
if (mime_types_variant)
{
source_remote = create_remote_desktop_source (session,
mime_types_variant,
&error);
if (!source_remote)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Invalid mime type list: %s",
error->message);
return TRUE;
}
}
if (source_remote)
{
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Setting remote desktop clipboard source: %p from %s",
source_remote, session->peer_name);
g_set_object (&session->current_source, source_remote);
meta_selection_set_owner (selection,
META_SELECTION_CLIPBOARD,
META_SELECTION_SOURCE (source_remote));
}
else
{
MetaSelectionSource *owner;
owner = meta_selection_get_current_owner (selection,
META_SELECTION_CLIPBOARD);
if (owner)
emit_owner_changed (session, owner);
}
session->is_clipboard_enabled = TRUE;
session->owner_changed_handler_id =
g_signal_connect (selection, "owner-changed",
G_CALLBACK (on_selection_owner_changed),
session);
meta_dbus_remote_desktop_session_complete_enable_clipboard (skeleton,
invocation);
return TRUE;
}
static gboolean
cancel_transfer_request (gpointer key,
gpointer value,
gpointer user_data)
{
GTask *task = G_TASK (value);
MetaRemoteDesktopSession *session = user_data;
meta_selection_source_remote_cancel_transfer (session->current_source,
task);
return TRUE;
}
static void
meta_remote_desktop_session_cancel_transfer_requests (MetaRemoteDesktopSession *session)
{
g_return_if_fail (session->current_source);
g_hash_table_foreach_remove (session->transfer_requests,
cancel_transfer_request,
session);
}
static gboolean
transfer_request_cleanup_timout (gpointer user_data)
{
MetaRemoteDesktopSession *session = user_data;
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Cancel unanswered SelectionTransfer requests for %s, "
"waited for %.02f seconds already",
session->peer_name,
TRANSFER_REQUEST_CLEANUP_TIMEOUT_MS / 1000.0);
meta_remote_desktop_session_cancel_transfer_requests (session);
session->transfer_request_timeout_id = 0;
return G_SOURCE_REMOVE;
}
static void
reset_current_selection_source (MetaRemoteDesktopSession *session)
{
MetaDisplay *display = meta_get_display ();
MetaSelection *selection = meta_display_get_selection (display);
if (!session->current_source)
return;
meta_selection_unset_owner (selection,
META_SELECTION_CLIPBOARD,
META_SELECTION_SOURCE (session->current_source));
meta_remote_desktop_session_cancel_transfer_requests (session);
g_clear_handle_id (&session->transfer_request_timeout_id, g_source_remove);
g_clear_object (&session->current_source);
}
static void
cancel_selection_read (MetaRemoteDesktopSession *session)
{
if (!session->read_data)
return;
g_cancellable_cancel (session->read_data->cancellable);
session->read_data->session = NULL;
session->read_data = NULL;
}
static gboolean
handle_disable_clipboard (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
MetaDisplay *display = meta_get_display ();
MetaSelection *selection = meta_display_get_selection (display);
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Disable clipboard for %s",
g_dbus_method_invocation_get_sender (invocation));
if (!session->is_clipboard_enabled)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Was not enabled");
return TRUE;
}
g_clear_signal_handler (&session->owner_changed_handler_id, selection);
reset_current_selection_source (session);
cancel_selection_read (session);
meta_dbus_remote_desktop_session_complete_disable_clipboard (skeleton,
invocation);
return TRUE;
}
static gboolean
handle_set_selection (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
GVariant *arg_options)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
g_autoptr (GVariant) mime_types_variant = NULL;
g_autoptr (GError) error = NULL;
if (!session->is_clipboard_enabled)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Clipboard not enabled");
return TRUE;
}
if (session->current_source)
{
meta_remote_desktop_session_cancel_transfer_requests (session);
g_clear_handle_id (&session->transfer_request_timeout_id,
g_source_remove);
}
mime_types_variant = g_variant_lookup_value (arg_options,
"mime-types",
G_VARIANT_TYPE_STRING_ARRAY);
if (mime_types_variant)
{
g_autoptr (MetaSelectionSourceRemote) source_remote = NULL;
MetaDisplay *display = meta_get_display ();
source_remote = create_remote_desktop_source (session,
mime_types_variant,
&error);
if (!source_remote)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Invalid format list: %s",
error->message);
return TRUE;
}
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Set selection for %s to %p",
g_dbus_method_invocation_get_sender (invocation),
source_remote);
g_set_object (&session->current_source, source_remote);
meta_selection_set_owner (meta_display_get_selection (display),
META_SELECTION_CLIPBOARD,
META_SELECTION_SOURCE (source_remote));
}
else
{
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Unset selection for %s",
g_dbus_method_invocation_get_sender (invocation));
reset_current_selection_source (session);
}
meta_dbus_remote_desktop_session_complete_set_selection (skeleton,
invocation);
return TRUE;
}
static void
reset_transfer_cleanup_timeout (MetaRemoteDesktopSession *session)
{
g_clear_handle_id (&session->transfer_request_timeout_id, g_source_remove);
session->transfer_request_timeout_id =
g_timeout_add (TRANSFER_REQUEST_CLEANUP_TIMEOUT_MS,
transfer_request_cleanup_timout,
session);
}
void
meta_remote_desktop_session_request_transfer (MetaRemoteDesktopSession *session,
const char *mime_type,
GTask *task)
{
const char *object_path;
session->transfer_serial++;
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Emit SelectionTransfer ('%s', %u) for %s",
mime_type,
session->transfer_serial,
session->peer_name);
g_hash_table_insert (session->transfer_requests,
GUINT_TO_POINTER (session->transfer_serial),
task);
reset_transfer_cleanup_timeout (session);
object_path = g_dbus_interface_skeleton_get_object_path (
G_DBUS_INTERFACE_SKELETON (session));
g_dbus_connection_emit_signal (session->connection,
NULL,
object_path,
"org.gnome.Mutter.RemoteDesktop.Session",
"SelectionTransfer",
g_variant_new ("(su)",
mime_type,
session->transfer_serial),
NULL);
}
static gboolean
handle_selection_write (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
GUnixFDList *fd_list_in,
unsigned int serial)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
g_autoptr (GError) error = NULL;
int pipe_fds[2];
g_autoptr (GUnixFDList) fd_list = NULL;
int fd_idx;
GVariant *fd_variant;
GTask *task;
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Write selection for %s",
g_dbus_method_invocation_get_sender (invocation));
if (!session->is_clipboard_enabled)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Clipboard not enabled");
return TRUE;
}
if (!session->current_source)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"No current selection owned");
return TRUE;
}
if (!g_hash_table_steal_extended (session->transfer_requests,
GUINT_TO_POINTER (serial),
NULL,
(gpointer *) &task))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Transfer serial %u doesn't match "
"any transfer request",
serial);
return TRUE;
}
if (!g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed open pipe: %s",
error->message);
return TRUE;
}
if (!g_unix_set_fd_nonblocking (pipe_fds[0], TRUE, &error))
{
close (pipe_fds[0]);
close (pipe_fds[1]);
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed to make pipe non-blocking: %s",
error->message);
return TRUE;
}
fd_list = g_unix_fd_list_new ();
fd_idx = g_unix_fd_list_append (fd_list, pipe_fds[1], NULL);
close (pipe_fds[1]);
fd_variant = g_variant_new_handle (fd_idx);
meta_selection_source_remote_complete_transfer (session->current_source,
pipe_fds[0],
task);
meta_dbus_remote_desktop_session_complete_selection_write (skeleton,
invocation,
fd_list,
fd_variant);
return TRUE;
}
static gboolean
handle_selection_write_done (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
unsigned int arg_serial,
gboolean arg_success)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Write selection done for %s",
g_dbus_method_invocation_get_sender (invocation));
if (!session->is_clipboard_enabled)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Clipboard not enabled");
return TRUE;
}
meta_dbus_remote_desktop_session_complete_selection_write_done (skeleton,
invocation);
return TRUE;
}
static void
transfer_cb (MetaSelection *selection,
GAsyncResult *res,
SelectionReadData *read_data)
{
g_autoptr (GError) error = NULL;
if (!meta_selection_transfer_finish (selection, res, &error))
{
g_warning ("Could not fetch selection data "
"for remote desktop session: %s",
error->message);
}
if (read_data->session)
{
meta_topic (META_DEBUG_REMOTE_DESKTOP, "Finished selection transfer for %s",
read_data->session->peer_name);
}
g_output_stream_close (read_data->stream, NULL, NULL);
g_clear_object (&read_data->stream);
g_clear_object (&read_data->cancellable);
if (read_data->session)
read_data->session->read_data = NULL;
g_free (read_data);
}
static gboolean
handle_selection_read (MetaDBusRemoteDesktopSession *skeleton,
GDBusMethodInvocation *invocation,
GUnixFDList *fd_list_in,
const char *mime_type)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton);
MetaDisplay *display = meta_get_display ();
MetaSelection *selection = meta_display_get_selection (display);
MetaSelectionSource *source;
g_autoptr (GError) error = NULL;
int pipe_fds[2];
g_autoptr (GUnixFDList) fd_list = NULL;
int fd_idx;
GVariant *fd_variant;
SelectionReadData *read_data;
meta_topic (META_DEBUG_REMOTE_DESKTOP,
"Read selection for %s",
g_dbus_method_invocation_get_sender (invocation));
if (!session->is_clipboard_enabled)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Clipboard not enabled");
return TRUE;
}
source = meta_selection_get_current_owner (selection,
META_SELECTION_CLIPBOARD);
if (!source)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FILE_NOT_FOUND,
"No selection owner available");
return TRUE;
}
if (is_own_source (session, source))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Tried to read own selection");
return TRUE;
}
if (session->read_data)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_LIMITS_EXCEEDED,
"Tried to read in parallel");
return TRUE;
}
if (!g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed open pipe: %s",
error->message);
return TRUE;
}
if (!g_unix_set_fd_nonblocking (pipe_fds[0], TRUE, &error))
{
close (pipe_fds[0]);
close (pipe_fds[1]);
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Failed to make pipe non-blocking: %s",
error->message);
return TRUE;
}
fd_list = g_unix_fd_list_new ();
fd_idx = g_unix_fd_list_append (fd_list, pipe_fds[0], NULL);
close (pipe_fds[0]);
fd_variant = g_variant_new_handle (fd_idx);
session->read_data = read_data = g_new0 (SelectionReadData, 1);
read_data->session = session;
read_data->stream = g_unix_output_stream_new (pipe_fds[1], TRUE);
read_data->cancellable = g_cancellable_new ();
meta_selection_transfer_async (selection,
META_SELECTION_CLIPBOARD,
mime_type,
-1,
read_data->stream,
read_data->cancellable,
(GAsyncReadyCallback) transfer_cb,
read_data);
meta_dbus_remote_desktop_session_complete_selection_read (skeleton,
invocation,
fd_list,
fd_variant);
return TRUE;
}
static void
meta_remote_desktop_session_init_iface (MetaDBusRemoteDesktopSessionIface *iface)
{
iface->handle_start = handle_start;
iface->handle_stop = handle_stop;
iface->handle_notify_keyboard_keycode = handle_notify_keyboard_keycode;
iface->handle_notify_keyboard_keysym = handle_notify_keyboard_keysym;
iface->handle_notify_pointer_button = handle_notify_pointer_button;
iface->handle_notify_pointer_axis = handle_notify_pointer_axis;
iface->handle_notify_pointer_axis_discrete = handle_notify_pointer_axis_discrete;
iface->handle_notify_pointer_motion_relative = handle_notify_pointer_motion_relative;
iface->handle_notify_pointer_motion_absolute = handle_notify_pointer_motion_absolute;
iface->handle_notify_touch_down = handle_notify_touch_down;
iface->handle_notify_touch_motion = handle_notify_touch_motion;
iface->handle_notify_touch_up = handle_notify_touch_up;
iface->handle_enable_clipboard = handle_enable_clipboard;
iface->handle_disable_clipboard = handle_disable_clipboard;
iface->handle_set_selection = handle_set_selection;
iface->handle_selection_write = handle_selection_write;
iface->handle_selection_write_done = handle_selection_write_done;
iface->handle_selection_read = handle_selection_read;
}
static void
meta_remote_desktop_session_client_vanished (MetaDbusSession *dbus_session)
{
meta_remote_desktop_session_close (META_REMOTE_DESKTOP_SESSION (dbus_session));
}
static void
meta_dbus_session_init_iface (MetaDbusSessionInterface *iface)
{
iface->client_vanished = meta_remote_desktop_session_client_vanished;
}
static void
meta_remote_desktop_session_finalize (GObject *object)
{
MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (object);
MetaDisplay *display = meta_get_display ();
MetaSelection *selection = meta_display_get_selection (display);
g_assert (!meta_remote_desktop_session_is_running (session));
g_clear_signal_handler (&session->owner_changed_handler_id, selection);
reset_current_selection_source (session);
cancel_selection_read (session);
g_hash_table_unref (session->transfer_requests);
g_clear_object (&session->handle);
g_free (session->peer_name);
g_free (session->session_id);
g_free (session->object_path);
G_OBJECT_CLASS (meta_remote_desktop_session_parent_class)->finalize (object);
}
static void
meta_remote_desktop_session_init (MetaRemoteDesktopSession *session)
{
MetaDBusRemoteDesktopSession *skeleton =
META_DBUS_REMOTE_DESKTOP_SESSION (session);
GRand *rand;
static unsigned int global_session_number = 0;
rand = g_rand_new ();
session->session_id = meta_generate_random_id (rand, 32);
g_rand_free (rand);
meta_dbus_remote_desktop_session_set_session_id (skeleton, session->session_id);
session->object_path =
g_strdup_printf (META_REMOTE_DESKTOP_SESSION_DBUS_PATH "/u%u",
++global_session_number);
session->transfer_requests = g_hash_table_new (NULL, NULL);
}
static void
meta_remote_desktop_session_class_init (MetaRemoteDesktopSessionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = meta_remote_desktop_session_finalize;
}
static MetaRemoteDesktopSessionHandle *
meta_remote_desktop_session_handle_new (MetaRemoteDesktopSession *session)
{
MetaRemoteDesktopSessionHandle *handle;
handle = g_object_new (META_TYPE_REMOTE_DESKTOP_SESSION_HANDLE, NULL);
handle->session = session;
return handle;
}
static void
meta_remote_desktop_session_handle_stop (MetaRemoteAccessHandle *handle)
{
MetaRemoteDesktopSession *session;
session = META_REMOTE_DESKTOP_SESSION_HANDLE (handle)->session;
if (!session)
return;
meta_remote_desktop_session_close (session);
}
static void
meta_remote_desktop_session_handle_init (MetaRemoteDesktopSessionHandle *handle)
{
}
static void
meta_remote_desktop_session_handle_class_init (MetaRemoteDesktopSessionHandleClass *klass)
{
MetaRemoteAccessHandleClass *remote_access_handle_class =
META_REMOTE_ACCESS_HANDLE_CLASS (klass);
remote_access_handle_class->stop = meta_remote_desktop_session_handle_stop;
}