/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright 2014 Red Hat, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * Author: Carlos Garnacho */ /** * SECTION:input-settings * @title: MetaInputSettings * @short_description: Mutter input device configuration */ #include "config.h" #include #include "meta-backend-private.h" #include "meta-input-settings-private.h" #include "x11/meta-input-settings-x11.h" #ifdef HAVE_NATIVE_BACKEND #include "native/meta-backend-native.h" #include "native/meta-input-settings-native.h" #endif #include typedef struct _MetaInputSettingsPrivate MetaInputSettingsPrivate; typedef struct _DeviceMappingInfo DeviceMappingInfo; struct _DeviceMappingInfo { MetaInputSettings *input_settings; ClutterInputDevice *device; GSettings *settings; }; struct _MetaInputSettingsPrivate { ClutterDeviceManager *device_manager; MetaMonitorManager *monitor_manager; guint monitors_changed_id; GSettings *mouse_settings; GSettings *touchpad_settings; GSettings *trackball_settings; GSettings *keyboard_settings; GHashTable *mappable_devices; }; typedef void (*ConfigBoolFunc) (MetaInputSettings *input_settings, ClutterInputDevice *device, gboolean setting); typedef void (*ConfigDoubleFunc) (MetaInputSettings *input_settings, ClutterInputDevice *device, gdouble value); typedef void (*ConfigUintFunc) (MetaInputSettings *input_settings, ClutterInputDevice *device, guint value); G_DEFINE_TYPE_WITH_PRIVATE (MetaInputSettings, meta_input_settings, G_TYPE_OBJECT) static GSList * meta_input_settings_get_devices (MetaInputSettings *settings, ClutterInputDeviceType type) { MetaInputSettingsPrivate *priv; const GSList *devices; GSList *list = NULL; priv = meta_input_settings_get_instance_private (settings); devices = clutter_device_manager_peek_devices (priv->device_manager); while (devices) { ClutterInputDevice *device = devices->data; if (clutter_input_device_get_device_type (device) == type && clutter_input_device_get_device_mode (device) != CLUTTER_INPUT_MODE_MASTER) list = g_slist_prepend (list, device); devices = devices->next; } return list; } static void meta_input_settings_dispose (GObject *object) { MetaInputSettings *settings = META_INPUT_SETTINGS (object); MetaInputSettingsPrivate *priv = meta_input_settings_get_instance_private (settings); g_clear_object (&priv->mouse_settings); g_clear_object (&priv->touchpad_settings); g_clear_object (&priv->trackball_settings); g_clear_object (&priv->keyboard_settings); g_clear_pointer (&priv->mappable_devices, g_hash_table_unref); if (priv->monitors_changed_id && priv->monitor_manager) { g_signal_handler_disconnect (priv->monitor_manager, priv->monitors_changed_id); priv->monitors_changed_id = 0; } g_clear_object (&priv->monitor_manager); G_OBJECT_CLASS (meta_input_settings_parent_class)->dispose (object); } static void settings_device_set_bool_setting (MetaInputSettings *input_settings, ClutterInputDevice *device, ConfigBoolFunc func, gboolean enabled) { func (input_settings, device, enabled); } static void settings_set_bool_setting (MetaInputSettings *input_settings, ClutterInputDeviceType type, ConfigBoolFunc func, gboolean enabled) { GSList *devices, *d; devices = meta_input_settings_get_devices (input_settings, type); for (d = devices; d; d = d->next) settings_device_set_bool_setting (input_settings, d->data, func, enabled); g_slist_free (devices); } static void settings_device_set_double_setting (MetaInputSettings *input_settings, ClutterInputDevice *device, ConfigDoubleFunc func, gdouble value) { func (input_settings, device, value); } static void settings_set_double_setting (MetaInputSettings *input_settings, ClutterInputDeviceType type, ConfigDoubleFunc func, gdouble value) { GSList *devices, *d; devices = meta_input_settings_get_devices (input_settings, type); for (d = devices; d; d = d->next) settings_device_set_double_setting (input_settings, d->data, func, value); g_slist_free (devices); } static void settings_device_set_uint_setting (MetaInputSettings *input_settings, ClutterInputDevice *device, ConfigUintFunc func, guint value) { (func) (input_settings, device, value); } static void settings_set_uint_setting (MetaInputSettings *input_settings, ClutterInputDeviceType type, ConfigUintFunc func, guint value) { GSList *devices, *d; devices = meta_input_settings_get_devices (input_settings, type); for (d = devices; d; d = d->next) settings_device_set_uint_setting (input_settings, d->data, func, value); g_slist_free (devices); } static void update_touchpad_left_handed (MetaInputSettings *input_settings, ClutterInputDevice *device) { MetaInputSettingsClass *input_settings_class; GDesktopTouchpadHandedness handedness; MetaInputSettingsPrivate *priv; gboolean enabled = FALSE; if (device && clutter_input_device_get_device_type (device) != CLUTTER_TOUCHPAD_DEVICE) return; priv = meta_input_settings_get_instance_private (input_settings); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); handedness = g_settings_get_enum (priv->touchpad_settings, "left-handed"); switch (handedness) { case G_DESKTOP_TOUCHPAD_HANDEDNESS_RIGHT: enabled = FALSE; break; case G_DESKTOP_TOUCHPAD_HANDEDNESS_LEFT: enabled = TRUE; break; case G_DESKTOP_TOUCHPAD_HANDEDNESS_MOUSE: enabled = g_settings_get_boolean (priv->mouse_settings, "left-handed"); break; default: g_assert_not_reached (); } if (device) { settings_device_set_bool_setting (input_settings, device, input_settings_class->set_left_handed, enabled); } else { settings_set_bool_setting (input_settings, CLUTTER_TOUCHPAD_DEVICE, input_settings_class->set_left_handed, enabled); } } static void update_mouse_left_handed (MetaInputSettings *input_settings, ClutterInputDevice *device) { MetaInputSettingsClass *input_settings_class; MetaInputSettingsPrivate *priv; gboolean enabled; if (device && clutter_input_device_get_device_type (device) != CLUTTER_POINTER_DEVICE) return; priv = meta_input_settings_get_instance_private (input_settings); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); enabled = g_settings_get_boolean (priv->mouse_settings, "left-handed"); if (device) { settings_device_set_bool_setting (input_settings, device, input_settings_class->set_left_handed, enabled); } else { GDesktopTouchpadHandedness touchpad_handedness; settings_set_bool_setting (input_settings, CLUTTER_POINTER_DEVICE, input_settings_class->set_left_handed, enabled); touchpad_handedness = g_settings_get_enum (priv->touchpad_settings, "left-handed"); /* Also update touchpads if they're following mouse settings */ if (touchpad_handedness == G_DESKTOP_TOUCHPAD_HANDEDNESS_MOUSE) update_touchpad_left_handed (input_settings, NULL); } } static GSettings * get_settings_for_device_type (MetaInputSettings *input_settings, ClutterInputDeviceType type) { MetaInputSettingsPrivate *priv; priv = meta_input_settings_get_instance_private (input_settings); switch (type) { case CLUTTER_POINTER_DEVICE: return priv->mouse_settings; case CLUTTER_TOUCHPAD_DEVICE: return priv->touchpad_settings; default: return NULL; } } static void update_device_speed (MetaInputSettings *input_settings, ClutterInputDevice *device) { GSettings *settings; ConfigDoubleFunc func; const gchar *key = "speed"; func = META_INPUT_SETTINGS_GET_CLASS (input_settings)->set_speed; if (device) { settings = get_settings_for_device_type (input_settings, clutter_input_device_get_device_type (device)); if (!settings) return; settings_device_set_double_setting (input_settings, device, func, g_settings_get_double (settings, key)); } else { settings = get_settings_for_device_type (input_settings, CLUTTER_POINTER_DEVICE); settings_set_double_setting (input_settings, CLUTTER_POINTER_DEVICE, func, g_settings_get_double (settings, key)); settings = get_settings_for_device_type (input_settings, CLUTTER_TOUCHPAD_DEVICE); settings_set_double_setting (input_settings, CLUTTER_TOUCHPAD_DEVICE, func, g_settings_get_double (settings, key)); } } static void update_device_natural_scroll (MetaInputSettings *input_settings, ClutterInputDevice *device) { GSettings *settings; ConfigBoolFunc func; const gchar *key = "natural-scroll"; func = META_INPUT_SETTINGS_GET_CLASS (input_settings)->set_invert_scroll; if (device) { settings = get_settings_for_device_type (input_settings, clutter_input_device_get_device_type (device)); if (!settings) return; settings_device_set_bool_setting (input_settings, device, func, g_settings_get_boolean (settings, key)); } else { settings = get_settings_for_device_type (input_settings, CLUTTER_POINTER_DEVICE); settings_set_bool_setting (input_settings, CLUTTER_POINTER_DEVICE, func, g_settings_get_boolean (settings, key)); settings = get_settings_for_device_type (input_settings, CLUTTER_TOUCHPAD_DEVICE); settings_set_bool_setting (input_settings, CLUTTER_TOUCHPAD_DEVICE, func, g_settings_get_boolean (settings, key)); } } static void update_touchpad_tap_enabled (MetaInputSettings *input_settings, ClutterInputDevice *device) { MetaInputSettingsClass *input_settings_class; MetaInputSettingsPrivate *priv; gboolean enabled; if (device && clutter_input_device_get_device_type (device) != CLUTTER_TOUCHPAD_DEVICE) return; priv = meta_input_settings_get_instance_private (input_settings); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); enabled = g_settings_get_boolean (priv->touchpad_settings, "tap-to-click"); if (device) { settings_device_set_bool_setting (input_settings, device, input_settings_class->set_tap_enabled, enabled); } else { settings_set_bool_setting (input_settings, CLUTTER_TOUCHPAD_DEVICE, input_settings_class->set_tap_enabled, enabled); } } static void update_touchpad_edge_scroll (MetaInputSettings *input_settings, ClutterInputDevice *device) { MetaInputSettingsClass *input_settings_class; gboolean edge_scroll_enabled; MetaInputSettingsPrivate *priv; if (device && clutter_input_device_get_device_type (device) != CLUTTER_TOUCHPAD_DEVICE) return; priv = meta_input_settings_get_instance_private (input_settings); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); edge_scroll_enabled = g_settings_get_boolean (priv->touchpad_settings, "edge-scrolling-enabled"); if (device) { settings_device_set_bool_setting (input_settings, device, input_settings_class->set_edge_scroll, edge_scroll_enabled); } else { settings_set_bool_setting (input_settings, CLUTTER_TOUCHPAD_DEVICE, (ConfigBoolFunc) input_settings_class->set_edge_scroll, edge_scroll_enabled); } } static void update_touchpad_click_method (MetaInputSettings *input_settings, ClutterInputDevice *device) { MetaInputSettingsClass *input_settings_class; GDesktopTouchpadScrollMethod method; MetaInputSettingsPrivate *priv; if (device && clutter_input_device_get_device_type (device) != CLUTTER_TOUCHPAD_DEVICE) return; priv = meta_input_settings_get_instance_private (input_settings); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); method = g_settings_get_enum (priv->touchpad_settings, "click-method"); if (device) { settings_device_set_uint_setting (input_settings, device, input_settings_class->set_click_method, method); } else { settings_set_uint_setting (input_settings, CLUTTER_TOUCHPAD_DEVICE, (ConfigUintFunc) input_settings_class->set_click_method, method); } } static void update_touchpad_send_events (MetaInputSettings *input_settings, ClutterInputDevice *device) { MetaInputSettingsClass *input_settings_class; MetaInputSettingsPrivate *priv; GDesktopDeviceSendEvents mode; if (device && clutter_input_device_get_device_type (device) != CLUTTER_TOUCHPAD_DEVICE) return; priv = meta_input_settings_get_instance_private (input_settings); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); mode = g_settings_get_enum (priv->touchpad_settings, "send-events"); if (device) { settings_device_set_uint_setting (input_settings, device, input_settings_class->set_send_events, mode); } else { settings_set_uint_setting (input_settings, CLUTTER_TOUCHPAD_DEVICE, input_settings_class->set_send_events, mode); } } static gboolean device_is_trackball (ClutterInputDevice *device) { gboolean is_trackball; char *name; if (clutter_input_device_get_device_mode (device) == CLUTTER_INPUT_MODE_MASTER) return FALSE; name = g_ascii_strdown (clutter_input_device_get_device_name (device), -1); is_trackball = strstr (name, "trackball") != NULL; g_free (name); return is_trackball; } static void update_trackball_scroll_button (MetaInputSettings *input_settings, ClutterInputDevice *device) { MetaInputSettingsClass *input_settings_class; MetaInputSettingsPrivate *priv; guint button; if (device && !device_is_trackball (device)) return; priv = meta_input_settings_get_instance_private (input_settings); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); /* This key is 'i' in the schema but it also specifies a minimum * range of 0 so the cast here is safe. */ button = (guint) g_settings_get_int (priv->trackball_settings, "scroll-wheel-emulation-button"); if (device) { input_settings_class->set_scroll_button (input_settings, device, button); } else if (!device) { const GSList *devices; devices = clutter_device_manager_peek_devices (priv->device_manager); while (devices) { device = devices->data; if (device_is_trackball (device)) input_settings_class->set_scroll_button (input_settings, device, button); devices = devices->next; } } } static void update_keyboard_repeat (MetaInputSettings *input_settings) { MetaInputSettingsClass *input_settings_class; MetaInputSettingsPrivate *priv; guint delay, interval; gboolean repeat; priv = meta_input_settings_get_instance_private (input_settings); repeat = g_settings_get_boolean (priv->keyboard_settings, "repeat"); delay = g_settings_get_uint (priv->keyboard_settings, "delay"); interval = g_settings_get_uint (priv->keyboard_settings, "repeat-interval"); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); input_settings_class->set_keyboard_repeat (input_settings, repeat, delay, interval); } static MetaOutput * meta_input_settings_find_output (MetaInputSettings *input_settings, GSettings *settings, ClutterInputDevice *device) { MetaInputSettingsPrivate *priv; guint n_values, n_outputs, i; MetaOutput *outputs; gchar **edid; priv = meta_input_settings_get_instance_private (input_settings); edid = g_settings_get_strv (settings, "display"); n_values = g_strv_length (edid); if (n_values != 3) { g_warning ("EDID configuration for device '%s' " "is incorrect, must have 3 values", clutter_input_device_get_device_name (device)); return NULL; } if (!*edid[0] && !*edid[1] && !*edid[2]) return NULL; outputs = meta_monitor_manager_get_outputs (priv->monitor_manager, &n_outputs); for (i = 0; i < n_outputs; i++) { if (g_strcmp0 (outputs[i].vendor, edid[0]) == 0 && g_strcmp0 (outputs[i].product, edid[1]) == 0 && g_strcmp0 (outputs[i].serial, edid[2]) == 0) return &outputs[i]; } return NULL; } static void update_device_display (MetaInputSettings *input_settings, GSettings *settings, ClutterInputDevice *device) { MetaInputSettingsClass *input_settings_class; MetaInputSettingsPrivate *priv; gfloat matrix[6] = { 1, 0, 0, 0, 1, 0 }; MetaOutput *output; priv = meta_input_settings_get_instance_private (input_settings); input_settings_class = META_INPUT_SETTINGS_GET_CLASS (input_settings); output = meta_input_settings_find_output (input_settings, settings, device); if (output) meta_monitor_manager_get_monitor_matrix (priv->monitor_manager, output, matrix); input_settings_class->set_matrix (input_settings, device, matrix); } static void meta_input_settings_changed_cb (GSettings *settings, const char *key, gpointer user_data) { MetaInputSettings *input_settings = META_INPUT_SETTINGS (user_data); MetaInputSettingsPrivate *priv = meta_input_settings_get_instance_private (input_settings); if (settings == priv->mouse_settings) { if (strcmp (key, "left-handed") == 0) update_mouse_left_handed (input_settings, NULL); else if (strcmp (key, "speed") == 0) update_device_speed (input_settings, NULL); else if (strcmp (key, "natural-scroll") == 0) update_device_natural_scroll (input_settings, NULL); } else if (settings == priv->touchpad_settings) { if (strcmp (key, "left-handed") == 0) update_touchpad_left_handed (input_settings, NULL); else if (strcmp (key, "speed") == 0) update_device_speed (input_settings, NULL); else if (strcmp (key, "natural-scroll") == 0) update_device_natural_scroll (input_settings, NULL); else if (strcmp (key, "tap-to-click") == 0) update_touchpad_tap_enabled (input_settings, NULL); else if (strcmp (key, "send-events") == 0) update_touchpad_send_events (input_settings, NULL); else if (strcmp (key, "edge-scrolling-enabled") == 0) update_touchpad_edge_scroll (input_settings, NULL); else if (strcmp (key, "click-method") == 0) update_touchpad_click_method (input_settings, NULL); } else if (settings == priv->trackball_settings) { if (strcmp (key, "scroll-wheel-emulation-button") == 0) update_trackball_scroll_button (input_settings, NULL); } else if (settings == priv->keyboard_settings) { if (strcmp (key, "repeat") == 0 || strcmp (key, "repeat-interval") == 0 || strcmp (key, "delay") == 0) update_keyboard_repeat (input_settings); } } static void mapped_device_changed_cb (GSettings *settings, const gchar *key, DeviceMappingInfo *info) { if (strcmp (key, "display") == 0) update_device_display (info->input_settings, settings, info->device); } static GSettings * lookup_device_settings (ClutterInputDevice *device) { const gchar *group, *schema, *vendor, *product; ClutterInputDeviceType type; GSettings *settings; gchar *path; type = clutter_input_device_get_device_type (device); if (type == CLUTTER_TOUCHSCREEN_DEVICE) { group = "touchscreens"; schema = "org.gnome.desktop.peripherals.touchscreen"; } else if (type == CLUTTER_TABLET_DEVICE || type == CLUTTER_PEN_DEVICE || type == CLUTTER_ERASER_DEVICE || type == CLUTTER_CURSOR_DEVICE) { group = "tablets"; schema = "org.gnome.desktop.peripherals.tablet"; } else return NULL; vendor = clutter_input_device_get_vendor_id (device); product = clutter_input_device_get_product_id (device); path = g_strdup_printf ("/org/gnome/desktop/peripherals/%s/%s:%s/", group, vendor, product); settings = g_settings_new_with_path (schema, path); g_free (path); return settings; } static void monitors_changed_cb (MetaMonitorManager *monitor_manager, MetaInputSettings *input_settings) { MetaInputSettingsPrivate *priv; ClutterInputDevice *device; GSettings *settings; GHashTableIter iter; priv = meta_input_settings_get_instance_private (input_settings); g_hash_table_iter_init (&iter, priv->mappable_devices); while (g_hash_table_iter_next (&iter, (gpointer *) &device, (gpointer *) &settings)) update_device_display (input_settings, settings, device); } static gboolean check_add_mappable_device (MetaInputSettings *input_settings, ClutterInputDevice *device) { MetaInputSettingsPrivate *priv; DeviceMappingInfo *info; GSettings *settings; settings = lookup_device_settings (device); if (!settings) return FALSE; priv = meta_input_settings_get_instance_private (input_settings); info = g_new0 (DeviceMappingInfo, 1); info->input_settings = input_settings; info->device = device; info->settings = settings; g_signal_connect_data (settings, "changed", G_CALLBACK (mapped_device_changed_cb), info, (GClosureNotify) g_free, 0); g_hash_table_insert (priv->mappable_devices, device, settings); update_device_display (input_settings, settings, device); return TRUE; } static void apply_device_settings (MetaInputSettings *input_settings, ClutterInputDevice *device) { update_mouse_left_handed (input_settings, device); update_device_speed (input_settings, device); update_device_natural_scroll (input_settings, device); update_touchpad_left_handed (input_settings, device); update_device_speed (input_settings, device); update_device_natural_scroll (input_settings, device); update_touchpad_tap_enabled (input_settings, device); update_touchpad_send_events (input_settings, device); update_touchpad_edge_scroll (input_settings, device); update_touchpad_click_method (input_settings, device); update_trackball_scroll_button (input_settings, device); } static void meta_input_settings_device_added (ClutterDeviceManager *device_manager, ClutterInputDevice *device, MetaInputSettings *input_settings) { if (clutter_input_device_get_device_mode (device) == CLUTTER_INPUT_MODE_MASTER) return; apply_device_settings (input_settings, device); check_add_mappable_device (input_settings, device); } static void meta_input_settings_device_removed (ClutterDeviceManager *device_manager, ClutterInputDevice *device, MetaInputSettings *input_settings) { MetaInputSettingsPrivate *priv; priv = meta_input_settings_get_instance_private (input_settings); g_hash_table_remove (priv->mappable_devices, device); } static void check_mappable_devices (MetaInputSettings *input_settings) { MetaInputSettingsPrivate *priv; const GSList *devices, *l; priv = meta_input_settings_get_instance_private (input_settings); devices = clutter_device_manager_peek_devices (priv->device_manager); for (l = devices; l; l = l->next) { ClutterInputDevice *device = l->data; if (clutter_input_device_get_device_mode (device) == CLUTTER_INPUT_MODE_MASTER) continue; check_add_mappable_device (input_settings, device); } } static void meta_input_settings_constructed (GObject *object) { MetaInputSettings *input_settings = META_INPUT_SETTINGS (object); apply_device_settings (input_settings, NULL); update_keyboard_repeat (input_settings); check_mappable_devices (input_settings); } static void meta_input_settings_class_init (MetaInputSettingsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = meta_input_settings_dispose; object_class->constructed = meta_input_settings_constructed; } static void meta_input_settings_init (MetaInputSettings *settings) { MetaInputSettingsPrivate *priv; priv = meta_input_settings_get_instance_private (settings); priv->device_manager = clutter_device_manager_get_default (); g_signal_connect (priv->device_manager, "device-added", G_CALLBACK (meta_input_settings_device_added), settings); g_signal_connect (priv->device_manager, "device-removed", G_CALLBACK (meta_input_settings_device_removed), settings); priv->mouse_settings = g_settings_new ("org.gnome.desktop.peripherals.mouse"); g_signal_connect (priv->mouse_settings, "changed", G_CALLBACK (meta_input_settings_changed_cb), settings); priv->touchpad_settings = g_settings_new ("org.gnome.desktop.peripherals.touchpad"); g_signal_connect (priv->touchpad_settings, "changed", G_CALLBACK (meta_input_settings_changed_cb), settings); priv->trackball_settings = g_settings_new ("org.gnome.desktop.peripherals.trackball"); g_signal_connect (priv->trackball_settings, "changed", G_CALLBACK (meta_input_settings_changed_cb), settings); priv->keyboard_settings = g_settings_new ("org.gnome.desktop.peripherals.keyboard"); g_signal_connect (priv->keyboard_settings, "changed", G_CALLBACK (meta_input_settings_changed_cb), settings); priv->mappable_devices = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_object_unref); priv->monitor_manager = g_object_ref (meta_monitor_manager_get ()); g_signal_connect (priv->monitor_manager, "monitors-changed", G_CALLBACK (monitors_changed_cb), settings); } MetaInputSettings * meta_input_settings_create (void) { #ifdef HAVE_NATIVE_BACKEND MetaBackend *backend; backend = meta_get_backend (); if (META_IS_BACKEND_NATIVE (backend)) return g_object_new (META_TYPE_INPUT_SETTINGS_NATIVE, NULL); #endif if (!meta_is_wayland_compositor ()) return g_object_new (META_TYPE_INPUT_SETTINGS_X11, NULL); return NULL; }