backends: Add MetaInputMapper

This object takes care of mapping absolute devices to monitors,
to do so it uses 3 heuristics, in this order of preference:
- If a device is known to be builtin, it's assigned to the
  builtin monitor.
- If input device and monitor match sizes (with an error margin
  of 5%)
- If input device name and monitor vendor/product in EDID match
  somehow (from "full", through "partial", to just "vendor")

The most favorable outputs are then assigned to each device, making
sure not to assign two devices of the same kind to the same output.

This object replaces (and is mostly 1:1 with) GsdDeviceMapper in
g-s-d. That object would perform these same heuristics, and let
mutter indirectly know through settings changes. This object allows
doing the same in-process.
This commit is contained in:
Carlos Garnacho 2018-04-20 16:34:32 +02:00 committed by Georges Basile Stavracas Neto
parent df48b94889
commit 08a5e660d3
4 changed files with 661 additions and 0 deletions

View File

@ -141,6 +141,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 \

View File

@ -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 */

View File

@ -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);
}
}

View File

@ -184,6 +184,8 @@ mutter_sources = [
'backends/meta-idle-monitor-dbus.c',
'backends/meta-idle-monitor-dbus.h',
'backends/meta-idle-monitor-private.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',