diff --git a/configure.ac b/configure.ac
index 0e5d5f9..bfbecd8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -253,10 +253,12 @@ case $host_os in
have_wacom=no
else
if test x$enable_gudev != xno; then
+ PKG_CHECK_MODULES(LIBWACOM, [libwacom >= $LIBWACOM_REQUIRED_VERSION])
PKG_CHECK_MODULES(WACOM, [libwacom >= $LIBWACOM_REQUIRED_VERSION x11 xi xtst gudev-1.0 gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION xorg-wacom librsvg-2.0 >= $LIBRSVG_REQUIRED_VERSION])
else
AC_MSG_ERROR([GUdev is necessary to compile Wacom support])
fi
+ AC_DEFINE_UNQUOTED(HAVE_WACOM, 1, [Define to 1 if wacom support is available])
have_wacom=yes
fi
;;
diff --git a/plugins/common/Makefile.am b/plugins/common/Makefile.am
index b0e907c..3b84b21 100644
--- a/plugins/common/Makefile.am
+++ b/plugins/common/Makefile.am
@@ -3,25 +3,35 @@ plugin_name = common
noinst_LTLIBRARIES = libcommon.la
libcommon_la_SOURCES = \
+ edid.h \
+ edid-parse.c \
+ gsd-device-mapper.c \
+ gsd-device-mapper.h \
gsd-keygrab.c \
gsd-keygrab.h \
gsd-input-helper.c \
gsd-input-helper.h
libcommon_la_CPPFLAGS = \
+ -I$(top_srcdir)/data/ \
$(AM_CPPFLAGS)
libcommon_la_CFLAGS = \
$(PLUGIN_CFLAGS) \
+ $(GNOME_DESKTOP_CFLAGS) \
$(SETTINGS_PLUGIN_CFLAGS) \
$(COMMON_CFLAGS) \
+ $(LIBWACOM_CFLAGS) \
$(AM_CFLAGS)
libcommon_la_LDFLAGS = \
$(GSD_PLUGIN_LDFLAGS)
libcommon_la_LIBADD = \
+ -lm \
$(SETTINGS_PLUGIN_LIBS) \
+ $(GNOME_DESKTOP_LIBS) \
+ $(LIBWACOM_LIBS) \
$(COMMON_LIBS)
libexec_PROGRAMS = gsd-test-input-helper
diff --git a/plugins/common/edid-parse.c b/plugins/common/edid-parse.c
new file mode 100644
index 0000000..5b3283a
--- /dev/null
+++ b/plugins/common/edid-parse.c
@@ -0,0 +1,539 @@
+/*
+ * Copyright 2007 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* Author: Soren Sandmann <sandmann@redhat.com> */
+
+#include "edid.h"
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <glib.h>
+
+static int
+get_bit (int in, int bit)
+{
+ return (in & (1 << bit)) >> bit;
+}
+
+static int
+get_bits (int in, int begin, int end)
+{
+ int mask = (1 << (end - begin + 1)) - 1;
+
+ return (in >> begin) & mask;
+}
+
+static int
+decode_header (const uchar *edid)
+{
+ if (memcmp (edid, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static int
+decode_vendor_and_product_identification (const uchar *edid, MonitorInfo *info)
+{
+ int is_model_year;
+
+ /* Manufacturer Code */
+ info->manufacturer_code[0] = get_bits (edid[0x08], 2, 6);
+ info->manufacturer_code[1] = get_bits (edid[0x08], 0, 1) << 3;
+ info->manufacturer_code[1] |= get_bits (edid[0x09], 5, 7);
+ info->manufacturer_code[2] = get_bits (edid[0x09], 0, 4);
+ info->manufacturer_code[3] = '\0';
+
+ info->manufacturer_code[0] += 'A' - 1;
+ info->manufacturer_code[1] += 'A' - 1;
+ info->manufacturer_code[2] += 'A' - 1;
+
+ /* Product Code */
+ info->product_code = edid[0x0b] << 8 | edid[0x0a];
+
+ /* Serial Number */
+ info->serial_number =
+ edid[0x0c] | edid[0x0d] << 8 | edid[0x0e] << 16 | edid[0x0f] << 24;
+
+ /* Week and Year */
+ is_model_year = FALSE;
+ switch (edid[0x10])
+ {
+ case 0x00:
+ info->production_week = -1;
+ break;
+
+ case 0xff:
+ info->production_week = -1;
+ is_model_year = TRUE;
+ break;
+
+ default:
+ info->production_week = edid[0x10];
+ break;
+ }
+
+ if (is_model_year)
+ {
+ info->production_year = -1;
+ info->model_year = 1990 + edid[0x11];
+ }
+ else
+ {
+ info->production_year = 1990 + edid[0x11];
+ info->model_year = -1;
+ }
+
+ return TRUE;
+}
+
+static int
+decode_edid_version (const uchar *edid, MonitorInfo *info)
+{
+ info->major_version = edid[0x12];
+ info->minor_version = edid[0x13];
+
+ return TRUE;
+}
+
+static int
+decode_display_parameters (const uchar *edid, MonitorInfo *info)
+{
+ /* Digital vs Analog */
+ info->is_digital = get_bit (edid[0x14], 7);
+
+ if (info->is_digital)
+ {
+ int bits;
+
+ static const int bit_depth[8] =
+ {
+ -1, 6, 8, 10, 12, 14, 16, -1
+ };
+
+ static const Interface interfaces[6] =
+ {
+ UNDEFINED, DVI, HDMI_A, HDMI_B, MDDI, DISPLAY_PORT
+ };
+
+ bits = get_bits (edid[0x14], 4, 6);
+ info->connector.digital.bits_per_primary = bit_depth[bits];
+
+ bits = get_bits (edid[0x14], 0, 3);
+
+ if (bits <= 5)
+ info->connector.digital.interface = interfaces[bits];
+ else
+ info->connector.digital.interface = UNDEFINED;
+ }
+ else
+ {
+ int bits = get_bits (edid[0x14], 5, 6);
+
+ static const double levels[][3] =
+ {
+ { 0.7, 0.3, 1.0 },
+ { 0.714, 0.286, 1.0 },
+ { 1.0, 0.4, 1.4 },
+ { 0.7, 0.0, 0.7 },
+ };
+
+ info->connector.analog.video_signal_level = levels[bits][0];
+ info->connector.analog.sync_signal_level = levels[bits][1];
+ info->connector.analog.total_signal_level = levels[bits][2];
+
+ info->connector.analog.blank_to_black = get_bit (edid[0x14], 4);
+
+ info->connector.analog.separate_hv_sync = get_bit (edid[0x14], 3);
+ info->connector.analog.composite_sync_on_h = get_bit (edid[0x14], 2);
+ info->connector.analog.composite_sync_on_green = get_bit (edid[0x14], 1);
+
+ info->connector.analog.serration_on_vsync = get_bit (edid[0x14], 0);
+ }
+
+ /* Screen Size / Aspect Ratio */
+ if (edid[0x15] == 0 && edid[0x16] == 0)
+ {
+ info->width_mm = -1;
+ info->height_mm = -1;
+ info->aspect_ratio = -1.0;
+ }
+ else if (edid[0x16] == 0)
+ {
+ info->width_mm = -1;
+ info->height_mm = -1;
+ info->aspect_ratio = 100.0 / (edid[0x15] + 99);
+ }
+ else if (edid[0x15] == 0)
+ {
+ info->width_mm = -1;
+ info->height_mm = -1;
+ info->aspect_ratio = 100.0 / (edid[0x16] + 99);
+ info->aspect_ratio = 1/info->aspect_ratio; /* portrait */
+ }
+ else
+ {
+ info->width_mm = 10 * edid[0x15];
+ info->height_mm = 10 * edid[0x16];
+ }
+
+ /* Gamma */
+ if (edid[0x17] == 0xFF)
+ info->gamma = -1.0;
+ else
+ info->gamma = (edid[0x17] + 100.0) / 100.0;
+
+ /* Features */
+ info->standby = get_bit (edid[0x18], 7);
+ info->suspend = get_bit (edid[0x18], 6);
+ info->active_off = get_bit (edid[0x18], 5);
+
+ if (info->is_digital)
+ {
+ info->connector.digital.rgb444 = TRUE;
+ if (get_bit (edid[0x18], 3))
+ info->connector.digital.ycrcb444 = 1;
+ if (get_bit (edid[0x18], 4))
+ info->connector.digital.ycrcb422 = 1;
+ }
+ else
+ {
+ int bits = get_bits (edid[0x18], 3, 4);
+ ColorType color_type[4] =
+ {
+ MONOCHROME, RGB, OTHER_COLOR, UNDEFINED_COLOR
+ };
+
+ info->connector.analog.color_type = color_type[bits];
+ }
+
+ info->srgb_is_standard = get_bit (edid[0x18], 2);
+
+ /* In 1.3 this is called "has preferred timing" */
+ info->preferred_timing_includes_native = get_bit (edid[0x18], 1);
+
+ /* FIXME: In 1.3 this indicates whether the monitor accepts GTF */
+ info->continuous_frequency = get_bit (edid[0x18], 0);
+ return TRUE;
+}
+
+static double
+decode_fraction (int high, int low)
+{
+ double result = 0.0;
+ int i;
+
+ high = (high << 2) | low;
+
+ for (i = 0; i < 10; ++i)
+ result += get_bit (high, i) * pow (2, i - 10);
+
+ return result;
+}
+
+static int
+decode_color_characteristics (const uchar *edid, MonitorInfo *info)
+{
+ info->red_x = decode_fraction (edid[0x1b], get_bits (edid[0x19], 6, 7));
+ info->red_y = decode_fraction (edid[0x1c], get_bits (edid[0x19], 5, 4));
+ info->green_x = decode_fraction (edid[0x1d], get_bits (edid[0x19], 2, 3));
+ info->green_y = decode_fraction (edid[0x1e], get_bits (edid[0x19], 0, 1));
+ info->blue_x = decode_fraction (edid[0x1f], get_bits (edid[0x1a], 6, 7));
+ info->blue_y = decode_fraction (edid[0x20], get_bits (edid[0x1a], 4, 5));
+ info->white_x = decode_fraction (edid[0x21], get_bits (edid[0x1a], 2, 3));
+ info->white_y = decode_fraction (edid[0x22], get_bits (edid[0x1a], 0, 1));
+
+ return TRUE;
+}
+
+static int
+decode_established_timings (const uchar *edid, MonitorInfo *info)
+{
+ static const Timing established[][8] =
+ {
+ {
+ { 800, 600, 60 },
+ { 800, 600, 56 },
+ { 640, 480, 75 },
+ { 640, 480, 72 },
+ { 640, 480, 67 },
+ { 640, 480, 60 },
+ { 720, 400, 88 },
+ { 720, 400, 70 }
+ },
+ {
+ { 1280, 1024, 75 },
+ { 1024, 768, 75 },
+ { 1024, 768, 70 },
+ { 1024, 768, 60 },
+ { 1024, 768, 87 },
+ { 832, 624, 75 },
+ { 800, 600, 75 },
+ { 800, 600, 72 }
+ },
+ {
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { 1152, 870, 75 }
+ },
+ };
+
+ int i, j, idx;
+
+ idx = 0;
+ for (i = 0; i < 3; ++i)
+ {
+ for (j = 0; j < 8; ++j)
+ {
+ int byte = edid[0x23 + i];
+
+ if (get_bit (byte, j) && established[i][j].frequency != 0)
+ info->established[idx++] = established[i][j];
+ }
+ }
+ return TRUE;
+}
+
+static int
+decode_standard_timings (const uchar *edid, MonitorInfo *info)
+{
+ int i;
+
+ for (i = 0; i < 8; i++)
+ {
+ int first = edid[0x26 + 2 * i];
+ int second = edid[0x27 + 2 * i];
+
+ if (first != 0x01 && second != 0x01)
+ {
+ int w = 8 * (first + 31);
+ int h = 0;
+
+ switch (get_bits (second, 6, 7))
+ {
+ case 0x00: h = (w / 16) * 10; break;
+ case 0x01: h = (w / 4) * 3; break;
+ case 0x02: h = (w / 5) * 4; break;
+ case 0x03: h = (w / 16) * 9; break;
+ }
+
+ info->standard[i].width = w;
+ info->standard[i].height = h;
+ info->standard[i].frequency = get_bits (second, 0, 5) + 60;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+decode_lf_string (const uchar *s, int n_chars, char *result)
+{
+ int i;
+ for (i = 0; i < n_chars; ++i)
+ {
+ if (s[i] == 0x0a)
+ {
+ *result++ = '\0';
+ break;
+ }
+ else if (s[i] == 0x00)
+ {
+ /* Convert embedded 0's to spaces */
+ *result++ = ' ';
+ }
+ else
+ {
+ *result++ = s[i];
+ }
+ }
+}
+
+static void
+decode_display_descriptor (const uchar *desc,
+ MonitorInfo *info)
+{
+ switch (desc[0x03])
+ {
+ case 0xFC:
+ decode_lf_string (desc + 5, 13, info->dsc_product_name);
+ break;
+ case 0xFF:
+ decode_lf_string (desc + 5, 13, info->dsc_serial_number);
+ break;
+ case 0xFE:
+ decode_lf_string (desc + 5, 13, info->dsc_string);
+ break;
+ case 0xFD:
+ /* Range Limits */
+ break;
+ case 0xFB:
+ /* Color Point */
+ break;
+ case 0xFA:
+ /* Timing Identifications */
+ break;
+ case 0xF9:
+ /* Color Management */
+ break;
+ case 0xF8:
+ /* Timing Codes */
+ break;
+ case 0xF7:
+ /* Established Timings */
+ break;
+ case 0x10:
+ break;
+ }
+}
+
+static void
+decode_detailed_timing (const uchar *timing,
+ DetailedTiming *detailed)
+{
+ int bits;
+ StereoType stereo[] =
+ {
+ NO_STEREO, NO_STEREO, FIELD_RIGHT, FIELD_LEFT,
+ TWO_WAY_RIGHT_ON_EVEN, TWO_WAY_LEFT_ON_EVEN,
+ FOUR_WAY_INTERLEAVED, SIDE_BY_SIDE
+ };
+
+ detailed->pixel_clock = (timing[0x00] | timing[0x01] << 8) * 10000;
+ detailed->h_addr = timing[0x02] | ((timing[0x04] & 0xf0) << 4);
+ detailed->h_blank = timing[0x03] | ((timing[0x04] & 0x0f) << 8);
+ detailed->v_addr = timing[0x05] | ((timing[0x07] & 0xf0) << 4);
+ detailed->v_blank = timing[0x06] | ((timing[0x07] & 0x0f) << 8);
+ detailed->h_front_porch = timing[0x08] | get_bits (timing[0x0b], 6, 7) << 8;
+ detailed->h_sync = timing[0x09] | get_bits (timing[0x0b], 4, 5) << 8;
+ detailed->v_front_porch =
+ get_bits (timing[0x0a], 4, 7) | get_bits (timing[0x0b], 2, 3) << 4;
+ detailed->v_sync =
+ get_bits (timing[0x0a], 0, 3) | get_bits (timing[0x0b], 0, 1) << 4;
+ detailed->width_mm = timing[0x0c] | get_bits (timing[0x0e], 4, 7) << 8;
+ detailed->height_mm = timing[0x0d] | get_bits (timing[0x0e], 0, 3) << 8;
+ detailed->right_border = timing[0x0f];
+ detailed->top_border = timing[0x10];
+
+ detailed->interlaced = get_bit (timing[0x11], 7);
+
+ /* Stereo */
+ bits = get_bits (timing[0x11], 5, 6) << 1 | get_bit (timing[0x11], 0);
+ detailed->stereo = stereo[bits];
+
+ /* Sync */
+ bits = timing[0x11];
+
+ detailed->digital_sync = get_bit (bits, 4);
+ if (detailed->digital_sync)
+ {
+ detailed->connector.digital.composite = !get_bit (bits, 3);
+
+ if (detailed->connector.digital.composite)
+ {
+ detailed->connector.digital.serrations = get_bit (bits, 2);
+ detailed->connector.digital.negative_vsync = FALSE;
+ }
+ else
+ {
+ detailed->connector.digital.serrations = FALSE;
+ detailed->connector.digital.negative_vsync = !get_bit (bits, 2);
+ }
+
+ detailed->connector.digital.negative_hsync = !get_bit (bits, 0);
+ }
+ else
+ {
+ detailed->connector.analog.bipolar = get_bit (bits, 3);
+ detailed->connector.analog.serrations = get_bit (bits, 2);
+ detailed->connector.analog.sync_on_green = !get_bit (bits, 1);
+ }
+}
+
+static int
+decode_descriptors (const uchar *edid, MonitorInfo *info)
+{
+ int i;
+ int timing_idx;
+
+ timing_idx = 0;
+
+ for (i = 0; i < 4; ++i)
+ {
+ int index = 0x36 + i * 18;
+
+ if (edid[index + 0] == 0x00 && edid[index + 1] == 0x00)
+ {
+ decode_display_descriptor (edid + index, info);
+ }
+ else
+ {
+ decode_detailed_timing (edid + index, &(info->detailed_timings[timing_idx++]));
+ }
+ }
+
+ info->n_detailed_timings = timing_idx;
+
+ return TRUE;
+}
+
+static void
+decode_check_sum (const uchar *edid,
+ MonitorInfo *info)
+{
+ int i;
+ uchar check = 0;
+
+ for (i = 0; i < 128; ++i)
+ check += edid[i];
+
+ info->checksum = check;
+}
+
+MonitorInfo *
+decode_edid (const uchar *edid)
+{
+ MonitorInfo *info = g_new0 (MonitorInfo, 1);
+
+ decode_check_sum (edid, info);
+
+ if (decode_header (edid)
+ && decode_vendor_and_product_identification (edid, info)
+ && decode_edid_version (edid, info)
+ && decode_display_parameters (edid, info)
+ && decode_color_characteristics (edid, info)
+ && decode_established_timings (edid, info)
+ && decode_standard_timings (edid, info)
+ && decode_descriptors (edid, info))
+ {
+ return info;
+ }
+ else
+ {
+ g_free (info);
+ return NULL;
+ }
+}
diff --git a/plugins/common/edid.h b/plugins/common/edid.h
new file mode 100644
index 0000000..703c639
--- /dev/null
+++ b/plugins/common/edid.h
@@ -0,0 +1,195 @@
+/* edid.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Soren Sandmann <sandmann@redhat.com>
+ */
+
+#ifndef EDID_H
+#define EDID_H
+
+typedef unsigned char uchar;
+typedef struct MonitorInfo MonitorInfo;
+typedef struct Timing Timing;
+typedef struct DetailedTiming DetailedTiming;
+
+typedef enum
+{
+ UNDEFINED,
+ DVI,
+ HDMI_A,
+ HDMI_B,
+ MDDI,
+ DISPLAY_PORT
+} Interface;
+
+typedef enum
+{
+ UNDEFINED_COLOR,
+ MONOCHROME,
+ RGB,
+ OTHER_COLOR
+} ColorType;
+
+typedef enum
+{
+ NO_STEREO,
+ FIELD_RIGHT,
+ FIELD_LEFT,
+ TWO_WAY_RIGHT_ON_EVEN,
+ TWO_WAY_LEFT_ON_EVEN,
+ FOUR_WAY_INTERLEAVED,
+ SIDE_BY_SIDE
+} StereoType;
+
+struct Timing
+{
+ int width;
+ int height;
+ int frequency;
+};
+
+struct DetailedTiming
+{
+ int pixel_clock;
+ int h_addr;
+ int h_blank;
+ int h_sync;
+ int h_front_porch;
+ int v_addr;
+ int v_blank;
+ int v_sync;
+ int v_front_porch;
+ int width_mm;
+ int height_mm;
+ int right_border;
+ int top_border;
+ int interlaced;
+ StereoType stereo;
+
+ int digital_sync;
+ union
+ {
+ struct
+ {
+ int bipolar;
+ int serrations;
+ int sync_on_green;
+ } analog;
+
+ struct
+ {
+ int composite;
+ int serrations;
+ int negative_vsync;
+ int negative_hsync;
+ } digital;
+ } connector;
+};
+
+struct MonitorInfo
+{
+ int checksum;
+ char manufacturer_code[4];
+ int product_code;
+ unsigned int serial_number;
+
+ int production_week; /* -1 if not specified */
+ int production_year; /* -1 if not specified */
+ int model_year; /* -1 if not specified */
+
+ int major_version;
+ int minor_version;
+
+ int is_digital;
+
+ union
+ {
+ struct
+ {
+ int bits_per_primary;
+ Interface interface;
+ int rgb444;
+ int ycrcb444;
+ int ycrcb422;
+ } digital;
+
+ struct
+ {
+ double video_signal_level;
+ double sync_signal_level;
+ double total_signal_level;
+
+ int blank_to_black;
+
+ int separate_hv_sync;
+ int composite_sync_on_h;
+ int composite_sync_on_green;
+ int serration_on_vsync;
+ ColorType color_type;
+ } analog;
+ } connector;
+
+ int width_mm; /* -1 if not specified */
+ int height_mm; /* -1 if not specified */
+ double aspect_ratio; /* -1.0 if not specififed */
+
+ double gamma; /* -1.0 if not specified */
+
+ int standby;
+ int suspend;
+ int active_off;
+
+ int srgb_is_standard;
+ int preferred_timing_includes_native;
+ int continuous_frequency;
+
+ double red_x;
+ double red_y;
+ double green_x;
+ double green_y;
+ double blue_x;
+ double blue_y;
+ double white_x;
+ double white_y;
+
+ Timing established[24]; /* Terminated by 0x0x0 */
+ Timing standard[8];
+
+ int n_detailed_timings;
+ DetailedTiming detailed_timings[4]; /* If monitor has a preferred
+ * mode, it is the first one
+ * (whether it has, is
+ * determined by the
+ * preferred_timing_includes
+ * bit.
+ */
+
+ /* Optional product description */
+ char dsc_serial_number[14];
+ char dsc_product_name[14];
+ char dsc_string[14]; /* Unspecified ASCII data */
+};
+
+MonitorInfo *decode_edid (const uchar *data);
+char *make_display_name (const MonitorInfo *info);
+char *make_display_size_string (int width_mm, int height_mm);
+
+#endif
diff --git a/plugins/common/gsd-device-mapper.c b/plugins/common/gsd-device-mapper.c
new file mode 100644
index 0000000..d8ea62a
--- /dev/null
+++ b/plugins/common/gsd-device-mapper.c
@@ -0,0 +1,1293 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Carlos Garnacho <carlosg@gnome.org>
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gtk/gtkx.h>
+#include <X11/Xatom.h>
+
+#if HAVE_WACOM
+#include <libwacom/libwacom.h>
+#endif
+
+#include "gsd-device-mapper.h"
+#include "gsd-input-helper.h"
+#include "gsd-enums.h"
+#include "edid.h"
+
+typedef struct _GsdInputInfo GsdInputInfo;
+typedef struct _GsdOutputInfo GsdOutputInfo;
+typedef struct _MappingHelper MappingHelper;
+typedef struct _DeviceMapHelper DeviceMapHelper;
+
+#define NUM_ELEMS_MATRIX 9
+#define KEY_DISPLAY "display"
+#define KEY_ROTATION "rotation"
+
+typedef enum {
+ GSD_INPUT_IS_SYSTEM_INTEGRATED = 1 << 0, /* eg. laptop tablets/touchscreens */
+ GSD_INPUT_IS_SCREEN_INTEGRATED = 1 << 1, /* eg. Wacom Cintiq devices */
+ GSD_INPUT_IS_TOUCH = 1 << 2, /* touch device, either touchscreen or tablet */
+ GSD_INPUT_IS_PEN = 1 << 3, /* pen device, either touchscreen or tablet */
+ GSD_INPUT_IS_ERASER = 1 << 4, /* eraser device, either touchscreen or tablet */
+ GSD_INPUT_IS_PAD = 1 << 5 /* pad device, most usually in tablets */
+} GsdInputCapabilityFlags;
+
+typedef enum {
+ GSD_PRIO_BUILTIN, /* Output is builtin, applies mainly to system-integrated devices */
+ GSD_PRIO_EDID_MATCH_FULL, /* Full EDID model match, eg. "Cintiq 12WX" */
+ GSD_PRIO_EDID_MATCH_PARTIAL, /* Partial EDID model match, eg. "Cintiq" */
+ GSD_PRIO_EDID_MATCH_VENDOR, /* EDID vendor match, eg. "WAC" for Wacom */
+ N_OUTPUT_PRIORITIES
+} GsdOutputPriority;
+
+struct _GsdInputInfo {
+ GdkDevice *device;
+ GSettings *settings;
+ GsdDeviceMapper *mapper;
+ GsdOutputInfo *output;
+ GsdOutputInfo *guessed_output;
+ guint changed_id;
+ GsdInputCapabilityFlags capabilities;
+};
+
+struct _GsdOutputInfo {
+ GnomeRROutput *output;
+ GList *input_devices;
+};
+
+struct _DeviceMapHelper {
+ GsdInputInfo *input;
+ GnomeRROutput *candidates[N_OUTPUT_PRIORITIES];
+ GsdOutputPriority highest_prio;
+ guint n_candidates;
+};
+
+struct _MappingHelper {
+ GArray *device_maps;
+};
+
+struct _GsdDeviceMapper {
+ GObject parent_instance;
+ GdkScreen *screen;
+ GnomeRRScreen *rr_screen;
+ GHashTable *input_devices; /* GdkDevice -> GsdInputInfo */
+ GHashTable *output_devices; /* GnomeRROutput -> GsdOutputInfo */
+#if HAVE_WACOM
+ WacomDeviceDatabase *wacom_db;
+#endif
+};
+
+struct _GsdDeviceMapperClass {
+ GObjectClass parent_class;
+};
+
+/* Array order matches GsdWacomRotation */
+struct {
+ GnomeRRRotation rotation;
+ /* Coordinate Transformation Matrix */
+ gfloat matrix[NUM_ELEMS_MATRIX];
+} rotation_matrices[] = {
+ { GNOME_RR_ROTATION_0, { 1, 0, 0, 0, 1, 0, 0, 0, 1 } },
+ { GNOME_RR_ROTATION_90, { 0, -1, 1, 1, 0, 0, 0, 0, 1 } },
+ { GNOME_RR_ROTATION_270, { 0, 1, 0, -1, 0, 1, 0, 0, 1 } },
+ { GNOME_RR_ROTATION_180, { -1, 0, 1, 0, -1, 1, 0, 0, 1 } }
+};
+
+enum {
+ PROP_0,
+ PROP_SCREEN
+};
+
+enum {
+ DEVICE_CHANGED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+static void gsd_device_mapper_initable_iface_init (GInitableIface *iface);
+static GsdOutputInfo * output_info_new (GnomeRROutput *output);
+
+G_DEFINE_TYPE_WITH_CODE (GsdDeviceMapper, gsd_device_mapper, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ gsd_device_mapper_initable_iface_init))
+
+static XDevice *
+open_device (GdkDevice *device)
+{
+ XDevice *xdev;
+ int id;
+
+ id = gdk_x11_device_get_id (device);
+
+ gdk_error_trap_push ();
+ xdev = XOpenDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), id);
+ if (gdk_error_trap_pop () || (xdev == NULL))
+ return NULL;
+
+ return xdev;
+}
+
+static gboolean
+device_apply_property (GdkDevice *device,
+ PropertyHelper *property)
+{
+ gboolean retval;
+ XDevice *xdev;
+
+ xdev = open_device (device);
+
+ if (!xdev)
+ return FALSE;
+
+ retval = device_set_property (xdev, gdk_device_get_name (device), property);
+ XCloseDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdev);
+ return retval;
+}
+
+static gboolean
+device_set_matrix (GdkDevice *device,
+ gfloat matrix[NUM_ELEMS_MATRIX])
+{
+ PropertyHelper property = {
+ .name = "Coordinate Transformation Matrix",
+ .nitems = 9,
+ .format = 32,
+ .type = gdk_x11_get_xatom_by_name ("FLOAT"),
+ .data.i = (int *) matrix,
+ };
+
+ g_debug ("Setting '%s' matrix to:\n %f,%f,%f,\n %f,%f,%f,\n %f,%f,%f",
+ gdk_device_get_name (device),
+ matrix[0], matrix[1], matrix[2],
+ matrix[3], matrix[4], matrix[5],
+ matrix[6], matrix[7], matrix[8]);
+
+ return device_apply_property (device, &property);
+}
+
+static void
+get_edid (GnomeRROutput *output,
+ gchar **vendor,
+ gchar **product,
+ gchar **serial)
+{
+ const guchar *edid_data;
+ MonitorInfo *info;
+
+ if (vendor)
+ *vendor = NULL;
+ if (product)
+ *product = NULL;
+ if (serial)
+ *serial = NULL;
+
+ edid_data = gnome_rr_output_get_edid_data (output, NULL);
+
+ if (!edid_data)
+ return;
+
+ info = decode_edid (edid_data);
+
+ if (vendor)
+ *vendor = g_strndup (info->manufacturer_code, 4);
+ if (product) {
+ if (info->dsc_product_name[0])
+ *product = g_strndup (info->dsc_product_name, 14);
+ else
+ *product = g_strdup_printf ("0x%04x", (unsigned) info->product_code);
+ }
+
+ if (serial) {
+ if (info->dsc_serial_number[0])
+ *serial = g_strndup (info->dsc_serial_number, 14);
+ else
+ *serial = g_strdup_printf ("0x%04x", (unsigned) info->serial_number);
+ }
+
+ g_free (info);
+}
+
+/* Finds an output which matches the given EDID information. Any NULL
+ * parameter will be interpreted to match any value. */
+static GnomeRROutput *
+find_output_by_edid (GnomeRRScreen *rr_screen,
+ const gchar *edid[3])
+{
+ GnomeRROutput **outputs;
+ GnomeRROutput *retval = NULL;
+ guint i;
+
+ outputs = gnome_rr_screen_list_outputs (rr_screen);
+
+ for (i = 0; outputs[i] != NULL; i++) {
+ gchar *vendor, *product, *serial;
+ gboolean match;
+
+ get_edid (outputs[i], &vendor, &product, &serial);
+
+ match = (edid[0] == NULL || g_strcmp0 (edid[0], vendor) == 0) && \
+ (edid[1] == NULL || g_strcmp0 (edid[1], product) == 0) && \
+ (edid[2] == NULL || g_strcmp0 (edid[2], serial) == 0);
+
+ g_debug ("Checking match between ['%s','%s','%s'] and ['%s','%s','%s']",
+ edid[0], edid[1], edid[2], vendor, product, serial);
+
+ g_free (vendor);
+ g_free (product);
+ g_free (serial);
+
+ if (match) {
+ g_debug ("Found a match");
+ retval = outputs[i];
+ break;
+ }
+ }
+
+ if (retval == NULL)
+ g_debug ("Did not find a matching output for EDID '%s,%s,%s'",
+ edid[0], edid[1], edid[2]);
+ return retval;
+}
+
+static GnomeRROutput *
+find_builtin_output (GnomeRRScreen *rr_screen)
+{
+ GnomeRROutput **outputs;
+ guint i;
+
+ outputs = gnome_rr_screen_list_outputs (rr_screen);
+
+ for (i = 0; outputs[i] != NULL; i++) {
+ if (!gnome_rr_output_is_laptop (outputs[i]))
+ continue;
+
+ return outputs[i];
+ }
+
+ g_debug ("Did not find a built-in monitor");
+ return NULL;
+}
+
+static GnomeRROutput *
+monitor_to_output (GsdDeviceMapper *mapper,
+ gint monitor_num)
+{
+ GnomeRROutput **outputs;
+ guint i;
+
+ outputs = gnome_rr_screen_list_outputs (mapper->rr_screen);
+
+ for (i = 0; outputs[i] != NULL; i++) {
+ GnomeRRCrtc *crtc = gnome_rr_output_get_crtc (outputs[i]);
+ gint x, y;
+
+ if (!crtc)
+ continue;
+
+ gnome_rr_crtc_get_position (crtc, &x, &y);
+
+ if (monitor_num == gdk_screen_get_monitor_at_point (mapper->screen, x, y))
+ return outputs[i];
+ }
+
+ return NULL;
+}
+
+static MappingHelper *
+mapping_helper_new (void)
+{
+ MappingHelper *helper;
+
+ helper = g_new0 (MappingHelper, 1);
+ helper->device_maps = g_array_new (FALSE, FALSE, sizeof (DeviceMapHelper));
+
+ return helper;
+}
+
+static void
+mapping_helper_free (MappingHelper *helper)
+{
+ g_array_unref (helper->device_maps);
+ g_free (helper);
+}
+
+static void
+mapping_helper_add (MappingHelper *helper,
+ GsdInputInfo *input,
+ GnomeRROutput *outputs[N_OUTPUT_PRIORITIES])
+{
+ guint i, pos, highest = N_OUTPUT_PRIORITIES;
+ DeviceMapHelper info = { 0 }, *prev;
+
+ info.input = input;
+
+ for (i = 0; i < N_OUTPUT_PRIORITIES; i++) {
+ if (outputs[i] == NULL)
+ continue;
+
+ if (highest > i)
+ highest = i;
+
+ info.candidates[i] = outputs[i];
+ info.n_candidates++;
+ }
+
+ info.highest_prio = highest;
+ pos = helper->device_maps->len;
+
+ for (i = 0; i < helper->device_maps->len; i++) {
+ prev = &g_array_index (helper->device_maps, DeviceMapHelper, i);
+
+ if (prev->highest_prio < info.highest_prio)
+ 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);
+}
+
+/* This function gets a map of outputs, sorted by confidence, for a given device,
+ * the array can actually contain NULLs if no output matched a priority. */
+static void
+input_info_guess_candidates (GsdInputInfo *input,
+ GnomeRROutput *outputs[N_OUTPUT_PRIORITIES])
+{
+ gboolean found = FALSE;
+ const gchar *name;
+ gchar **split;
+
+ name = gdk_device_get_name (input->device);
+ split = g_strsplit (name, " ", -1);
+
+ /* On Wacom devices that are integrated on a not-in-system screen (eg. Cintiqs),
+ * there is usually a minimal relation between the input device name and the EDID
+ * vendor/model fields. Attempt to find matching outputs and fill in the map
+ * from GSD_PRIO_EDID_MATCH_FULL to GSD_PRIO_EDID_MATCH_VENDOR.
+ */
+ if (input->capabilities & GSD_INPUT_IS_SCREEN_INTEGRATED &&
+ g_str_has_prefix (name, "Wacom ")) {
+ gchar *product = g_strdup_printf ("%s %s", split[1], split[2]);
+ const gchar *edids[][3] = {
+ { "WAC", product, NULL },
+ { "WAC", split[1], NULL },
+ { "WAC", NULL, NULL }
+ };
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (edids); i++) {
+ /* i + 1 matches the desired priority, we skip GSD_PRIO_BUILTIN here */
+ outputs[i + 1] =
+ find_output_by_edid (input->mapper->rr_screen,
+ edids[i]);
+ found |= outputs[i + 1] != NULL;
+ }
+
+ g_free (product);
+ }
+
+ /* For input devices that we certainly know that are system-integrated, or
+ * for screen-integrated devices we weren't able to find an output for,
+ * find the builtin screen.
+ */
+ if ((input->capabilities & GSD_INPUT_IS_SYSTEM_INTEGRATED) ||
+ (!found && input->capabilities & GSD_INPUT_IS_SCREEN_INTEGRATED)) {
+ outputs[GSD_PRIO_BUILTIN] =
+ find_builtin_output (input->mapper->rr_screen);
+ }
+
+ g_strfreev (split);
+}
+
+static gboolean
+output_has_input_type (GsdOutputInfo *info,
+ guint capabilities)
+{
+ GList *devices;
+
+ for (devices = info->input_devices; devices; devices = devices->next) {
+ GsdInputInfo *input = devices->data;
+
+ if (input->capabilities == capabilities)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GnomeRROutput *
+settings_get_display (GSettings *settings,
+ GsdDeviceMapper *mapper)
+{
+ GnomeRROutput *output = NULL;
+ gchar **edid;
+ guint nvalues;
+
+ edid = g_settings_get_strv (settings, KEY_DISPLAY);
+ nvalues = g_strv_length (edid);
+
+ if (nvalues == 3) {
+ output = find_output_by_edid (mapper->rr_screen, (const gchar **) edid);
+ } else {
+ g_warning ("Unable to get display property. Got %d items, "
+ "expected %d items.\n", nvalues, 3);
+ }
+
+ g_strfreev (edid);
+
+ return output;
+}
+
+static void
+settings_set_display (GSettings *settings,
+ GnomeRROutput *output)
+{
+ gchar **prev, *edid[4] = { NULL, NULL, NULL, NULL };
+ GVariant *value;
+ gsize nvalues;
+
+ prev = g_settings_get_strv (settings, KEY_DISPLAY);
+ nvalues = g_strv_length (prev);
+
+ if (output)
+ get_edid (output, &edid[0], &edid[1], &edid[2]);
+
+ if (nvalues != 3 ||
+ g_strcmp0 (prev[0], edid[0]) != 0 ||
+ g_strcmp0 (prev[1], edid[1]) != 0 ||
+ g_strcmp0 (prev[2], edid[2]) != 0) {
+ value = g_variant_new_strv ((const gchar * const *) &edid, 3);
+ g_settings_set_value (settings, KEY_DISPLAY, value);
+ }
+
+ g_free (edid[0]);
+ g_free (edid[1]);
+ g_free (edid[2]);
+ g_strfreev (prev);
+}
+
+static void
+input_info_set_output (GsdInputInfo *input,
+ GsdOutputInfo *output,
+ gboolean guessed,
+ gboolean save)
+{
+ GnomeRROutput *rr_output = NULL;
+ GsdOutputInfo **ptr;
+
+ if (guessed) {
+ /* If there is already a non-guessed input, go for it */
+ if (input->output)
+ return;
+
+ ptr = &input->guessed_output;
+ } else {
+ /* Unset guessed output */
+ if (input->guessed_output)
+ input_info_set_output (input, NULL, TRUE, FALSE);
+ ptr = &input->output;
+ }
+
+ if (*ptr == output)
+ return;
+
+ if (*ptr) {
+ (*ptr)->input_devices = g_list_remove ((*ptr)->input_devices,
+ input);
+ }
+
+ if (output) {
+ output->input_devices = g_list_prepend (output->input_devices,
+ input);
+ rr_output = output->output;
+ }
+
+ if (input->settings && !guessed && save)
+ settings_set_display (input->settings, rr_output);
+
+ *ptr = output;
+}
+
+static GsdOutputInfo *
+input_info_get_output (GsdInputInfo *input)
+{
+ if (input->output)
+ return input->output;
+
+ if (input->guessed_output)
+ return input->guessed_output;
+
+ if (g_hash_table_size (input->mapper->output_devices) == 1) {
+ GsdOutputInfo *output;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init (&iter, input->mapper->output_devices);
+ g_hash_table_iter_next (&iter, NULL, (gpointer *) &output);
+
+ return output;
+ }
+
+ return NULL;
+}
+
+static void
+init_device_rotation_matrix (GsdWacomRotation rotation,
+ float matrix[NUM_ELEMS_MATRIX])
+{
+ memcpy (matrix, rotation_matrices[rotation].matrix,
+ sizeof (rotation_matrices[rotation].matrix));
+}
+
+static void
+init_output_rotation_matrix (GnomeRRRotation rotation,
+ float matrix[NUM_ELEMS_MATRIX])
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (rotation_matrices); i++) {
+ if (rotation_matrices[i].rotation != rotation)
+ continue;
+
+ memcpy (matrix, rotation_matrices[i].matrix, sizeof (rotation_matrices[i].matrix));
+ return;
+ }
+
+ /* We know nothing about this rotation */
+ init_device_rotation_matrix (GSD_WACOM_ROTATION_NONE, matrix);
+}
+
+static void
+multiply_matrix (float a[NUM_ELEMS_MATRIX],
+ float b[NUM_ELEMS_MATRIX],
+ float res[NUM_ELEMS_MATRIX])
+{
+ float result[NUM_ELEMS_MATRIX];
+
+ result[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
+ result[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
+ result[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
+ result[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
+ result[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
+ result[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
+ result[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
+ result[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
+ result[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
+
+ memcpy (res, result, sizeof (result));
+}
+
+static void
+calculate_viewport_matrix (const GdkRectangle mapped,
+ const GdkRectangle desktop,
+ float viewport[NUM_ELEMS_MATRIX])
+{
+ float x_scale = (float) mapped.x / desktop.width;
+ float y_scale = (float) mapped.y / desktop.height;
+ float width_scale = (float) mapped.width / desktop.width;
+ float height_scale = (float) mapped.height / desktop.height;
+
+ viewport[0] = width_scale;
+ viewport[1] = 0.0f;
+ viewport[2] = x_scale;
+
+ viewport[3] = 0.0f;
+ viewport[4] = height_scale;
+ viewport[5] = y_scale;
+
+ viewport[6] = 0.0f;
+ viewport[7] = 0.0f;
+ viewport[8] = 1.0f;
+}
+
+static gint
+monitor_for_output (GnomeRROutput *output)
+{
+ GdkScreen *screen = gdk_screen_get_default ();
+ GnomeRRCrtc *crtc = gnome_rr_output_get_crtc (output);
+ gint x, y;
+
+ if (!crtc)
+ return -1;
+
+ gnome_rr_crtc_get_position (crtc, &x, &y);
+
+ return gdk_screen_get_monitor_at_point (screen, x, y);
+}
+
+static void
+input_info_get_matrix (GsdInputInfo *input,
+ float matrix[NUM_ELEMS_MATRIX])
+{
+ GsdOutputInfo *output;
+ GnomeRRCrtc *crtc;
+
+ output = input_info_get_output (input);
+
+ if (output)
+ crtc = gnome_rr_output_get_crtc (output->output);
+
+ if (!output || !crtc) {
+ init_output_rotation_matrix (GNOME_RR_ROTATION_0, matrix);
+ } else {
+ GdkScreen *screen = gdk_screen_get_default ();
+ float viewport[NUM_ELEMS_MATRIX];
+ float output_rot[NUM_ELEMS_MATRIX];
+ GdkRectangle display, desktop = { 0 };
+ GnomeRRRotation rotation;
+ int monitor;
+
+ g_debug ("Mapping '%s' to output '%s'",
+ gdk_device_get_name (input->device),
+ gnome_rr_output_get_name (output->output));
+
+ rotation = gnome_rr_crtc_get_current_rotation (crtc);
+ init_output_rotation_matrix (rotation, output_rot);
+
+ desktop.width = gdk_screen_get_width (screen);
+ desktop.height = gdk_screen_get_height (screen);
+
+ monitor = monitor_for_output (output->output);
+ gdk_screen_get_monitor_geometry (screen, monitor, &display);
+ calculate_viewport_matrix (display, desktop, viewport);
+
+ multiply_matrix (viewport, output_rot, matrix);
+ }
+
+ /* Apply device rotation after output rotation */
+ if (input->settings &&
+ (input->capabilities &
+ (GSD_INPUT_IS_SYSTEM_INTEGRATED | GSD_INPUT_IS_SCREEN_INTEGRATED)) == 0) {
+ gint rotation;
+
+ rotation = g_settings_get_enum (input->settings, KEY_ROTATION);
+
+ if (rotation > 0) {
+ float device_rot[NUM_ELEMS_MATRIX];
+
+ g_debug ("Applying device rotation %d to '%s'",
+ rotation, gdk_device_get_name (input->device));
+
+ init_device_rotation_matrix (rotation, device_rot);
+ multiply_matrix (matrix, device_rot, matrix);
+ }
+ }
+}
+
+static void
+input_info_remap (GsdInputInfo *input)
+{
+ float matrix[NUM_ELEMS_MATRIX] = { 0 };
+
+ if (input->capabilities & GSD_INPUT_IS_PAD)
+ return;
+
+ input_info_get_matrix (input, matrix);
+
+ g_debug ("About to remap device '%s'",
+ gdk_device_get_name (input->device));
+
+ if (!device_set_matrix (input->device, matrix)) {
+ g_warning ("Failed to map device '%s'",
+ gdk_device_get_name (input->device));
+ }
+
+ g_signal_emit (input->mapper, signals[DEVICE_CHANGED], 0, input->device);
+}
+
+static void
+mapper_apply_helper_info (GsdDeviceMapper *mapper,
+ MappingHelper *helper)
+{
+ guint i, j;
+
+ /* Now, decide which input claims which output */
+ for (i = 0; i < helper->device_maps->len; i++) {
+ GsdOutputInfo *last = NULL, *output = NULL;
+ DeviceMapHelper *info;
+
+ info = &g_array_index (helper->device_maps, DeviceMapHelper, i);
+ g_debug ("Mapping input device '%s', candidates: %d, Best candidate: %s",
+ gdk_device_get_name (info->input->device), info->n_candidates,
+ (info->highest_prio < N_OUTPUT_PRIORITIES) ?
+ gnome_rr_output_get_name (info->candidates[info->highest_prio]) : "none");
+
+ for (j = 0; j < N_OUTPUT_PRIORITIES; j++) {
+ if (!info->candidates[j])
+ continue;
+
+ output = g_hash_table_lookup (mapper->output_devices,
+ info->candidates[j]);
+
+ if (!output) {
+ g_debug ("Output '%s' had no information associated, creating it ad-hoc",
+ gnome_rr_output_get_name (info->candidates[j]));
+ output = output_info_new (info->candidates[j]);
+ g_hash_table_insert (mapper->output_devices,
+ info->candidates[j], output);
+ }
+
+ last = output;
+
+ if ((info->input->capabilities &
+ (GSD_INPUT_IS_SYSTEM_INTEGRATED | GSD_INPUT_IS_SCREEN_INTEGRATED))) {
+ /* A single output is hardly going to have multiple devices
+ * with the same capabilities, so punt any next output.
+ */
+ if (output_has_input_type (output, info->input->capabilities))
+ continue;
+ }
+
+ input_info_set_output (info->input, output, TRUE, FALSE);
+ break;
+ }
+
+ /* Assign the last candidate if we came up empty */
+ if (!info->input->guessed_output && last)
+ input_info_set_output (info->input, last, TRUE, FALSE);
+
+ input_info_remap (info->input);
+ }
+}
+
+static void
+mapper_recalculate_candidates (GsdDeviceMapper *mapper)
+{
+ MappingHelper *helper;
+ GHashTableIter iter;
+ GsdInputInfo *input;
+
+ helper = mapping_helper_new ();
+ g_hash_table_iter_init (&iter, mapper->input_devices);
+ g_debug ("(Re)mapping all input devices");
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &input)) {
+ GnomeRROutput *outputs[N_OUTPUT_PRIORITIES] = { 0 };
+
+ /* Device has an output from settings */
+ if (input->output)
+ continue;
+
+ input_info_guess_candidates (input, outputs);
+ mapping_helper_add (helper, input, outputs);
+ }
+
+ mapper_apply_helper_info (mapper, helper);
+ mapping_helper_free (helper);
+}
+
+static void
+mapper_recalculate_input (GsdDeviceMapper *mapper,
+ GsdInputInfo *input)
+{
+ GnomeRROutput *outputs[N_OUTPUT_PRIORITIES] = { 0 };
+ MappingHelper *helper;
+
+ /* Device has an output from settings */
+ if (input->output)
+ return;
+
+ g_debug ("(Re)mapping input device '%s'",
+ gdk_device_get_name (input->device));
+
+ helper = mapping_helper_new ();
+ input_info_guess_candidates (input, outputs);
+ mapping_helper_add (helper, input, outputs);
+
+ mapper_apply_helper_info (mapper, helper);
+ mapping_helper_free (helper);
+}
+
+static gboolean
+input_info_update_capabilities_from_tool_type (GsdInputInfo *info)
+{
+ const char *tool_type;
+ int deviceid;
+
+ deviceid = gdk_x11_device_get_id (info->device);
+ tool_type = xdevice_get_wacom_tool_type (deviceid);
+
+ if (!tool_type)
+ return FALSE;
+
+ if (g_str_equal (tool_type, "STYLUS"))
+ info->capabilities |= GSD_INPUT_IS_PEN;
+ else if (g_str_equal (tool_type, "ERASER"))
+ info->capabilities |= GSD_INPUT_IS_ERASER;
+ else if (g_str_equal (tool_type, "PAD"))
+ info->capabilities |= GSD_INPUT_IS_PAD;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+input_info_update_capabilities (GsdInputInfo *info)
+{
+#if HAVE_WACOM
+ WacomDevice *wacom_device;
+ gchar *devpath;
+
+ info->capabilities = 0;
+ devpath = xdevice_get_device_node (gdk_x11_device_get_id (info->device));
+ wacom_device = libwacom_new_from_path (info->mapper->wacom_db, devpath,
+ WFALLBACK_GENERIC, NULL);
+
+ if (wacom_device) {
+ WacomIntegrationFlags integration_flags;
+
+ integration_flags = libwacom_get_integration_flags (wacom_device);
+
+ if (integration_flags & WACOM_DEVICE_INTEGRATED_DISPLAY)
+ info->capabilities |= GSD_INPUT_IS_SCREEN_INTEGRATED;
+
+ if (integration_flags & WACOM_DEVICE_INTEGRATED_SYSTEM)
+ info->capabilities |= GSD_INPUT_IS_SYSTEM_INTEGRATED;
+
+ libwacom_destroy (wacom_device);
+ }
+
+ g_free (devpath);
+#else
+ info->capabilities = 0;
+#endif /* HAVE_WACOM */
+
+ if (!input_info_update_capabilities_from_tool_type (info)) {
+ GdkInputSource source;
+
+ /* Fallback to GdkInputSource */
+ source = gdk_device_get_source (info->device);
+
+ if (source == GDK_SOURCE_TOUCHSCREEN)
+ info->capabilities |= GSD_INPUT_IS_TOUCH | GSD_INPUT_IS_SCREEN_INTEGRATED;
+ else if (source == GDK_SOURCE_PEN)
+ info->capabilities |= GSD_INPUT_IS_PEN;
+ else if (source == GDK_SOURCE_ERASER)
+ info->capabilities |= GSD_INPUT_IS_ERASER;
+ }
+}
+
+static void
+device_settings_changed_cb (GSettings *settings,
+ gchar *key,
+ GsdInputInfo *input)
+{
+ if (g_str_equal (key, KEY_DISPLAY)) {
+ GnomeRROutput *rr_output;
+ GsdOutputInfo *output;
+
+ rr_output = settings_get_display (settings, input->mapper);
+
+ if (rr_output) {
+ output = g_hash_table_lookup (input->mapper->output_devices,
+ rr_output);
+ input_info_set_output (input, output, FALSE, FALSE);
+ input_info_remap (input);
+ } else {
+ /* Guess an output for this device */
+ mapper_recalculate_input (input->mapper, input);
+ }
+ } else if (g_str_equal (key, KEY_ROTATION)) {
+ /* Remap the device so the new rotation is applied */
+ input_info_remap (input);
+ }
+}
+
+static GsdInputInfo *
+input_info_new (GdkDevice *device,
+ GSettings *settings,
+ GsdDeviceMapper *mapper)
+{
+ GnomeRROutput *rr_output = NULL;
+ GsdOutputInfo *output = NULL;
+ GsdInputInfo *info;
+
+ info = g_new0 (GsdInputInfo, 1);
+ info->device = device;
+ info->settings = (settings) ? g_object_ref (settings) : NULL;
+ info->mapper = mapper;
+
+ if (info->settings) {
+ info->changed_id = g_signal_connect (info->settings, "changed",
+ G_CALLBACK (device_settings_changed_cb),
+ info);
+
+ /* Assign output from config */
+ rr_output = settings_get_display (settings, mapper);
+ }
+
+ input_info_update_capabilities (info);
+
+ if (rr_output) {
+ output = g_hash_table_lookup (mapper->output_devices,
+ rr_output);
+ input_info_set_output (info, output, FALSE, FALSE);
+ input_info_remap (info);
+ } else {
+ mapper_recalculate_input (mapper, info);
+ }
+
+ return info;
+}
+
+static void
+input_info_free (GsdInputInfo *info)
+{
+ input_info_set_output (info, NULL, FALSE, FALSE);
+ input_info_set_output (info, NULL, TRUE, FALSE);
+
+ if (info->settings && info->changed_id)
+ g_signal_handler_disconnect (info->settings, info->changed_id);
+
+ if (info->settings)
+ g_object_unref (info->settings);
+
+ g_free (info);
+}
+
+static GsdOutputInfo *
+output_info_new (GnomeRROutput *output)
+{
+ GsdOutputInfo *info;
+
+ info = g_new0 (GsdOutputInfo, 1);
+ info->output = output;
+
+ return info;
+}
+
+static void
+output_info_free (GsdOutputInfo *info)
+{
+ while (info->input_devices) {
+ GsdInputInfo *input = info->input_devices->data;
+
+ if (input->output == info)
+ input_info_set_output (input, NULL, FALSE, FALSE);
+ if (input->guessed_output == info)
+ input_info_set_output (input, NULL, TRUE, FALSE);
+ }
+
+ g_free (info);
+}
+
+static void
+gsd_device_mapper_finalize (GObject *object)
+{
+ GsdDeviceMapper *mapper = GSD_DEVICE_MAPPER (object);
+
+ g_hash_table_unref (mapper->input_devices);
+
+ if (mapper->output_devices)
+ g_hash_table_unref (mapper->output_devices);
+
+#if HAVE_WACOM
+ libwacom_database_destroy (mapper->wacom_db);
+#endif
+
+ G_OBJECT_CLASS (gsd_device_mapper_parent_class)->finalize (object);
+}
+
+static void
+_device_mapper_update_outputs (GsdDeviceMapper *mapper)
+{
+ GnomeRROutput **outputs;
+ GHashTable *map;
+ gint i = 0;
+
+ map = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) output_info_free);
+ outputs = gnome_rr_screen_list_outputs (mapper->rr_screen);
+ g_debug ("(Re)building output map\n");
+
+ while (outputs[i]) {
+ GsdOutputInfo *info = NULL;
+
+ if (mapper->output_devices) {
+ info = g_hash_table_lookup (mapper->output_devices,
+ outputs[i]);
+
+ if (info)
+ g_hash_table_steal (mapper->output_devices,
+ outputs[i]);
+ }
+
+ if (!info)
+ info = output_info_new (outputs[i]);
+
+ g_hash_table_insert (map, outputs[i], info);
+ i++;
+ }
+
+ if (mapper->output_devices)
+ g_hash_table_unref (mapper->output_devices);
+
+ mapper->output_devices = map;
+ mapper_recalculate_candidates (mapper);
+}
+
+static void
+outputs_changed_cb (GnomeRRScreen *rr_screen,
+ GnomeRROutput *output,
+ GsdDeviceMapper *mapper)
+{
+ _device_mapper_update_outputs (mapper);
+}
+
+static void
+screen_changed_cb (GnomeRRScreen *rr_screen,
+ GsdDeviceMapper *mapper)
+{
+ _device_mapper_update_outputs (mapper);
+}
+
+static gboolean
+gsd_device_mapper_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsdDeviceMapper *mapper;
+
+ mapper = GSD_DEVICE_MAPPER (initable);
+ mapper->rr_screen = gnome_rr_screen_new (mapper->screen, error);
+
+ if (!mapper->rr_screen)
+ return FALSE;
+
+ g_signal_connect (mapper->rr_screen, "changed",
+ G_CALLBACK (screen_changed_cb), initable);
+ g_signal_connect (mapper->rr_screen, "output-connected",
+ G_CALLBACK (outputs_changed_cb), initable);
+ g_signal_connect (mapper->rr_screen, "output-disconnected",
+ G_CALLBACK (outputs_changed_cb), initable);
+ _device_mapper_update_outputs (GSD_DEVICE_MAPPER (initable));
+ return TRUE;
+}
+
+static void
+gsd_device_mapper_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = gsd_device_mapper_initable_init;
+}
+
+static void
+gsd_device_mapper_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsdDeviceMapper *mapper = GSD_DEVICE_MAPPER (object);
+
+ switch (param_id) {
+ case PROP_SCREEN:
+ mapper->screen = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ }
+}
+
+static void
+gsd_device_mapper_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsdDeviceMapper *mapper = GSD_DEVICE_MAPPER (object);
+
+ switch (param_id) {
+ case PROP_SCREEN:
+ g_value_set_object (value, mapper->screen);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ }
+}
+
+static void
+gsd_device_mapper_class_init (GsdDeviceMapperClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gsd_device_mapper_set_property;
+ object_class->get_property = gsd_device_mapper_get_property;
+ object_class->finalize = gsd_device_mapper_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_SCREEN,
+ g_param_spec_object ("screen",
+ "Screen",
+ "Screen",
+ GDK_TYPE_SCREEN,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE));
+ signals[DEVICE_CHANGED] =
+ g_signal_new ("device-changed",
+ GSD_TYPE_DEVICE_MAPPER,
+ G_SIGNAL_RUN_LAST, 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GDK_TYPE_DEVICE);
+}
+
+static void
+gsd_device_mapper_init (GsdDeviceMapper *mapper)
+{
+ mapper->input_devices = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) input_info_free);
+#if HAVE_WACOM
+ mapper->wacom_db = libwacom_database_new ();
+#endif
+}
+
+GsdDeviceMapper *
+gsd_device_mapper_get (void)
+{
+ GsdDeviceMapper *mapper;
+ GdkScreen *screen;
+
+ screen = gdk_screen_get_default ();
+ g_return_val_if_fail (screen != NULL, NULL);
+
+ mapper = g_object_get_data (G_OBJECT (screen), "gsd-device-mapper-data");
+
+ if (!mapper) {
+ GError *error = NULL;
+
+ mapper = g_initable_new (GSD_TYPE_DEVICE_MAPPER, NULL, &error,
+ "screen", screen, NULL);
+ if (error) {
+ g_critical ("Could not create device mapper: %s", error->message);
+ g_error_free (error);
+ } else {
+ g_object_set_data_full (G_OBJECT (screen), "gsd-device-mapper-data",
+ mapper, (GDestroyNotify) g_object_unref);
+ }
+ }
+
+ return mapper;
+}
+
+void
+gsd_device_mapper_add_input (GsdDeviceMapper *mapper,
+ GdkDevice *device,
+ GSettings *settings)
+{
+ GsdInputInfo *info;
+
+ g_return_if_fail (mapper != NULL);
+ g_return_if_fail (GDK_IS_DEVICE (device));
+ g_return_if_fail (!settings || G_IS_SETTINGS (settings));
+
+ if (g_hash_table_contains (mapper->input_devices, device))
+ return;
+
+ info = input_info_new (device, settings, mapper);
+ g_hash_table_insert (mapper->input_devices, device, info);
+}
+
+void
+gsd_device_mapper_remove_input (GsdDeviceMapper *mapper,
+ GdkDevice *device)
+{
+ g_return_if_fail (mapper != NULL);
+ g_return_if_fail (GDK_IS_DEVICE (device));
+
+ g_hash_table_remove (mapper->input_devices, device);
+}
+
+GnomeRROutput *
+gsd_device_mapper_get_device_output (GsdDeviceMapper *mapper,
+ GdkDevice *device)
+{
+ GsdOutputInfo *output;
+ GsdInputInfo *input;
+
+ g_return_val_if_fail (mapper != NULL, NULL);
+ g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
+
+ input = g_hash_table_lookup (mapper->input_devices, device);
+ output = input_info_get_output (input);
+
+ if (!output)
+ return NULL;
+
+ return output->output;
+}
+
+gint
+gsd_device_mapper_get_device_monitor (GsdDeviceMapper *mapper,
+ GdkDevice *device)
+{
+ GsdOutputInfo *output;
+ GsdInputInfo *input;
+
+ g_return_val_if_fail (GSD_IS_DEVICE_MAPPER (mapper), -1);
+ g_return_val_if_fail (GDK_IS_DEVICE (device), -1);
+
+ input = g_hash_table_lookup (mapper->input_devices, device);
+
+ if (!input)
+ return -1;
+
+ output = input_info_get_output (input);
+
+ if (!output)
+ return -1;
+
+ return monitor_for_output (output->output);
+}
+
+void
+gsd_device_mapper_set_device_output (GsdDeviceMapper *mapper,
+ GdkDevice *device,
+ GnomeRROutput *output)
+{
+ GsdInputInfo *input_info;
+ GsdOutputInfo *output_info;
+
+ g_return_if_fail (mapper != NULL);
+ g_return_if_fail (GDK_IS_DEVICE (device));
+
+ input_info = g_hash_table_lookup (mapper->input_devices, device);
+ output_info = g_hash_table_lookup (mapper->output_devices, output);
+
+ if (!input_info || !output_info)
+ return;
+
+ input_info_set_output (input_info, output_info, FALSE, TRUE);
+ input_info_remap (input_info);
+}
+
+void
+gsd_device_mapper_set_device_monitor (GsdDeviceMapper *mapper,
+ GdkDevice *device,
+ gint monitor_num)
+{
+ GnomeRROutput *output;
+
+ g_return_if_fail (GSD_IS_DEVICE_MAPPER (mapper));
+ g_return_if_fail (GDK_IS_DEVICE (device));
+
+ output = monitor_to_output (mapper, monitor_num);
+ gsd_device_mapper_set_device_output (mapper, device, output);
+}
diff --git a/plugins/common/gsd-device-mapper.h b/plugins/common/gsd-device-mapper.h
new file mode 100644
index 0000000..cce4b60
--- /dev/null
+++ b/plugins/common/gsd-device-mapper.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Carlos Garnacho <carlosg@gnome.org>
+ *
+ * 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.
+ */
+
+#ifndef __GSD_DEVICE_MAPPER_H__
+#define __GSD_DEVICE_MAPPER_H__
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+#undef GNOME_DESKTOP_USE_UNSTABLE_API
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_DEVICE_MAPPER (gsd_device_mapper_get_type ())
+#define GSD_DEVICE_MAPPER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_DEVICE_MAPPER, GsdDeviceMapper))
+#define GSD_DEVICE_MAPPER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSD_TYPE_DEVICE_MAPPER, GsdDeviceMapperClass))
+#define GSD_IS_DEVICE_MAPPER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_DEVICE_MAPPER))
+#define GSD_IS_DEVICE_MAPPER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_DEVICE_MAPPER))
+#define GSD_DEVICE_MAPPER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_DEVICE_MAPPER, GsdDeviceMapperClass))
+
+typedef struct _GsdDeviceMapper GsdDeviceMapper;
+typedef struct _GsdDeviceMapperClass GsdDeviceMapperClass;
+
+GType gsd_device_mapper_get_type (void) G_GNUC_CONST;
+GsdDeviceMapper * gsd_device_mapper_get (void);
+
+void gsd_device_mapper_add_input (GsdDeviceMapper *mapper,
+ GdkDevice *device,
+ GSettings *settings);
+void gsd_device_mapper_remove_input (GsdDeviceMapper *mapper,
+ GdkDevice *device);
+void gsd_device_mapper_add_output (GsdDeviceMapper *mapper,
+ GnomeRROutput *output);
+void gsd_device_mapper_remove_output (GsdDeviceMapper *mapper,
+ GnomeRROutput *output);
+
+GnomeRROutput * gsd_device_mapper_get_device_output (GsdDeviceMapper *mapper,
+ GdkDevice *device);
+
+void gsd_device_mapper_set_device_output (GsdDeviceMapper *mapper,
+ GdkDevice *device,
+ GnomeRROutput *output);
+
+gint gsd_device_mapper_get_device_monitor (GsdDeviceMapper *mapper,
+ GdkDevice *device);
+void gsd_device_mapper_set_device_monitor (GsdDeviceMapper *mapper,
+ GdkDevice *device,
+ gint monitor_num);
+
+G_END_DECLS
+
+#endif /* __GSD_DEVICE_MAPPER_H__ */
diff --git a/plugins/common/gsd-input-helper.c b/plugins/common/gsd-input-helper.c
index d5d2a2a..fcf6768 100644
--- a/plugins/common/gsd-input-helper.c
+++ b/plugins/common/gsd-input-helper.c
@@ -571,3 +571,38 @@ get_disabled_devices (GdkDeviceManager *manager)
return ret;
}
+
+const char *
+xdevice_get_wacom_tool_type (int deviceid)
+{
+ unsigned long nitems, bytes_after;
+ unsigned char *data = NULL;
+ Atom prop, realtype, tool;
+ GdkDisplay *display;
+ int realformat, rc;
+ const gchar *ret = NULL;
+
+ gdk_error_trap_push ();
+
+ display = gdk_display_get_default ();
+ prop = gdk_x11_get_xatom_by_name ("Wacom Tool Type");
+
+ rc = XIGetProperty (GDK_DISPLAY_XDISPLAY (display),
+ deviceid, prop, 0, 1, False,
+ XA_ATOM, &realtype, &realformat, &nitems,
+ &bytes_after, &data);
+
+ gdk_error_trap_pop_ignored ();
+
+ if (rc != Success || nitems == 0)
+ return NULL;
+
+ if (realtype == XA_ATOM) {
+ tool = *((Atom*) data);
+ ret = gdk_x11_get_xatom_name (tool);
+ }
+
+ XFree (data);
+
+ return ret;
+}
diff --git a/plugins/common/gsd-input-helper.h b/plugins/common/gsd-input-helper.h
index 0bf328a..262f3b7 100644
--- a/plugins/common/gsd-input-helper.h
+++ b/plugins/common/gsd-input-helper.h
@@ -82,6 +82,8 @@ GList * get_disabled_devices (GdkDeviceManager *manager);
char * xdevice_get_device_node (int deviceid);
int xdevice_get_last_tool_id (int deviceid);
+const char * xdevice_get_wacom_tool_type (int deviceid);
+
G_END_DECLS
#endif /* __GSD_INPUT_HELPER_H */
diff --git a/plugins/wacom/gsd-wacom-manager.c b/plugins/wacom/gsd-wacom-manager.c
index 5b01047..6ec2d8a 100644
--- a/plugins/wacom/gsd-wacom-manager.c
+++ b/plugins/wacom/gsd-wacom-manager.c
@@ -48,6 +48,7 @@
#include "gsd-wacom-manager.h"
#include "gsd-wacom-device.h"
#include "gsd-wacom-osd-window.h"
+#include "gsd-device-mapper.h"
#define GSD_WACOM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_WACOM_MANAGER, GsdWacomManagerPrivate))
@@ -81,6 +82,8 @@ struct GsdWacomManagerPrivate
GHashTable *devices; /* key = GdkDevice, value = GsdWacomDevice */
GList *rr_screens;
+ GsdDeviceMapper *device_mapper;
+
/* button capture */
GSList *screens;
int opcode;
@@ -173,22 +176,6 @@ wacom_set_property (GsdWacomDevice *device,
}
static void
-set_rotation (GsdWacomDevice *device,
- GsdWacomRotation rotation)
-{
- gchar rot = rotation;
- PropertyHelper property = {
- .name = "Wacom Rotation",
- .nitems = 1,
- .format = 8,
- .type = XA_INTEGER,
- .data.c = &rot,
- };
-
- wacom_set_property (device, &property);
-}
-
-static void
set_pressurecurve (GsdWacomDevice *device,
GVariant *value)
{
@@ -237,73 +224,6 @@ set_area (GsdWacomDevice *device,
g_variant_unref (value);
}
-/* Returns the rotation to apply a device relative to the current rotation of the output */
-static GsdWacomRotation
-get_relative_rotation (GsdWacomRotation device_rotation,
- GsdWacomRotation output_rotation)
-{
- GsdWacomRotation rotations[] = { GSD_WACOM_ROTATION_HALF,
- GSD_WACOM_ROTATION_CW,
- GSD_WACOM_ROTATION_NONE,
- GSD_WACOM_ROTATION_CCW };
- guint i;
-
- if (device_rotation == output_rotation)
- return GSD_WACOM_ROTATION_NONE;
-
- if (output_rotation == GSD_WACOM_ROTATION_NONE)
- return device_rotation;
-
- for (i = 0; i < G_N_ELEMENTS (rotations); i++){
- if (device_rotation == rotations[i])
- break;
- }
-
- if (output_rotation == GSD_WACOM_ROTATION_HALF)
- return rotations[(i + G_N_ELEMENTS (rotations) - 2) % G_N_ELEMENTS (rotations)];
-
- if (output_rotation == GSD_WACOM_ROTATION_CW)
- return rotations[(i + G_N_ELEMENTS (rotations) - 1) % G_N_ELEMENTS (rotations)];
-
- if (output_rotation == GSD_WACOM_ROTATION_CCW)
- return rotations[(i + 1) % G_N_ELEMENTS (rotations)];
-
- /* fallback */
- return GSD_WACOM_ROTATION_NONE;
-}
-
-static void
-set_display (GsdWacomDevice *device,
- GVariant *value)
-{
- GsdWacomRotation device_rotation;
- GsdWacomRotation output_rotation;
- GSettings *settings;
- float matrix[NUM_ELEMS_MATRIX];
- PropertyHelper property = {
- .name = "Coordinate Transformation Matrix",
- .nitems = NUM_ELEMS_MATRIX,
- .format = 32,
- .type = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "FLOAT", True),
- };
-
- gsd_wacom_device_get_display_matrix (device, matrix);
-
- property.data.i = (gint*)(&matrix);
- g_debug ("Applying matrix to device...");
- wacom_set_property (device, &property);
-
- /* Compute rotation to apply relative to the output */
- settings = gsd_wacom_device_get_settings (device);
- device_rotation = g_settings_get_enum (settings, KEY_ROTATION);
- output_rotation = gsd_wacom_device_get_display_rotation (device);
-
- /* Apply display rotation to device */
- set_rotation (device, get_relative_rotation (device_rotation, output_rotation));
-
- g_variant_unref (value);
-}
-
static void
set_absolute (GsdWacomDevice *device,
gint is_absolute)
@@ -377,7 +297,8 @@ set_keep_aspect (GsdWacomDevice *device,
{
GVariant *values[4], *variant;
guint i;
-
+ GdkDevice *gdk_device;
+ GsdDeviceMapper *mapper;
gint *area;
gint monitor = GSD_WACOM_SET_ALL_MONITORS;
GsdWacomRotation rotation;
@@ -412,7 +333,9 @@ set_keep_aspect (GsdWacomDevice *device,
}
/* Get corresponding monitor size */
- monitor = gsd_wacom_device_get_display_monitor (device);
+ mapper = gsd_device_mapper_get ();
+ g_object_get (device, "gdk-device", &gdk_device, NULL);
+ monitor = gsd_device_mapper_get_device_monitor (mapper, gdk_device);
/* Adjust area to match the monitor aspect ratio */
g_debug ("Initial device area: (%d,%d) (%d,%d)", area[0], area[1], area[2], area[3]);
@@ -715,7 +638,6 @@ set_wacom_settings (GsdWacomManager *manager,
gsd_wacom_device_type_to_string (gsd_wacom_device_get_device_type (device)));
settings = gsd_wacom_device_get_settings (device);
- set_rotation (device, g_settings_get_enum (settings, KEY_ROTATION));
set_touch (device, g_settings_get_boolean (settings, KEY_TOUCH));
type = gsd_wacom_device_get_device_type (device);
@@ -760,7 +682,6 @@ set_wacom_settings (GsdWacomManager *manager,
set_keep_aspect (device, g_settings_get_boolean (settings, KEY_KEEP_ASPECT));
set_area (device, g_settings_get_value (settings, KEY_AREA));
}
- set_display (device, g_settings_get_value (settings, KEY_DISPLAY));
/* only pen and eraser have pressure threshold and curve settings */
if (type == WACOM_TYPE_STYLUS ||
@@ -779,8 +700,7 @@ wacom_settings_changed (GSettings *settings,
type = gsd_wacom_device_get_device_type (device);
if (g_str_equal (key, KEY_ROTATION)) {
- if (type != WACOM_TYPE_PAD)
- set_rotation (device, g_settings_get_enum (settings, key));
+ /* Real device rotation is handled in GsdDeviceMapper */
} else if (g_str_equal (key, KEY_TOUCH)) {
set_touch (device, g_settings_get_boolean (settings, key));
} else if (g_str_equal (key, KEY_TPCBUTTON)) {
@@ -796,9 +716,7 @@ wacom_settings_changed (GSettings *settings,
type != WACOM_TYPE_TOUCH)
set_area (device, g_settings_get_value (settings, key));
} else if (g_str_equal (key, KEY_DISPLAY)) {
- if (type != WACOM_TYPE_CURSOR &&
- type != WACOM_TYPE_PAD)
- set_display (device, g_settings_get_value (settings, key));
+ /* Unhandled, GsdDeviceMapper handles this */
} else if (g_str_equal (key, KEY_KEEP_ASPECT)) {
if (type != WACOM_TYPE_CURSOR &&
type != WACOM_TYPE_PAD &&
@@ -973,6 +891,16 @@ device_added_cb (GdkDeviceManager *device_manager,
g_signal_connect (G_OBJECT (settings), "changed",
G_CALLBACK (wacom_settings_changed), device);
+ /* Map devices, the xrandr module handles touchscreens in general, so bypass these here */
+ if (gsd_wacom_device_get_device_type (device) == WACOM_TYPE_PAD ||
+ gsd_wacom_device_get_device_type (device) == WACOM_TYPE_STYLUS ||
+ gsd_wacom_device_get_device_type (device) == WACOM_TYPE_ERASER ||
+ (gsd_wacom_device_get_device_type (device) == WACOM_TYPE_TOUCH &&
+ !gsd_wacom_device_is_screen_tablet (device))) {
+ gsd_device_mapper_add_input (manager->priv->device_mapper,
+ gdk_device, settings);
+ }
+
if (gsd_wacom_device_get_device_type (device) == WACOM_TYPE_STYLUS ||
gsd_wacom_device_get_device_type (device) == WACOM_TYPE_ERASER) {
GList *styli, *l;
@@ -1003,6 +931,9 @@ device_removed_cb (GdkDeviceManager *device_manager,
gdk_device_get_name (gdk_device));
g_hash_table_remove (manager->priv->devices, gdk_device);
+ gsd_device_mapper_remove_input (manager->priv->device_mapper,
+ gdk_device);
+
/* Enable this chunk of code if you want to valgrind
* test-wacom. It will exit when there are no Wacom devices left */
#if 0
@@ -1171,9 +1102,11 @@ generate_key (GsdWacomTabletButton *wbutton,
}
static void
-switch_monitor (GsdWacomDevice *device)
+switch_monitor (GsdWacomManager *manager,
+ GsdWacomDevice *device)
{
gint current_monitor, n_monitors;
+ GdkDevice *gdk_device;
/* We dont; do that for screen tablets, sorry... */
if (gsd_wacom_device_is_screen_tablet (device))
@@ -1185,15 +1118,19 @@ switch_monitor (GsdWacomDevice *device)
if (n_monitors < 2)
return;
- current_monitor = gsd_wacom_device_get_display_monitor (device);
+ g_object_get (device, "gdk-device", &gdk_device, NULL);
+ current_monitor =
+ gsd_device_mapper_get_device_monitor (manager->priv->device_mapper,
+ gdk_device);
/* Select next monitor */
current_monitor++;
if (current_monitor >= n_monitors)
- current_monitor = GSD_WACOM_SET_ALL_MONITORS;
+ current_monitor = 0;
- gsd_wacom_device_set_display (device, current_monitor);
+ gsd_device_mapper_set_device_monitor (manager->priv->device_mapper,
+ gdk_device, current_monitor);
}
static const char*
@@ -1303,7 +1240,7 @@ filter_button_events (XEvent *xevent,
/* Switch monitor */
if (g_settings_get_enum (wbutton->settings, KEY_ACTION_TYPE) == GSD_WACOM_ACTION_TYPE_SWITCH_MONITOR) {
if (xiev->evtype == XI_ButtonRelease)
- switch_monitor (device);
+ switch_monitor (manager, device);
return GDK_FILTER_REMOVE;
}
@@ -1343,6 +1280,8 @@ gsd_wacom_manager_idle_cb (GsdWacomManager *manager)
gnome_settings_profile_start (NULL);
+ manager->priv->device_mapper = gsd_device_mapper_get ();
+
manager->priv->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
set_devicepresence_handler (manager);
@@ -1402,7 +1341,6 @@ on_screen_changed_cb (GnomeRRScreen *rr_screen,
set_keep_aspect (device, g_settings_get_boolean (settings, KEY_KEEP_ASPECT));
set_area (device, g_settings_get_value (settings, KEY_AREA));
}
- set_display (device, g_settings_get_value (settings, KEY_DISPLAY));
}
g_list_free (devices);
}
diff --git a/plugins/xrandr/gsd-xrandr-manager.c b/plugins/xrandr/gsd-xrandr-manager.c
index 8e6aa83..bc02f67 100644
--- a/plugins/xrandr/gsd-xrandr-manager.c
+++ b/plugins/xrandr/gsd-xrandr-manager.c
@@ -47,16 +47,13 @@
#include <libgnome-desktop/gnome-rr.h>
#include <libgnome-desktop/gnome-pnp-ids.h>
-#ifdef HAVE_WACOM
-#include <libwacom/libwacom.h>
-#endif /* HAVE_WACOM */
-
#include "gsd-enums.h"
#include "gsd-input-helper.h"
#include "gnome-settings-plugin.h"
#include "gnome-settings-profile.h"
#include "gnome-settings-session.h"
#include "gsd-xrandr-manager.h"
+#include "gsd-device-mapper.h"
#define GSD_XRANDR_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_XRANDR_MANAGER, GsdXrandrManagerPrivate))
@@ -117,16 +114,17 @@ struct GsdXrandrManagerPrivate {
GDBusConnection *connection;
GCancellable *bus_cancellable;
+ GsdDeviceMapper *device_mapper;
+ GdkDeviceManager *device_manager;
+ guint device_added_id;
+ guint device_removed_id;
+
/* fn-F7 status */
int current_fn_f7_config; /* -1 if no configs */
GnomeRRConfig **fn_f7_configs; /* NULL terminated, NULL if there are no configs */
/* Last time at which we got a "screen got reconfigured" event; see on_randr_event() */
guint32 last_config_timestamp;
-
-#ifdef HAVE_WACOM
- WacomDeviceDatabase *wacom_db;
-#endif /* HAVE_WACOM */
};
static const GnomeRRRotation possible_rotations[] = {
@@ -1491,137 +1489,6 @@ get_next_rotation (GnomeRRRotation allowed_rotations, GnomeRRRotation current_ro
}
}
-struct {
- GnomeRRRotation rotation;
- /* evdev */
- gboolean x_axis_inversion;
- gboolean y_axis_inversion;
- gboolean axes_swap;
-} evdev_rotations[] = {
- { GNOME_RR_ROTATION_0, 0, 0, 0 },
- { GNOME_RR_ROTATION_90, 1, 0, 1 },
- { GNOME_RR_ROTATION_180, 1, 1, 0 },
- { GNOME_RR_ROTATION_270, 0, 1, 1 }
-};
-
-static guint
-get_rotation_index (GnomeRRRotation rotation)
-{
- guint i;
-
- for (i = 0; i < G_N_ELEMENTS (evdev_rotations); i++) {
- if (evdev_rotations[i].rotation == rotation)
- return i;
- }
- g_assert_not_reached ();
-}
-
-static gboolean
-is_wacom_tablet_device (GsdXrandrManager *mgr,
- XDeviceInfo *device_info)
-{
-#ifdef HAVE_WACOM
- GsdXrandrManagerPrivate *priv = mgr->priv;
- gchar *device_node;
- WacomDevice *wacom_device;
- gboolean is_tablet = FALSE;
-
- if (priv->wacom_db == NULL)
- priv->wacom_db = libwacom_database_new ();
-
- device_node = xdevice_get_device_node (device_info->id);
- if (device_node == NULL)
- return FALSE;
-
- wacom_device = libwacom_new_from_path (priv->wacom_db, device_node, FALSE, NULL);
- g_free (device_node);
- if (wacom_device == NULL) {
- g_free (device_node);
- return FALSE;
- }
- is_tablet = libwacom_has_touch (wacom_device) &&
- libwacom_is_builtin (wacom_device);
-
- libwacom_destroy (wacom_device);
-
- return is_tablet;
-#else /* HAVE_WACOM */
- return FALSE;
-#endif /* HAVE_WACOM */
-}
-
-static void
-rotate_touchscreens (GsdXrandrManager *mgr,
- GnomeRRRotation rotation)
-{
- XDeviceInfo *device_info;
- gint n_devices;
- guint i, rot_idx;
-
- if (!supports_xinput_devices ())
- return;
-
- g_debug ("Rotating touchscreen devices");
-
- device_info = XListInputDevices (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &n_devices);
- if (device_info == NULL)
- return;
-
- rot_idx = get_rotation_index (rotation);
-
- for (i = 0; i < n_devices; i++) {
- if (is_wacom_tablet_device (mgr, &device_info[i])) {
- g_debug ("Not rotating tablet device '%s'", device_info[i].name);
- continue;
- }
-
- if (device_info_is_touchscreen (&device_info[i]) ||
- device_info_is_tablet (&device_info[i])) {
- XDevice *device;
- char c = evdev_rotations[rot_idx].axes_swap;
- PropertyHelper axes_swap = {
- .name = "Evdev Axes Swap",
- .nitems = 1,
- .format = 8,
- .type = XA_INTEGER,
- .data.c = &c,
- };
-
- g_debug ("About to rotate '%s'", device_info[i].name);
-
- gdk_error_trap_push ();
- device = XOpenDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), device_info[i].id);
- if (gdk_error_trap_pop () || (device == NULL))
- continue;
-
- if (device_set_property (device, device_info[i].name, &axes_swap) != FALSE) {
- char axis[] = {
- evdev_rotations[rot_idx].x_axis_inversion,
- evdev_rotations[rot_idx].y_axis_inversion
- };
- PropertyHelper axis_invert = {
- .name = "Evdev Axis Inversion",
- .nitems = 2,
- .format = 8,
- .type = XA_INTEGER,
- .data.c = axis,
- };
-
- device_set_property (device, device_info[i].name, &axis_invert);
-
- g_debug ("Rotated '%s' to configuration '%d, %d, %d'",
- device_info[i].name,
- evdev_rotations[rot_idx].x_axis_inversion,
- evdev_rotations[rot_idx].y_axis_inversion,
- evdev_rotations[rot_idx].axes_swap);
- }
-
- XCloseDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), device);
- }
- }
- XFreeDeviceList (device_info);
-}
-
/* We use this when the XF86RotateWindows key is pressed, or the
* orientation of a tablet changes. The key is present
* on some tablet PCs; they use it so that the user can rotate the tablet
@@ -1639,7 +1506,7 @@ handle_rotate_windows (GsdXrandrManager *mgr,
int num_allowed_rotations;
GnomeRRRotation allowed_rotations;
GnomeRRRotation next_rotation;
- gboolean success, show_error;
+ gboolean show_error;
g_debug ("Handling XF86RotateWindows with rotation %d", rotation);
@@ -1673,9 +1540,7 @@ handle_rotate_windows (GsdXrandrManager *mgr,
gnome_rr_output_info_set_rotation (rotatable_output_info, next_rotation);
- success = apply_configuration (mgr, current, timestamp, show_error, TRUE);
- if (success)
- rotate_touchscreens (mgr, next_rotation);
+ apply_configuration (mgr, current, timestamp, show_error, TRUE);
out:
g_object_unref (current);
@@ -2040,6 +1905,56 @@ power_client_changed_cb (UpClient *client, gpointer data)
}
}
+static void
+manager_device_added (GsdXrandrManager *manager,
+ GdkDevice *device)
+{
+ if (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER ||
+ gdk_device_get_source (device) != GDK_SOURCE_TOUCHSCREEN)
+ return;
+
+ gsd_device_mapper_add_input (manager->priv->device_mapper, device, NULL);
+}
+
+static void
+manager_device_removed (GsdXrandrManager *manager,
+ GdkDevice *device)
+{
+ if (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER ||
+ gdk_device_get_source (device) != GDK_SOURCE_TOUCHSCREEN)
+ return;
+
+ gsd_device_mapper_remove_input (manager->priv->device_mapper, device);
+}
+
+static void
+manager_init_devices (GsdXrandrManager *manager)
+{
+ GdkDisplay *display;
+ GList *devices, *d;
+ GdkScreen *screen;
+
+ screen = gdk_screen_get_default ();
+ display = gdk_screen_get_display (screen);
+
+ manager->priv->device_mapper = gsd_device_mapper_get ();
+ manager->priv->device_manager = gdk_display_get_device_manager (display);
+ manager->priv->device_added_id =
+ g_signal_connect_swapped (manager->priv->device_manager, "device-added",
+ G_CALLBACK (manager_device_added), manager);
+ manager->priv->device_removed_id =
+ g_signal_connect_swapped (manager->priv->device_manager, "device-removed",
+ G_CALLBACK (manager_device_removed), manager);
+
+ devices = gdk_device_manager_list_devices (manager->priv->device_manager,
+ GDK_DEVICE_TYPE_SLAVE);
+
+ for (d = devices; d; d = d->next)
+ manager_device_added (manager, d->data);
+
+ g_list_free (devices);
+}
+
gboolean
gsd_xrandr_manager_start (GsdXrandrManager *manager,
GError **error)
@@ -2081,6 +1996,8 @@ gsd_xrandr_manager_start (GsdXrandrManager *manager,
log_msg ("State of screen after initial configuration:\n");
log_screen (manager->priv->rw_screen);
+ manager_init_devices (manager);
+
log_close ();
gnome_settings_profile_end (NULL);
@@ -2127,12 +2044,12 @@ gsd_xrandr_manager_stop (GsdXrandrManager *manager)
manager->priv->connection = NULL;
}
-#ifdef HAVE_WACOM
- if (manager->priv->wacom_db != NULL) {
- libwacom_database_destroy (manager->priv->wacom_db);
- manager->priv->wacom_db = NULL;
+ if (manager->priv->device_manager != NULL) {
+ g_signal_handler_disconnect (manager->priv->device_manager,
+ manager->priv->device_added_id);
+ g_signal_handler_disconnect (manager->priv->device_manager,
+ manager->priv->device_removed_id);
}
-#endif /* HAVE_WACOM */
log_open ();
log_msg ("STOPPING XRANDR PLUGIN\n------------------------------------------------------------\n");