mutter/src/backends/x11/meta-input-settings-x11.c

759 lines
24 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2014 Red Hat
*
* 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.
*
* Author: Carlos Garnacho <carlosg@gnome.org>
*/
#include "config.h"
#include "meta-backend-x11.h"
#include "meta-input-settings-x11.h"
#include <string.h>
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
#include <X11/extensions/XInput2.h>
#include <X11/XKBlib.h>
#ifdef HAVE_LIBGUDEV
#include <gudev/gudev.h>
#endif
#include <meta/errors.h>
typedef struct _MetaInputSettingsX11Private
{
#ifdef HAVE_LIBGUDEV
GUdevClient *udev_client;
#endif
} MetaInputSettingsX11Private;
G_DEFINE_TYPE_WITH_PRIVATE (MetaInputSettingsX11, meta_input_settings_x11,
META_TYPE_INPUT_SETTINGS)
enum {
SCROLL_METHOD_FIELD_2FG,
SCROLL_METHOD_FIELD_EDGE,
SCROLL_METHOD_FIELD_BUTTON,
SCROLL_METHOD_NUM_FIELDS
};
static void *
get_property (ClutterInputDevice *device,
const gchar *property,
Atom type,
int format,
gulong nitems)
{
MetaBackend *backend = meta_get_backend ();
Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend));
gulong nitems_ret, bytes_after_ret;
int rc, device_id, format_ret;
Atom property_atom, type_ret;
guchar *data_ret = NULL;
property_atom = XInternAtom (xdisplay, property, True);
if (!property_atom)
return NULL;
device_id = clutter_input_device_get_device_id (device);
rc = XIGetProperty (xdisplay, device_id, property_atom,
0, 10, False, type, &type_ret, &format_ret,
&nitems_ret, &bytes_after_ret, &data_ret);
if (rc == Success && type_ret == type && format_ret == format && nitems_ret >= nitems)
{
if (nitems_ret > nitems)
g_warning ("Property '%s' for device '%s' returned %lu items, expected %lu",
property, clutter_input_device_get_device_name (device), nitems_ret, nitems);
return data_ret;
}
meta_XFree (data_ret);
return NULL;
}
static void
change_property (ClutterInputDevice *device,
const gchar *property,
Atom type,
int format,
void *data,
gulong nitems)
{
MetaBackend *backend = meta_get_backend ();
Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend));
int device_id;
Atom property_atom;
guchar *data_ret;
property_atom = XInternAtom (xdisplay, property, True);
if (!property_atom)
return;
device_id = clutter_input_device_get_device_id (device);
data_ret = get_property (device, property, type, format, nitems);
if (!data_ret)
return;
XIChangeProperty (xdisplay, device_id, property_atom, type,
format, XIPropModeReplace, data, nitems);
meta_XFree (data_ret);
}
static void
meta_input_settings_x11_set_send_events (MetaInputSettings *settings,
ClutterInputDevice *device,
GDesktopDeviceSendEvents mode)
{
guchar values[2] = { 0 }; /* disabled, disabled-on-external-mouse */
guchar *available;
available = get_property (device, "libinput Send Events Modes Available",
XA_INTEGER, 8, 2);
if (!available)
return;
switch (mode)
{
case G_DESKTOP_DEVICE_SEND_EVENTS_DISABLED:
values[0] = 1;
break;
case G_DESKTOP_DEVICE_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE:
values[1] = 1;
break;
default:
break;
}
if ((values[0] && !available[0]) || (values[1] && !available[1]))
g_warning ("Device '%s' does not support sendevents mode %d\n",
clutter_input_device_get_device_name (device), mode);
else
change_property (device, "libinput Send Events Mode Enabled",
XA_INTEGER, 8, &values, 2);
meta_XFree (available);
}
static void
meta_input_settings_x11_set_matrix (MetaInputSettings *settings,
ClutterInputDevice *device,
gfloat matrix[6])
{
MetaBackend *backend = meta_get_backend ();
Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend));
gfloat full_matrix[9] = { matrix[0], matrix[1], matrix[2],
matrix[3], matrix[4], matrix[5],
0, 0, 1 };
change_property (device, "Coordinate Transformation Matrix",
XInternAtom (xdisplay, "FLOAT", False),
32, &full_matrix, 9);
}
static void
meta_input_settings_x11_set_speed (MetaInputSettings *settings,
ClutterInputDevice *device,
gdouble speed)
{
MetaBackend *backend = meta_get_backend ();
Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend));
gfloat value = speed;
change_property (device, "libinput Accel Speed",
XInternAtom (xdisplay, "FLOAT", False),
32, &value, 1);
}
static void
meta_input_settings_x11_set_left_handed (MetaInputSettings *settings,
ClutterInputDevice *device,
gboolean enabled)
{
guchar value;
if (clutter_input_device_get_device_type (device) == CLUTTER_TABLET_DEVICE)
{
value = enabled ? 3 : 0;
change_property (device, "Wacom Rotation",
XA_INTEGER, 8, &value, 1);
}
else
{
value = enabled ? 1 : 0;
change_property (device, "libinput Left Handed Enabled",
XA_INTEGER, 8, &value, 1);
}
}
static void
meta_input_settings_x11_set_tap_enabled (MetaInputSettings *settings,
ClutterInputDevice *device,
gboolean enabled)
{
guchar value = (enabled) ? 1 : 0;
change_property (device, "libinput Tapping Enabled",
XA_INTEGER, 8, &value, 1);
}
static void
meta_input_settings_x11_set_invert_scroll (MetaInputSettings *settings,
ClutterInputDevice *device,
gboolean inverted)
{
guchar value = (inverted) ? 1 : 0;
change_property (device, "libinput Natural Scrolling Enabled",
XA_INTEGER, 8, &value, 1);
}
static void
meta_input_settings_x11_set_edge_scroll (MetaInputSettings *settings,
ClutterInputDevice *device,
gboolean edge_scroll_enabled)
{
guchar values[SCROLL_METHOD_NUM_FIELDS] = { 0 }; /* 2fg, edge, button. The last value is unused */
guchar *current = NULL;
guchar *available = NULL;
available = get_property (device, "libinput Scroll Methods Available",
XA_INTEGER, 8, SCROLL_METHOD_NUM_FIELDS);
if (!available || !available[SCROLL_METHOD_FIELD_EDGE])
goto out;
current = get_property (device, "libinput Scroll Method Enabled",
XA_INTEGER, 8, SCROLL_METHOD_NUM_FIELDS);
if (!current)
goto out;
memcpy (values, current, SCROLL_METHOD_NUM_FIELDS);
values[SCROLL_METHOD_FIELD_EDGE] = !!edge_scroll_enabled;
change_property (device, "libinput Scroll Method Enabled",
XA_INTEGER, 8, &values, SCROLL_METHOD_NUM_FIELDS);
out:
meta_XFree (current);
meta_XFree (available);
}
static void
meta_input_settings_x11_set_two_finger_scroll (MetaInputSettings *settings,
ClutterInputDevice *device,
gboolean two_finger_scroll_enabled)
{
guchar values[SCROLL_METHOD_NUM_FIELDS] = { 0 }; /* 2fg, edge, button. The last value is unused */
guchar *current = NULL;
guchar *available = NULL;
available = get_property (device, "libinput Scroll Methods Available",
XA_INTEGER, 8, SCROLL_METHOD_NUM_FIELDS);
if (!available || !available[SCROLL_METHOD_FIELD_2FG])
goto out;
current = get_property (device, "libinput Scroll Method Enabled",
XA_INTEGER, 8, SCROLL_METHOD_NUM_FIELDS);
if (!current)
goto out;
memcpy (values, current, SCROLL_METHOD_NUM_FIELDS);
values[SCROLL_METHOD_FIELD_2FG] = !!two_finger_scroll_enabled;
change_property (device, "libinput Scroll Method Enabled",
XA_INTEGER, 8, &values, SCROLL_METHOD_NUM_FIELDS);
out:
meta_XFree (current);
meta_XFree (available);
}
static void
meta_input_settings_x11_set_scroll_button (MetaInputSettings *settings,
ClutterInputDevice *device,
guint button)
{
change_property (device, "libinput Button Scrolling Button",
XA_INTEGER, 32, &button, 1);
}
static void
meta_input_settings_x11_set_click_method (MetaInputSettings *settings,
ClutterInputDevice *device,
GDesktopTouchpadClickMethod mode)
{
guchar values[2] = { 0 }; /* buttonareas, clickfinger */
guchar *defaults, *available;
available = get_property (device, "libinput Click Methods Available",
XA_INTEGER, 8, 2);
if (!available)
return;
switch (mode)
{
case G_DESKTOP_TOUCHPAD_CLICK_METHOD_DEFAULT:
defaults = get_property (device, "libinput Click Method Enabled Default",
XA_INTEGER, 8, 2);
if (!defaults)
break;
memcpy (values, defaults, 2);
meta_XFree (defaults);
break;
case G_DESKTOP_TOUCHPAD_CLICK_METHOD_NONE:
break;
case G_DESKTOP_TOUCHPAD_CLICK_METHOD_AREAS:
values[0] = 1;
break;
case G_DESKTOP_TOUCHPAD_CLICK_METHOD_FINGERS:
values[1] = 1;
break;
default:
g_assert_not_reached ();
return;
}
if ((values[0] && !available[0]) || (values[1] && !available[1]))
g_warning ("Device '%s' does not support click method %d\n",
clutter_input_device_get_device_name (device), mode);
else
change_property (device, "libinput Click Method Enabled",
XA_INTEGER, 8, &values, 2);
meta_XFree(available);
}
static void
meta_input_settings_x11_set_keyboard_repeat (MetaInputSettings *settings,
gboolean enabled,
guint delay,
guint interval)
{
MetaBackend *backend = meta_get_backend ();
Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend));
if (enabled)
{
XAutoRepeatOn (xdisplay);
XkbSetAutoRepeatRate (xdisplay, XkbUseCoreKbd, delay, interval);
}
else
{
XAutoRepeatOff (xdisplay);
}
}
static gboolean
has_udev_property (MetaInputSettings *settings,
ClutterInputDevice *device,
const char *property_name)
{
#ifdef HAVE_LIBGUDEV
MetaInputSettingsX11 *settings_x11 = META_INPUT_SETTINGS_X11 (settings);
MetaInputSettingsX11Private *priv =
meta_input_settings_x11_get_instance_private (settings_x11);
const char *device_node;
GUdevDevice *udev_device = NULL;
GUdevDevice *parent_udev_device = NULL;
device_node = clutter_input_device_get_device_node (device);
if (!device_node)
return FALSE;
udev_device = g_udev_client_query_by_device_file (priv->udev_client,
device_node);
if (!udev_device)
return FALSE;
if (NULL != g_udev_device_get_property (udev_device, property_name))
{
g_object_unref (udev_device);
return TRUE;
}
parent_udev_device = g_udev_device_get_parent (udev_device);
g_object_unref (udev_device);
if (!parent_udev_device)
return FALSE;
if (NULL != g_udev_device_get_property (parent_udev_device, property_name))
{
g_object_unref (parent_udev_device);
return TRUE;
}
g_object_unref (parent_udev_device);
return FALSE;
#else
g_warning ("Failed to set acceleration profile: no udev support");
return FALSE;
#endif
}
static gboolean
is_mouse (MetaInputSettings *settings,
ClutterInputDevice *device)
{
return (has_udev_property (settings, device, "ID_INPUT_MOUSE") &&
!has_udev_property (settings, device, "ID_INPUT_POINTINGSTICK"));
}
static gboolean
is_trackball (MetaInputSettings *settings,
ClutterInputDevice *device)
{
return meta_input_device_is_trackball (device);
}
static void
set_device_accel_profile (ClutterInputDevice *device,
GDesktopPointerAccelProfile profile)
{
guchar *defaults, *available;
guchar values[2] = { 0 }; /* adaptive, flat */
defaults = get_property (device, "libinput Accel Profile Enabled Default",
XA_INTEGER, 8, 2);
if (!defaults)
return;
available = get_property (device, "libinput Accel Profiles Available",
XA_INTEGER, 8, 2);
if (!available)
goto err_available;
switch (profile)
{
case G_DESKTOP_POINTER_ACCEL_PROFILE_FLAT:
values[0] = 0;
values[1] = 1;
break;
case G_DESKTOP_POINTER_ACCEL_PROFILE_ADAPTIVE:
values[0] = 1;
values[1] = 0;
break;
default:
g_warn_if_reached ();
case G_DESKTOP_POINTER_ACCEL_PROFILE_DEFAULT:
values[0] = defaults[0];
values[1] = defaults[1];
break;
}
change_property (device, "libinput Accel Profile Enabled",
XA_INTEGER, 8, &values, 2);
meta_XFree (available);
err_available:
meta_XFree (defaults);
}
static void
meta_input_settings_x11_set_mouse_accel_profile (MetaInputSettings *settings,
ClutterInputDevice *device,
GDesktopPointerAccelProfile profile)
{
if (!is_mouse (settings, device))
return;
set_device_accel_profile (device, profile);
}
static void
meta_input_settings_x11_set_trackball_accel_profile (MetaInputSettings *settings,
ClutterInputDevice *device,
GDesktopPointerAccelProfile profile)
{
if (!is_trackball (settings, device))
return;
set_device_accel_profile (device, profile);
}
static void
meta_input_settings_x11_set_tablet_mapping (MetaInputSettings *settings,
ClutterInputDevice *device,
GDesktopTabletMapping mapping)
{
MetaDisplay *display = meta_get_display ();
MetaBackend *backend = meta_get_backend ();
Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend));
int device_id = clutter_input_device_get_device_id (device);
XDevice *xdev;
if (!display)
return;
/* Grab the puke bucket! */
meta_error_trap_push (display);
xdev = XOpenDevice (xdisplay, device_id);
if (xdev)
{
XSetDeviceMode (xdisplay, xdev,
mapping == G_DESKTOP_TABLET_MAPPING_ABSOLUTE ?
Absolute : Relative);
XCloseDevice (xdisplay, xdev);
}
if (meta_error_trap_pop_with_return (display))
{
g_warning ("Could not set tablet mapping for %s",
clutter_input_device_get_device_name (device));
}
}
static gboolean
device_query_area (ClutterInputDevice *device,
gint *width,
gint *height)
{
MetaBackend *backend = meta_get_backend ();
Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend));
gint device_id, n_devices, i;
XIDeviceInfo *info;
Atom abs_x, abs_y;
*width = *height = 0;
device_id = clutter_input_device_get_device_id (device);
info = XIQueryDevice (xdisplay, device_id, &n_devices);
if (n_devices == 0)
return FALSE;
abs_x = XInternAtom (xdisplay, "Abs X", True);
abs_y = XInternAtom (xdisplay, "Abs Y", True);
for (i = 0; i < info->num_classes; i++)
{
XIValuatorClassInfo *valuator = (XIValuatorClassInfo *) info->classes[i];
if (valuator->type != XIValuatorClass)
continue;
if (valuator->label == abs_x)
*width = valuator->max - valuator->min;
else if (valuator->label == abs_y)
*height = valuator->max - valuator->min;
}
XIFreeDeviceInfo (info);
return TRUE;
}
static void
update_tablet_area (MetaInputSettings *settings,
ClutterInputDevice *device,
gint32 *area)
{
change_property (device, "Wacom Tablet Area",
XA_INTEGER, 32, area, 4);
}
static void
meta_input_settings_x11_set_tablet_area (MetaInputSettings *settings,
ClutterInputDevice *device,
gdouble padding_left,
gdouble padding_right,
gdouble padding_top,
gdouble padding_bottom)
{
gint32 width, height, area[4] = { 0 };
if (!device_query_area (device, &width, &height))
return;
area[0] = width * padding_left;
area[1] = height * padding_top;
area[2] = width - (width * padding_right);
area[2] = height - (height * padding_bottom);
update_tablet_area (settings, device, area);
}
static void
meta_input_settings_x11_set_tablet_keep_aspect (MetaInputSettings *settings,
ClutterInputDevice *device,
MetaOutput *output,
gboolean keep_aspect)
{
gint32 width, height, dev_width, dev_height, area[4] = { 0 };
if (!device_query_area (device, &dev_width, &dev_height))
return;
if (keep_aspect)
{
gdouble output_aspect, dev_aspect;
if (output && output->crtc)
{
width = output->crtc->rect.width;
height = output->crtc->rect.height;
}
else
{
MetaMonitorManager *monitor_manager;
MetaBackend *backend;
backend = meta_get_backend ();
monitor_manager = meta_backend_get_monitor_manager (backend);
meta_monitor_manager_get_screen_limits (monitor_manager,
&width, &height);
}
output_aspect = (gdouble) width / height;
dev_aspect = (gdouble) dev_width / dev_height;
if (dev_aspect > output_aspect)
dev_width = dev_height * output_aspect;
else if (dev_aspect < output_aspect)
dev_height = dev_width / output_aspect;
}
area[2] = dev_width;
area[3] = dev_height;
update_tablet_area (settings, device, area);
}
static void
meta_input_settings_x11_dispose (GObject *object)
{
#ifdef HAVE_LIBGUDEV
MetaInputSettingsX11 *settings_x11 = META_INPUT_SETTINGS_X11 (object);
MetaInputSettingsX11Private *priv =
meta_input_settings_x11_get_instance_private (settings_x11);
g_clear_object (&priv->udev_client);
#endif
G_OBJECT_CLASS (meta_input_settings_x11_parent_class)->dispose (object);
}
static guint
action_to_button (GDesktopStylusButtonAction action,
guint button)
{
switch (action)
{
case G_DESKTOP_STYLUS_BUTTON_ACTION_MIDDLE:
return CLUTTER_BUTTON_MIDDLE;
case G_DESKTOP_STYLUS_BUTTON_ACTION_RIGHT:
return CLUTTER_BUTTON_SECONDARY;
case G_DESKTOP_STYLUS_BUTTON_ACTION_BACK:
return 8;
case G_DESKTOP_STYLUS_BUTTON_ACTION_FORWARD:
return 9;
case G_DESKTOP_STYLUS_BUTTON_ACTION_DEFAULT:
default:
return button;
}
}
static void
meta_input_settings_x11_set_stylus_button_map (MetaInputSettings *settings,
ClutterInputDevice *device,
ClutterInputDeviceTool *tool,
GDesktopStylusButtonAction primary,
GDesktopStylusButtonAction secondary)
{
MetaDisplay *display = meta_get_display ();
MetaBackend *backend = meta_get_backend ();
Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend));
int device_id = clutter_input_device_get_device_id (device);
XDevice *xdev;
if (!display)
return;
/* Grab the puke bucket! */
meta_error_trap_push (display);
xdev = XOpenDevice (xdisplay, device_id);
if (xdev)
{
guchar map[3] = {
CLUTTER_BUTTON_PRIMARY,
action_to_button (primary, CLUTTER_BUTTON_MIDDLE),
action_to_button (secondary, CLUTTER_BUTTON_SECONDARY),
};
XSetDeviceButtonMapping (xdisplay, xdev, map, G_N_ELEMENTS (map));
XCloseDevice (xdisplay, xdev);
}
if (meta_error_trap_pop_with_return (display))
{
g_warning ("Could not set stylus button map for %s",
clutter_input_device_get_device_name (device));
}
}
static void
meta_input_settings_x11_set_stylus_pressure (MetaInputSettings *settings,
ClutterInputDevice *device,
ClutterInputDeviceTool *tool,
const gint32 pressure[4])
{
guchar values[4] = { pressure[0], pressure[1], pressure[2], pressure[3] };
change_property (device, "Wacom Pressurecurve", XA_INTEGER, 8,
&values, G_N_ELEMENTS (values));
}
static void
meta_input_settings_x11_class_init (MetaInputSettingsX11Class *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MetaInputSettingsClass *input_settings_class = META_INPUT_SETTINGS_CLASS (klass);
object_class->dispose = meta_input_settings_x11_dispose;
input_settings_class->set_send_events = meta_input_settings_x11_set_send_events;
input_settings_class->set_matrix = meta_input_settings_x11_set_matrix;
input_settings_class->set_speed = meta_input_settings_x11_set_speed;
input_settings_class->set_left_handed = meta_input_settings_x11_set_left_handed;
input_settings_class->set_tap_enabled = meta_input_settings_x11_set_tap_enabled;
input_settings_class->set_invert_scroll = meta_input_settings_x11_set_invert_scroll;
input_settings_class->set_edge_scroll = meta_input_settings_x11_set_edge_scroll;
input_settings_class->set_two_finger_scroll = meta_input_settings_x11_set_two_finger_scroll;
input_settings_class->set_scroll_button = meta_input_settings_x11_set_scroll_button;
input_settings_class->set_click_method = meta_input_settings_x11_set_click_method;
input_settings_class->set_keyboard_repeat = meta_input_settings_x11_set_keyboard_repeat;
input_settings_class->set_tablet_mapping = meta_input_settings_x11_set_tablet_mapping;
input_settings_class->set_tablet_keep_aspect = meta_input_settings_x11_set_tablet_keep_aspect;
input_settings_class->set_tablet_area = meta_input_settings_x11_set_tablet_area;
input_settings_class->set_mouse_accel_profile = meta_input_settings_x11_set_mouse_accel_profile;
input_settings_class->set_trackball_accel_profile = meta_input_settings_x11_set_trackball_accel_profile;
input_settings_class->set_stylus_pressure = meta_input_settings_x11_set_stylus_pressure;
input_settings_class->set_stylus_button_map = meta_input_settings_x11_set_stylus_button_map;
}
static void
meta_input_settings_x11_init (MetaInputSettingsX11 *settings)
{
#ifdef HAVE_LIBGUDEV
MetaInputSettingsX11Private *priv =
meta_input_settings_x11_get_instance_private (settings);
const char *subsystems[] = { NULL };
priv->udev_client = g_udev_client_new (subsystems);
#endif
}