/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright 2018 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 . * * Author: Carlos Garnacho */ #include "config.h" #ifdef HAVE_LIBGUDEV #include #endif #include "meta-input-mapper-private.h" #include "meta-monitor-manager-private.h" #include "meta-logical-monitor.h" #include "meta-backend-private.h" #define MAX_SIZE_MATCH_DIFF 0.05 typedef struct _MetaMapperInputInfo MetaMapperInputInfo; typedef struct _MetaMapperOutputInfo MetaMapperOutputInfo; typedef struct _MappingHelper MappingHelper; typedef struct _DeviceCandidates DeviceCandidates; typedef struct _DeviceMatch DeviceMatch; struct _MetaInputMapper { GObject parent_instance; MetaMonitorManager *monitor_manager; ClutterSeat *seat; GHashTable *input_devices; /* ClutterInputDevice -> MetaMapperInputInfo */ GHashTable *output_devices; /* MetaLogicalMonitor -> MetaMapperOutputInfo */ #ifdef HAVE_LIBGUDEV GUdevClient *udev_client; #endif }; typedef enum { META_INPUT_CAP_TOUCH = 1 << 0, /* touch device, either touchscreen or tablet */ META_INPUT_CAP_STYLUS = 1 << 1, /* tablet pen */ META_INPUT_CAP_ERASER = 1 << 2, /* tablet eraser */ META_INPUT_CAP_PAD = 1 << 3, /* pad device, most usually in tablets */ META_INPUT_CAP_CURSOR = 1 << 4 /* pointer-like device in tablets */ } MetaInputCapabilityFlags; typedef enum { META_MATCH_EDID_VENDOR, /* EDID vendor match, eg. "WAC" for Wacom */ META_MATCH_EDID_PARTIAL, /* Partial EDID model match, eg. "Cintiq" */ META_MATCH_EDID_FULL, /* Full EDID model match, eg. "Cintiq 12WX" */ META_MATCH_SIZE, /* Size from input device and output match */ META_MATCH_IS_BUILTIN, /* Output is builtin, applies mainly to system-integrated devices */ N_OUTPUT_MATCHES } MetaOutputMatchType; struct _MetaMapperInputInfo { ClutterInputDevice *device; MetaInputMapper *mapper; MetaMapperOutputInfo *output; guint builtin : 1; }; struct _MetaMapperOutputInfo { MetaLogicalMonitor *logical_monitor; GList *input_devices; MetaInputCapabilityFlags attached_caps; }; struct _MappingHelper { GArray *device_maps; }; struct _DeviceMatch { MetaMonitor *monitor; uint32_t score; }; struct _DeviceCandidates { MetaMapperInputInfo *input; GArray *matches; /* Array of DeviceMatch */ int best; }; enum { DEVICE_MAPPED, N_SIGNALS }; static guint signals[N_SIGNALS] = { 0, }; G_DEFINE_TYPE (MetaInputMapper, meta_input_mapper, G_TYPE_OBJECT) static MetaMapperInputInfo * mapper_input_info_new (ClutterInputDevice *device, MetaInputMapper *mapper, gboolean builtin) { MetaMapperInputInfo *info; info = g_new0 (MetaMapperInputInfo, 1); info->mapper = mapper; info->device = device; info->builtin = builtin; return info; } static void mapper_input_info_free (MetaMapperInputInfo *info) { g_free (info); } static MetaMapperOutputInfo * mapper_output_info_new (MetaLogicalMonitor *logical_monitor) { MetaMapperOutputInfo *info; info = g_new0 (MetaMapperOutputInfo, 1); info->logical_monitor = logical_monitor; return info; } static void mapper_output_info_free (MetaMapperOutputInfo *info) { g_free (info); } static MetaInputCapabilityFlags mapper_input_info_get_caps (MetaMapperInputInfo *info) { ClutterInputDeviceType type; type = clutter_input_device_get_device_type (info->device); switch (type) { case CLUTTER_TOUCHSCREEN_DEVICE: return META_INPUT_CAP_TOUCH; case CLUTTER_TABLET_DEVICE: case CLUTTER_PEN_DEVICE: return META_INPUT_CAP_STYLUS; case CLUTTER_ERASER_DEVICE: return META_INPUT_CAP_ERASER; case CLUTTER_CURSOR_DEVICE: return META_INPUT_CAP_CURSOR; case CLUTTER_PAD_DEVICE: return META_INPUT_CAP_PAD; default: return 0; } } static void mapper_input_info_set_output (MetaMapperInputInfo *input, MetaMapperOutputInfo *output, MetaMonitor *monitor) { if (input->output == output) return; input->output = output; g_signal_emit (input->mapper, signals[DEVICE_MAPPED], 0, input->device, output ? output->logical_monitor : NULL, monitor); } static void mapper_output_info_add_input (MetaMapperOutputInfo *output, MetaMapperInputInfo *input, MetaMonitor *monitor) { g_assert (input->output == NULL); output->input_devices = g_list_prepend (output->input_devices, input); output->attached_caps |= mapper_input_info_get_caps (input); mapper_input_info_set_output (input, output, monitor); } static void mapper_output_info_remove_input (MetaMapperOutputInfo *output, MetaMapperInputInfo *input) { GList *l; g_assert (input->output == output); output->input_devices = g_list_remove (output->input_devices, input); output->attached_caps = 0; for (l = output->input_devices; l; l = l->next) output->attached_caps |= mapper_input_info_get_caps (l->data); mapper_input_info_set_output (input, NULL, NULL); } static void mapper_output_info_clear_inputs (MetaMapperOutputInfo *output) { while (output->input_devices) { MetaMapperInputInfo *input = output->input_devices->data; mapper_input_info_set_output (input, NULL, NULL); output->input_devices = g_list_remove (output->input_devices, input); } output->attached_caps = 0; } static void clear_candidates (DeviceCandidates *candidates) { g_clear_pointer (&candidates->matches, g_array_unref); } static void mapping_helper_init (MappingHelper *helper) { helper->device_maps = g_array_new (FALSE, FALSE, sizeof (DeviceCandidates)); g_array_set_clear_func (helper->device_maps, (GDestroyNotify) clear_candidates); } static void mapping_helper_release (MappingHelper *helper) { g_array_unref (helper->device_maps); } static gboolean match_edid (MetaMapperInputInfo *input, MetaMonitor *monitor, MetaOutputMatchType *match_type) { const gchar *dev_name; dev_name = clutter_input_device_get_device_name (input->device); if (strcasestr (dev_name, meta_monitor_get_vendor (monitor)) == NULL) return FALSE; *match_type = META_MATCH_EDID_VENDOR; if (strcasestr (dev_name, meta_monitor_get_product (monitor)) != NULL) { *match_type = META_MATCH_EDID_FULL; } else { int i; g_auto (GStrv) split = NULL; split = g_strsplit (meta_monitor_get_product (monitor), " ", -1); for (i = 0; split[i]; i++) { if (strcasestr (dev_name, split[i]) != NULL) { *match_type = META_MATCH_EDID_PARTIAL; break; } } } return TRUE; } static gboolean input_device_get_physical_size (MetaInputMapper *mapper, ClutterInputDevice *device, double *width, double *height) { #ifdef HAVE_LIBGUDEV g_autoptr (GUdevDevice) udev_device = NULL; const char *node; node = clutter_input_device_get_device_node (device); udev_device = g_udev_client_query_by_device_file (mapper->udev_client, node); if (udev_device && g_udev_device_has_property (udev_device, "ID_INPUT_WIDTH_MM")) { *width = g_udev_device_get_property_as_double (udev_device, "ID_INPUT_WIDTH_MM"); *height = g_udev_device_get_property_as_double (udev_device, "ID_INPUT_HEIGHT_MM"); return TRUE; } #endif return FALSE; } static gboolean match_size (MetaMapperInputInfo *input, MetaMonitor *monitor) { double w_diff, h_diff; int o_width, o_height; double i_width, i_height; if (!input_device_get_physical_size (input->mapper, input->device, &i_width, &i_height)) return FALSE; meta_monitor_get_physical_dimensions (monitor, &o_width, &o_height); w_diff = ABS (1 - ((double) o_width / i_width)); h_diff = ABS (1 - ((double) o_height / i_height)); return w_diff < MAX_SIZE_MATCH_DIFF && h_diff < MAX_SIZE_MATCH_DIFF; } static gboolean match_builtin (MetaInputMapper *mapper, MetaMonitor *monitor) { return monitor == meta_monitor_manager_get_laptop_panel (mapper->monitor_manager); } static int sort_by_score (DeviceMatch *match1, DeviceMatch *match2) { return (int) match1->score - match2->score; } static void guess_candidates (MetaInputMapper *mapper, MetaMapperInputInfo *input, DeviceCandidates *info) { GList *monitors, *l; monitors = meta_monitor_manager_get_monitors (mapper->monitor_manager); for (l = monitors; l; l = l->next) { MetaOutputMatchType edid_match; DeviceMatch match = { l->data, 0 }; if (match_edid (input, l->data, &edid_match)) match.score |= 1 << edid_match; if (match_size (input, l->data)) match.score |= 1 << META_MATCH_SIZE; if (input->builtin && match_builtin (mapper, l->data)) match.score |= 1 << META_MATCH_IS_BUILTIN; if (match.score > 0) g_array_append_val (info->matches, match); } if (info->matches->len == 0) { DeviceMatch match = { 0 }; match.monitor = meta_monitor_manager_get_laptop_panel (mapper->monitor_manager); g_array_append_val (info->matches, match); info->best = 0; } else { DeviceMatch *best; g_array_sort (info->matches, (GCompareFunc) sort_by_score); best = &g_array_index (info->matches, DeviceMatch, 0); info->best = best->score; } } static void mapping_helper_add (MappingHelper *helper, MetaMapperInputInfo *input, MetaInputMapper *mapper) { DeviceCandidates info = { 0, }; guint i, pos = 0; info.input = input; info.matches = g_array_new (FALSE, TRUE, sizeof (DeviceMatch)); guess_candidates (mapper, input, &info); for (i = 0; i < helper->device_maps->len; i++) { DeviceCandidates *elem; elem = &g_array_index (helper->device_maps, DeviceCandidates, i); if (elem->best > info.best) pos = i; } if (pos >= helper->device_maps->len) g_array_append_val (helper->device_maps, info); else g_array_insert_val (helper->device_maps, pos, info); } static void mapping_helper_apply (MappingHelper *helper, MetaInputMapper *mapper) { guint i, j; /* Now, decide which input claims which output */ for (i = 0; i < helper->device_maps->len; i++) { DeviceCandidates *info; info = &g_array_index (helper->device_maps, DeviceCandidates, i); for (j = 0; j < info->matches->len; j++) { MetaLogicalMonitor *logical_monitor; MetaMapperOutputInfo *output; MetaMonitor *monitor; DeviceMatch *match; match = &g_array_index (info->matches, DeviceMatch, j); monitor = match->monitor; logical_monitor = meta_monitor_get_logical_monitor (monitor); output = g_hash_table_lookup (mapper->output_devices, logical_monitor); if (!output) continue; if (output->attached_caps & mapper_input_info_get_caps (info->input)) continue; mapper_output_info_add_input (output, info->input, monitor); break; } } } static void mapper_recalculate_candidates (MetaInputMapper *mapper) { MetaMapperInputInfo *input; MappingHelper helper; GHashTableIter iter; mapping_helper_init (&helper); g_hash_table_iter_init (&iter, mapper->input_devices); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &input)) mapping_helper_add (&helper, input, mapper); mapping_helper_apply (&helper, mapper); mapping_helper_release (&helper); } static void mapper_recalculate_input (MetaInputMapper *mapper, MetaMapperInputInfo *input) { MappingHelper helper; mapping_helper_init (&helper); mapping_helper_add (&helper, input, mapper); mapping_helper_apply (&helper, mapper); mapping_helper_release (&helper); } static void mapper_update_outputs (MetaInputMapper *mapper) { MetaMapperOutputInfo *output; GList *logical_monitors, *l; GHashTableIter iter; g_hash_table_iter_init (&iter, mapper->output_devices); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &output)) { mapper_output_info_clear_inputs (output); g_hash_table_iter_remove (&iter); } logical_monitors = meta_monitor_manager_get_logical_monitors (mapper->monitor_manager); for (l = logical_monitors; l; l = l->next) { MetaLogicalMonitor *logical_monitor = l->data; MetaMapperOutputInfo *info; info = mapper_output_info_new (logical_monitor); g_hash_table_insert (mapper->output_devices, logical_monitor, info); } mapper_recalculate_candidates (mapper); } static void input_mapper_monitors_changed_cb (MetaMonitorManager *monitor_manager, MetaInputMapper *mapper) { mapper_update_outputs (mapper); } static void input_mapper_device_removed_cb (ClutterSeat *seat, ClutterInputDevice *device, MetaInputMapper *mapper) { meta_input_mapper_remove_device (mapper, device); } static void meta_input_mapper_finalize (GObject *object) { MetaInputMapper *mapper = META_INPUT_MAPPER (object); g_signal_handlers_disconnect_by_func (mapper->monitor_manager, input_mapper_monitors_changed_cb, mapper); g_signal_handlers_disconnect_by_func (mapper->seat, input_mapper_device_removed_cb, mapper); g_hash_table_unref (mapper->input_devices); g_hash_table_unref (mapper->output_devices); #ifdef HAVE_LIBGUDEV g_clear_object (&mapper->udev_client); #endif G_OBJECT_CLASS (meta_input_mapper_parent_class)->finalize (object); } static void meta_input_mapper_constructed (GObject *object) { #ifdef HAVE_LIBGUDEV const char *udev_subsystems[] = { "input", NULL }; #endif MetaInputMapper *mapper = META_INPUT_MAPPER (object); MetaBackend *backend; G_OBJECT_CLASS (meta_input_mapper_parent_class)->constructed (object); #ifdef HAVE_LIBGUDEV mapper->udev_client = g_udev_client_new (udev_subsystems); #endif mapper->seat = clutter_backend_get_default_seat (clutter_get_default_backend ()); g_signal_connect (mapper->seat, "device-removed", G_CALLBACK (input_mapper_device_removed_cb), mapper); backend = meta_get_backend (); mapper->monitor_manager = meta_backend_get_monitor_manager (backend); g_signal_connect (mapper->monitor_manager, "monitors-changed-internal", G_CALLBACK (input_mapper_monitors_changed_cb), mapper); mapper_update_outputs (mapper); } static void meta_input_mapper_class_init (MetaInputMapperClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = meta_input_mapper_constructed; object_class->finalize = meta_input_mapper_finalize; signals[DEVICE_MAPPED] = g_signal_new ("device-mapped", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, CLUTTER_TYPE_INPUT_DEVICE, G_TYPE_POINTER, G_TYPE_POINTER); } static void meta_input_mapper_init (MetaInputMapper *mapper) { mapper->input_devices = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) mapper_input_info_free); mapper->output_devices = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) mapper_output_info_free); } MetaInputMapper * meta_input_mapper_new (void) { return g_object_new (META_TYPE_INPUT_MAPPER, NULL); } void meta_input_mapper_add_device (MetaInputMapper *mapper, ClutterInputDevice *device, gboolean builtin) { MetaMapperInputInfo *info; g_return_if_fail (mapper != NULL); g_return_if_fail (device != NULL); if (g_hash_table_contains (mapper->input_devices, device)) return; info = mapper_input_info_new (device, mapper, builtin); g_hash_table_insert (mapper->input_devices, device, info); mapper_recalculate_input (mapper, info); } void meta_input_mapper_remove_device (MetaInputMapper *mapper, ClutterInputDevice *device) { MetaMapperInputInfo *input; g_return_if_fail (mapper != NULL); g_return_if_fail (device != NULL); input = g_hash_table_lookup (mapper->input_devices, device); if (input) { if (input->output) mapper_output_info_remove_input (input->output, input); g_hash_table_remove (mapper->input_devices, device); } } ClutterInputDevice * meta_input_mapper_get_logical_monitor_device (MetaInputMapper *mapper, MetaLogicalMonitor *logical_monitor, ClutterInputDeviceType device_type) { MetaMapperOutputInfo *output; GList *l; output = g_hash_table_lookup (mapper->output_devices, logical_monitor); if (!output) return NULL; for (l = output->input_devices; l; l = l->next) { MetaMapperInputInfo *input = l->data; if (clutter_input_device_get_device_type (input->device) == device_type) return input->device; } return NULL; } MetaLogicalMonitor * meta_input_mapper_get_device_logical_monitor (MetaInputMapper *mapper, ClutterInputDevice *device) { MetaMapperOutputInfo *output; MetaLogicalMonitor *logical_monitor; GHashTableIter iter; GList *l; g_hash_table_iter_init (&iter, mapper->output_devices); while (g_hash_table_iter_next (&iter, (gpointer *) &logical_monitor, (gpointer *) &output)) { for (l = output->input_devices; l; l = l->next) { MetaMapperInputInfo *input = l->data; if (input->device == device) return logical_monitor; } } return NULL; }