summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am2
-rw-r--r--src/backends/meta-input-mapper-private.h40
-rw-r--r--src/backends/meta-input-mapper.c617
3 files changed, 659 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 6522e27ad..65a021a30 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -136,6 +136,8 @@ libmutter_@LIBMUTTER_API_VERSION@_la_SOURCES = \
backends/meta-idle-monitor-private.h \
backends/meta-idle-monitor-dbus.c \
backends/meta-idle-monitor-dbus.h \
+ backends/meta-input-mapper.c \
+ backends/meta-input-mapper-private.h \
backends/meta-input-settings.c \
backends/meta-input-settings-private.h \
backends/meta-logical-monitor.c \
diff --git a/src/backends/meta-input-mapper-private.h b/src/backends/meta-input-mapper-private.h
new file mode 100644
index 000000000..53bc66b06
--- /dev/null
+++ b/src/backends/meta-input-mapper-private.h
@@ -0,0 +1,40 @@
+/* -*- 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Carlos Garnacho <carlosg@gnome.org>
+ */
+
+#ifndef META_INPUT_MAPPER_H
+#define META_INPUT_MAPPER_H
+
+#include <clutter/clutter.h>
+
+#define META_TYPE_INPUT_MAPPER (meta_input_mapper_get_type ())
+
+G_DECLARE_FINAL_TYPE (MetaInputMapper, meta_input_mapper,
+ META, INPUT_MAPPER, GObject)
+
+MetaInputMapper * meta_input_mapper_new (void);
+
+void meta_input_mapper_add_device (MetaInputMapper *mapper,
+ ClutterInputDevice *device,
+ gboolean builtin);
+void meta_input_mapper_remove_device (MetaInputMapper *mapper,
+ ClutterInputDevice *device);
+
+#endif /* META_INPUT_MAPPER_H */
diff --git a/src/backends/meta-input-mapper.c b/src/backends/meta-input-mapper.c
new file mode 100644
index 000000000..026dacb58
--- /dev/null
+++ b/src/backends/meta-input-mapper.c
@@ -0,0 +1,617 @@
+/* -*- 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Carlos Garnacho <carlosg@gnome.org>
+ */
+
+#include "config.h"
+
+#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;
+
+struct _MetaInputMapper
+{
+ GObject parent_instance;
+ MetaMonitorManager *monitor_manager;
+ ClutterDeviceManager *input_device_manager;
+ GHashTable *input_devices; /* ClutterInputDevice -> MetaMapperInputInfo */
+ GHashTable *output_devices; /* MetaLogicalMonitor -> MetaMapperOutputInfo */
+};
+
+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_IS_BUILTIN, /* Output is builtin, applies mainly to system-integrated devices */
+ META_MATCH_SIZE, /* Size from input device and output match */
+ META_MATCH_EDID_FULL, /* Full EDID model match, eg. "Cintiq 12WX" */
+ META_MATCH_EDID_PARTIAL, /* Partial EDID model match, eg. "Cintiq" */
+ META_MATCH_EDID_VENDOR, /* EDID vendor match, eg. "WAC" for Wacom */
+ 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 _DeviceCandidates
+{
+ MetaMapperInputInfo *input;
+
+ MetaMonitor *candidates[N_OUTPUT_MATCHES];
+
+ MetaOutputMatchType 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
+mapping_helper_init (MappingHelper *helper)
+{
+ helper->device_maps = g_array_new (FALSE, FALSE, sizeof (DeviceCandidates));
+}
+
+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
+ {
+ char **split;
+ int i = 0;
+
+ split = g_strsplit (meta_monitor_get_product (monitor), " ", -1);
+
+ while (split[i])
+ {
+ if (strcasestr (dev_name, split[i]) != NULL)
+ {
+ *match_type = META_MATCH_EDID_PARTIAL;
+ break;
+ }
+ }
+
+ g_strfreev (split);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+find_size_match (MetaMapperInputInfo *input,
+ GList *monitors,
+ MetaMonitor **matched_monitor)
+{
+ double min_w_diff, min_h_diff;
+ double i_width, i_height;
+ gboolean found = FALSE;
+ GList *l;
+
+ min_w_diff = min_h_diff = MAX_SIZE_MATCH_DIFF;
+
+ if (!clutter_input_device_get_physical_size (input->device, &i_width, &i_height))
+ return FALSE;
+
+ for (l = monitors; l; l = l->next)
+ {
+ MetaMonitor *monitor = l->data;
+ double w_diff, h_diff;
+ int o_width, o_height;
+
+ 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));
+
+ if (w_diff >= min_w_diff || h_diff >= min_h_diff)
+ continue;
+
+ *matched_monitor = monitor;
+ min_w_diff = w_diff;
+ min_h_diff = h_diff;
+ found = TRUE;
+ }
+
+ return found;
+}
+
+static gboolean
+find_builtin_output (MetaInputMapper *mapper,
+ MetaMonitor **matched_monitor)
+{
+ MetaMonitor *panel;
+
+ panel = meta_monitor_manager_get_laptop_panel (mapper->monitor_manager);
+ *matched_monitor = panel;
+ return panel != NULL;
+}
+
+static gboolean
+guess_candidates (MetaInputMapper *mapper,
+ MetaMapperInputInfo *input,
+ DeviceCandidates *info)
+{
+ MetaOutputMatchType best = N_OUTPUT_MATCHES;
+ GList *monitors, *l;
+ MetaMonitor *matched_monitor = NULL;
+
+ monitors = meta_monitor_manager_get_monitors (mapper->monitor_manager);
+
+ for (l = monitors; l; l = l->next)
+ {
+ MetaOutputMatchType edid_match;
+
+ if (match_edid (input, l->data, &edid_match))
+ {
+ best = MIN (best, edid_match);
+ info->candidates[edid_match] = l->data;
+ }
+ }
+
+ if (find_size_match (input, monitors, &matched_monitor))
+ {
+ best = MIN (best, META_MATCH_SIZE);
+ info->candidates[META_MATCH_SIZE] = matched_monitor;
+ }
+
+ if (input->builtin)
+ {
+ best = MIN (best, META_MATCH_IS_BUILTIN);
+ find_builtin_output (mapper, &info->candidates[META_MATCH_IS_BUILTIN]);
+ }
+
+ if (best < N_OUTPUT_MATCHES)
+ {
+ info->best = best;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static void
+mapping_helper_add (MappingHelper *helper,
+ MetaMapperInputInfo *input,
+ MetaInputMapper *mapper)
+{
+ DeviceCandidates info = { 0, };
+ guint i, pos = 0;
+
+ info.input = input;
+
+ if (!guess_candidates (mapper, input, &info))
+ return;
+
+ 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;
+
+ /* Now, decide which input claims which output */
+ for (i = 0; i < helper->device_maps->len; i++)
+ {
+ MetaMapperOutputInfo *output;
+ DeviceCandidates *info;
+ MetaOutputMatchType j;
+
+ info = &g_array_index (helper->device_maps, DeviceCandidates, i);
+
+ for (j = 0; j < N_OUTPUT_MATCHES; j++)
+ {
+ MetaLogicalMonitor *logical_monitor;
+
+ if (!info->candidates[j])
+ continue;
+
+ logical_monitor =
+ meta_monitor_get_logical_monitor (info->candidates[j]);
+ 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,
+ info->candidates[j]);
+ 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 (ClutterDeviceManager *device_manager,
+ 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->input_device_manager,
+ input_mapper_device_removed_cb,
+ mapper);
+
+ g_hash_table_unref (mapper->input_devices);
+ g_hash_table_unref (mapper->output_devices);
+
+ G_OBJECT_CLASS (meta_input_mapper_parent_class)->finalize (object);
+}
+
+static void
+meta_input_mapper_constructed (GObject *object)
+{
+ MetaInputMapper *mapper = META_INPUT_MAPPER (object);
+ MetaBackend *backend;
+
+ G_OBJECT_CLASS (meta_input_mapper_parent_class)->constructed (object);
+
+ mapper->input_device_manager = clutter_device_manager_get_default ();
+ g_signal_connect (mapper->input_device_manager, "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);
+ }
+}