mutter/src/backends/meta-eis-client.c
Jonas Ådahl 69fad154b9 eis: Use either standalone or shared devices for absolute input
Sometimes it makes no sense to have a shared pointer device, for example
when they have no set region occupying the global stage coordinate
space. This applies to for example window screen cast based pointer
device regions - they are always local to the window, and have no
position.

We do need shared absolute devices in some cases though, primarily
multi-head remote desktop, where it must be possible to keep a button
reliably pressed when crossing monitors that have their own
corresponding regions.

To handle this, outsource all this policy to the one who drives the
emulated input devices. Remote desktop sessions where the screen casts
correspond to specific monitors (physical or virtual), we need to make
sure they map to the stage coordinate space, while for window screencast
or area screencasts, we create standalone absolute pointer devices with
a single region each.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3228>
2023-09-01 15:44:01 +02:00

867 lines
26 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2021 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "backends/meta-eis-client.h"
#include "backends/meta-backend-private.h"
#include "backends/meta-monitor-manager-private.h"
#include "clutter/clutter-mutter.h"
#include "core/meta-anonymous-file.h"
#define MAX_BUTTON 128
#define MAX_KEY 0x2ff /* KEY_MAX as of 5.13 */
typedef struct _MetaEisDevice MetaEisDevice;
struct _MetaEisDevice
{
struct eis_device *eis_device;
ClutterVirtualInputDevice *device;
MetaEisViewport *viewport;
guchar button_state[(MAX_BUTTON + 7) / 8];
guchar key_state[(MAX_KEY + 7) / 8];
};
struct _MetaEisClient
{
GObject parent_instance;
MetaEis *eis;
struct eis_client *eis_client;
struct eis_seat *eis_seat;
GHashTable *eis_devices; /* eis_device => MetaEisDevice*/
gulong viewports_changed_handler_id;
};
G_DEFINE_TYPE (MetaEisClient, meta_eis_client, G_TYPE_OBJECT)
typedef void (* MetaEisDeviceConfigFunc) (MetaEisClient *client,
struct eis_device *device,
gpointer user_data);
static bool
bit_is_set (const guchar *array,
int bit)
{
return !!(array[bit / 8] & (1 << (bit % 8)));
}
static void
bit_set (guchar *array,
int bit)
{
array[bit / 8] |= (1 << (bit % 8));
}
static void
bit_clear (guchar *array,
int bit)
{
array[bit / 8] &= ~(1 << (bit % 8));
}
static void
notify_key (MetaEisDevice *device,
uint32_t key,
gboolean is_press)
{
ClutterKeyState state;
state = is_press ? CLUTTER_KEY_STATE_PRESSED : CLUTTER_KEY_STATE_RELEASED;
clutter_virtual_input_device_notify_key (device->device,
g_get_monotonic_time (),
key,
state);
}
static void
notify_button (MetaEisDevice *device,
uint32_t button,
gboolean is_press)
{
ClutterButtonState state;
state = is_press ? CLUTTER_BUTTON_STATE_PRESSED : CLUTTER_BUTTON_STATE_RELEASED;
clutter_virtual_input_device_notify_button (device->device,
g_get_monotonic_time (),
button,
state);
}
static void
remove_device (MetaEisClient *client,
struct eis_device *eis_device,
gboolean remove_from_hashtable)
{
MetaEisDevice *device = eis_device_get_user_data (eis_device);
struct eis_keymap *eis_keymap = eis_device_keyboard_get_keymap (eis_device);
if (eis_keymap)
{
MetaAnonymousFile *f = eis_keymap_get_user_data (eis_keymap);
if (f)
meta_anonymous_file_free (f);
}
eis_device_pause (eis_device);
eis_device_remove (eis_device);
g_clear_pointer (&device->eis_device, eis_device_unref);
g_clear_object (&device->device);
if (remove_from_hashtable)
g_hash_table_remove (client->eis_devices, eis_device);
}
static gboolean
drop_device (gpointer htkey,
gpointer value,
gpointer data)
{
MetaEisClient *client = data;
struct eis_device *eis_device = htkey;
MetaEisDevice *device = eis_device_get_user_data (eis_device);
uint32_t key, button;
for (key = 0; key < MAX_KEY; key++)
{
if (bit_is_set (device->key_state, key))
notify_key (device, key, FALSE);
}
for (button = 0; button < MAX_BUTTON; button++)
{
if (bit_is_set (device->button_state, key))
notify_button (device, button, FALSE);
}
remove_device (client, eis_device, FALSE);
return TRUE;
}
static void
meta_eis_device_free (MetaEisDevice *device)
{
eis_device_unref (device->eis_device);
free (device);
}
static void
configure_rel (MetaEisClient *client,
struct eis_device *eis_device,
gpointer user_data)
{
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_POINTER);
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_BUTTON);
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_SCROLL);
}
static void
configure_keyboard (MetaEisClient *client,
struct eis_device *eis_device,
gpointer user_data)
{
size_t len;
MetaAnonymousFile *f;
int fd = -1;
char *data;
struct xkb_keymap *xkb_keymap;
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_KEYBOARD);
xkb_keymap =
meta_backend_get_keymap (meta_eis_get_backend (client->eis));
if (!xkb_keymap)
return;
data = xkb_keymap_get_as_string (xkb_keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
if (!data)
return;
len = strlen (data);
f = meta_anonymous_file_new (len, (uint8_t*)data);
if (f)
fd = meta_anonymous_file_open_fd (f, META_ANONYMOUS_FILE_MAPMODE_SHARED);
g_free (data);
if (fd != -1)
{
struct eis_keymap *eis_keymap;
eis_keymap = eis_device_new_keymap (eis_device, EIS_KEYMAP_TYPE_XKB,
fd, len);
/* libeis dup()s the fd */
meta_anonymous_file_close_fd (fd);
/* The memfile must be kept alive while the device is alive */
eis_keymap_set_user_data (eis_keymap, f);
eis_keymap_add (eis_keymap);
eis_keymap_unref (eis_keymap);
}
}
static gboolean
has_region (struct eis_device *eis_device,
int x,
int y,
int width,
int height)
{
int i = 0;
while (TRUE)
{
struct eis_region *region;
region = eis_device_get_region (eis_device, i++);
if (!region)
return FALSE;
if (eis_region_get_x (region) == x &&
eis_region_get_y (region) == y &&
eis_region_get_width (region) == width &&
eis_region_get_height (region) == height)
return TRUE;
}
}
static void
add_viewport_region (struct eis_device *eis_device,
MetaEisViewport *viewport)
{
gboolean has_position;
int x, y;
int width, height;
double scale;
const char *mapping_id;
struct eis_region *eis_region;
if (meta_eis_viewport_get_position (viewport, &x, &y))
has_position = TRUE;
meta_eis_viewport_get_size (viewport, &width, &height);
scale = meta_eis_viewport_get_physical_scale (viewport);
if (has_region (eis_device, x, y, width, height))
return;
eis_region = eis_device_new_region (eis_device);
if (has_position)
eis_region_set_offset (eis_region, x, y);
eis_region_set_size (eis_region, width, height);
eis_region_set_physical_scale (eis_region, scale);
mapping_id = meta_eis_viewport_get_mapping_id (viewport);
g_warn_if_fail (mapping_id);
eis_region_set_mapping_id (eis_region, mapping_id);
eis_region_set_user_data (eis_region, viewport);
eis_region_add (eis_region);
eis_region_unref (eis_region);
}
static void
configure_abs_shared (MetaEisClient *client,
struct eis_device *eis_device,
gpointer user_data)
{
MetaEisViewport *viewport = META_EIS_VIEWPORT (user_data);
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_BUTTON);
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_SCROLL);
add_viewport_region (eis_device, viewport);
}
static void
configure_abs_standalone (MetaEisClient *client,
struct eis_device *eis_device,
gpointer user_data)
{
MetaEisViewport *viewport = META_EIS_VIEWPORT (user_data);
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_BUTTON);
eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_SCROLL);
add_viewport_region (eis_device, viewport);
}
static MetaEisDevice *
add_device (MetaEisClient *client,
struct eis_seat *eis_seat,
ClutterInputDeviceType type,
const char *name_suffix,
MetaEisDeviceConfigFunc extra_config_func,
gpointer extra_config_user_data)
{
MetaBackend *backend = meta_eis_get_backend (client->eis);
MetaEisDevice *device;
ClutterSeat *seat = meta_backend_get_default_seat (backend);
ClutterVirtualInputDevice *virtual_device;
struct eis_device *eis_device;
gchar *name;
virtual_device = clutter_seat_create_virtual_device (seat, type);
eis_device = eis_seat_new_device (eis_seat);
name = g_strdup_printf ("%s %s", eis_client_get_name (client->eis_client),
name_suffix);
eis_device_configure_name (eis_device, name);
if (extra_config_func)
extra_config_func (client, eis_device, extra_config_user_data);
device = g_new0 (MetaEisDevice, 1);
device->eis_device = eis_device_ref (eis_device);
device->device = virtual_device;
eis_device_set_user_data (eis_device, device);
g_hash_table_insert (client->eis_devices,
eis_device, /* owns the initial ref now */
device);
eis_device_add (eis_device);
eis_device_resume (eis_device);
g_free (name);
return device;
}
static void
handle_motion_relative (MetaEisClient *client,
struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
double dx, dy;
dx = eis_event_pointer_get_dx (event);
dy = eis_event_pointer_get_dy (event);
clutter_virtual_input_device_notify_relative_motion (device->device,
g_get_monotonic_time (),
dx, dy);
}
static MetaEisViewport *
find_viewport (struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
double x, y;
struct eis_region *region;
if (device->viewport)
return device->viewport;
x = eis_event_pointer_get_absolute_x (event);
y = eis_event_pointer_get_absolute_y (event);
region = eis_device_get_region_at (eis_device, x, y);
if (!region)
return NULL;
return META_EIS_VIEWPORT (eis_region_get_user_data (region));
}
static void
handle_motion_absolute (MetaEisClient *client,
struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
MetaEisViewport *viewport;
double x, y;
viewport = find_viewport (event);
if (!viewport)
return;
x = eis_event_pointer_get_absolute_x (event);
y = eis_event_pointer_get_absolute_y (event);
if (!meta_eis_viewport_transform_coordinate (viewport, x, y, &x, &y))
return;
clutter_virtual_input_device_notify_absolute_motion (device->device,
g_get_monotonic_time (),
x, y);
}
static void
handle_scroll (MetaEisClient *client,
struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
double dx, dy;
dx = eis_event_scroll_get_dx (event);
dy = eis_event_scroll_get_dy (event);
clutter_virtual_input_device_notify_scroll_continuous (device->device,
g_get_monotonic_time (),
dx, dy,
CLUTTER_SCROLL_SOURCE_UNKNOWN,
CLUTTER_SCROLL_FINISHED_NONE);
}
static void
handle_scroll_stop (MetaEisClient *client,
struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
ClutterScrollFinishFlags finish_flags = CLUTTER_SCROLL_FINISHED_NONE;
if (eis_event_scroll_get_stop_x (event))
finish_flags |= CLUTTER_SCROLL_FINISHED_HORIZONTAL;
if (eis_event_scroll_get_stop_y (event))
finish_flags |= CLUTTER_SCROLL_FINISHED_VERTICAL;
if (finish_flags != CLUTTER_SCROLL_FINISHED_NONE)
clutter_virtual_input_device_notify_scroll_continuous (device->device,
g_get_monotonic_time (),
0.0, 0.0,
CLUTTER_SCROLL_SOURCE_UNKNOWN,
finish_flags);
}
static void
handle_scroll_cancel (MetaEisClient *client,
struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
double dx = 0.0, dy = 0.0;
/* There's no real match for the EIS scroll cancel event, so let's just send a
* really small scroll event that hopefully resets the scroll speed to
* something where kinetic scrolling is not viable.
*/
if (eis_event_scroll_get_stop_x (event))
dx = 0.01;
if (eis_event_scroll_get_stop_y (event))
dy = 0.01;
if (dx != 0.0 || dy != 0.0)
clutter_virtual_input_device_notify_scroll_continuous (device->device,
g_get_monotonic_time (),
dx, dy,
CLUTTER_SCROLL_SOURCE_UNKNOWN,
CLUTTER_SCROLL_FINISHED_NONE);
}
static void
handle_scroll_discrete (MetaEisClient *client,
struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
int dx, dy;
/* FIXME: need to handle the remainders here for high-resolution scrolling */
dx = eis_event_scroll_get_discrete_dx (event) / 120;
dy = eis_event_scroll_get_discrete_dy (event) / 120;
/* Intentionally interleaved */
while (dx || dy)
{
if (dx > 0)
{
clutter_virtual_input_device_notify_discrete_scroll (device->device,
g_get_monotonic_time (),
CLUTTER_SCROLL_RIGHT,
CLUTTER_SCROLL_SOURCE_UNKNOWN);
dx--;
}
else if (dx < 0)
{
clutter_virtual_input_device_notify_discrete_scroll (device->device,
g_get_monotonic_time (),
CLUTTER_SCROLL_LEFT,
CLUTTER_SCROLL_SOURCE_UNKNOWN);
dx++;
}
if (dy > 0)
{
clutter_virtual_input_device_notify_discrete_scroll (device->device,
g_get_monotonic_time (),
CLUTTER_SCROLL_DOWN,
CLUTTER_SCROLL_SOURCE_UNKNOWN);
dy--;
}
else if (dy < 0)
{
clutter_virtual_input_device_notify_discrete_scroll (device->device,
g_get_monotonic_time (),
CLUTTER_SCROLL_UP,
CLUTTER_SCROLL_SOURCE_UNKNOWN);
dy++;
}
}
}
static void
handle_button (MetaEisClient *client,
struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
uint32_t button;
gboolean is_press = eis_event_button_get_is_press (event);
button = eis_event_button_get_button (event);
switch (button)
{
case 0x110: /* BTN_LEFT */
button = CLUTTER_BUTTON_PRIMARY;
break;
case 0x111: /* BTN_RIGHT */
button = CLUTTER_BUTTON_SECONDARY;
break;
case 0x112: /* BTN_MIDDLE */
button = CLUTTER_BUTTON_MIDDLE;
break;
default:
if (button > 0x110)
button -= 0x110;
break;
}
if (button > MAX_BUTTON)
return;
if (is_press && !bit_is_set (device->button_state, button))
bit_set (device->button_state, button);
else if (!is_press && bit_is_set (device->button_state, button))
bit_clear (device->button_state, button);
else
return; /* Duplicate press/release, should've been filtered by libeis */
notify_button (device,
button,
eis_event_button_get_is_press (event));
}
static void
handle_key (MetaEisClient *client,
struct eis_event *event)
{
struct eis_device *eis_device = eis_event_get_device (event);
MetaEisDevice *device = eis_device_get_user_data (eis_device);
uint32_t key;
gboolean is_press = eis_event_keyboard_get_key_is_press (event);
key = eis_event_keyboard_get_key (event);
if (key > MAX_KEY)
return;
if (is_press && !bit_is_set (device->key_state, key))
bit_set (device->key_state, key);
else if (!is_press && bit_is_set (device->key_state, key))
bit_clear (device->key_state, key);
else
return; /* Duplicate press/release, should've been filtered by libeis */
notify_key (device, key, is_press);
}
static gboolean
drop_kbd_devices (gpointer htkey,
gpointer value,
gpointer data)
{
struct eis_device *eis_device = htkey;
if (!eis_device_has_capability (eis_device, EIS_DEVICE_CAP_KEYBOARD))
return FALSE;
return drop_device (htkey, value, data);
}
static void
on_keymap_changed (MetaBackend *backend,
gpointer data)
{
MetaEisClient *client = data;
/* Changing the keymap means we have to remove our device and recreate it
* with the new keymap.
*/
g_hash_table_foreach_remove (client->eis_devices,
drop_kbd_devices,
client);
add_device (client,
client->eis_seat,
CLUTTER_KEYBOARD_DEVICE,
"virtual keyboard",
configure_keyboard, NULL);
}
static void
add_abs_pointer_devices (MetaEisClient *client)
{
MetaEisDevice *shared_device = NULL;
GList *viewports;
GList *l;
viewports = meta_eis_peek_viewports (client->eis);
if (!viewports)
return; /* FIXME: should be an error */
for (l = viewports; l; l = l->next)
{
MetaEisViewport *viewport = l->data;
if (meta_eis_viewport_is_standalone (viewport))
{
MetaEisDevice *device;
device = add_device (client,
client->eis_seat,
CLUTTER_POINTER_DEVICE,
"standalone virtual absolute pointer",
configure_abs_standalone,
viewport);
device->viewport = viewport;
}
else
{
if (!shared_device)
{
shared_device = add_device (client,
client->eis_seat,
CLUTTER_POINTER_DEVICE,
"shared virtual absolute pointer",
configure_abs_shared,
viewport);
}
else
{
add_viewport_region (shared_device->eis_device, viewport);
}
}
}
}
gboolean
meta_eis_client_process_event (MetaEisClient *client,
struct eis_event *event)
{
enum eis_event_type type = eis_event_get_type (event);
struct eis_seat *eis_seat;
switch (type)
{
case EIS_EVENT_SEAT_BIND:
eis_seat = eis_event_get_seat (event);
if (eis_event_seat_has_capability (event, EIS_DEVICE_CAP_POINTER))
{
add_device (client,
eis_seat,
CLUTTER_POINTER_DEVICE,
"virtual pointer",
configure_rel, NULL);
}
if (eis_event_seat_has_capability (event, EIS_DEVICE_CAP_KEYBOARD))
{
add_device (client,
eis_seat,
CLUTTER_KEYBOARD_DEVICE,
"virtual keyboard",
configure_keyboard, NULL);
g_signal_connect (meta_eis_get_backend (client->eis),
"keymap-changed",
G_CALLBACK (on_keymap_changed),
client);
}
add_abs_pointer_devices (client);
break;
/* We only have one seat, so if the client unbinds from that
* just disconnect it, no point keeping it alive */
case EIS_EVENT_DEVICE_CLOSED:
remove_device (client, eis_event_get_device (event), TRUE);
break;
case EIS_EVENT_POINTER_MOTION:
handle_motion_relative (client, event);
break;
case EIS_EVENT_POINTER_MOTION_ABSOLUTE:
handle_motion_absolute (client, event);
break;
case EIS_EVENT_BUTTON_BUTTON:
handle_button (client, event);
break;
case EIS_EVENT_SCROLL_DELTA:
handle_scroll (client, event);
break;
case EIS_EVENT_SCROLL_STOP:
handle_scroll_stop (client, event);
break;
case EIS_EVENT_SCROLL_CANCEL:
handle_scroll_cancel (client, event);
break;
case EIS_EVENT_SCROLL_DISCRETE:
handle_scroll_discrete (client, event);
break;
case EIS_EVENT_KEYBOARD_KEY:
handle_key (client, event);
break;
case EIS_EVENT_FRAME:
/* FIXME: we should be accumulating the above events */
break;
case EIS_EVENT_DEVICE_START_EMULATING:
break;
case EIS_EVENT_DEVICE_STOP_EMULATING:
break;
default:
g_warning ("Unhandled EIS event type %d", type);
return FALSE;
}
return TRUE;
}
static gboolean
drop_abs_devices (gpointer key,
gpointer value,
gpointer data)
{
struct eis_device *eis_device = key;
if (!eis_device_has_capability (eis_device, EIS_DEVICE_CAP_POINTER_ABSOLUTE))
return FALSE;
return drop_device (key, value, data);
}
static void
update_viewports (MetaEisClient *client)
{
g_hash_table_foreach_remove (client->eis_devices,
drop_abs_devices,
client);
add_abs_pointer_devices (client);
}
static void
on_viewports_changed (MetaEis *eis,
MetaEisClient *client)
{
update_viewports (client);
}
static void
meta_eis_client_disconnect (MetaEisClient *client)
{
g_clear_signal_handler (&client->viewports_changed_handler_id, client->eis);
g_hash_table_foreach_remove (client->eis_devices, drop_device, client);
g_clear_pointer (&client->eis_seat, eis_seat_unref);
if (client->eis_client)
eis_client_disconnect (client->eis_client);
g_clear_pointer (&client->eis_client, eis_client_unref);
}
MetaEisClient *
meta_eis_client_new (MetaEis *eis,
struct eis_client *eis_client)
{
MetaEisClient *client;
struct eis_seat *eis_seat;
client = g_object_new (META_TYPE_EIS_CLIENT, NULL);
client->eis = eis;
client->eis_client = eis_client_ref (eis_client);
eis_client_set_user_data (client->eis_client, client);
/* We're relying on some third party to filter clients for us */
eis_client_connect (eis_client);
/* We only support one seat for now. libeis keeps the ref for the
* seat and we don't need to care about it.
* The capabilities we define are the maximum capabilities, the client
* may only bind to a subset of those, reducing the capabilities
* of the seat in the process.
*/
eis_seat = eis_client_new_seat (eis_client, "mutter default seat");
if (meta_eis_get_device_types (eis) & META_EIS_DEVICE_TYPE_KEYBOARD)
{
eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_KEYBOARD);
}
if (meta_eis_get_device_types (eis) & META_EIS_DEVICE_TYPE_POINTER)
{
eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_POINTER);
eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_POINTER_ABSOLUTE);
eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_BUTTON);
eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_SCROLL);
}
eis_seat_add (eis_seat);
eis_seat_unref (eis_seat);
client->eis_seat = eis_seat_ref (eis_seat);
client->eis_devices = g_hash_table_new_full (g_direct_hash, g_direct_equal,
(GDestroyNotify) eis_device_unref,
(GDestroyNotify) meta_eis_device_free);
client->viewports_changed_handler_id =
g_signal_connect (eis, "viewports-changed",
G_CALLBACK (on_viewports_changed),
client);
update_viewports (client);
return client;
}
static void
meta_eis_client_init (MetaEisClient *client)
{
}
static void
meta_eis_client_finalize (GObject *object)
{
MetaEisClient *client = META_EIS_CLIENT (object);
g_signal_handlers_disconnect_by_func (meta_eis_get_backend (client->eis),
on_keymap_changed,
client);
meta_eis_client_disconnect (client);
G_OBJECT_CLASS (meta_eis_client_parent_class)->finalize (object);
}
static void
meta_eis_client_class_init (MetaEisClientClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = meta_eis_client_finalize;
}