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 */ + +#include "edid.h" +#include +#include +#include +#include + +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 + */ + +#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 + * + * 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 +#include +#include + +#if HAVE_WACOM +#include +#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 + * + * 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 +#undef GNOME_DESKTOP_USE_UNSTABLE_API +#include + +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 #include -#ifdef HAVE_WACOM -#include -#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");