Files
grd/grd-session.c
2026-02-13 13:06:50 +09:00

1990 lines
63 KiB
C

/*
* Copyright (C) 2015 Red Hat Inc.
* Copyright (C) 2020-2021 Pascal Nowack
*
* 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.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#include "config.h"
#include "grd-session.h"
#include <gio/gunixfdlist.h>
#include <glib-object.h>
#include <glib-unix.h>
#include <libei.h>
#include <linux/input-event-codes.h>
#include <sys/mman.h>
#include <xkbcommon/xkbcommon.h>
#include "grd-clipboard.h"
#include "grd-dbus-mutter-remote-desktop.h"
#include "grd-context.h"
#include "grd-private.h"
#include "grd-stream.h"
#include "grd-utils.h"
enum
{
PROP_0,
PROP_CONTEXT,
};
enum
{
READY,
STARTED,
STOPPED,
TOUCH_DEVICE_ADDED,
TOUCH_DEVICE_REMOVED,
N_SIGNALS
};
static guint signals[N_SIGNALS];
typedef enum _EvdevButtonType
{
EVDEV_BUTTON_TYPE_NONE,
EVDEV_BUTTON_TYPE_KEY,
EVDEV_BUTTON_TYPE_BUTTON,
} EvdevButtonType;
typedef enum _ScreenCastType
{
SCREEN_CAST_TYPE_NONE,
SCREEN_CAST_TYPE_MONITOR,
SCREEN_CAST_TYPE_WINDOW,
SCREEN_CAST_TYPE_AREA,
SCREEN_CAST_TYPE_VIRTUAL,
} ScreenCastType;
typedef struct _AsyncDBusRecordCallContext
{
GrdSession *session;
uint32_t stream_id;
ScreenCastType screen_cast_type;
} AsyncDBusRecordCallContext;
typedef struct _GrdRegion
{
struct ei_device *ei_device;
struct ei_region *ei_region;
} GrdRegion;
struct _GrdTouchContact
{
struct ei_touch *ei_touch_contact;
};
typedef struct _GrdEiPing
{
GTask *task;
struct ei_ping *ping;
} GrdEiPing;
typedef struct _GrdSessionPrivate
{
GrdContext *context;
GrdDBusMutterRemoteDesktopSession *remote_desktop_session;
GrdDBusMutterScreenCastSession *screen_cast_session;
gboolean is_ready;
GrdClipboard *clipboard;
struct ei *ei;
struct ei_seat *ei_seat;
struct ei_device *ei_pointer;
struct ei_device *ei_abs_pointer;
struct ei_device *ei_keyboard;
struct ei_device *ei_touch;
uint32_t ei_sequence;
GSource *ei_source;
GHashTable *abs_pointer_regions;
GHashTable *touch_regions;
struct xkb_context *xkb_context;
struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state;
GCancellable *cancellable;
gboolean started;
gboolean locked_modifier_valid;
gboolean caps_lock_state;
gboolean num_lock_state;
gulong caps_lock_state_changed_id;
gulong num_lock_state_changed_id;
GHashTable *pings;
} GrdSessionPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GrdSession, grd_session, G_TYPE_OBJECT)
static void
finish_and_free_ping (GrdEiPing *ping);
GrdContext *
grd_session_get_context (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
return priv->context;
}
static void
clear_ei (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
g_clear_pointer (&priv->xkb_state, xkb_state_unref);
g_clear_pointer (&priv->xkb_keymap, xkb_keymap_unref);
g_clear_pointer (&priv->xkb_context, xkb_context_unref);
g_clear_pointer (&priv->touch_regions, g_hash_table_unref);
g_clear_pointer (&priv->abs_pointer_regions, g_hash_table_unref);
g_clear_pointer (&priv->ei_touch, ei_device_unref);
g_clear_pointer (&priv->ei_keyboard, ei_device_unref);
g_clear_pointer (&priv->ei_abs_pointer, ei_device_unref);
g_clear_pointer (&priv->ei_pointer, ei_device_unref);
g_clear_pointer (&priv->ei_seat, ei_seat_unref);
g_clear_pointer (&priv->ei_source, g_source_destroy);
g_clear_pointer (&priv->ei, ei_unref);
}
static void
clear_session (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
clear_ei (session);
g_clear_signal_handler (&priv->caps_lock_state_changed_id,
priv->remote_desktop_session);
g_clear_signal_handler (&priv->num_lock_state_changed_id,
priv->remote_desktop_session);
g_clear_object (&priv->remote_desktop_session);
g_clear_object (&priv->screen_cast_session);
}
void
grd_session_stop (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
if (priv->cancellable && g_cancellable_is_cancelled (priv->cancellable))
return;
GRD_SESSION_GET_CLASS (session)->stop (session);
clear_ei (session);
if (priv->remote_desktop_session && priv->started)
{
GrdDBusMutterRemoteDesktopSession *proxy = priv->remote_desktop_session;
g_autoptr (GError) error = NULL;
if (!grd_dbus_mutter_remote_desktop_session_call_stop_sync (proxy, NULL, &error))
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) &&
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
g_warning ("Failed to stop: %s", error->message);
}
}
if (priv->cancellable)
g_cancellable_cancel (priv->cancellable);
clear_session (session);
g_signal_emit (session, signals[STOPPED], 0);
}
gboolean
grd_session_flush_input_finish (GrdSession *session,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
void
grd_session_flush_input_async (GrdSession *session,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GrdEiPing *ping;
g_assert (priv->is_ready);
ping = g_new0 (GrdEiPing, 1);
ping->task = g_task_new (session,
cancellable,
callback,
user_data);
ping->ping = ei_new_ping (priv->ei);
ei_ping (ping->ping);
g_hash_table_insert (priv->pings, ping->ping, ping);
}
static void
on_screen_cast_stream_start_finished (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusMutterScreenCastStream *stream_proxy = GRD_DBUS_MUTTER_SCREEN_CAST_STREAM (object);
g_autoptr (GError) error = NULL;
if (!grd_dbus_mutter_screen_cast_stream_call_start_finish (stream_proxy,
result,
&error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to start screen cast stream: %s", error->message);
grd_session_stop (GRD_SESSION (user_data));
return;
}
}
static void
on_screen_cast_stream_proxy_acquired (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autofree AsyncDBusRecordCallContext *async_context = user_data;
g_autoptr (GrdDBusMutterScreenCastStream) stream_proxy = NULL;
GrdSession *session;
GrdSessionPrivate *priv;
GrdSessionClass *klass;
g_autoptr (GError) error = NULL;
GrdStream *stream;
stream_proxy = grd_dbus_mutter_screen_cast_stream_proxy_new_finish (result, &error);
if (!stream_proxy)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to acquire stream proxy: %s", error->message);
grd_session_stop (async_context->session);
return;
}
session = async_context->session;
priv = grd_session_get_instance_private (session);
klass = GRD_SESSION_GET_CLASS (session);
stream = grd_stream_new (async_context->stream_id, stream_proxy,
priv->cancellable, &error);
if (!stream)
{
g_warning ("Failed to create stream: %s", error->message);
grd_session_stop (async_context->session);
return;
}
klass->on_stream_created (session, async_context->stream_id, stream);
grd_dbus_mutter_screen_cast_stream_call_start (stream_proxy,
priv->cancellable,
on_screen_cast_stream_start_finished,
session);
}
static const char *
screen_cast_type_to_string (ScreenCastType type)
{
switch (type)
{
case SCREEN_CAST_TYPE_NONE:
return "none";
case SCREEN_CAST_TYPE_MONITOR:
return "monitor";
case SCREEN_CAST_TYPE_WINDOW:
return "window";
case SCREEN_CAST_TYPE_AREA:
return "area";
case SCREEN_CAST_TYPE_VIRTUAL:
return "virtual";
}
g_assert_not_reached ();
}
static void
on_record_finished (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusMutterScreenCastSession *proxy = GRD_DBUS_MUTTER_SCREEN_CAST_SESSION (object);
g_autofree AsyncDBusRecordCallContext *async_context = user_data;
ScreenCastType screen_cast_type = async_context->screen_cast_type;
GrdSession *session;
GrdSessionPrivate *priv;
GDBusConnection *connection;
g_autofree char *stream_path = NULL;
g_autoptr (GError) error = NULL;
gboolean retval = FALSE;
switch (screen_cast_type)
{
case SCREEN_CAST_TYPE_NONE:
g_assert_not_reached ();
break;
case SCREEN_CAST_TYPE_MONITOR:
retval = grd_dbus_mutter_screen_cast_session_call_record_monitor_finish (
proxy, &stream_path, result, &error);
break;
case SCREEN_CAST_TYPE_WINDOW:
case SCREEN_CAST_TYPE_AREA:
g_assert_not_reached ();
break;
case SCREEN_CAST_TYPE_VIRTUAL:
retval = grd_dbus_mutter_screen_cast_session_call_record_virtual_finish (
proxy, &stream_path, result, &error);
break;
}
if (!retval)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to record %s: %s",
screen_cast_type_to_string (screen_cast_type), error->message);
grd_session_stop (async_context->session);
return;
}
session = async_context->session;
priv = grd_session_get_instance_private (session);
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (proxy));
grd_dbus_mutter_screen_cast_stream_proxy_new (connection,
G_DBUS_PROXY_FLAGS_NONE,
MUTTER_SCREEN_CAST_BUS_NAME,
stream_path,
priv->cancellable,
on_screen_cast_stream_proxy_acquired,
g_steal_pointer (&async_context));
}
void
grd_session_record_monitor (GrdSession *session,
uint32_t stream_id,
const char *connector,
GrdScreenCastCursorMode cursor_mode)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GrdSessionClass *klass = GRD_SESSION_GET_CLASS (session);
GVariantBuilder properties_builder;
AsyncDBusRecordCallContext *async_context;
g_assert (klass->on_stream_created);
async_context = g_malloc0 (sizeof (AsyncDBusRecordCallContext));
async_context->session = session;
async_context->stream_id = stream_id;
async_context->screen_cast_type = SCREEN_CAST_TYPE_MONITOR;
g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&properties_builder, "{sv}",
"cursor-mode", g_variant_new_uint32 (cursor_mode));
grd_dbus_mutter_screen_cast_session_call_record_monitor (priv->screen_cast_session,
connector ? connector : "",
g_variant_builder_end (&properties_builder),
priv->cancellable,
on_record_finished,
async_context);
}
void
grd_session_record_virtual (GrdSession *session,
uint32_t stream_id,
GrdScreenCastCursorMode cursor_mode,
gboolean is_platform)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GrdSessionClass *klass = GRD_SESSION_GET_CLASS (session);
GVariantBuilder properties_builder;
AsyncDBusRecordCallContext *async_context;
g_assert (klass->on_stream_created);
async_context = g_malloc0 (sizeof (AsyncDBusRecordCallContext));
async_context->session = session;
async_context->stream_id = stream_id;
async_context->screen_cast_type = SCREEN_CAST_TYPE_VIRTUAL;
g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&properties_builder, "{sv}",
"cursor-mode", g_variant_new_uint32 (cursor_mode));
g_variant_builder_add (&properties_builder, "{sv}",
"is-platform", g_variant_new_boolean (is_platform));
grd_dbus_mutter_screen_cast_session_call_record_virtual (priv->screen_cast_session,
g_variant_builder_end (&properties_builder),
priv->cancellable,
on_record_finished,
async_context);
}
void
grd_session_notify_keyboard_keycode (GrdSession *session,
uint32_t keycode,
GrdKeyState state)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
if (!priv->ei_keyboard)
return;
ei_device_keyboard_key (priv->ei_keyboard, keycode, state);
ei_device_frame (priv->ei_keyboard, g_get_monotonic_time ());
}
static gboolean
pick_keycode_for_keysym_in_current_group (GrdSession *session,
uint32_t keysym,
uint32_t *keycode_out,
uint32_t *level_out)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state;
uint32_t keycode, layout;
xkb_keycode_t min_keycode, max_keycode;
g_assert (keycode_out);
xkb_keymap = priv->xkb_keymap;
xkb_state = priv->xkb_state;
layout = xkb_state_serialize_layout (xkb_state, XKB_STATE_LAYOUT_EFFECTIVE);
min_keycode = xkb_keymap_min_keycode (xkb_keymap);
max_keycode = xkb_keymap_max_keycode (xkb_keymap);
for (keycode = min_keycode; keycode < max_keycode; keycode++)
{
int num_levels, level;
num_levels = xkb_keymap_num_levels_for_key (xkb_keymap, keycode, layout);
for (level = 0; level < num_levels; level++)
{
const xkb_keysym_t *syms;
int num_syms, sym;
num_syms = xkb_keymap_key_get_syms_by_level (xkb_keymap, keycode,
layout, level, &syms);
for (sym = 0; sym < num_syms; sym++)
{
if (syms[sym] == keysym)
{
*keycode_out = keycode;
if (level_out)
*level_out = level;
return TRUE;
}
}
}
}
return FALSE;
}
static uint32_t
xkb_keycode_to_evdev (uint32_t xkb_keycode)
{
return xkb_keycode - 8;
}
static void
apply_level_modifiers (GrdSession *session,
uint64_t time_us,
uint32_t level,
uint32_t key_state)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
uint32_t keysym, keycode, evcode;
if (level == 0)
return;
if (level == 1)
{
keysym = XKB_KEY_Shift_L;
}
else if (level == 2)
{
keysym = XKB_KEY_ISO_Level3_Shift;
}
else
{
g_warning ("Unhandled level: %d", level);
return;
}
if (!pick_keycode_for_keysym_in_current_group (session, keysym,
&keycode, NULL))
return;
evcode = xkb_keycode_to_evdev (keycode);
ei_device_keyboard_key (priv->ei_keyboard, evcode, key_state);
ei_device_frame (priv->ei_keyboard, time_us);
}
static EvdevButtonType
get_button_type (uint16_t code)
{
switch (code)
{
case BTN_TOOL_PEN:
case BTN_TOOL_RUBBER:
case BTN_TOOL_BRUSH:
case BTN_TOOL_PENCIL:
case BTN_TOOL_AIRBRUSH:
case BTN_TOOL_MOUSE:
case BTN_TOOL_LENS:
case BTN_TOOL_QUINTTAP:
case BTN_TOOL_DOUBLETAP:
case BTN_TOOL_TRIPLETAP:
case BTN_TOOL_QUADTAP:
case BTN_TOOL_FINGER:
case BTN_TOUCH:
return EVDEV_BUTTON_TYPE_NONE;
}
if (code >= KEY_ESC && code <= KEY_MICMUTE)
return EVDEV_BUTTON_TYPE_KEY;
if (code >= BTN_MISC && code <= BTN_GEAR_UP)
return EVDEV_BUTTON_TYPE_BUTTON;
if (code >= KEY_OK && code <= KEY_LIGHTS_TOGGLE)
return EVDEV_BUTTON_TYPE_KEY;
if (code >= BTN_DPAD_UP && code <= BTN_DPAD_RIGHT)
return EVDEV_BUTTON_TYPE_BUTTON;
if (code >= KEY_ALS_TOGGLE && code <= KEY_KBDINPUTASSIST_CANCEL)
return EVDEV_BUTTON_TYPE_KEY;
if (code >= BTN_TRIGGER_HAPPY && code <= BTN_TRIGGER_HAPPY40)
return EVDEV_BUTTON_TYPE_BUTTON;
return EVDEV_BUTTON_TYPE_NONE;
}
void
grd_session_notify_keyboard_keysym (GrdSession *session,
uint32_t keysym,
GrdKeyState state)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
int64_t now_us;
uint32_t keycode = 0, level = 0, evcode = 0;
if (!priv->xkb_state || !priv->ei_keyboard)
return;
now_us = g_get_monotonic_time ();
if (!pick_keycode_for_keysym_in_current_group (session,
keysym,
&keycode, &level))
{
g_warning ("No keycode found for keyval %x in current group", keysym);
return;
}
evcode = xkb_keycode_to_evdev (keycode);
if (get_button_type (evcode) != EVDEV_BUTTON_TYPE_KEY)
{
g_warning ("Unknown/invalid key 0x%x pressed", evcode);
return;
}
if (state)
apply_level_modifiers (session, now_us, level, state);
ei_device_keyboard_key (priv->ei_keyboard, evcode, state);
ei_device_frame (priv->ei_keyboard, now_us);
if (!state)
apply_level_modifiers (session, now_us, level, state);
}
void
grd_session_notify_pointer_button (GrdSession *session,
int32_t button,
GrdButtonState state)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
if (!priv->ei_abs_pointer)
return;
ei_device_button_button (priv->ei_abs_pointer, button, state);
ei_device_frame (priv->ei_abs_pointer, g_get_monotonic_time ());
}
void
grd_session_notify_pointer_axis (GrdSession *session,
double dx,
double dy,
GrdPointerAxisFlags flags)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
if (!priv->ei_abs_pointer)
return;
if (flags & GRD_POINTER_AXIS_FLAGS_FINISH)
ei_device_scroll_stop (priv->ei_abs_pointer, true, true);
else
ei_device_scroll_delta (priv->ei_abs_pointer, dx, dy);
ei_device_frame (priv->ei_abs_pointer, g_get_monotonic_time ());
}
void
grd_session_notify_pointer_axis_discrete (GrdSession *session,
GrdPointerAxis axis,
int steps)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
if (!priv->ei_abs_pointer)
return;
ei_device_scroll_discrete (priv->ei_abs_pointer,
axis == GRD_POINTER_AXIS_HORIZONTAL ? steps * 120 : 0,
axis == GRD_POINTER_AXIS_VERTICAL ? steps * 120 : 0);
ei_device_frame (priv->ei_abs_pointer, g_get_monotonic_time ());
}
void
grd_session_notify_pointer_motion (GrdSession *session,
double dx,
double dy)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
if (!priv->ei_pointer)
return;
ei_device_pointer_motion (priv->ei_pointer, dx, dy);
ei_device_frame (priv->ei_pointer, g_get_monotonic_time ());
}
static gboolean
transform_position (GrdSession *session,
GHashTable *regions,
GrdStream *stream,
const GrdEventMotionAbs *motion_abs,
struct ei_device **ei_device,
double *x,
double *y)
{
GrdRegion *region;
double scale_x;
double scale_y;
double scaled_x;
double scaled_y;
g_assert (motion_abs->input_rect_width > 0);
g_assert (motion_abs->input_rect_height > 0);
region = g_hash_table_lookup (regions, grd_stream_get_mapping_id (stream));
if (!region)
return FALSE;
scale_x = ((double) motion_abs->input_rect_width) /
ei_region_get_width (region->ei_region);
scale_y = ((double) motion_abs->input_rect_height) /
ei_region_get_height (region->ei_region);
scaled_x = motion_abs->x / scale_x;
scaled_y = motion_abs->y / scale_y;
*ei_device = region->ei_device;
*x = ei_region_get_x (region->ei_region) + scaled_x;
*y = ei_region_get_y (region->ei_region) + scaled_y;
return TRUE;
}
void
grd_session_notify_pointer_motion_absolute (GrdSession *session,
GrdStream *stream,
const GrdEventMotionAbs *motion_abs)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
struct ei_device *ei_device = NULL;
double x = 0;
double y = 0;
if (!transform_position (session, priv->abs_pointer_regions,
stream, motion_abs, &ei_device, &x, &y))
return;
ei_device_pointer_motion_absolute (ei_device, x, y);
ei_device_frame (ei_device, g_get_monotonic_time ());
}
gboolean
grd_session_has_touch_device (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
return !!priv->ei_touch;
}
GrdTouchContact *
grd_session_acquire_touch_contact (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GrdTouchContact *touch_contact;
g_assert (priv->ei_touch);
touch_contact = g_new0 (GrdTouchContact, 1);
touch_contact->ei_touch_contact = ei_device_touch_new (priv->ei_touch);
return touch_contact;
}
void
grd_session_release_touch_contact (GrdSession *session,
GrdTouchContact *touch_contact)
{
g_clear_pointer (&touch_contact->ei_touch_contact, ei_touch_unref);
g_free (touch_contact);
}
void
grd_session_notify_touch_down (GrdSession *session,
const GrdTouchContact *touch_contact,
GrdStream *stream,
const GrdEventMotionAbs *motion_abs)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
struct ei_device *ei_device = NULL;
double x = 0;
double y = 0;
if (!transform_position (session, priv->touch_regions, stream, motion_abs,
&ei_device, &x, &y))
return;
ei_touch_down (touch_contact->ei_touch_contact, x, y);
}
void
grd_session_notify_touch_motion (GrdSession *session,
const GrdTouchContact *touch_contact,
GrdStream *stream,
const GrdEventMotionAbs *motion_abs)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
struct ei_device *ei_device = NULL;
double x = 0;
double y = 0;
if (!transform_position (session, priv->touch_regions, stream, motion_abs,
&ei_device, &x, &y))
return;
ei_touch_motion (touch_contact->ei_touch_contact, x, y);
}
void
grd_session_notify_touch_up (GrdSession *session,
GrdTouchContact *touch_contact)
{
ei_touch_up (touch_contact->ei_touch_contact);
}
void
grd_session_notify_touch_cancel (GrdSession *session,
GrdTouchContact *touch_contact)
{
ei_touch_cancel (touch_contact->ei_touch_contact);
}
void
grd_session_notify_touch_device_frame (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
g_assert (priv->ei_touch);
ei_device_frame (priv->ei_touch, g_get_monotonic_time ());
}
static GVariant *
serialize_mime_type_tables (GList *mime_type_tables)
{
GVariantBuilder builder;
GList *l;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
for (l = mime_type_tables; l; l = l->next)
{
GrdMimeTypeTable *mime_type_table = l->data;
GrdMimeType mime_type;
const char *mime_type_string;
mime_type = mime_type_table->mime_type;
mime_type_string = grd_mime_type_to_string (mime_type);
g_variant_builder_add (&builder, "s", mime_type_string);
}
return g_variant_builder_end (&builder);
}
static GVariant *
serialize_clipboard_options (GList *mime_type_tables)
{
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
if (mime_type_tables)
{
g_variant_builder_add (&builder, "{sv}", "mime-types",
serialize_mime_type_tables (mime_type_tables));
}
return g_variant_builder_end (&builder);
}
gboolean
grd_session_enable_clipboard (GrdSession *session,
GrdClipboard *clipboard,
GList *mime_type_tables)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GVariant *options_variant;
g_autoptr (GError) error = NULL;
if (!priv->remote_desktop_session)
return FALSE;
options_variant = serialize_clipboard_options (mime_type_tables);
if (!grd_dbus_mutter_remote_desktop_session_call_enable_clipboard_sync (
priv->remote_desktop_session, options_variant, NULL, &error))
{
g_warning ("Failed to enable clipboard: %s", error->message);
return FALSE;
}
priv->clipboard = clipboard;
return TRUE;
}
void
grd_session_disable_clipboard (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
priv->clipboard = NULL;
if (!priv->remote_desktop_session)
return;
grd_dbus_mutter_remote_desktop_session_call_disable_clipboard (
priv->remote_desktop_session, NULL, NULL, NULL);
}
void
grd_session_set_selection (GrdSession *session,
GList *mime_type_tables)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GVariant *options_variant;
g_autoptr (GError) error = NULL;
options_variant = serialize_clipboard_options (mime_type_tables);
if (!grd_dbus_mutter_remote_desktop_session_call_set_selection_sync (
priv->remote_desktop_session, options_variant, NULL, &error))
g_warning ("Failed to set selection: %s", error->message);
}
void
grd_session_selection_write (GrdSession *session,
unsigned int serial,
const uint8_t *data,
uint32_t size)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
g_autoptr (GError) error = NULL;
g_autoptr (GVariant) fd_variant = NULL;
g_autoptr (GUnixFDList) fd_list = NULL;
int fd_idx = -1;
int fd;
if (!data || !size)
{
grd_dbus_mutter_remote_desktop_session_call_selection_write_done (
priv->remote_desktop_session, serial, FALSE, NULL, NULL, NULL);
return;
}
if (!grd_dbus_mutter_remote_desktop_session_call_selection_write_sync (
priv->remote_desktop_session, serial, NULL, &fd_variant, &fd_list,
NULL, &error))
{
g_warning ("Failed to write selection for serial %u: %s",
serial, error->message);
return;
}
g_variant_get (fd_variant, "h", &fd_idx);
if (!G_IS_UNIX_FD_LIST (fd_list) ||
fd_idx < 0 || fd_idx >= g_unix_fd_list_get_length (fd_list))
{
g_warning ("Failed to acquire file descriptor for serial %u: Invalid "
"file descriptor list sent by display server", serial);
return;
}
fd = g_unix_fd_list_get (fd_list, fd_idx, &error);
if (fd == -1)
{
g_warning ("Failed to acquire file descriptor for serial %u: %s",
serial, error->message);
return;
}
if (write (fd, data, size) < 0)
{
grd_dbus_mutter_remote_desktop_session_call_selection_write_done (
priv->remote_desktop_session, serial, FALSE, NULL, NULL, NULL);
close (fd);
return;
}
grd_dbus_mutter_remote_desktop_session_call_selection_write_done (
priv->remote_desktop_session, serial, TRUE, NULL, NULL, NULL);
close (fd);
}
int
grd_session_selection_read (GrdSession *session,
GrdMimeType mime_type)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
g_autoptr (GError) error = NULL;
g_autoptr (GVariant) fd_variant = NULL;
g_autoptr (GUnixFDList) fd_list = NULL;
int fd_idx = -1;
int fd;
const char *mime_type_string;
mime_type_string = grd_mime_type_to_string (mime_type);
if (!grd_dbus_mutter_remote_desktop_session_call_selection_read_sync (
priv->remote_desktop_session, mime_type_string, NULL, &fd_variant,
&fd_list, NULL, &error))
{
g_warning ("Failed to read selection: %s", error->message);
return -1;
}
g_variant_get (fd_variant, "h", &fd_idx);
if (!G_IS_UNIX_FD_LIST (fd_list) ||
fd_idx < 0 || fd_idx >= g_unix_fd_list_get_length (fd_list))
{
g_warning ("Failed to acquire file descriptor: Invalid file descriptor "
"list sent by display server");
return -1;
}
fd = g_unix_fd_list_get (fd_list, fd_idx, &error);
if (fd == -1)
{
g_warning ("Failed to acquire file descriptor: %s", error->message);
return -1;
}
return fd;
}
static void
maybe_emit_started_signal (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
if (!priv->is_ready ||
!priv->started)
return;
g_signal_emit (session, signals[STARTED], 0);
}
static void
on_session_start_finished (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusMutterRemoteDesktopSession *proxy;
GrdSession *session;
GrdSessionPrivate *priv;
g_autoptr (GError) error = NULL;
proxy = GRD_DBUS_MUTTER_REMOTE_DESKTOP_SESSION (object);
if (!grd_dbus_mutter_remote_desktop_session_call_start_finish (proxy,
result,
&error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to start session: %s", error->message);
grd_session_stop (GRD_SESSION (user_data));
return;
}
session = GRD_SESSION (user_data);
priv = grd_session_get_instance_private (session);
priv->started = TRUE;
maybe_emit_started_signal (session);
}
static void
start_session (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GrdDBusMutterRemoteDesktopSession *proxy = priv->remote_desktop_session;
grd_dbus_mutter_remote_desktop_session_call_start (proxy,
priv->cancellable,
on_session_start_finished,
session);
}
static void
on_screen_cast_session_proxy_acquired (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusMutterScreenCastSession *session_proxy;
GrdSession *session;
GrdSessionPrivate *priv;
g_autoptr (GError) error = NULL;
session_proxy =
grd_dbus_mutter_screen_cast_session_proxy_new_finish (result, &error);
if (!session_proxy)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to acquire screen cast session proxy: %s\n",
error->message);
grd_session_stop (GRD_SESSION (user_data));
return;
}
session = GRD_SESSION (user_data);
priv = grd_session_get_instance_private (session);
priv->screen_cast_session = session_proxy;
start_session (session);
}
static void
on_screen_cast_session_created (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GrdDBusMutterScreenCast *screen_cast_proxy;
GrdSession *session;
GrdSessionPrivate *priv;
GDBusConnection *connection;
g_autofree char *session_path = NULL;
g_autoptr (GError) error = NULL;
screen_cast_proxy = GRD_DBUS_MUTTER_SCREEN_CAST (source_object);
if (!grd_dbus_mutter_screen_cast_call_create_session_finish (screen_cast_proxy,
&session_path,
res,
&error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to start screen cast session: %s\n", error->message);
grd_session_stop (GRD_SESSION (user_data));
return;
}
session = GRD_SESSION (user_data);
priv = grd_session_get_instance_private (session);
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (screen_cast_proxy));
grd_dbus_mutter_screen_cast_session_proxy_new (connection,
G_DBUS_PROXY_FLAGS_NONE,
MUTTER_SCREEN_CAST_BUS_NAME,
session_path,
priv->cancellable,
on_screen_cast_session_proxy_acquired,
session);
}
static void
on_remote_desktop_session_closed (GrdDBusMutterRemoteDesktopSession *session_proxy,
GrdSession *session)
{
g_debug ("Remote desktop session closed, stopping session");
clear_session (session);
grd_session_stop (session);
}
static void
on_remote_desktop_session_selection_owner_changed (GrdDBusMutterRemoteDesktopSession *session_proxy,
GVariant *options_variant,
GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GVariant *is_owner_variant, *mime_types_variant;
GVariantIter iter;
const char *mime_string;
GrdMimeType mime_type;
GList *mime_type_list = NULL;
if (!priv->clipboard)
return;
is_owner_variant = g_variant_lookup_value (options_variant, "session-is-owner",
G_VARIANT_TYPE ("b"));
if (is_owner_variant && g_variant_get_boolean (is_owner_variant))
return;
mime_types_variant = g_variant_lookup_value (options_variant, "mime-types",
G_VARIANT_TYPE ("(as)"));
if (!mime_types_variant)
return;
g_variant_iter_init (&iter, g_variant_get_child_value (mime_types_variant, 0));
while (g_variant_iter_loop (&iter, "s", &mime_string))
{
mime_type = grd_mime_type_from_string (mime_string);
if (mime_type != GRD_MIME_TYPE_NONE)
{
mime_type_list = g_list_append (mime_type_list,
GUINT_TO_POINTER (mime_type));
g_debug ("Clipboard[SelectionOwnerChanged]: Server advertises mime "
"type %s", mime_string);
}
else
{
g_debug ("Clipboard[SelectionOwnerChanged]: Server advertised unknown "
"mime type: %s", mime_string);
}
}
if (mime_type_list)
grd_clipboard_update_client_mime_type_list (priv->clipboard, mime_type_list);
}
static void
on_remote_desktop_session_selection_transfer (GrdDBusMutterRemoteDesktopSession *session_proxy,
char *mime_type_string,
unsigned int serial,
GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GrdMimeType mime_type;
if (!priv->clipboard)
return;
mime_type = grd_mime_type_from_string (mime_type_string);
if (mime_type == GRD_MIME_TYPE_NONE)
{
grd_dbus_mutter_remote_desktop_session_call_selection_write_done (
priv->remote_desktop_session, serial, FALSE, NULL, NULL, NULL);
return;
}
grd_clipboard_request_client_content_for_mime_type (priv->clipboard,
mime_type, serial);
}
static gboolean
grd_ei_source_prepare (gpointer user_data)
{
GrdSession *session = GRD_SESSION (user_data);
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
return !!ei_peek_event (priv->ei);
}
static gboolean
setup_xkb_keymap (GrdSession *session,
struct ei_keymap *keymap,
GError **error)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
struct xkb_context *xkb_context = NULL;
struct xkb_keymap *xkb_keymap = NULL;
struct xkb_state *xkb_state = NULL;
size_t keymap_size;
g_autofree char *buf = NULL;
g_clear_pointer (&priv->xkb_state, xkb_state_unref);
g_clear_pointer (&priv->xkb_keymap, xkb_keymap_unref);
g_clear_pointer (&priv->xkb_context, xkb_context_unref);
xkb_context = xkb_context_new (XKB_CONTEXT_NO_FLAGS);
if (!xkb_context)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create XKB context");
goto err;
}
keymap_size = ei_keymap_get_size (keymap);
buf = g_malloc0 (keymap_size + 1);
while (TRUE)
{
int ret;
ret = read (ei_keymap_get_fd (keymap), buf, keymap_size);
if (ret > 0)
{
break;
}
else if (ret == 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Keyboard layout was empty");
goto err;
}
else if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
{
continue;
}
else
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"Failed to read layout: %s", g_strerror (errno));
goto err;
}
}
xkb_keymap = xkb_keymap_new_from_string (xkb_context, buf,
XKB_KEYMAP_FORMAT_TEXT_V1,
XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!xkb_keymap)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create XKB keymap");
goto err;
}
xkb_state = xkb_state_new (xkb_keymap);
if (!xkb_state)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create XKB state");
goto err;
}
priv->xkb_context = xkb_context;
priv->xkb_keymap = xkb_keymap;
priv->xkb_state = xkb_state;
return TRUE;
err:
g_clear_pointer (&xkb_state, xkb_state_unref);
g_clear_pointer (&xkb_keymap, xkb_keymap_unref);
g_clear_pointer (&xkb_context, xkb_context_unref);
return FALSE;
}
static gboolean
process_keymap (GrdSession *session,
struct ei_device *device,
GError **error)
{
struct ei_keymap *keymap;
enum ei_keymap_type type;
keymap = ei_device_keyboard_get_keymap (device);
if (!keymap)
return TRUE;
type = ei_keymap_get_type (keymap);
switch (type)
{
case EI_KEYMAP_TYPE_XKB:
return setup_xkb_keymap (session, keymap, error);
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unknown keyboard layout type");
return FALSE;
}
return TRUE;
}
static void
grd_region_free (GrdRegion *region)
{
ei_region_unref (region->ei_region);
ei_device_unref (region->ei_device);
g_free (region);
}
static void
maybe_dispose_ei_abs_pointer (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
g_hash_table_remove_all (priv->abs_pointer_regions);
g_clear_pointer (&priv->ei_abs_pointer, ei_device_unref);
}
static void
maybe_dispose_ei_touch (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
g_signal_emit (session, signals[TOUCH_DEVICE_REMOVED], 0);
g_hash_table_remove_all (priv->touch_regions);
g_clear_pointer (&priv->ei_touch, ei_device_unref);
}
static void
process_regions (GrdSession *session,
struct ei_device *ei_device,
GHashTable *regions)
{
size_t i = 0;
struct ei_region *ei_region;
while ((ei_region = ei_device_get_region (ei_device, i++)))
{
const char *mapping_id;
GrdRegion *region;
mapping_id = ei_region_get_mapping_id (ei_region);
if (!mapping_id)
continue;
g_debug ("ei: New region: mapping-id: %s, %ux%u (%u, %u)",
mapping_id,
ei_region_get_width (ei_region), ei_region_get_height (ei_region),
ei_region_get_x (ei_region), ei_region_get_y (ei_region));
g_assert (ei_region_get_width (ei_region) > 0);
g_assert (ei_region_get_height (ei_region) > 0);
region = g_new0 (GrdRegion, 1);
region->ei_device = ei_device_ref (ei_device);
region->ei_region = ei_region_ref (ei_region);
g_hash_table_insert (regions, g_strdup (mapping_id), region);
}
}
static gboolean
grd_ei_source_dispatch (gpointer user_data)
{
GrdSession *session = GRD_SESSION (user_data);
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
struct ei_event *event;
ei_dispatch (priv->ei);
while ((event = ei_get_event (priv->ei)))
{
enum ei_event_type ei_event_type = ei_event_get_type (event);
gboolean handled = TRUE;
switch (ei_event_type)
{
case EI_EVENT_CONNECT:
if (!priv->is_ready)
{
priv->is_ready = TRUE;
g_signal_emit (session, signals[READY], 0);
maybe_emit_started_signal (session);
}
break;
case EI_EVENT_DISCONNECT:
break;
case EI_EVENT_SEAT_ADDED:
if (priv->ei_seat)
break;
priv->ei_seat = ei_seat_ref (ei_event_get_seat (event));
ei_seat_bind_capabilities (priv->ei_seat,
EI_DEVICE_CAP_POINTER,
EI_DEVICE_CAP_KEYBOARD,
EI_DEVICE_CAP_POINTER_ABSOLUTE,
EI_DEVICE_CAP_BUTTON,
EI_DEVICE_CAP_SCROLL,
EI_DEVICE_CAP_TOUCH,
NULL);
break;
case EI_EVENT_SEAT_REMOVED:
if (ei_event_get_seat (event) == priv->ei_seat)
g_clear_pointer (&priv->ei_seat, ei_seat_unref);
break;
case EI_EVENT_DEVICE_ADDED:
{
struct ei_device *device = ei_event_get_device (event);
if (ei_device_has_capability (device, EI_DEVICE_CAP_KEYBOARD))
{
g_autoptr (GError) error = NULL;
g_clear_pointer (&priv->ei_keyboard, ei_device_unref);
priv->ei_keyboard = ei_device_ref (device);
if (!process_keymap (session, priv->ei_keyboard, &error))
{
g_warning ("Failed to handle keyboard layout: %s",
error->message);
ei_event_unref (event);
grd_session_stop (session);
return G_SOURCE_REMOVE;
}
}
if (ei_device_has_capability (device, EI_DEVICE_CAP_POINTER))
{
g_clear_pointer (&priv->ei_pointer, ei_device_unref);
priv->ei_pointer = ei_device_ref (device);
}
if (ei_device_has_capability (device, EI_DEVICE_CAP_POINTER_ABSOLUTE))
{
maybe_dispose_ei_abs_pointer (session);
priv->ei_abs_pointer = ei_device_ref (device);
process_regions (session, device, priv->abs_pointer_regions);
}
if (ei_device_has_capability (device, EI_DEVICE_CAP_TOUCH))
{
maybe_dispose_ei_touch (session);
priv->ei_touch = ei_device_ref (device);
process_regions (session, device, priv->touch_regions);
}
break;
}
case EI_EVENT_DEVICE_RESUMED:
if (ei_event_get_device (event) == priv->ei_pointer)
ei_device_start_emulating (priv->ei_pointer, ++priv->ei_sequence);
if (ei_event_get_device (event) == priv->ei_abs_pointer)
ei_device_start_emulating (priv->ei_abs_pointer, ++priv->ei_sequence);
if (ei_event_get_device (event) == priv->ei_keyboard)
ei_device_start_emulating (priv->ei_keyboard, ++priv->ei_sequence);
if (ei_event_get_device (event) == priv->ei_touch)
{
ei_device_start_emulating (priv->ei_touch, ++priv->ei_sequence);
g_signal_emit (session, signals[TOUCH_DEVICE_ADDED], 0);
}
break;
case EI_EVENT_DEVICE_PAUSED:
break;
case EI_EVENT_DEVICE_REMOVED:
if (ei_event_get_device (event) == priv->ei_pointer)
g_clear_pointer (&priv->ei_pointer, ei_device_unref);
if (ei_event_get_device (event) == priv->ei_abs_pointer)
maybe_dispose_ei_abs_pointer (session);
if (ei_event_get_device (event) == priv->ei_keyboard)
g_clear_pointer (&priv->ei_keyboard, ei_device_unref);
if (ei_event_get_device (event) == priv->ei_touch)
maybe_dispose_ei_touch (session);
break;
case EI_EVENT_KEYBOARD_MODIFIERS:
if (priv->xkb_keymap)
{
GrdSessionClass *klass = GRD_SESSION_GET_CLASS (session);
uint32_t latched_mods =
ei_event_keyboard_get_xkb_mods_latched (event);
uint32_t locked_mods =
ei_event_keyboard_get_xkb_mods_locked (event);
gboolean caps_lock_state;
gboolean num_lock_state;
caps_lock_state =
!!((latched_mods | locked_mods) &
(1 << xkb_keymap_mod_get_index (priv->xkb_keymap,
XKB_MOD_NAME_CAPS)));
num_lock_state =
!!((latched_mods | locked_mods) &
(1 << xkb_keymap_mod_get_index (priv->xkb_keymap,
XKB_MOD_NAME_NUM)));
if (!priv->locked_modifier_valid ||
caps_lock_state != priv->caps_lock_state)
{
g_debug ("Caps lock state: %s",
caps_lock_state ? "locked" : "unlocked");
priv->caps_lock_state = caps_lock_state;
if (klass->on_caps_lock_state_changed)
{
klass->on_caps_lock_state_changed (session,
caps_lock_state);
}
}
if (!priv->locked_modifier_valid ||
num_lock_state != priv->num_lock_state)
{
g_debug ("Num lock state: %s",
num_lock_state ? "locked" : "unlocked");
priv->num_lock_state = num_lock_state;
if (klass->on_num_lock_state_changed)
{
klass->on_num_lock_state_changed (session,
num_lock_state);
}
}
priv->locked_modifier_valid = TRUE;
}
else
{
g_warning ("Couldn't update caps lock / num lock state, "
"no keymap");
}
break;
case EI_EVENT_PONG:
{
GrdEiPing *ping = NULL;
if (g_hash_table_steal_extended (priv->pings,
ei_event_pong_get_ping (event),
NULL,
(gpointer *) &ping))
finish_and_free_ping (ping);
break;
}
default:
handled = FALSE;
break;
}
if (handled)
{
g_debug ("ei: Handled event type %s",
ei_event_type_to_string (ei_event_type));
}
else
{
g_debug ("ei: Didn't handle event type %s",
ei_event_type_to_string (ei_event_type));
}
ei_event_unref (event);
}
return G_SOURCE_CONTINUE;
}
static void
on_eis_connected (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusMutterRemoteDesktopSession *proxy =
GRD_DBUS_MUTTER_REMOTE_DESKTOP_SESSION (object);
g_autoptr (GVariant) fd_variant = NULL;
g_autoptr (GUnixFDList) fd_list = NULL;
GrdSession *session;
GrdSessionPrivate *priv;
int fd_idx = -1;
int fd;
g_autoptr (GError) error = NULL;
const char *remote_desktop_session_id;
GrdDBusMutterScreenCast *screen_cast_proxy;
GVariantBuilder properties_builder;
GVariant *properties_variant;
int ret;
if (!grd_dbus_mutter_remote_desktop_session_call_connect_to_eis_finish (proxy,
&fd_variant,
&fd_list,
result,
&error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to connect to EIS: %s",
error->message);
grd_session_stop (GRD_SESSION (user_data));
return;
}
session = GRD_SESSION (user_data);
priv = grd_session_get_instance_private (session);
fd_idx = g_variant_get_handle (fd_variant);
if (!G_IS_UNIX_FD_LIST (fd_list) ||
fd_idx < 0 || fd_idx >= g_unix_fd_list_get_length (fd_list))
{
g_warning ("Failed to acquire file descriptor for EI backend: Invalid "
"file descriptor list sent by display server");
grd_session_stop (GRD_SESSION (user_data));
return;
}
fd = g_unix_fd_list_get (fd_list, fd_idx, &error);
priv->ei = ei_new_sender (session);
ei_configure_name (priv->ei, "gnome-remote-desktop");
ret = ei_setup_backend_fd (priv->ei, fd);
if (ret < 0)
{
g_warning ("Failed to setup libei backend: %s", g_strerror (-ret));
grd_session_stop (GRD_SESSION (user_data));
return;
}
priv->ei_source = grd_create_fd_source (ei_get_fd (priv->ei),
"libei",
grd_ei_source_prepare,
grd_ei_source_dispatch,
session, NULL);
g_source_attach (priv->ei_source, NULL);
g_source_unref (priv->ei_source);
g_signal_connect (priv->remote_desktop_session, "closed",
G_CALLBACK (on_remote_desktop_session_closed),
session);
g_signal_connect (priv->remote_desktop_session, "selection-owner-changed",
G_CALLBACK (on_remote_desktop_session_selection_owner_changed),
session);
g_signal_connect (priv->remote_desktop_session, "selection-transfer",
G_CALLBACK (on_remote_desktop_session_selection_transfer),
session);
remote_desktop_session_id =
grd_dbus_mutter_remote_desktop_session_get_session_id (priv->remote_desktop_session);
g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&properties_builder, "{sv}",
"remote-desktop-session-id",
g_variant_new_string (remote_desktop_session_id));
g_variant_builder_add (&properties_builder, "{sv}",
"disable-animations",
g_variant_new_boolean (TRUE));
properties_variant = g_variant_builder_end (&properties_builder);
screen_cast_proxy = grd_context_get_mutter_screen_cast_proxy (priv->context);
grd_dbus_mutter_screen_cast_call_create_session (screen_cast_proxy,
properties_variant,
priv->cancellable,
on_screen_cast_session_created,
session);
}
static void
on_remote_desktop_session_proxy_acquired (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GrdDBusMutterRemoteDesktopSession *session_proxy;
GrdSession *session;
GrdSessionPrivate *priv;
g_autoptr (GError) error = NULL;
GVariantBuilder options_builder;
session_proxy =
grd_dbus_mutter_remote_desktop_session_proxy_new_finish (result, &error);
if (!session_proxy)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to acquire remote desktop session proxy: %s",
error->message);
grd_session_stop (GRD_SESSION (user_data));
return;
}
session = GRD_SESSION (user_data);
priv = grd_session_get_instance_private (session);
priv->remote_desktop_session = session_proxy;
g_variant_builder_init (&options_builder, G_VARIANT_TYPE ("a{sv}"));
grd_dbus_mutter_remote_desktop_session_call_connect_to_eis (
priv->remote_desktop_session,
g_variant_builder_end (&options_builder),
NULL,
priv->cancellable,
on_eis_connected,
session);
}
static void
on_remote_desktop_session_created (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GrdDBusMutterRemoteDesktop *remote_desktop_proxy;
GrdSession *session;
GrdSessionPrivate *priv;
GDBusConnection *connection;
g_autofree char *session_path = NULL;
g_autoptr (GError) error = NULL;
remote_desktop_proxy = GRD_DBUS_MUTTER_REMOTE_DESKTOP (source_object);
if (!grd_dbus_mutter_remote_desktop_call_create_session_finish (remote_desktop_proxy,
&session_path,
res,
&error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to start remote desktop session: %s\n", error->message);
grd_session_stop (GRD_SESSION (user_data));
return;
}
session = GRD_SESSION (user_data);
priv = grd_session_get_instance_private (session);
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (remote_desktop_proxy));
grd_dbus_mutter_remote_desktop_session_proxy_new (connection,
G_DBUS_PROXY_FLAGS_NONE,
MUTTER_REMOTE_DESKTOP_BUS_NAME,
session_path,
priv->cancellable,
on_remote_desktop_session_proxy_acquired,
session);
}
void
grd_session_start (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
GrdDBusMutterRemoteDesktop *remote_desktop_proxy;
priv->cancellable = g_cancellable_new ();
remote_desktop_proxy = grd_context_get_mutter_remote_desktop_proxy (priv->context);
g_assert (remote_desktop_proxy);
grd_dbus_mutter_remote_desktop_call_create_session (remote_desktop_proxy,
priv->cancellable,
on_remote_desktop_session_created,
session);
}
gboolean
grd_session_is_ready (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
return priv->is_ready;
}
static void
grd_session_finalize (GObject *object)
{
GrdSession *session = GRD_SESSION (object);
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
if (priv->cancellable)
g_assert (g_cancellable_is_cancelled (priv->cancellable));
g_clear_object (&priv->cancellable);
g_assert (!priv->ei_source);
g_clear_pointer (&priv->pings, g_hash_table_unref);
g_clear_pointer (&priv->touch_regions, g_hash_table_unref);
g_clear_pointer (&priv->abs_pointer_regions, g_hash_table_unref);
g_assert (!priv->xkb_state);
g_assert (!priv->xkb_keymap);
g_assert (!priv->xkb_context);
g_assert (!priv->ei_touch);
g_assert (!priv->ei_keyboard);
g_assert (!priv->ei_abs_pointer);
g_assert (!priv->ei_pointer);
g_assert (!priv->ei_seat);
g_assert (!priv->ei);
g_assert (!priv->remote_desktop_session);
g_assert (!priv->screen_cast_session);
G_OBJECT_CLASS (grd_session_parent_class)->finalize (object);
}
static void
grd_session_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GrdSession *session = GRD_SESSION (object);
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
switch (prop_id)
{
case PROP_CONTEXT:
priv->context = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
grd_session_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GrdSession *session = GRD_SESSION (object);
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
switch (prop_id)
{
case PROP_CONTEXT:
g_value_set_object (value, priv->context);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
grd_ei_ping_free (GrdEiPing *ping)
{
g_object_unref (ping->task);
g_clear_pointer (&ping->ping, ei_ping_unref);
g_free (ping);
}
static void
cancel_and_free_ping (gpointer user_data)
{
GrdEiPing *ping = user_data;
GCancellable *cancellable;
cancellable = g_task_get_cancellable (ping->task);
if (!g_cancellable_is_cancelled (cancellable))
g_cancellable_cancel (cancellable);
g_task_return_error_if_cancelled (ping->task);
grd_ei_ping_free (ping);
}
static void
finish_and_free_ping (GrdEiPing *ping)
{
if (!g_task_return_error_if_cancelled (ping->task))
g_task_return_boolean (ping->task, TRUE);
grd_ei_ping_free (ping);
}
static void
grd_session_init (GrdSession *session)
{
GrdSessionPrivate *priv = grd_session_get_instance_private (session);
priv->abs_pointer_regions =
g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify) grd_region_free);
priv->touch_regions =
g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify) grd_region_free);
priv->pings = g_hash_table_new_full (NULL, NULL,
NULL, cancel_and_free_ping);
}
static void
grd_session_class_init (GrdSessionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = grd_session_finalize;
object_class->set_property = grd_session_set_property;
object_class->get_property = grd_session_get_property;
g_object_class_install_property (object_class,
PROP_CONTEXT,
g_param_spec_object ("context",
"GrdContext",
"The GrdContext instance",
GRD_TYPE_CONTEXT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
signals[READY] = g_signal_new ("ready",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[STARTED] = g_signal_new ("started",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[STOPPED] = g_signal_new ("stopped",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[TOUCH_DEVICE_ADDED] = g_signal_new ("touch-device-added",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[TOUCH_DEVICE_REMOVED] = g_signal_new ("touch-device-removed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}