From f5aa66f135c9e7e9c57363c8933e6a87d0900112 Mon Sep 17 00:00:00 2001
From: Frederic Crozat <fcrozat@suse.com>
Date: Fri, 4 Mar 2016 15:47:30 +0100
Subject: [PATCH 1/3] Revert "mouse: Remove support for non-libinput mouse
 configurations"

This reverts commit 66c211ff24bec6a938d6a6a0dd8730f4689ef383.
---
 plugins/mouse/gsd-mouse-manager.c | 1255 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 1252 insertions(+), 3 deletions(-)

diff --git a/plugins/mouse/gsd-mouse-manager.c b/plugins/mouse/gsd-mouse-manager.c
index cf2c28c..da7b378 100644
--- a/plugins/mouse/gsd-mouse-manager.c
+++ b/plugins/mouse/gsd-mouse-manager.c
@@ -19,17 +19,39 @@
 
 #include "config.h"
 
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#ifdef __linux
+#include <sys/prctl.h>
+#endif
+
 #include <locale.h>
 
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdkkeysyms.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
 
 #include <gdesktop-enums.h>
 
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XIproto.h>
+
 #include "gnome-settings-bus.h"
 #include "gnome-settings-profile.h"
 #include "gsd-mouse-manager.h"
+#include "gsd-input-helper.h"
 #include "gsd-enums.h"
 #include "gsd-settings-migrate.h"
 
@@ -38,20 +60,42 @@
 #define GSD_SETTINGS_MOUSE_SCHEMA  "org.gnome.settings-daemon.peripherals.mouse"
 #define GSETTINGS_MOUSE_SCHEMA     "org.gnome.desktop.peripherals.mouse"
 #define GSETTINGS_TOUCHPAD_SCHEMA  "org.gnome.desktop.peripherals.touchpad"
+#define GSETTINGS_TRACKBALL_SCHEMA "org.gnome.desktop.peripherals.trackball"
+
+/* Keys for both touchpad and mouse */
+#define KEY_LEFT_HANDED         "left-handed"                /* a boolean for mouse, an enum for touchpad */
+#define KEY_SPEED               "speed"
+
+/* Touchpad settings */
+#define KEY_EDGE_SCROLLING_ENABLED       "edge-scrolling-enabled"
+#define KEY_TAP_TO_CLICK                 "tap-to-click"
+#define KEY_SEND_EVENTS                  "send-events"
+#define KEY_NATURAL_SCROLL_ENABLED       "natural-scroll"
 
 /* Mouse settings */
 #define KEY_LOCATE_POINTER               "locate-pointer"
 #define KEY_DWELL_CLICK_ENABLED          "dwell-click-enabled"
 #define KEY_SECONDARY_CLICK_ENABLED      "secondary-click-enabled"
 
+/* Trackball settings */
+#define KEY_SCROLL_WHEEL_BUTTON          "scroll-wheel-emulation-button"
+
 struct GsdMouseManagerPrivate
 {
         guint start_idle_id;
         GSettings *touchpad_settings;
-        GSettings *mouse_a11y_settings;
         GSettings *mouse_settings;
+        GSettings *mouse_a11y_settings;
+        GSettings *trackball_settings;
         GSettings *gsd_mouse_settings;
+        GdkDeviceManager *device_manager;
+        guint device_added_id;
+        guint device_removed_id;
+        GHashTable *blacklist;
+
         gboolean mousetweaks_daemon_running;
+        gboolean syndaemon_spawned;
+        GPid syndaemon_pid;
         gboolean locate_pointer_spawned;
         GPid locate_pointer_pid;
 };
@@ -59,6 +103,12 @@ struct GsdMouseManagerPrivate
 static void     gsd_mouse_manager_class_init  (GsdMouseManagerClass *klass);
 static void     gsd_mouse_manager_init        (GsdMouseManager      *mouse_manager);
 static void     gsd_mouse_manager_finalize    (GObject             *object);
+static void     set_tap_to_click              (GdkDevice           *device,
+                                               gboolean             state,
+                                               gboolean             left_handed);
+static void     ensure_touchpad_active        (GsdMouseManager     *manager);
+static gboolean get_touchpad_enabled          (GsdMouseManager     *manager);
+
 
 G_DEFINE_TYPE (GsdMouseManager, gsd_mouse_manager, G_TYPE_OBJECT)
 
@@ -76,6 +126,775 @@ gsd_mouse_manager_class_init (GsdMouseManagerClass *klass)
         g_type_class_add_private (klass, sizeof (GsdMouseManagerPrivate));
 }
 
+static XDevice *
+open_gdk_device (GdkDevice *device)
+{
+        XDevice *xdevice;
+        int id;
+
+        g_object_get (G_OBJECT (device), "device-id", &id, NULL);
+
+        gdk_error_trap_push ();
+
+        xdevice = XOpenDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), id);
+
+        if (gdk_error_trap_pop () != 0)
+                return NULL;
+
+        return xdevice;
+}
+
+static gboolean
+device_info_is_trackball (XDeviceInfo *device_info)
+{
+        gboolean retval;
+
+        retval = (device_info->type == XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), XI_TRACKBALL, False));
+        if (retval == FALSE &&
+            device_info->name != NULL) {
+                char *lowercase;
+
+                lowercase = g_ascii_strdown (device_info->name, -1);
+                retval = strstr (lowercase, "trackball") != NULL;
+                g_free (lowercase);
+        }
+
+        return retval;
+}
+
+static gboolean
+device_is_trackball (GdkDevice *device)
+{
+        XDeviceInfo *device_info;
+        gboolean retval = FALSE;
+        gint n_devices;
+        guint i;
+        int id;
+
+        g_object_get (G_OBJECT (device), "device-id", &id, NULL);
+
+        gdk_error_trap_push ();
+
+        device_info = XListInputDevices (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &n_devices);
+        if (device_info == NULL)
+                return retval;
+
+        for (i = 0; i < n_devices; i++) {
+                if (device_info[i].id != id)
+                        continue;
+
+                retval = device_info_is_trackball (&device_info[i]);
+                break;
+        }
+        XFreeDeviceList (device_info);
+
+        if (gdk_error_trap_pop () != 0)
+                return FALSE;
+
+        return retval;
+}
+
+static gboolean
+device_is_blacklisted (GsdMouseManager *manager,
+                       GdkDevice       *device)
+{
+        int id;
+        g_object_get (G_OBJECT (device), "device-id", &id, NULL);
+        if (g_hash_table_lookup (manager->priv->blacklist, GINT_TO_POINTER (id)) != NULL) {
+                g_debug ("device %s (%d) is blacklisted", gdk_device_get_name (device), id);
+                return TRUE;
+        }
+        return FALSE;
+}
+
+static gboolean
+device_is_ignored (GsdMouseManager *manager,
+		   GdkDevice       *device)
+{
+	GdkInputSource source;
+	const char *name;
+
+	if (device_is_blacklisted (manager, device))
+		return TRUE;
+
+	source = gdk_device_get_source (device);
+	if (source != GDK_SOURCE_MOUSE &&
+	    source != GDK_SOURCE_TOUCHPAD &&
+	    source != GDK_SOURCE_CURSOR)
+		return TRUE;
+
+	name = gdk_device_get_name (device);
+	if (name != NULL && g_str_equal ("Virtual core XTEST pointer", name))
+		return TRUE;
+
+	return FALSE;
+}
+
+static void
+configure_button_layout (guchar   *buttons,
+                         gint      n_buttons,
+                         gboolean  left_handed)
+{
+        const gint left_button = 1;
+        gint right_button;
+        gint i;
+
+        /* if the button is higher than 2 (3rd button) then it's
+         * probably one direction of a scroll wheel or something else
+         * uninteresting
+         */
+        right_button = MIN (n_buttons, 3);
+
+        /* If we change things we need to make sure we only swap buttons.
+         * If we end up with multiple physical buttons assigned to the same
+         * logical button the server will complain. This code assumes physical
+         * button 0 is the physical left mouse button, and that the physical
+         * button other than 0 currently assigned left_button or right_button
+         * is the physical right mouse button.
+         */
+
+        /* check if the current mapping satisfies the above assumptions */
+        if (buttons[left_button - 1] != left_button &&
+            buttons[left_button - 1] != right_button)
+                /* The current mapping is weird. Swapping buttons is probably not a
+                 * good idea.
+                 */
+                return;
+
+        /* check if we are left_handed and currently not swapped */
+        if (left_handed && buttons[left_button - 1] == left_button) {
+                /* find the right button */
+                for (i = 0; i < n_buttons; i++) {
+                        if (buttons[i] == right_button) {
+                                buttons[i] = left_button;
+                                break;
+                        }
+                }
+                /* swap the buttons */
+                buttons[left_button - 1] = right_button;
+        }
+        /* check if we are not left_handed but are swapped */
+        else if (!left_handed && buttons[left_button - 1] == right_button) {
+                /* find the right button */
+                for (i = 0; i < n_buttons; i++) {
+                        if (buttons[i] == left_button) {
+                                buttons[i] = right_button;
+                                break;
+                        }
+                }
+                /* swap the buttons */
+                buttons[left_button - 1] = left_button;
+        }
+}
+
+static gboolean
+xinput_device_has_buttons (GdkDevice *device)
+{
+        int i;
+        XAnyClassInfo *class_info;
+
+        /* FIXME can we use the XDevice's classes here instead? */
+        XDeviceInfo *device_info, *info;
+        gint n_devices;
+        int id;
+
+        /* Find the XDeviceInfo for the GdkDevice */
+        g_object_get (G_OBJECT (device), "device-id", &id, NULL);
+
+        device_info = XListInputDevices (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &n_devices);
+        if (device_info == NULL)
+                return FALSE;
+
+        info = NULL;
+        for (i = 0; i < n_devices; i++) {
+                if (device_info[i].id == id) {
+                        info = &device_info[i];
+                        break;
+                }
+        }
+        if (info == NULL)
+                goto bail;
+
+        class_info = info->inputclassinfo;
+        for (i = 0; i < info->num_classes; i++) {
+                if (class_info->class == ButtonClass) {
+                        XButtonInfo *button_info;
+
+                        button_info = (XButtonInfo *) class_info;
+                        if (button_info->num_buttons > 0) {
+                                XFreeDeviceList (device_info);
+                                return TRUE;
+                        }
+                }
+
+                class_info = (XAnyClassInfo *) (((guchar *) class_info) +
+                                                class_info->length);
+        }
+
+bail:
+        XFreeDeviceList (device_info);
+
+        return FALSE;
+}
+
+static gboolean
+touchpad_has_single_button (XDevice *device)
+{
+        Atom type, prop;
+        int format;
+        unsigned long nitems, bytes_after;
+        unsigned char *data;
+        gboolean is_single_button = FALSE;
+        int rc;
+
+        prop = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Capabilities", False);
+        if (!prop)
+                return FALSE;
+
+        gdk_error_trap_push ();
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), device, prop, 0, 1, False,
+                                XA_INTEGER, &type, &format, &nitems,
+                                &bytes_after, &data);
+        if (rc == Success && type == XA_INTEGER && format == 8 && nitems >= 3)
+                is_single_button = (data[0] == 1 && data[1] == 0 && data[2] == 0);
+
+        if (rc == Success)
+                XFree (data);
+
+        gdk_error_trap_pop_ignored ();
+
+        return is_single_button;
+}
+
+static void
+set_left_handed (GsdMouseManager *manager,
+                 GdkDevice       *device,
+                 gboolean mouse_left_handed,
+                 gboolean touchpad_left_handed)
+{
+        XDevice *xdevice;
+        guchar *buttons;
+        gsize buttons_capacity = 16;
+        gboolean left_handed;
+        gint n_buttons;
+
+        if (!xinput_device_has_buttons (device))
+                return;
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        xdevice = open_gdk_device (device);
+        if (xdevice == NULL)
+                return;
+
+	g_debug ("setting handedness on %s", gdk_device_get_name (device));
+
+        buttons = g_new (guchar, buttons_capacity);
+
+        /* If the device is a touchpad, swap tap buttons
+         * around too, otherwise a tap would be a right-click */
+        if (xdevice_is_synaptics (xdevice)) {
+                gboolean tap = g_settings_get_boolean (manager->priv->touchpad_settings, KEY_TAP_TO_CLICK);
+                gboolean single_button = touchpad_has_single_button (xdevice);
+
+                left_handed = touchpad_left_handed;
+
+                if (tap && !single_button)
+                        set_tap_to_click (device, tap, left_handed);
+
+                if (single_button)
+                        goto out;
+        } else {
+                left_handed = mouse_left_handed;
+        }
+
+        gdk_error_trap_push ();
+
+        n_buttons = XGetDeviceButtonMapping (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                             buttons,
+                                             buttons_capacity);
+
+        while (n_buttons > buttons_capacity) {
+                buttons_capacity = n_buttons;
+                buttons = (guchar *) g_realloc (buttons,
+                                                buttons_capacity * sizeof (guchar));
+
+                n_buttons = XGetDeviceButtonMapping (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                                     buttons,
+                                                     buttons_capacity);
+        }
+
+        configure_button_layout (buttons, n_buttons, left_handed);
+
+        XSetDeviceButtonMapping (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice, buttons, n_buttons);
+        gdk_error_trap_pop_ignored ();
+
+out:
+        xdevice_close (xdevice);
+        g_free (buttons);
+}
+
+static void
+set_motion (GsdMouseManager *manager,
+            GdkDevice       *device)
+{
+        XDevice *xdevice;
+        XPtrFeedbackControl feedback;
+        XFeedbackState *states, *state;
+        int num_feedbacks;
+        int numerator, denominator;
+        gfloat motion_acceleration;
+        int motion_threshold;
+        GSettings *settings;
+        gdouble speed;
+        guint i;
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        xdevice = open_gdk_device (device);
+        if (xdevice == NULL)
+                return;
+
+	g_debug ("setting motion on %s", gdk_device_get_name (device));
+
+        if (xdevice_is_synaptics (xdevice))
+                settings = manager->priv->touchpad_settings;
+        else
+                settings = manager->priv->mouse_settings;
+
+        speed = g_settings_get_double (settings, KEY_SPEED);
+
+        /* Calculate acceleration and threshold */
+        motion_acceleration = (speed + 1) * 5; /* speed is [-1..1], map to [0..10] */
+        motion_threshold = CLAMP (10 - floor (motion_acceleration), 1, 10);
+
+        if (motion_acceleration >= 1.0) {
+                /* we want to get the acceleration, with a resolution of 0.5
+                 */
+                if ((motion_acceleration - floor (motion_acceleration)) < 0.25) {
+                        numerator = floor (motion_acceleration);
+                        denominator = 1;
+                } else if ((motion_acceleration - floor (motion_acceleration)) < 0.5) {
+                        numerator = ceil (2.0 * motion_acceleration);
+                        denominator = 2;
+                } else if ((motion_acceleration - floor (motion_acceleration)) < 0.75) {
+                        numerator = floor (2.0 *motion_acceleration);
+                        denominator = 2;
+                } else {
+                        numerator = ceil (motion_acceleration);
+                        denominator = 1;
+                }
+        } else if (motion_acceleration < 1.0 && motion_acceleration > 0) {
+                /* This we do to 1/10ths */
+                numerator = floor (motion_acceleration * 10) + 1;
+                denominator= 10;
+        } else {
+                numerator = -1;
+                denominator = -1;
+        }
+
+        gdk_error_trap_push ();
+
+        /* Get the list of feedbacks for the device */
+        states = XGetFeedbackControl (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice, &num_feedbacks);
+        if (states == NULL)
+                goto out;
+        state = (XFeedbackState *) states;
+        for (i = 0; i < num_feedbacks; i++) {
+                if (state->class == PtrFeedbackClass) {
+                        /* And tell the device */
+                        feedback.class      = PtrFeedbackClass;
+                        feedback.length     = sizeof (XPtrFeedbackControl);
+                        feedback.id         = state->id;
+                        feedback.threshold  = motion_threshold;
+                        feedback.accelNum   = numerator;
+                        feedback.accelDenom = denominator;
+
+                        g_debug ("Setting accel %d/%d, threshold %d for device '%s'",
+                                 numerator, denominator, motion_threshold, gdk_device_get_name (device));
+
+                        XChangeFeedbackControl (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                                xdevice,
+                                                DvAccelNum | DvAccelDenom | DvThreshold,
+                                                (XFeedbackControl *) &feedback);
+
+                        break;
+                }
+                state = (XFeedbackState *) ((char *) state + state->length);
+        }
+
+        if (gdk_error_trap_pop ())
+                g_warning ("Error setting acceleration on \"%s\"", gdk_device_get_name (device));
+
+        XFreeFeedbackList (states);
+
+    out:
+
+        xdevice_close (xdevice);
+}
+
+/* Ensure that syndaemon dies together with us, to avoid running several of
+ * them */
+static void
+setup_syndaemon (gpointer user_data)
+{
+#ifdef __linux
+        prctl (PR_SET_PDEATHSIG, SIGHUP);
+#endif
+}
+
+static gboolean
+have_program_in_path (const char *name)
+{
+        gchar *path;
+        gboolean result;
+
+        path = g_find_program_in_path (name);
+        result = (path != NULL);
+        g_free (path);
+        return result;
+}
+
+static void
+syndaemon_died (GPid pid, gint status, gpointer user_data)
+{
+        GsdMouseManager *manager = GSD_MOUSE_MANAGER (user_data);
+
+        g_debug ("syndaemon stopped with status %i", status);
+        g_spawn_close_pid (pid);
+        manager->priv->syndaemon_spawned = FALSE;
+}
+
+static int
+set_disable_w_typing (GsdMouseManager *manager, gboolean state)
+{
+        if (state && synaptics_is_present ()) {
+                GError *error = NULL;
+                GPtrArray *args;
+
+                if (manager->priv->syndaemon_spawned)
+                        return 0;
+
+                if (!have_program_in_path ("syndaemon"))
+                        return 0;
+
+                args = g_ptr_array_new ();
+
+                g_ptr_array_add (args, "syndaemon");
+                g_ptr_array_add (args, "-i");
+                g_ptr_array_add (args, "1.0");
+                g_ptr_array_add (args, "-t");
+                g_ptr_array_add (args, "-K");
+                g_ptr_array_add (args, "-R");
+                g_ptr_array_add (args, NULL);
+
+                /* we must use G_SPAWN_DO_NOT_REAP_CHILD to avoid
+                 * double-forking, otherwise syndaemon will immediately get
+                 * killed again through (PR_SET_PDEATHSIG when the intermediate
+                 * process dies */
+                g_spawn_async (g_get_home_dir (), (char **) args->pdata, NULL,
+                                G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD, setup_syndaemon, NULL,
+                                &manager->priv->syndaemon_pid, &error);
+
+                manager->priv->syndaemon_spawned = (error == NULL);
+                g_ptr_array_free (args, FALSE);
+
+                if (error) {
+                        g_warning ("Failed to launch syndaemon: %s", error->message);
+                        g_error_free (error);
+                } else {
+                        g_child_watch_add (manager->priv->syndaemon_pid, syndaemon_died, manager);
+                        g_debug ("Launched syndaemon");
+                }
+        } else if (manager->priv->syndaemon_spawned) {
+                kill (manager->priv->syndaemon_pid, SIGHUP);
+                g_spawn_close_pid (manager->priv->syndaemon_pid);
+                manager->priv->syndaemon_spawned = FALSE;
+                g_debug ("Killed syndaemon");
+        }
+
+        return 0;
+}
+
+static void
+set_tap_to_click (GdkDevice *device,
+                  gboolean   state,
+                  gboolean   left_handed)
+{
+        int format, rc;
+        unsigned long nitems, bytes_after;
+        XDevice *xdevice;
+        unsigned char* data;
+        Atom prop_capabilities, prop, type;
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        prop = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Tap Action", False);
+        prop_capabilities = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Capabilities", False);
+        if (!prop || !prop_capabilities)
+                return;
+
+        xdevice = open_gdk_device (device);
+        if (xdevice == NULL)
+                return;
+
+        if (!xdevice_is_synaptics (xdevice)) {
+                xdevice_close (xdevice);
+                return;
+        }
+
+	g_debug ("setting tap to click on %s", gdk_device_get_name (device));
+
+        gdk_error_trap_push ();
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice, prop_capabilities, 0, 1,
+                                 False, XA_INTEGER, &type, &format, &nitems, &bytes_after, &data);
+        if (rc == Success && type == XA_INTEGER && format == 8 && nitems >= 1) {
+                if (!(data[0])) {
+                        g_debug ("No hardware buttons, enabling tap to click on %s", gdk_device_get_name (device));
+                        state = TRUE;
+                }
+
+                XFree (data);
+        }
+
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice, prop, 0, 2,
+                                 False, XA_INTEGER, &type, &format, &nitems,
+                                 &bytes_after, &data);
+
+        if (rc == Success && type == XA_INTEGER && format == 8 && nitems >= 7) {
+                /* Set RLM mapping for 1/2/3 fingers*/
+                data[4] = (state) ? ((left_handed) ? 3 : 1) : 0;
+                data[5] = (state) ? ((left_handed) ? 1 : 3) : 0;
+                data[6] = (state) ? 2 : 0;
+                XChangeDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice, prop, XA_INTEGER, 8,
+                                       PropModeReplace, data, nitems);
+        }
+
+        if (rc == Success)
+                XFree (data);
+
+        if (gdk_error_trap_pop ())
+                g_warning ("Error in setting tap to click on \"%s\"", gdk_device_get_name (device));
+
+        xdevice_close (xdevice);
+}
+
+static void
+set_horiz_scroll (GdkDevice *device,
+                  gboolean   state)
+{
+        int rc;
+        XDevice *xdevice;
+        Atom act_type, prop_edge, prop_twofinger;
+        int act_format;
+        unsigned long nitems, bytes_after;
+        unsigned char *data;
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        prop_edge = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Edge Scrolling", False);
+        prop_twofinger = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Two-Finger Scrolling", False);
+
+        if (!prop_edge || !prop_twofinger)
+                return;
+
+        xdevice = open_gdk_device (device);
+        if (xdevice == NULL)
+                return;
+
+        if (!xdevice_is_synaptics (xdevice)) {
+                xdevice_close (xdevice);
+                return;
+        }
+
+	g_debug ("setting horiz scroll on %s", gdk_device_get_name (device));
+
+        gdk_error_trap_push ();
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                 prop_edge, 0, 1, False,
+                                 XA_INTEGER, &act_type, &act_format, &nitems,
+                                 &bytes_after, &data);
+        if (rc == Success && act_type == XA_INTEGER &&
+            act_format == 8 && nitems >= 2) {
+                data[1] = (state && data[0]);
+                XChangeDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                       prop_edge, XA_INTEGER, 8,
+                                       PropModeReplace, data, nitems);
+        }
+
+        XFree (data);
+
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                 prop_twofinger, 0, 1, False,
+                                 XA_INTEGER, &act_type, &act_format, &nitems,
+                                 &bytes_after, &data);
+        if (rc == Success && act_type == XA_INTEGER &&
+            act_format == 8 && nitems >= 2) {
+                data[1] = (state && data[0]);
+                XChangeDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                       prop_twofinger, XA_INTEGER, 8,
+                                       PropModeReplace, data, nitems);
+        }
+
+        if (gdk_error_trap_pop ())
+                g_warning ("Error in setting horiz scroll on \"%s\"", gdk_device_get_name (device));
+
+        if (rc == Success)
+                XFree (data);
+
+        xdevice_close (xdevice);
+
+}
+
+static void
+set_edge_scrolling_enabled (GsdMouseManager         *manager,
+                            GdkDevice               *device,
+                            gboolean                 enabled)
+{
+        int rc;
+        XDevice *xdevice;
+        Atom act_type, prop, prop_edge, prop_twofinger;
+        int act_format;
+        unsigned long nitems, bytes_after;
+        unsigned char *data;
+        GsdTouchpadScrollMethod method = GSD_TOUCHPAD_SCROLL_METHOD_DISABLED;
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        prop = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Capabilities", True);
+        prop_edge = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Edge Scrolling", False);
+        prop_twofinger = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Two-Finger Scrolling", False);
+
+        if (!prop_edge || !prop_twofinger || !prop)
+                return;
+
+        xdevice = open_gdk_device (device);
+        if (xdevice == NULL)
+                return;
+
+        if (!xdevice_is_synaptics (xdevice)) {
+                xdevice_close (xdevice);
+                return;
+        }
+
+	g_debug ("setting edge scroll on %s", gdk_device_get_name (device));
+
+        gdk_error_trap_push ();
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                 prop, 0, 4, False,
+                                 XA_INTEGER, &act_type, &act_format, &nitems,
+                                 &bytes_after, &data);
+        if (rc == Success && act_type != None) {
+                /* Two-finger scrolling is supported, so enable it */
+                if (data[3])
+                        method = GSD_TOUCHPAD_SCROLL_METHOD_TWO_FINGER_SCROLLING;
+
+                XFree (data);
+        }
+
+        if (enabled && method != GSD_TOUCHPAD_SCROLL_METHOD_TWO_FINGER_SCROLLING)
+                method = GSD_TOUCHPAD_SCROLL_METHOD_EDGE_SCROLLING;
+        else
+                method = GSD_TOUCHPAD_SCROLL_METHOD_DISABLED;
+
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                 prop_edge, 0, 1, False,
+                                 XA_INTEGER, &act_type, &act_format, &nitems,
+                                 &bytes_after, &data);
+        if (rc == Success && act_type == XA_INTEGER &&
+            act_format == 8 && nitems >= 2) {
+                data[0] = (method == GSD_TOUCHPAD_SCROLL_METHOD_EDGE_SCROLLING) ? 1 : 0;
+                XChangeDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                       prop_edge, XA_INTEGER, 8,
+                                       PropModeReplace, data, nitems);
+        }
+
+        XFree (data);
+
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                 prop_twofinger, 0, 1, False,
+                                 XA_INTEGER, &act_type, &act_format, &nitems,
+                                 &bytes_after, &data);
+        if (rc == Success && act_type == XA_INTEGER &&
+            act_format == 8 && nitems >= 2) {
+                data[0] = (method == GSD_TOUCHPAD_SCROLL_METHOD_TWO_FINGER_SCROLLING) ? 1 : 0;
+                XChangeDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                       prop_twofinger, XA_INTEGER, 8,
+                                       PropModeReplace, data, nitems);
+        }
+
+        if (gdk_error_trap_pop ())
+                g_warning ("Error in setting edge scroll on \"%s\"", gdk_device_get_name (device));
+
+        if (rc == Success)
+                XFree (data);
+
+        xdevice_close (xdevice);
+}
+
+static void
+set_touchpad_disabled (GdkDevice *device)
+{
+        int id;
+        XDevice *xdevice;
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        g_object_get (G_OBJECT (device), "device-id", &id, NULL);
+
+        g_debug ("Trying to set device disabled for \"%s\" (%d)", gdk_device_get_name (device), id);
+
+        xdevice = open_gdk_device (device);
+        if (xdevice == NULL)
+                return;
+
+        if (!xdevice_is_synaptics (xdevice)) {
+                xdevice_close (xdevice);
+                return;
+        }
+
+        if (set_synaptics_device_enabled (id, FALSE) == FALSE)
+                g_warning ("Error disabling device \"%s\" (%d)", gdk_device_get_name (device), id);
+        else
+                g_debug ("Disabled device \"%s\" (%d)", gdk_device_get_name (device), id);
+
+        xdevice_close (xdevice);
+}
+
+static void
+set_touchpad_enabled (int id)
+{
+        XDevice *xdevice;
+
+        if (xdevice_is_libinput (id))
+                return;
+
+        g_debug ("Trying to set device enabled for %d", id);
+
+        gdk_error_trap_push ();
+        xdevice = XOpenDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), id);
+        if (gdk_error_trap_pop () != 0)
+                return;
+
+        if (!xdevice_is_synaptics (xdevice)) {
+                xdevice_close (xdevice);
+                return;
+        }
+
+        if (set_synaptics_device_enabled (id, TRUE) == FALSE)
+                g_warning ("Error enabling device \"%d\"", id);
+        else
+                g_debug ("Enabled device %d", id);
+
+        xdevice_close (xdevice);
+}
+
 static void
 set_locate_pointer (GsdMouseManager *manager,
                     gboolean         state)
@@ -143,32 +962,418 @@ set_mousetweaks_daemon (GsdMouseManager *manager,
         g_free (comm);
 }
 
+static gboolean
+get_touchpad_handedness (GsdMouseManager *manager, gboolean mouse_left_handed)
+{
+        switch (g_settings_get_enum (manager->priv->touchpad_settings, KEY_LEFT_HANDED)) {
+        case GSD_TOUCHPAD_HANDEDNESS_RIGHT:
+                return FALSE;
+        case GSD_TOUCHPAD_HANDEDNESS_LEFT:
+                return TRUE;
+        case GSD_TOUCHPAD_HANDEDNESS_MOUSE:
+                return mouse_left_handed;
+        default:
+                g_assert_not_reached ();
+        }
+}
+
+static void
+set_natural_scroll (GsdMouseManager *manager,
+                    GdkDevice       *device,
+                    gboolean         natural_scroll)
+{
+        XDevice *xdevice;
+        Atom scrolling_distance, act_type;
+        int rc, act_format;
+        unsigned long nitems, bytes_after;
+        unsigned char *data;
+        glong *ptr;
+
+        xdevice = open_gdk_device (device);
+        if (xdevice == NULL)
+                return;
+
+        if (!xdevice_is_synaptics (xdevice)) {
+                xdevice_close (xdevice);
+                return;
+        }
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        g_debug ("Trying to set %s for \"%s\"",
+                 natural_scroll ? "natural (reverse) scroll" : "normal scroll",
+                 gdk_device_get_name (device));
+
+        scrolling_distance = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                          "Synaptics Scrolling Distance", False);
+
+        gdk_error_trap_push ();
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                 scrolling_distance, 0, 2, False,
+                                 XA_INTEGER, &act_type, &act_format, &nitems,
+                                 &bytes_after, &data);
+
+        if (rc == Success && act_type == XA_INTEGER && act_format == 32 && nitems >= 2) {
+                ptr = (glong *) data;
+
+                if (natural_scroll) {
+                        ptr[0] = -abs(ptr[0]);
+                        ptr[1] = -abs(ptr[1]);
+                } else {
+                        ptr[0] = abs(ptr[0]);
+                        ptr[1] = abs(ptr[1]);
+                }
+
+                XChangeDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice,
+                                       scrolling_distance, XA_INTEGER, act_format,
+                                       PropModeReplace, data, nitems);
+        }
+
+        if (gdk_error_trap_pop ())
+                g_warning ("Error setting %s for \"%s\"",
+                           natural_scroll ? "natural (reverse) scroll" : "normal scroll",
+                           gdk_device_get_name (device));
+
+        if (rc == Success)
+                XFree (data);
+
+        xdevice_close (xdevice);
+}
+
+static void
+set_scroll_wheel_button (GsdMouseManager *manager,
+                         GdkDevice       *device)
+{
+        Atom wheel_prop, button_prop;
+        XDevice *xdevice;
+        Atom type;
+        int format;
+        unsigned long nitems, bytes_after;
+        unsigned char *data = NULL;
+        int button;
+        int rc;
+
+        if (!device_is_trackball (device))
+                return;
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        xdevice = open_gdk_device (device);
+        if (xdevice == NULL)
+                return;
+
+        wheel_prop = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                  "Evdev Wheel Emulation", True);
+        button_prop = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                   "Evdev Wheel Emulation Button", True);
+
+        if (!wheel_prop || !button_prop) {
+                xdevice_close (xdevice);
+                return;
+        }
+
+        g_debug ("setting scroll wheel emulation on %s", gdk_device_get_name (device));
+
+        gdk_error_trap_push ();
+
+        button = g_settings_get_int (manager->priv->trackball_settings, KEY_SCROLL_WHEEL_BUTTON);
+
+        /* Whether scroll wheel emulation is enabled */
+        rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                 xdevice, wheel_prop, 0, 1, False, XA_INTEGER, &type, &format,
+                                 &nitems, &bytes_after, &data);
+
+        if (rc == Success && format == 8 && type == XA_INTEGER && nitems == 1) {
+                data[0] = button > 0 ? 1 : 0;
+
+                XChangeDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                       xdevice, wheel_prop, type, format, PropModeReplace, data, nitems);
+        }
+
+        if (data) {
+                XFree (data);
+                data = NULL;
+        }
+
+        /* Which button is used for the emulation */
+        if (button > 0) {
+                rc = XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                         xdevice, button_prop, 0, 1, False, XA_INTEGER, &type, &format,
+                                         &nitems, &bytes_after, &data);
+
+                if (rc == Success && format == 8 && type == XA_INTEGER && nitems == 1) {
+                        data[0] = button;
+
+                        XChangeDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                                               xdevice, button_prop, type, format, PropModeReplace, data, nitems);
+                }
+
+                if (data)
+                        XFree (data);
+        }
+
+        if (gdk_error_trap_pop ())
+                g_warning ("Error in setting scroll wheel emulation on \"%s\"", gdk_device_get_name (device));
+
+        xdevice_close (xdevice);
+}
+
+static gboolean
+get_touchpad_enabled (GsdMouseManager *manager)
+{
+        GDesktopDeviceSendEvents send_events;
+
+        send_events = g_settings_get_enum (manager->priv->touchpad_settings, KEY_SEND_EVENTS);
+
+        if (send_events == G_DESKTOP_DEVICE_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE) {
+                /* FIXME: mouse_is_present() also finds internal ones... */
+                return (!mouse_is_present () && !trackball_is_present ());
+
+        }
+
+        return send_events == G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED ? TRUE : FALSE;
+}
+
+static void
+set_mouse_settings (GsdMouseManager *manager,
+                    GdkDevice       *device)
+{
+        gboolean mouse_left_handed, touchpad_left_handed;
+
+        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                return;
+
+        mouse_left_handed = g_settings_get_boolean (manager->priv->mouse_settings, KEY_LEFT_HANDED);
+        touchpad_left_handed = get_touchpad_handedness (manager, mouse_left_handed);
+        set_left_handed (manager, device, mouse_left_handed, touchpad_left_handed);
+
+        set_motion (manager, device);
+
+        set_tap_to_click (device, g_settings_get_boolean (manager->priv->touchpad_settings, KEY_TAP_TO_CLICK), touchpad_left_handed);
+        set_edge_scrolling_enabled (manager, device, g_settings_get_boolean (manager->priv->touchpad_settings, KEY_EDGE_SCROLLING_ENABLED));
+        set_horiz_scroll (device, TRUE);
+        set_natural_scroll (manager, device, g_settings_get_boolean (manager->priv->touchpad_settings, KEY_NATURAL_SCROLL_ENABLED));
+
+        set_scroll_wheel_button (manager, device);
+}
+
 static void
 mouse_callback (GSettings       *settings,
                 const gchar     *key,
                 GsdMouseManager *manager)
 {
+        GList *devices, *l;
+
         if (g_str_equal (key, KEY_DWELL_CLICK_ENABLED) ||
             g_str_equal (key, KEY_SECONDARY_CLICK_ENABLED)) {
                 set_mousetweaks_daemon (manager,
                                         g_settings_get_boolean (settings, KEY_DWELL_CLICK_ENABLED),
                                         g_settings_get_boolean (settings, KEY_SECONDARY_CLICK_ENABLED));
+                return;
         } else if (g_str_equal (key, KEY_LOCATE_POINTER)) {
                 set_locate_pointer (manager, g_settings_get_boolean (settings, KEY_LOCATE_POINTER));
+                return;
         }
+
+        devices = gdk_device_manager_list_devices (manager->priv->device_manager, GDK_DEVICE_TYPE_SLAVE);
+
+        for (l = devices; l != NULL; l = l->next) {
+                GdkDevice *device = l->data;
+
+                if (device_is_ignored (manager, device))
+                        continue;
+
+                if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                        continue;
+
+                if (g_str_equal (key, KEY_LEFT_HANDED)) {
+                        gboolean mouse_left_handed;
+                        mouse_left_handed = g_settings_get_boolean (settings, KEY_LEFT_HANDED);
+                        set_left_handed (manager, device, mouse_left_handed, get_touchpad_handedness (manager, mouse_left_handed));
+                } else if (g_str_equal (key, KEY_SPEED)) {
+                        set_motion (manager, device);
+                }
+        }
+        g_list_free (devices);
+}
+
+static void
+touchpad_callback (GSettings       *settings,
+                   const gchar     *key,
+                   GsdMouseManager *manager)
+{
+        GList *devices, *l;
+
+        devices = gdk_device_manager_list_devices (manager->priv->device_manager, GDK_DEVICE_TYPE_SLAVE);
+
+        for (l = devices; l != NULL; l = l->next) {
+                GdkDevice *device = l->data;
+
+                if (device_is_ignored (manager, device))
+                        continue;
+
+                if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                        continue;
+
+                if (g_str_equal (key, KEY_TAP_TO_CLICK)) {
+                        gboolean mouse_left_handed;
+                        mouse_left_handed = g_settings_get_boolean (manager->priv->mouse_settings, KEY_LEFT_HANDED);
+                        set_tap_to_click (device, g_settings_get_boolean (settings, key),
+                                          get_touchpad_handedness (manager, mouse_left_handed));
+                } else if (g_str_equal (key, KEY_EDGE_SCROLLING_ENABLED)) {
+                        set_edge_scrolling_enabled (manager, device, g_settings_get_boolean (settings, key));
+                        set_horiz_scroll (device, TRUE);
+                } else if (g_str_equal (key, KEY_SPEED)) {
+                        set_motion (manager, device);
+                } else if (g_str_equal (key, KEY_LEFT_HANDED)) {
+                        gboolean mouse_left_handed;
+                        mouse_left_handed = g_settings_get_boolean (manager->priv->mouse_settings, KEY_LEFT_HANDED);
+                        set_left_handed (manager, device, mouse_left_handed, get_touchpad_handedness (manager, mouse_left_handed));
+                } else if (g_str_equal (key, KEY_NATURAL_SCROLL_ENABLED)) {
+                        set_natural_scroll (manager, device, g_settings_get_boolean (settings, key));
+                }
+        }
+        g_list_free (devices);
+
+        if (g_str_equal (key, KEY_SEND_EVENTS)) {
+                ensure_touchpad_active (manager);
+        }
+}
+
+static void
+trackball_callback (GSettings       *settings,
+                    const gchar     *key,
+                    GsdMouseManager *manager)
+{
+        GList *devices, *l;
+
+        devices = gdk_device_manager_list_devices (manager->priv->device_manager, GDK_DEVICE_TYPE_SLAVE);
+
+        for (l = devices; l != NULL; l = l->next) {
+                GdkDevice *device = l->data;
+
+                if (device_is_ignored (manager, device))
+                        continue;
+
+                if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                        return;
+
+                set_scroll_wheel_button (manager, device);
+        }
+        g_list_free (devices);
+}
+
+/* Re-enable touchpad when any other pointing device isn't present. */
+static void
+ensure_touchpad_active (GsdMouseManager *manager)
+{
+        GList *devices, *l;
+        gboolean state;
+
+        state = get_touchpad_enabled (manager);
+        if (state) {
+                devices = get_disabled_synaptics ();
+                for (l = devices; l != NULL; l = l->next) {
+                        int device_id;
+
+                        device_id = GPOINTER_TO_INT (l->data);
+                        set_touchpad_enabled (device_id);
+                }
+                g_list_free (devices);
+        } else {
+                devices = gdk_device_manager_list_devices (manager->priv->device_manager,
+                                                           GDK_DEVICE_TYPE_SLAVE);
+
+                for (l = devices; l != NULL; l = l->next) {
+                        GdkDevice *device = l->data;
+
+                        if (device_is_ignored (manager, device))
+                                continue;
+                        if (xdevice_is_libinput (gdk_x11_device_get_id (device)))
+                                continue;
+                        if (gdk_device_get_source (device) != GDK_SOURCE_TOUCHPAD)
+                                continue;
+
+                        set_touchpad_disabled (device);
+                }
+
+                g_list_free (devices);
+        }
+
+        set_disable_w_typing (manager, state);
+}
+
+static void
+device_added_cb (GdkDeviceManager *device_manager,
+                 GdkDevice        *device,
+                 GsdMouseManager  *manager)
+{
+        if (device_is_ignored (manager, device) == FALSE) {
+                if (run_custom_command (device, COMMAND_DEVICE_ADDED) == FALSE) {
+                        set_mouse_settings (manager, device);
+                } else {
+                        int id;
+                        g_object_get (G_OBJECT (device), "device-id", &id, NULL);
+                        g_hash_table_insert (manager->priv->blacklist,
+                                             GINT_TO_POINTER (id), GINT_TO_POINTER (1));
+                }
+
+                ensure_touchpad_active (manager);
+        }
+}
+
+static void
+device_removed_cb (GdkDeviceManager *device_manager,
+                   GdkDevice        *device,
+                   GsdMouseManager  *manager)
+{
+	int id;
+
+	/* Remove the device from the hash table so that
+	 * device_is_ignored () doesn't check for blacklisted devices */
+	g_object_get (G_OBJECT (device), "device-id", &id, NULL);
+	g_hash_table_remove (manager->priv->blacklist,
+			     GINT_TO_POINTER (id));
+
+        if (device_is_ignored (manager, device) == FALSE) {
+                run_custom_command (device, COMMAND_DEVICE_REMOVED);
+
+                ensure_touchpad_active (manager);
+        }
+}
+
+static void
+set_devicepresence_handler (GsdMouseManager *manager)
+{
+        GdkDeviceManager *device_manager;
+
+        device_manager = gdk_display_get_device_manager (gdk_display_get_default ());
+
+        manager->priv->device_added_id = g_signal_connect (G_OBJECT (device_manager), "device-added",
+                                                           G_CALLBACK (device_added_cb), manager);
+        manager->priv->device_removed_id = g_signal_connect (G_OBJECT (device_manager), "device-removed",
+                                                             G_CALLBACK (device_removed_cb), manager);
+        manager->priv->device_manager = device_manager;
 }
 
 static void
 gsd_mouse_manager_init (GsdMouseManager *manager)
 {
         manager->priv = GSD_MOUSE_MANAGER_GET_PRIVATE (manager);
+        manager->priv->blacklist = g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
 static gboolean
 gsd_mouse_manager_idle_cb (GsdMouseManager *manager)
 {
+        GList *devices, *l;
+
         gnome_settings_profile_start (NULL);
 
+        set_devicepresence_handler (manager);
+
         manager->priv->gsd_mouse_settings = g_settings_new (GSD_SETTINGS_MOUSE_SCHEMA);
         g_signal_connect (manager->priv->gsd_mouse_settings, "changed",
                           G_CALLBACK (mouse_callback), manager);
@@ -176,17 +1381,46 @@ gsd_mouse_manager_idle_cb (GsdMouseManager *manager)
         manager->priv->mouse_a11y_settings = g_settings_new ("org.gnome.desktop.a11y.mouse");
         g_signal_connect (manager->priv->mouse_a11y_settings, "changed",
                           G_CALLBACK (mouse_callback), manager);
-#if 0
+
         manager->priv->mouse_settings = g_settings_new (GSETTINGS_MOUSE_SCHEMA);
         g_signal_connect (manager->priv->mouse_settings, "changed",
                           G_CALLBACK (mouse_callback), manager);
-#endif
+
+        manager->priv->touchpad_settings = g_settings_new (GSETTINGS_TOUCHPAD_SCHEMA);
+        g_signal_connect (manager->priv->touchpad_settings, "changed",
+                          G_CALLBACK (touchpad_callback), manager);
+
+        manager->priv->trackball_settings = g_settings_new (GSETTINGS_TRACKBALL_SCHEMA);
+        g_signal_connect (manager->priv->trackball_settings, "changed",
+                          G_CALLBACK (trackball_callback), manager);
+
+        manager->priv->syndaemon_spawned = FALSE;
 
         set_locate_pointer (manager, g_settings_get_boolean (manager->priv->gsd_mouse_settings, KEY_LOCATE_POINTER));
         set_mousetweaks_daemon (manager,
                                 g_settings_get_boolean (manager->priv->mouse_a11y_settings, KEY_DWELL_CLICK_ENABLED),
                                 g_settings_get_boolean (manager->priv->mouse_a11y_settings, KEY_SECONDARY_CLICK_ENABLED));
 
+        devices = gdk_device_manager_list_devices (manager->priv->device_manager, GDK_DEVICE_TYPE_SLAVE);
+        for (l = devices; l != NULL; l = l->next) {
+                GdkDevice *device = l->data;
+
+                if (device_is_ignored (manager, device))
+                        continue;
+
+                if (run_custom_command (device, COMMAND_DEVICE_PRESENT) == FALSE) {
+                        set_mouse_settings (manager, device);
+                } else {
+                        int id;
+                        g_object_get (G_OBJECT (device), "device-id", &id, NULL);
+                        g_hash_table_insert (manager->priv->blacklist,
+                                             GINT_TO_POINTER (id), GINT_TO_POINTER (1));
+                }
+        }
+        g_list_free (devices);
+
+        ensure_touchpad_active (manager);
+
         gnome_settings_profile_end (NULL);
 
         manager->priv->start_idle_id = 0;
@@ -202,6 +1436,11 @@ gsd_mouse_manager_start (GsdMouseManager *manager,
 
         migrate_mouse_settings ();
 
+        if (!supports_xinput_devices ()) {
+                g_debug ("XInput is not supported, not applying any settings");
+                return TRUE;
+        }
+
         if (gnome_settings_is_wayland ())
                 return TRUE;
 
@@ -225,9 +1464,16 @@ gsd_mouse_manager_stop (GsdMouseManager *manager)
                 manager->priv->start_idle_id = 0;
         }
 
+        if (p->device_manager != NULL) {
+                g_signal_handler_disconnect (p->device_manager, p->device_added_id);
+                g_signal_handler_disconnect (p->device_manager, p->device_removed_id);
+                p->device_manager = NULL;
+        }
+
         g_clear_object (&p->mouse_a11y_settings);
         g_clear_object (&p->mouse_settings);
         g_clear_object (&p->touchpad_settings);
+        g_clear_object (&p->trackball_settings);
         g_clear_object (&p->gsd_mouse_settings);
 
         set_locate_pointer (manager, FALSE);
@@ -247,6 +1493,9 @@ gsd_mouse_manager_finalize (GObject *object)
 
         gsd_mouse_manager_stop (mouse_manager);
 
+        if (mouse_manager->priv->blacklist != NULL)
+                g_hash_table_destroy (mouse_manager->priv->blacklist);
+
         G_OBJECT_CLASS (gsd_mouse_manager_parent_class)->finalize (object);
 }
 
-- 
2.6.2


From 4b1ed6c7aec9dea2b70fe19c0156fed72899eba6 Mon Sep 17 00:00:00 2001
From: Frederic Crozat <fcrozat@suse.com>
Date: Fri, 4 Mar 2016 15:49:20 +0100
Subject: [PATCH 2/3] Revert "common: Remove unused functions"

This reverts commit 9287ef9ac5b119abdcbbabd920c19f353e577f90.
---
 plugins/common/gsd-input-helper.c  | 279 +++++++++++++++++++++++++++++++++++++
 plugins/common/gsd-input-helper.h  |  15 ++
 plugins/common/test-input-helper.c |   5 +-
 3 files changed, 298 insertions(+), 1 deletion(-)

diff --git a/plugins/common/gsd-input-helper.c b/plugins/common/gsd-input-helper.c
index 077ff1c..4dca795 100644
--- a/plugins/common/gsd-input-helper.c
+++ b/plugins/common/gsd-input-helper.c
@@ -115,6 +115,12 @@ supports_xinput_devices_with_opcode (int *opcode)
 }
 
 gboolean
+supports_xinput_devices (void)
+{
+	return supports_xinput_devices_with_opcode (NULL);
+}
+
+gboolean
 supports_xtest (void)
 {
         gint op_code, event, error;
@@ -154,6 +160,66 @@ supports_xinput2_devices (int *opcode)
         return TRUE;
 }
 
+gboolean
+xdevice_is_synaptics (XDevice *xdevice)
+{
+        Atom realtype, prop;
+        int realformat;
+        unsigned long nitems, bytes_after;
+        unsigned char *data;
+
+        /* we don't check on the type being XI_TOUCHPAD here,
+         * but having a "Synaptics Off" property should be enough */
+
+        prop = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Off", False);
+        if (!prop)
+                return FALSE;
+
+        gdk_error_trap_push ();
+        if ((XGetDeviceProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdevice, prop, 0, 1, False,
+                                XA_INTEGER, &realtype, &realformat, &nitems,
+                                &bytes_after, &data) == Success) && (realtype != None)) {
+                gdk_error_trap_pop_ignored ();
+                XFree (data);
+                return TRUE;
+        }
+        gdk_error_trap_pop_ignored ();
+
+        return FALSE;
+}
+
+gboolean
+synaptics_is_present (void)
+{
+        XDeviceInfo *device_info;
+        gint n_devices;
+        guint i;
+        gboolean retval;
+
+        retval = FALSE;
+
+        device_info = XListInputDevices (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &n_devices);
+        if (device_info == NULL)
+                return FALSE;
+
+        for (i = 0; i < n_devices; i++) {
+                XDevice *device;
+
+                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;
+
+                retval = xdevice_is_synaptics (device);
+                xdevice_close (device);
+                if (retval)
+                        break;
+        }
+        XFreeDeviceList (device_info);
+
+        return retval;
+}
+
 static gboolean
 device_type_is_present (GsdDeviceType type)
 {
@@ -181,6 +247,29 @@ mouse_is_present (void)
         return device_type_is_present (GSD_DEVICE_TYPE_MOUSE);
 }
 
+gboolean
+trackball_is_present (void)
+{
+        gboolean retval = FALSE;
+        GList *l, *mice = gsd_device_manager_list_devices (gsd_device_manager_get (),
+                                                           GSD_DEVICE_TYPE_MOUSE);
+        if (mice == NULL)
+                return FALSE;
+
+        for (l = mice; l != NULL; l = l->next) {
+                gchar *lowercase;
+                const gchar *name = gsd_device_get_name (l->data);
+                if (!name)
+                        continue;
+                lowercase = g_ascii_strdown (name, -1);
+                retval = strstr (lowercase, "trackball") != NULL;
+                g_free (lowercase);
+        }
+
+        g_list_free (mice);
+        return retval;
+}
+
 char *
 xdevice_get_device_node (int deviceid)
 {
@@ -339,6 +428,172 @@ set_device_enabled (int device_id,
         return TRUE;
 }
 
+gboolean
+set_synaptics_device_enabled (int device_id,
+                              gboolean enabled)
+{
+        Atom prop;
+        guchar value;
+
+        prop = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "Synaptics Off", False);
+        if (!prop)
+                return FALSE;
+
+        gdk_error_trap_push ();
+
+        value = enabled ? 0 : 1;
+        XIChangeProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+                          device_id, prop, XA_INTEGER, 8, PropModeReplace, &value, 1);
+
+        if (gdk_error_trap_pop ())
+                return FALSE;
+
+        return TRUE;
+}
+
+static const char *
+custom_command_to_string (CustomCommand command)
+{
+        switch (command) {
+        case COMMAND_DEVICE_ADDED:
+                return "added";
+        case COMMAND_DEVICE_REMOVED:
+                return "removed";
+        case COMMAND_DEVICE_PRESENT:
+                return "present";
+        default:
+                g_assert_not_reached ();
+        }
+}
+
+/* Run a custom command on device presence events. Parameters passed into
+ * the custom command are:
+ * command -t [added|removed|present] -i <device ID> <device name>
+ * Type 'added' and 'removed' signal 'device added' and 'device removed',
+ * respectively. Type 'present' signals 'device present at
+ * gnome-settings-daemon init'.
+ *
+ * The script is expected to run synchronously, and an exit value
+ * of "1" means that no other settings will be applied to this
+ * particular device.
+ *
+ * More options may be added in the future.
+ *
+ * This function returns TRUE if we should not apply any more settings
+ * to the device.
+ */
+gboolean
+run_custom_command (GdkDevice              *device,
+                    CustomCommand           command)
+{
+        GSettings *settings;
+        GError *error = NULL;
+        char *cmd;
+        char *argv[7];
+        int exit_status;
+        gboolean rc;
+        int id;
+        char *out;
+
+        settings = g_settings_new (INPUT_DEVICES_SCHEMA);
+        cmd = g_settings_get_string (settings, KEY_HOTPLUG_COMMAND);
+        g_object_unref (settings);
+
+        if (!cmd || cmd[0] == '\0') {
+                g_free (cmd);
+                return FALSE;
+        }
+
+        /* Easter egg! */
+        g_object_get (device, "device-id", &id, NULL);
+
+        argv[0] = cmd;
+        argv[1] = "-t";
+        argv[2] = (char *) custom_command_to_string (command);
+        argv[3] = "-i";
+        argv[4] = g_strdup_printf ("%d", id);
+        argv[5] = (char*) gdk_device_get_name (device);
+        argv[6] = NULL;
+
+        out = g_strjoinv (" ", argv);
+        g_debug ("About to launch command: %s", out);
+        g_free (out);
+
+        rc = g_spawn_sync (g_get_home_dir (), argv, NULL, G_SPAWN_SEARCH_PATH,
+                           NULL, NULL, NULL, NULL, &exit_status, &error);
+
+        if (rc == FALSE) {
+                g_warning ("Couldn't execute command '%s', verify that this is a valid command: %s", cmd, error->message);
+                g_clear_error (&error);
+        }
+
+        g_free (argv[0]);
+        g_free (argv[4]);
+
+        if (!g_spawn_check_exit_status (exit_status, &error)) {
+                if (g_error_matches (error, G_SPAWN_EXIT_ERROR, 1)) {
+                        g_clear_error (&error);
+                        return TRUE;
+                }
+                g_clear_error (&error);
+        }
+
+        return FALSE;
+}
+
+GList *
+get_disabled_synaptics (void)
+{
+        GdkDisplay *display;
+        XDeviceInfo *device_info;
+        gint n_devices, act_format, rc;
+        guint i;
+        GList *ret;
+        Atom prop, act_type;
+        unsigned long  nitems, bytes_after;
+        unsigned char *data;
+
+        ret = NULL;
+
+        display = gdk_display_get_default ();
+        prop = gdk_x11_get_xatom_by_name ("Synaptics Off");
+
+        gdk_error_trap_push ();
+
+        device_info = XListInputDevices (GDK_DISPLAY_XDISPLAY (display), &n_devices);
+        if (device_info == NULL) {
+                gdk_error_trap_pop_ignored ();
+
+                return ret;
+        }
+
+        for (i = 0; i < n_devices; i++) {
+                rc = XIGetProperty (GDK_DISPLAY_XDISPLAY (display),
+                                    device_info[i].id, prop, 0, 1, False,
+                                    XA_INTEGER, &act_type, &act_format,
+                                    &nitems, &bytes_after, &data);
+
+                if (rc != Success || act_type != XA_INTEGER ||
+                    act_format != 8 || nitems < 1)
+                        continue;
+
+                if (!(data[0])) {
+                        XFree (data);
+                        continue;
+                }
+
+                XFree (data);
+
+                ret = g_list_prepend (ret, GINT_TO_POINTER (device_info[i].id));
+        }
+
+        gdk_error_trap_pop_ignored ();
+
+        XFreeDeviceList (device_info);
+
+        return ret;
+}
+
 const char *
 xdevice_get_wacom_tool_type (int deviceid)
 {
@@ -425,3 +680,27 @@ xdevice_get_dimensions (int    deviceid,
 
         return (w != 0 && h != 0);
 }
+
+gboolean
+xdevice_is_libinput (gint deviceid)
+{
+        GdkDisplay *display = gdk_display_get_default ();
+        gulong nitems, bytes_after;
+        gint rc, format;
+        guchar *data;
+        Atom type;
+
+        gdk_error_trap_push ();
+
+        /* Lookup a libinput driver specific property */
+        rc = XIGetProperty (GDK_DISPLAY_XDISPLAY (display), deviceid,
+                            gdk_x11_get_xatom_by_name ("libinput Send Events Mode Enabled"),
+                            0, 1, False, XA_INTEGER, &type, &format, &nitems, &bytes_after, &data);
+
+        if (rc == Success)
+                XFree (data);
+
+        gdk_error_trap_pop_ignored ();
+
+        return rc == Success && nitems > 0;
+}
diff --git a/plugins/common/gsd-input-helper.h b/plugins/common/gsd-input-helper.h
index 31e2e47..85e6d07 100644
--- a/plugins/common/gsd-input-helper.h
+++ b/plugins/common/gsd-input-helper.h
@@ -44,20 +44,32 @@ typedef struct {
         } data;
 } PropertyHelper;
 
+gboolean  supports_xinput_devices  (void);
 gboolean  supports_xinput2_devices (int *opcode);
 gboolean  supports_xtest           (void);
 
 gboolean set_device_enabled       (int device_id,
                                    gboolean enabled);
 
+gboolean  set_synaptics_device_enabled (int device_id,
+                                        gboolean enabled);
+
+gboolean  xdevice_is_synaptics       (XDevice                *xdevice);
+
+gboolean  synaptics_is_present    (void);
 gboolean  touchpad_is_present     (void);
 gboolean  touchscreen_is_present  (void);
 gboolean  mouse_is_present        (void);
+gboolean  trackball_is_present    (void);
 
 gboolean  device_set_property     (XDevice                *xdevice,
                                    const char             *device_name,
                                    PropertyHelper         *property);
 
+gboolean  run_custom_command      (GdkDevice              *device,
+                                   CustomCommand           command);
+
+GList *   get_disabled_synaptics     (void);
 char *    xdevice_get_device_node  (int                     deviceid);
 int       xdevice_get_last_tool_id (int                     deviceid);
 gboolean  xdevice_get_dimensions   (int                     deviceid,
@@ -67,6 +79,9 @@ void      xdevice_close      (XDevice                *xdevice);
 
 const char * xdevice_get_wacom_tool_type (int               deviceid);
 
+gboolean  xdevice_is_libinput (gint deviceid);
+
+
 G_END_DECLS
 
 #endif /* __GSD_INPUT_HELPER_H */
diff --git a/plugins/common/test-input-helper.c b/plugins/common/test-input-helper.c
index 954ac30..e78f463 100644
--- a/plugins/common/test-input-helper.c
+++ b/plugins/common/test-input-helper.c
@@ -32,7 +32,7 @@
 int main (int argc, char **argv)
 {
         GList *devices, *l;
-	gboolean has_touchpad, has_touchscreen;
+	gboolean has_touchpad, has_touchscreen, has_trackball;
 
 	gtk_init (&argc, &argv);
 
@@ -42,6 +42,9 @@ int main (int argc, char **argv)
 	has_touchscreen = touchscreen_is_present ();
 	g_print ("Has touchscreen:\t\t\t%s\n", has_touchscreen ? "yes" : "no");
 
+	has_trackball = trackball_is_present ();
+	g_print ("Has trackball:\t\t\t\t%s\n", has_trackball ? "yes" : "no");
+
         devices = gsd_device_manager_list_devices (gsd_device_manager_get (), GSD_DEVICE_TYPE_MOUSE);
         for (l = devices; l != NULL; l = l->next)
                 g_print ("Device '%s' is a mouse\n", gsd_device_get_name (l->data));
-- 
2.6.2


From 2e27aeec3c5bcb9fe398bd3835226f261b60baf1 Mon Sep 17 00:00:00 2001
From: Frederic Crozat <fcrozat@suse.com>
Date: Fri, 4 Mar 2016 15:51:23 +0100
Subject: [PATCH 3/3] Revert "common: Remove "hotplug-command" helper"

This reverts commit c50ceb880e928506d987747f9e554079ad3d9826.
---
 ...e.settings-daemon.peripherals.gschema.xml.in.in |  8 +++
 plugins/common/Makefile.am                         |  3 +
 plugins/common/gsd-input-helper.c                  |  3 +
 plugins/common/gsd-input-helper.h                  |  6 ++
 plugins/common/input-device-example.sh             | 69 ++++++++++++++++++++++
 plugins/keyboard/gsd-keyboard-manager.c            | 18 ++++++
 6 files changed, 107 insertions(+)
 create mode 100644 plugins/common/input-device-example.sh

diff --git a/data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in b/data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in
index f4120e9..6a65f08 100644
--- a/data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in
+++ b/data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in
@@ -4,6 +4,7 @@
     <child name="keyboard" schema="org.gnome.settings-daemon.peripherals.keyboard"/>
     <child name="mouse" schema="org.gnome.settings-daemon.peripherals.mouse"/>
     <child name="touchscreen" schema="org.gnome.settings-daemon.peripherals.touchscreen"/>
+    <child name="input-devices" schema="org.gnome.settings-daemon.peripherals.input-devices"/>
   </schema>
   <schema gettext-domain="@GETTEXT_PACKAGE@" id="org.gnome.settings-daemon.peripherals.smartcard" path="/org/gnome/settings-daemon/peripherals/smartcard/">
     <key name="removal-action" enum="org.gnome.settings-daemon.GsdSmartcardRemovalAction">
@@ -67,6 +68,13 @@
       <_summary>Whether the tablet's orientation is locked, or rotated automatically.</_summary>
     </key>
   </schema>
+  <schema gettext-domain="@GETTEXT_PACKAGE@" id="org.gnome.settings-daemon.peripherals.input-devices" path="/org/gnome/settings-daemon/peripherals/input-devices/">
+    <key name="hotplug-command" type="s">
+      <default>''</default>
+      <_summary>Device hotplug custom command</_summary>
+      <_description>Command to be run when a device is added or removed. An exit value of 1 means that the device will not be handled further by gnome-settings-daemon.</_description>
+    </key>
+  </schema>
 
   <!-- Deprecated schemas/keys -->
   <schema id="org.gnome.settings-daemon.peripherals.mouse.deprecated">
diff --git a/plugins/common/Makefile.am b/plugins/common/Makefile.am
index 0331226..0f85351 100644
--- a/plugins/common/Makefile.am
+++ b/plugins/common/Makefile.am
@@ -82,6 +82,9 @@ test_egg_key_parsing_SOURCES = test-egg-key-parsing.c
 test_egg_key_parsing_LDADD = libcommon.la $(COMMON_LIBS)
 test_egg_key_parsing_CFLAGS = $(libcommon_la_CFLAGS)
 
+scriptsdir = $(datadir)/gnome-settings-daemon-@GSD_API_VERSION@
+scripts_DATA = input-device-example.sh
+
 EXTRA_DIST = $(scripts_DATA) test-plugin.h
 
 CLEANFILES = \
diff --git a/plugins/common/gsd-input-helper.c b/plugins/common/gsd-input-helper.c
index 4dca795..e3fe2d8 100644
--- a/plugins/common/gsd-input-helper.c
+++ b/plugins/common/gsd-input-helper.c
@@ -31,6 +31,9 @@
 #include "gsd-input-helper.h"
 #include "gsd-device-manager.h"
 
+#define INPUT_DEVICES_SCHEMA "org.gnome.settings-daemon.peripherals.input-devices"
+#define KEY_HOTPLUG_COMMAND  "hotplug-command"
+
 #define ABS_MT_X "Abs MT Position X"
 #define ABS_MT_Y "Abs MT Position Y"
 #define ABS_X "Abs X"
diff --git a/plugins/common/gsd-input-helper.h b/plugins/common/gsd-input-helper.h
index 85e6d07..aadd790 100644
--- a/plugins/common/gsd-input-helper.h
+++ b/plugins/common/gsd-input-helper.h
@@ -28,6 +28,12 @@ G_BEGIN_DECLS
 
 #define WACOM_SERIAL_IDS_PROP "Wacom Serial IDs"
 
+typedef enum {
+        COMMAND_DEVICE_ADDED,
+        COMMAND_DEVICE_REMOVED,
+        COMMAND_DEVICE_PRESENT
+} CustomCommand;
+
 /* Generic property setting code. Fill up the struct property with the property
  * data and pass it into device_set_property together with the device to be
  * changed.  Note: doesn't cater for non-zero offsets yet, but we don't have
diff --git a/plugins/common/input-device-example.sh b/plugins/common/input-device-example.sh
new file mode 100644
index 0000000..235cdc4
--- /dev/null
+++ b/plugins/common/input-device-example.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# This script is an example hotplug script for use with the various
+# input devices plugins.
+#
+# The script is called with the arguments:
+# -t [added|present|removed] <device name>
+#       added ... device was just plugged in
+#       present.. device was present at gnome-settings-daemon startup
+#       removed.. device was just removed
+# -i <device ID>
+#       device ID being the XInput device ID
+# <device name> The name of the device
+#
+# The script should return 1 if the device is to be
+# ignored from future configuration.
+#
+# Set the script to be used with:
+# gsettings set org.gnome.settings-daemon.peripherals.input-devices hotplug-command /path/to/script/input-devices.sh
+#
+
+args=`getopt "t:i:" $*`
+
+set -- $args
+
+while [ $# -gt 0 ]
+do
+    case $1 in
+    -t)
+        shift;
+        type="$1"
+        ;;
+     -i)
+        shift;
+        id="$1"
+        ;;
+     --)
+        shift;
+        device="$@"
+        break;
+        ;;
+    *)
+        echo "Unknown option $1";
+        exit 1
+        ;;
+    esac
+    shift
+done
+
+retval=0
+
+case $type in
+        added)
+                echo "Device '$device' (ID=$id) was added"
+                ;;
+        present)
+                echo "Device '$device' (ID=$id) was already present at startup"
+                ;;
+        removed)
+                echo "Device '$device' (ID=$id) was removed"
+                ;;
+        *)
+                echo "Unknown operation"
+                retval=1
+                ;;
+esac
+
+# All further processing will be disabled if $retval == 1
+exit $retval
diff --git a/plugins/keyboard/gsd-keyboard-manager.c b/plugins/keyboard/gsd-keyboard-manager.c
index ddeeee6..eb75c92 100644
--- a/plugins/keyboard/gsd-keyboard-manager.c
+++ b/plugins/keyboard/gsd-keyboard-manager.c
@@ -90,6 +90,7 @@ struct GsdKeyboardManagerPrivate
         GsdNumLockState old_state;
         GdkDeviceManager *device_manager;
         guint device_added_id;
+        guint device_removed_id;
 };
 
 static void     gsd_keyboard_manager_class_init  (GsdKeyboardManagerClass *klass);
@@ -363,6 +364,20 @@ device_added_cb (GdkDeviceManager   *device_manager,
         if (source == GDK_SOURCE_KEYBOARD) {
                 g_debug ("New keyboard plugged in, applying all settings");
                 apply_numlock (manager);
+                run_custom_command (device, COMMAND_DEVICE_ADDED);
+        }
+}
+
+static void
+device_removed_cb (GdkDeviceManager   *device_manager,
+                   GdkDevice          *device,
+                   GsdKeyboardManager *manager)
+{
+        GdkInputSource source;
+
+        source = gdk_device_get_source (device);
+        if (source == GDK_SOURCE_KEYBOARD) {
+                run_custom_command (device, COMMAND_DEVICE_REMOVED);
         }
 }
 
@@ -378,6 +393,8 @@ set_devicepresence_handler (GsdKeyboardManager *manager)
 
         manager->priv->device_added_id = g_signal_connect (G_OBJECT (device_manager), "device-added",
                                                            G_CALLBACK (device_added_cb), manager);
+        manager->priv->device_removed_id = g_signal_connect (G_OBJECT (device_manager), "device-removed",
+                                                             G_CALLBACK (device_removed_cb), manager);
         manager->priv->device_manager = device_manager;
 }
 
@@ -758,6 +775,7 @@ gsd_keyboard_manager_stop (GsdKeyboardManager *manager)
 
         if (p->device_manager != NULL) {
                 g_signal_handler_disconnect (p->device_manager, p->device_added_id);
+                g_signal_handler_disconnect (p->device_manager, p->device_removed_id);
                 p->device_manager = NULL;
         }
 
-- 
2.6.2

