diff --git a/configure.ac b/configure.ac
index 10b0bfe..91a4229 100644
--- a/configure.ac
+++ b/configure.ac
@@ -397,6 +397,29 @@ fi
 AM_CONDITIONAL(BUILD_PRINT_NOTIFICATIONS, [test x"$enable_cups" = x"yes"])
 
 # ---------------------------------------------------------------------------
+# Gamepads Support
+# ---------------------------------------------------------------------------
+# FIXME: Disable by default if required.
+have_gamepads_support=false
+AC_ARG_ENABLE(gamepads-support,
+  AC_HELP_STRING([--enable-gamepads-support],
+                 [turn on gamepads support]),
+       [case "${enableval}" in
+               yes) WANT_GAMEPADS_SUPPORT=yes ;;
+               no)  WANT_GAMEPADS_SUPPORT=no ;;
+               *) AC_MSG_ERROR(bad value ${enableval} for --enable-gamepads-support) ;;
+       esac],
+       [WANT_GAMEPADS_SUPPORT=yes])
+
+if test x$WANT_GAMEPADS_SUPPORT = xyes ; then
+       PKG_CHECK_MODULES(GAMEPADS, gudev-1.0 libsoup-2.4 json-glib-1.0, [have_gamepads_support=true], [have_gamepads_support=false])
+fi
+AM_CONDITIONAL(GAMEPADS_SUPPORT, test "x$have_gamepads_support" = "xtrue")
+
+AC_SUBST(GAMEPADS_CFLAGS)
+AC_SUBST(GAMEPADS_LIBS)
+
+# ---------------------------------------------------------------------------
 # Enable Profiling
 # ---------------------------------------------------------------------------
 AC_ARG_ENABLE(profiling,
@@ -507,6 +530,7 @@ plugins/color/Makefile
 plugins/common/Makefile
 plugins/cursor/Makefile
 plugins/dummy/Makefile
+plugins/gamepads/Makefile
 plugins/power/Makefile
 plugins/housekeeping/Makefile
 plugins/keyboard/Makefile
@@ -537,6 +561,7 @@ data/org.gnome.settings-daemon.plugins.updates.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.xrandr.gschema.xml.in
 data/org.gnome.settings-daemon.peripherals.wacom.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.print-notifications.gschema.xml.in
+data/org.gnome.settings-daemon.plugins.gamepads.gschema.xml.in
 po/Makefile.in
 man/Makefile
 ])
@@ -572,6 +597,7 @@ echo "
         Smartcard support:        ${have_smartcard_support}
         Cups support:             ${enable_cups}
         Wacom support:            ${have_wacom}
+        Gamepads support:         ${have_gamepads_support}
 ${NSS_DATABASE:+\
         System nssdb:             ${NSS_DATABASE}
 }\
diff --git a/data/Makefile.am b/data/Makefile.am
index 64ab3ea..cf23fac 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -38,6 +38,12 @@ else
 all_schemas += org.gnome.settings-daemon.peripherals.wacom.gschema.xml
 endif
 
+if GAMEPADS_SUPPORT
+gsettings_SCHEMAS += org.gnome.settings-daemon.plugins.gamepads.gschema.xml
+else
+all_schemas += org.gnome.settings-daemon.plugins.gamepads.gschema.xml
+endif
+
 @INTLTOOL_XML_NOMERGE_RULE@
 
 @GSETTINGS_RULES@
diff --git a/data/org.gnome.settings-daemon.plugins.gamepads.gschema.xml.in.in b/data/org.gnome.settings-daemon.plugins.gamepads.gschema.xml.in.in
new file mode 100644
index 0000000..d4ab0ff
--- /dev/null
+++ b/data/org.gnome.settings-daemon.plugins.gamepads.gschema.xml.in.in
@@ -0,0 +1,19 @@
+<schemalist>
+  <schema gettext-domain="@GETTEXT_PACKAGE@" id="org.gnome.settings-daemon.plugins.gamepads" path="/org/gnome/settings-daemon/plugins/gamepads/">
+    <key name="active" type="b">
+      <default>true</default>
+      <_summary>Activation of this plugin</_summary>
+      <_description>Whether this plugin would be activated by gnome-settings-daemon or not</_description>
+    </key>
+    <key name="priority" type="i">
+      <default>1</default>
+      <_summary>Priority to use for this plugin</_summary>
+      <_description>Priority to use for this plugin in gnome-settings-daemon startup queue</_description>
+    </key>
+    <key name="calibration-data" type="a{sa(biiii)}">
+      <default>[]</default>
+      <_summary>Calibration data</_summary>
+      <_description>Calibration data for already calibrated devices</_description>
+    </key>
+  </schema>
+</schemalist>
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index a4aa666..2adea28 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -51,5 +51,11 @@ else
 disabled_plugins += print-notifications
 endif
 
+if GAMEPADS_SUPPORT
+enabled_plugins += gamepads
+else
+disabled_plugins += gamepads
+endif
+
 SUBDIRS = common $(enabled_plugins)
 DIST_SUBDIRS = $(SUBDIRS) $(disabled_plugins)
diff --git a/plugins/gamepads/Makefile.am b/plugins/gamepads/Makefile.am
new file mode 100644
index 0000000..c3d4dfb
--- /dev/null
+++ b/plugins/gamepads/Makefile.am
@@ -0,0 +1,46 @@
+plugin_name = gamepads
+
+plugin_LTLIBRARIES = \
+	libgamepads.la
+
+libgamepads_la_SOURCES = 	\
+	gsd-gamepads-manager.c	\
+	gsd-gamepads-manager.h	\
+	gsd-gamepads-plugin.c	\
+	gsd-gamepads-plugin.h
+
+libgamepads_la_CPPFLAGS = \
+	-I$(top_srcdir)/gnome-settings-daemon		\
+	-DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\" \
+	-DBINDIR=\"$(bindir)\"				\
+	$(AM_CPPFLAGS)
+
+libgamepads_la_CFLAGS = \
+	$(GAMEPADS_CFLAGS)	\
+	$(PLUGIN_CFLAGS)		\
+	$(SETTINGS_PLUGIN_CFLAGS)	\
+	$(AM_CFLAGS)
+
+libgamepads_la_LDFLAGS = 			\
+	$(GSD_PLUGIN_LDFLAGS)
+
+libgamepads_la_LIBADD  = 			\
+	$(top_builddir)/gnome-settings-daemon/libgsd.la	\
+	$(GAMEPADS_LIBS)	\
+	$(SETTINGS_PLUGIN_LIBS)
+
+plugin_in_files = 			\
+	gamepads.gnome-settings-plugin.in
+
+plugin_DATA = $(plugin_in_files:.gnome-settings-plugin.in=.gnome-settings-plugin)
+
+EXTRA_DIST = 					\
+	$(plugin_in_files)
+
+CLEANFILES = 				\
+	$(plugin_DATA)
+
+DISTCLEANFILES =			\
+	$(plugin_DATA)
+
+@GSD_INTLTOOL_PLUGIN_RULE@
diff --git a/plugins/gamepads/gamepads.gnome-settings-plugin.in b/plugins/gamepads/gamepads.gnome-settings-plugin.in
new file mode 100644
index 0000000..041c528
--- /dev/null
+++ b/plugins/gamepads/gamepads.gnome-settings-plugin.in
@@ -0,0 +1,8 @@
+[GNOME Settings Plugin]
+Module=gamepads
+IAge=0
+_Name=Gamepads
+_Description=Gamepads plugin
+Authors=Zan Dobersek <zandobersek@gmail.com>
+Copyright=Copyright © 2012 Zan Dobersek
+Website=
diff --git a/plugins/gamepads/gsd-gamepads-manager.c b/plugins/gamepads/gsd-gamepads-manager.c
new file mode 100644
index 0000000..5c78f82
--- /dev/null
+++ b/plugins/gamepads/gsd-gamepads-manager.c
@@ -0,0 +1,1193 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Zan Dobersek <zandobersek@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <gudev/gudev.h>
+#include <json-glib/json-glib.h>
+#include <libsoup/soup.h>
+#include <linux/joystick.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "gnome-settings-profile.h"
+#include "gnome-settings-session.h"
+#include "gsd-gamepads-manager.h"
+
+#define GSD_GAMEPADS_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_GAMEPADS_MANAGER, GsdGamepadsManagerPrivate))
+
+struct GsdGamepadsManagerPrivate
+{
+        SoupSession *soup_session;
+        SoupMessage *soup_message;
+        GVariant *devices_dict;
+        GUdevClient *gudev_client;
+        GSettings *settings;
+
+        gchar *update_checksum;
+        GChecksum *local_data_checksum;
+        gchar local_data_read_buffer[256];
+
+        GCancellable *bus_cancellable;
+        GDBusNodeInfo *introspection_data;
+        GDBusConnection *dbus_connection;
+};
+
+enum {
+        PROP_0,
+};
+
+#define DEVICES_DATA_CHECKSUM_URL "https://raw.github.com/zdobersek/gamepads-json/master/gamepads.json.checksum"
+#define DEVICES_DATA_URL          "https://raw.github.com/zdobersek/gamepads-json/master/gamepads.json"
+
+#define GSD_DBUS_SERVICE            "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH               "/org/gnome/SettingsDaemon"
+#define GSD_GAMEPADS_DBUS_PATH      GSD_DBUS_PATH "/Gamepads"
+#define GSD_GAMEPADS_DBUS_INTERFACE "org.gnome.SettingsDaemon.Gamepads"
+
+static const gchar introspection_xml[] =
+"<node>"
+"  <interface name='org.gnome.SettingsDaemon.Gamepads'>"
+"    <method name='GetDeviceSupportStatus'>"
+"      <arg name='device_id' type='s' direction='in'/>"
+"      <arg name='device_file' type='s' direction='in'/>"
+"      <arg name='status' type='(sssbb)' direction='out'/>"
+"    </method>"
+"    <method name='GetDeviceCalibrationData'>"
+"      <arg name='device_file' type='s' direction='in'/>"
+"      <arg name='calibration_data' type='a(biiii)' direction='out'/>"
+"    </method>"
+"    <method name='SetDeviceCalibrationData'>"
+"      <arg name='device_id' type='s' direction='in'/>"
+"      <arg name='device_file' type='s' direction='in'/>"
+"      <arg name='calibration_data' type='a(biiii)' direction='in'/>"
+"    </method>"
+"  </interface>"
+"</node>";
+
+static const gchar * subsystems[] = { "input", 0 };
+
+static void     gsd_gamepads_manager_class_init  (GsdGamepadsManagerClass *klass);
+static void     gsd_gamepads_manager_init        (GsdGamepadsManager      *gamepads_manager);
+static void     gsd_gamepads_manager_finalize    (GObject                 *object);
+
+G_DEFINE_TYPE (GsdGamepadsManager, gsd_gamepads_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+/*
+ * Checks whether the GUdevDevice represents an actual 'joystick' device
+ * (as intepreted by the kernel).
+ */
+static gboolean
+is_js_device (GUdevDevice *gudev_device)
+{
+        const gchar *device_file;
+        const gchar *sysfs_path;
+
+        device_file = g_udev_device_get_device_file (gudev_device);
+        sysfs_path = g_udev_device_get_sysfs_path (gudev_device);
+
+        if (!device_file || !sysfs_path)
+                return FALSE;
+
+        if (!g_udev_device_has_property (gudev_device, "ID_INPUT") ||
+            !g_udev_device_has_property (gudev_device, "ID_INPUT_JOYSTICK"))
+                return FALSE;
+
+        return g_str_has_prefix (device_file, "/dev/input/js");
+}
+
+/*
+ * Gets the device name for an unsupported device through the kernel driver.
+ */
+static gchar*
+device_name_for_unsupported_device (gchar *device_file)
+{
+        char device_name[1024];
+        gchar *return_value = NULL;
+        gint fd;
+
+        fd = open (device_file, O_RDONLY | O_NONBLOCK);
+        if (fd < 0)
+                goto out;
+
+        if (ioctl (fd, JSIOCGNAME (sizeof (device_name)), device_name) < 0)
+                goto out;
+
+        return_value = g_strdup (device_name);
+
+out:
+        if (return_value == NULL)
+                return_value = g_strdup ("Unknown device");
+        return return_value;
+}
+
+/*
+ * Applies the buttons mapping to a device represented by the file descriptor.
+ */
+static void
+apply_buttons_mapping_to_device (gint fd, GVariant *device_dict)
+{
+        GVariant *buttons_mapping, *child;
+        GVariantIter iter;
+        int i;
+        uint8_t buttons_count;
+        uint16_t buttons_map[KEY_MAX - BTN_MISC + 1];
+
+        buttons_mapping = g_variant_lookup_value (device_dict, "buttons_mapping", G_VARIANT_TYPE_ARRAY);
+        if (buttons_mapping == NULL) {
+                g_warning ("Could not acquire buttons mapping for a supported device");
+                return;
+        }
+
+        if (ioctl (fd, JSIOCGBUTTONS, &buttons_count) < 0) {
+                g_warning ("Error when reading buttons count from device");
+                return;
+        }
+
+        if (buttons_count != g_variant_n_children (buttons_mapping)) {
+                g_warning ("Saved buttons mapping size and actual buttons count do not match");
+                return;
+        }
+
+        memset (buttons_map, 0, sizeof (buttons_map));
+
+        i = 0;
+        g_variant_iter_init (&iter, buttons_mapping);
+        while ((child = g_variant_iter_next_value (&iter))) {
+                buttons_map[i++] = (uint16_t) g_variant_get_int64 (child);
+                g_variant_unref (child);
+        }
+
+        if (ioctl (fd, JSIOCSBTNMAP, buttons_map) < 0)
+                g_warning ("Saved buttons mapping could not be applied to the device");
+}
+
+/*
+ * Applies the axes mapping to a device represented by the file descriptor.
+ */
+static void
+apply_axes_mapping_to_device (gint fd, GVariant *device_dict)
+{
+        GVariant *axes_mapping, *child;
+        GVariantIter iter;
+        int i;
+        uint8_t axes_count;
+        uint8_t axes_map[ABS_MAX + 1];
+
+        axes_mapping = g_variant_lookup_value (device_dict, "axes_mapping", G_VARIANT_TYPE_ARRAY);
+        if (axes_mapping == NULL) {
+                g_warning ("Could not acquire buttons mapping for a supported device");
+                return;
+        }
+
+        if (ioctl (fd, JSIOCGAXES, &axes_count) < 0) {
+                g_warning ("Error when reading axes count from device");
+                return;
+        }
+
+        if (axes_count != g_variant_n_children (axes_mapping)) {
+                g_warning ("Saved axes mapping size and actual axes count do not match");
+                return;
+        }
+
+        i = 0;
+        g_variant_iter_init (&iter, axes_mapping);
+        while ((child = g_variant_iter_next_value (&iter))) {
+                axes_map[i++] = (uint16_t) g_variant_get_int64 (child);
+                g_variant_unref (child);
+        }
+
+        if (ioctl (fd, JSIOCSAXMAP, axes_map) < 0)
+                g_warning ("Saved axes mapping could not be applied to the device");
+}
+
+/*
+ * Applies the calibration data to a device represented by the file descriptor.
+ */
+static void
+apply_calibration_data_to_device (gint fd, GVariant *calibration_data)
+{
+        GVariantIter iter;
+        GVariant *child;
+        gboolean axis_calibrated;
+        gint i;
+        gint32 axis_center_min, axis_center_max, axis_range_min, axis_range_max;
+        struct js_corr corr[ABS_MAX + 1];
+
+        g_variant_iter_init (&iter, calibration_data);
+        i = 0;
+        while ((child = g_variant_iter_next_value (&iter))) {
+                g_variant_get (child, "(biiii)", &axis_calibrated,
+                               &axis_center_min, &axis_center_max,
+                               &axis_range_min, &axis_range_max);
+
+                if (axis_calibrated) {
+                        corr[i].type = 1;
+                        corr[i].prec = 0;
+                        corr[i].coef[0] = axis_center_min;
+                        corr[i].coef[1] = axis_center_max;
+                        corr[i].coef[2] = axis_range_min;
+                        corr[i].coef[3] = axis_range_max;
+                } else {
+                        corr[i].type = 0;
+                        corr[i].prec = 0;
+                        memset (corr[i].coef, 0, sizeof (corr[i].coef));
+                }
+
+                g_variant_unref (child);
+                i++;
+        }
+
+        if (ioctl (fd, JSIOCSCORR, &corr) < 0)
+                g_warning ("Unable to apply new calibration data");
+}
+
+/*
+ * Checks whether the JsonNode is a valid node containing an integer.
+ */
+static gboolean
+is_valid_integer_node (JsonNode *node)
+{
+        return json_node_get_value_type (node) == G_TYPE_INT64;
+}
+
+/*
+ * Checks whether the JsonNode is a valid node containing a string.
+ */
+static gboolean
+is_valid_string_node (JsonNode *node)
+{
+        return json_node_get_value_type (node) == G_TYPE_STRING;
+}
+
+/*
+ * Checks whether the JsonNode is a valid node containing a boolean.
+ */
+static gboolean
+is_valid_boolean_node (JsonNode *node)
+{
+        return json_node_get_value_type (node) == G_TYPE_BOOLEAN;
+}
+
+/*
+ * Checks whether the JsonNode is a valid node containing a mapping of 
+ * acceptable form: [1, 2, 3, 4] or similar.
+ */
+static gboolean
+is_valid_mapping (JsonNode *node)
+{
+        GList *node_array_elements, *el;
+        JsonArray *node_array;
+        JsonNode *el_node;
+        gboolean node_array_is_valid;
+
+        if (!JSON_NODE_HOLDS_ARRAY (node))
+                return FALSE;
+
+        node_array = json_node_get_array (node);
+        node_array_elements = json_array_get_elements (node_array);
+
+        node_array_is_valid = TRUE;
+        for (el = g_list_first (node_array_elements); el; el = g_list_next (el)) {
+            el_node = (JsonNode *) el->data;
+            if (!is_valid_integer_node (el_node)) {
+                    node_array_is_valid = FALSE;
+                    break;
+            }
+        }
+
+        g_list_free (node_array_elements);
+        return node_array_is_valid;
+}
+
+/*
+ * Checks whether the root JsonNode has a valid scheme:
+ * - an object positioned at the root node
+ * - two members, 'format_verison' (integer node) and 'devices' (array node)
+ * - the 'devices' children are objects, containing the following children nodes:
+ *      - 'device_name' and 'device_id', both string nodes
+ *      - 'axes_mapping and buttons_mapping', both array nodes containing integers
+ *      - 'supports_calibration', an optional boolean node defaulting to FALSE
+ */
+static gboolean
+is_valid_json_data (JsonNode *root_node)
+{
+        GList *devices_array_elements, *el;
+        JsonArray *devices_array;
+        JsonNode *format_version_node, *devices_node, *el_node;
+        JsonObject *root_object, *el_object;
+        gboolean el_is_valid;
+
+        if (root_node == NULL)
+                return FALSE;
+
+        if (!JSON_NODE_HOLDS_OBJECT (root_node)) {
+                g_warning ("Invalid data - root node does not hold an object");
+                return FALSE;
+        }
+
+        root_object = json_node_get_object (root_node);
+        if (!json_object_has_member (root_object, "format_version") || !json_object_has_member (root_object, "devices")) {
+                g_warning ("Invalid data - root object should have two members, 'format_version' and 'devices'");
+                return FALSE;
+        }
+
+        format_version_node = json_object_get_member (root_object, "format_version");
+        if (!is_valid_integer_node (format_version_node)) {
+                g_warning ("Invalid data - 'format_version' node does not contain a fundamental type");
+                return FALSE;
+        }
+
+        devices_node = json_object_get_member (root_object, "devices");
+        if (!JSON_NODE_HOLDS_ARRAY (devices_node)) {
+                g_warning ("Invalid data - 'devices' node does not contain an array");
+                return FALSE;
+        }
+
+        devices_array = json_node_get_array (devices_node);
+        devices_array_elements = json_array_get_elements (devices_array);
+
+        el_is_valid = TRUE;
+        for (el = g_list_first (devices_array_elements); el; el = g_list_next (el)) {
+                el_node = (JsonNode *) el->data;
+                el_is_valid = FALSE;
+
+                if (!JSON_NODE_HOLDS_OBJECT (el_node)) {
+                        g_warning ("Invalid data - device element node should hold an object");
+                        break;
+                }
+
+                el_object = json_node_get_object (el_node);
+                if (!json_object_has_member (el_object, "device_name") || !json_object_has_member (el_object, "device_id")
+                    || !json_object_has_member (el_object, "buttons_mapping") || !json_object_has_member (el_object, "axes_mapping")) {
+                        g_warning ("Invalid data - device element object should have four members, "
+                                   "'device_name', 'device_id', 'buttons_mapping' and 'axes_mapping'");
+                        break;
+                }
+
+                if (!is_valid_string_node (json_object_get_member (el_object, "device_name"))
+                    || !is_valid_string_node (json_object_get_member (el_object, "device_id"))) {
+                        g_warning ("Invalid data - 'device_name' and 'device_id' members should hold a string");
+                        break;
+                }
+
+                if (!is_valid_mapping (json_object_get_member (el_object, "axes_mapping"))
+                    || !is_valid_mapping (json_object_get_member (el_object, "buttons_mapping"))) {
+                        g_warning ("Invalid data - mappings should hold an array of integers");
+                        break;
+                }
+
+                if (json_object_has_member (el_object, "supports_calibration")
+                    &&  !is_valid_boolean_node (json_object_get_member (el_object, "supports_calibration"))) {
+                        g_warning ("Invalid data - optional 'supports_calibration' member should hold a boolean");
+                }
+
+                el_is_valid = TRUE;
+        }
+
+        g_list_free (devices_array_elements);
+        return el_is_valid;
+}
+
+/*
+ * Sets the element of the mapping array at the specified index
+ * to the integer value of the JsonNode.
+ */
+static void
+append_element_to_mapping (JsonArray *array, guint index_, JsonNode *node, gint64 *mapping)
+{
+        mapping[index_] = json_node_get_int (node);
+}
+
+/*
+ * Creates a GVariant dictionary for the device that's presented by the JsonObject.
+ * The dictionary is of the following GVariant type format string: a{sv}
+ */
+static GVariant*
+device_dictionary_for_json_object (JsonObject *object)
+{
+        GVariant **dict_entries;
+        JsonArray *buttons_mapping_array, *axes_mapping_array;
+        gboolean supports_calibration;
+        gint64 *buttons_mapping, *axes_mapping;
+
+        dict_entries = g_new (GVariant *, 4);
+        dict_entries[0] = g_variant_new_dict_entry (g_variant_new_string ("name"),
+                                                    g_variant_new_variant (
+                                                            g_variant_new_string (json_object_get_string_member (object, "device_name"))));
+
+        buttons_mapping_array = json_object_get_array_member (object, "buttons_mapping");
+        buttons_mapping = g_new (gint64, json_array_get_length (buttons_mapping_array));
+        json_array_foreach_element (buttons_mapping_array, (JsonArrayForeach) append_element_to_mapping, buttons_mapping);
+        dict_entries[1] = g_variant_new_dict_entry (g_variant_new_string ("buttons_mapping"),
+                                                    g_variant_new_variant (g_variant_new_fixed_array (G_VARIANT_TYPE_INT64, buttons_mapping,
+                                                                                                      json_array_get_length (buttons_mapping_array),
+                                                                                                      sizeof (gint64))));
+        g_free (buttons_mapping);
+
+        axes_mapping_array = json_object_get_array_member (object, "axes_mapping");
+        axes_mapping = g_new (gint64, json_array_get_length (axes_mapping_array));
+        json_array_foreach_element (axes_mapping_array, (JsonArrayForeach) append_element_to_mapping, axes_mapping);
+        dict_entries[2] = g_variant_new_dict_entry (g_variant_new_string ("axes_mapping"),
+                                                    g_variant_new_variant (g_variant_new_fixed_array (G_VARIANT_TYPE_INT64, axes_mapping,
+                                                                                                      json_array_get_length (axes_mapping_array),
+                                                                                                      sizeof (gint64))));
+        g_free (axes_mapping);
+
+        supports_calibration = FALSE;
+        if (json_object_has_member (object, "supports_calibration"))
+                supports_calibration = json_object_get_boolean_member (object, "supports_calibration");
+        dict_entries[3] = g_variant_new_dict_entry (g_variant_new_string ("supports_calibration"),
+                                                    g_variant_new_variant (g_variant_new_boolean (supports_calibration)));
+
+        return g_variant_new_array (NULL, dict_entries, 4);
+}
+
+/*
+ * Flattens the data in the SoupMessage buffers into a string.
+ */
+static gchar*
+get_flattened_data_from_soup_message (SoupMessage *message)
+{
+        SoupBuffer *buffer;
+        gchar *data;
+
+        if (message->status_code != 200) {
+                g_warning ("Could not properly access data over request");
+                return NULL;
+        }
+
+        buffer = soup_message_body_flatten (message->response_body);
+        data = g_strdup(buffer->data);
+        soup_buffer_free (buffer);
+
+        return data;
+}
+
+/*
+ * Updates the GUdevDevice with it's belonging buttons and axes mappings, if any.
+ */
+static void
+ggm_update_device_mappings (GsdGamepadsManager *manager, GUdevDevice *gudev_device)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GVariant *device_dict_entry;
+        const gchar *device_file, *vendor_id, *model_id;
+        gchar* device_key;
+        int fd;
+
+        if (priv->devices_dict == NULL)
+                return;
+
+        device_file = g_udev_device_get_device_file (gudev_device);
+
+        if (!g_udev_device_has_property (gudev_device, "ID_VENDOR_ID")) {
+                g_warning ("vendor ID required but not found");
+                return;
+        }
+
+        if (!g_udev_device_has_property (gudev_device, "ID_MODEL_ID")) {
+                g_warning ("model ID required but not found");
+                return;
+        }
+
+        vendor_id = g_udev_device_get_property (gudev_device, "ID_VENDOR_ID");
+        model_id = g_udev_device_get_property (gudev_device, "ID_MODEL_ID");
+
+        device_key = g_strconcat (vendor_id, model_id, NULL);
+        device_dict_entry = g_variant_lookup_value (priv->devices_dict, device_key, G_VARIANT_TYPE_DICTIONARY);
+        if (device_dict_entry == NULL) {
+                g_debug ("No device data found for device with ID %s\n", device_key);
+                goto out;
+        }
+        g_debug ("got entry %s\n", g_variant_print (device_dict_entry, TRUE));
+
+        fd = open (device_file, O_NONBLOCK);
+        if (fd < 0) {
+                g_warning ("Unable to open device file %s", device_file);
+                goto out;
+        }
+
+        apply_buttons_mapping_to_device (fd, device_dict_entry);
+        apply_axes_mapping_to_device (fd, device_dict_entry);
+
+        close (fd);
+out:
+        g_free (device_key);
+}
+
+/*
+ * Updates the GUdevDevice with it's belonging calibration data, if any.
+ */
+static void
+ggm_update_device_calibration (GsdGamepadsManager *manager, GUdevDevice *gudev_device)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GVariant *calibration_data_dict, *calibration_data;
+        const gchar *device_file, *vendor_id, *model_id;
+        gchar* device_key;
+        int fd;
+
+        device_file = g_udev_device_get_device_file (gudev_device);
+
+        if (!g_udev_device_has_property (gudev_device, "ID_VENDOR_ID")) {
+                g_warning ("vendor ID required but not found");
+                return;
+        }
+
+        if (!g_udev_device_has_property (gudev_device, "ID_MODEL_ID")) {
+                g_warning ("model ID required but not found");
+                return;
+        }
+
+        vendor_id = g_udev_device_get_property (gudev_device, "ID_VENDOR_ID");
+        model_id = g_udev_device_get_property (gudev_device, "ID_MODEL_ID");
+
+        device_key = g_strconcat (vendor_id, model_id, NULL);
+
+        calibration_data_dict = g_settings_get_value (priv->settings, "calibration-data");
+        calibration_data = g_variant_lookup_value (calibration_data_dict, device_key, G_VARIANT_TYPE ("a(biiii)"));
+        if (calibration_data == NULL) {
+                g_debug ("No calibration data found for device with ID %s", device_key);
+                goto out;
+        }
+
+        fd = open (device_file, O_NONBLOCK);
+        if (fd < 0) {
+                g_warning ("Unable to open device file %s", device_file);
+                goto out;
+        }
+
+        apply_calibration_data_to_device (fd, calibration_data);
+
+        close (fd);
+
+out:
+        g_variant_unref (calibration_data_dict);
+        if (calibration_data)
+                g_variant_unref (calibration_data);
+
+        g_free (device_key);
+}
+
+/*
+ * Stores the calibration data for the device into the GSettings schema.
+ */
+static void
+ggm_store_calibration_data_for_device (GsdGamepadsManager *manager, gchar *device_id, GVariant *calibration_data)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GVariantBuilder *builder;
+        GVariantIter iter;
+        GVariant *current_calibration_data, *new_calibration_data, *child, *array;
+        gboolean stored = FALSE, ret;
+        gchar *saved_device_id;
+
+        builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sa(biiii)}"));
+
+        current_calibration_data = g_settings_get_value (priv->settings, "calibration-data");
+        g_variant_iter_init (&iter, current_calibration_data);
+        while ((child = g_variant_iter_next_value (&iter))) {
+                g_variant_get (child, "{s*}", &saved_device_id, &array);
+                if (g_strcmp0(saved_device_id, device_id) == 0) {
+                        g_variant_builder_add_value (builder, g_variant_new_dict_entry (g_variant_new_string (device_id),
+                                                                                        g_variant_ref (calibration_data)));
+                        stored = TRUE;
+                } else
+                        g_variant_builder_add_value (builder, g_variant_ref (child));
+
+                g_variant_unref (child);
+        }
+
+        if (!stored)
+                g_variant_builder_add_value (builder, g_variant_new_dict_entry (g_variant_new_string (device_id),
+                                                                                g_variant_ref (calibration_data)));
+
+        new_calibration_data = g_variant_builder_end (builder);
+        ret = g_settings_set_value (priv->settings, "calibration-data", new_calibration_data);
+        if (!ret)
+                g_warning ("Error occurred while saving the new calibration data");
+
+        g_variant_builder_unref (builder);
+}
+
+/*
+ * Updates all the connected joystick devices. This is only run at the startup of the daemon.
+ */
+static void
+ggm_update_connected_devices (GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GList *devices_list, *device;
+        GUdevDevice *gudev_device;
+
+        devices_list = g_udev_client_query_by_subsystem (priv->gudev_client, subsystems[0]);
+        for (device = g_list_first (devices_list); device; device = g_list_next (device)) {
+                gudev_device = G_UDEV_DEVICE (device->data);
+                if (is_js_device (gudev_device)) {
+                        ggm_update_device_mappings (manager, gudev_device);
+                        ggm_update_device_calibration (manager, gudev_device);
+                }
+                g_object_unref (gudev_device);
+        }
+
+        g_list_free (devices_list);
+}
+
+/*
+ * Constructs the main GVariant dictionary out of the JSON data, mapping every supported
+ * device's ID to that device's dictionary.
+ * The dictionary is of the following GVariant type format string: a{sv}
+ */
+static void
+ggm_construct_devices_dictionary_from_json_data (GsdGamepadsManager *manager, JsonNode *root)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GList *devices_array_elements, *el;
+        GVariant **dict_entries, *key, *value;
+        JsonArray *devices_array;
+        JsonObject *el_object;
+        guint length, i;
+
+        devices_array = json_object_get_array_member (json_node_get_object (root), "devices");
+
+        length = json_array_get_length (devices_array);
+        dict_entries = g_new (GVariant *, length);
+
+        devices_array_elements = json_array_get_elements (devices_array);
+        i = 0;
+        for (el = g_list_first (devices_array_elements); el; el = g_list_next (el)) {
+                el_object = json_node_get_object ((JsonNode *) el->data);
+
+                key = g_variant_new_string (json_object_get_string_member (el_object, "device_id"));
+                value = g_variant_new_variant (device_dictionary_for_json_object (el_object));
+                dict_entries[i++] = g_variant_new_dict_entry (key, value);
+        }
+
+        priv->devices_dict = g_variant_ref_sink (g_variant_new_array (NULL, dict_entries, length));
+        g_free (dict_entries);
+}
+
+/*
+ * Creates a JSON parser from the local device data file and checks its validity.
+ * If that's OK, it proceeds to construct the main GVariant device dictionary
+ * and updates all the connected devices afterwards.
+ */
+static void
+ggm_construct_devices_dictionary (GsdGamepadsManager *manager)
+{
+        GError *error = NULL;
+        JsonParser *parser;
+        gboolean ret;
+        gchar *devices_data_filename;
+
+        devices_data_filename = g_build_filename (g_get_user_data_dir (), "gsd-gamepads", "gamepads.json", NULL);
+
+        parser = json_parser_new ();
+        ret = json_parser_load_from_file (parser, devices_data_filename, &error);
+        g_free (devices_data_filename);
+
+        if (!ret) {
+                g_warning ("Could not parse devices data: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        if (!is_valid_json_data (json_parser_get_root (parser)))
+                goto out;
+
+        ggm_construct_devices_dictionary_from_json_data (manager, json_parser_get_root (parser));
+        ggm_update_connected_devices (manager);
+out:
+        g_object_unref (parser);
+}
+
+/*
+ * Writes the updated devices data to the local file.
+ */
+static void
+ggm_write_devices_data_update (GsdGamepadsManager *manager, SoupMessage *message)
+{
+        GError *error = NULL;
+        gboolean ret;
+        gchar *devices_data_filename, *devices_data;
+
+        devices_data = get_flattened_data_from_soup_message (message);
+        if (devices_data == NULL) {
+                g_warning ("Could not acquire updated devices data");
+                return;
+        }
+
+        devices_data_filename = g_build_filename (g_get_user_data_dir (), "gsd-gamepads", "gamepads.json", NULL);
+        ret = g_file_set_contents (devices_data_filename, devices_data, -1, &error);
+        if (!ret) {
+                g_warning ("Could not write updated devices data: %s", error->message);
+                g_error_free (error);
+        }
+
+        g_free (devices_data_filename);
+        g_free (devices_data);
+}
+
+static void
+update_message_completed_cb (SoupSession *session, SoupMessage *message, GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+
+        ggm_write_devices_data_update (manager, message);
+        ggm_construct_devices_dictionary (manager);
+
+        priv->soup_message = NULL;
+        g_object_unref (priv->soup_session);
+        priv->soup_session = NULL;
+}
+
+static void
+ggm_perform_devices_data_update (GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+
+        priv->soup_message = soup_message_new ("GET", DEVICES_DATA_URL);
+        if (priv->soup_message == NULL) {
+                g_warning ("Could not construct a request for devices data");
+                return;
+        }
+
+        soup_session_queue_message (priv->soup_session, priv->soup_message, (SoupSessionCallback) update_message_completed_cb, manager);
+}
+
+/*
+ * Gets the local data contents, computes thhe ckecksum and compares it
+ * to the up-to-date checksum, performing an update when mismatch occurs.
+ */
+static void
+ggm_compare_devices_data_checksums (GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GError *error = NULL;
+        gboolean ret;
+        gchar *devices_data_filename, *devices_data, *data_checksum;
+        gsize length;
+
+        devices_data_filename = g_build_filename (g_get_user_data_dir (), "gsd-gamepads", "gamepads.json", NULL);
+
+        ret = g_file_get_contents (devices_data_filename, &devices_data, &length, &error);
+        if (!ret) {
+                g_warning ("Error while accessing local devices data: %s", error->message);
+                g_error_free (error);
+
+                // There was an error while reading the local devices data, so try to force an update.
+                ggm_perform_devices_data_update (manager);
+                goto out;
+        }
+
+        data_checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA256, devices_data, length);
+
+        if (g_strcmp0 (priv->update_checksum, data_checksum) != 0) {
+                // The checksums differ so the update is necessary.
+                ggm_perform_devices_data_update (manager);
+        } else
+                ggm_construct_devices_dictionary (manager);
+
+        g_free (data_checksum);
+        g_free (devices_data);
+out:
+        g_free (devices_data_filename);
+
+        g_free (priv->update_checksum);
+        priv->update_checksum = NULL;
+}
+
+static void
+checksum_message_completed_cb (SoupSession *session, SoupMessage *message, GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+
+        priv->update_checksum = get_flattened_data_from_soup_message (message);
+        priv->soup_message = NULL;
+        if (priv->update_checksum == NULL) {
+                g_warning ("Could not acquire update checksum from the received data");
+
+                // Checksum couldn't be acquired, still try to force through the update.
+                ggm_perform_devices_data_update (manager);
+                return;
+        }
+
+        ggm_compare_devices_data_checksums (manager);
+}
+
+/*
+ * Queues a request for the up-to-date checksum.
+ */
+static gboolean
+get_devices_data_checksum_cb (GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+
+        priv->soup_message = soup_message_new ("GET", DEVICES_DATA_CHECKSUM_URL);
+        if (priv->soup_message == NULL) {
+                g_warning ("Could not construct a request for devices data checksum");
+
+                // Checksum request couldn't be created, still try to force through the update.
+                ggm_perform_devices_data_update (manager);
+                return FALSE;
+        }
+
+        soup_session_queue_message (priv->soup_session, priv->soup_message, (SoupSessionCallback) checksum_message_completed_cb, manager);
+        return FALSE;
+}
+
+/*
+ * Updates the device mappings and calibration for joystick devices when they are connected.
+ */
+static void
+ggm_gudev_client_uevent_cb (GUdevClient *gudev_client, gchar *action, GUdevDevice *gudev_device, GsdGamepadsManager *manager)
+{
+        if (g_strcmp0 (action, "add") != 0)
+                return;
+
+        if (!is_js_device (gudev_device))
+                return;
+
+        ggm_update_device_mappings (manager, gudev_device);
+        ggm_update_device_calibration (manager, gudev_device);
+}
+
+/*
+ * Handles the 'GetDeviceSupportStatus' DBus method call, checking the main GVariant
+ * devices dictionary to see whether the device is supported and returns corresponding value.
+ */
+static void
+ggm_dbus_get_device_support_status (GsdGamepadsManager *manager, GVariant *parameters, GDBusMethodInvocation *invocation)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GVariant *return_value, *device_dict_entry;
+        gchar *device_id, *device_file, *device_name;
+        gboolean is_supported, is_calibratable;
+
+        if (priv->devices_dict == NULL)
+                return;
+
+        g_variant_get (parameters, "(ss)",
+                       &device_id, &device_file);
+
+        device_dict_entry = g_variant_lookup_value (priv->devices_dict, device_id, G_VARIANT_TYPE_DICTIONARY);
+        if (device_dict_entry == NULL) {
+                device_name = device_name_for_unsupported_device (device_file);
+                is_supported = FALSE;
+                is_calibratable = FALSE;
+        } else {
+                device_name = g_variant_dup_string (g_variant_lookup_value (device_dict_entry, "name", G_VARIANT_TYPE_STRING), NULL);
+                is_supported = TRUE;
+                is_calibratable = g_variant_get_boolean (g_variant_lookup_value (device_dict_entry, "supports_calibration",
+                                                                                 G_VARIANT_TYPE_BOOLEAN));
+        }
+
+        return_value = g_variant_new ("(sssbb)", device_id, device_file, device_name, is_supported, is_calibratable);
+        g_dbus_method_invocation_return_value (invocation, g_variant_new_tuple (&return_value, 1));
+
+        g_free (device_name);
+}
+
+/*
+ * Handles the 'GetDeviceCalibrationData' DBus method call, querying for device's
+ * calibration data through the kernel driver and returning an array of axis-specific
+ * calibration values.
+ */
+static void
+ggm_dbus_get_device_calibration_data (GsdGamepadsManager *manager, GVariant *parameters, GDBusMethodInvocation *invocation)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GVariantBuilder *builder;
+        GVariant *return_value;
+        gboolean axis_calibrated;
+        gchar *device_file;
+        gint fd, i;
+        gint32 axis_center_min, axis_center_max, axis_range_min, axis_range_max;
+        uint8_t axes_count;
+        struct js_corr corr[ABS_MAX + 1];
+
+        g_variant_get (parameters, "(s)", &device_file);
+
+        fd = open (device_file, O_NONBLOCK);
+        if (fd < 0) {
+                g_warning ("Unable to open device file %s", device_file);
+                return;
+        }
+
+        if (ioctl (fd, JSIOCGAXES, &axes_count) < 0) {
+                g_warning ("Error when reading axes count from device");
+                return;
+        }
+
+        if (ioctl (fd, JSIOCGCORR, &corr) < 0) {
+                g_warning ("Error when reading current axes calibration from device");
+                return;
+        }
+
+        close (fd);
+
+        builder = g_variant_builder_new (G_VARIANT_TYPE ("a(biiii)"));
+
+        for (i = 0; i < axes_count; i++) {
+                if (corr[i].type == 0) {
+                        axis_calibrated = FALSE;
+                        axis_center_min = axis_center_max = 0;
+                        axis_range_min = axis_range_max = 0;
+                } else {
+                        axis_calibrated = TRUE;
+                        axis_center_min = corr[i].coef[0];
+                        axis_center_max = corr[i].coef[1];
+                        axis_range_min = corr[i].coef[2];
+                        axis_range_max = corr[i].coef[3];
+                }
+
+                g_variant_builder_add_value (builder, g_variant_new ("(biiii)", axis_calibrated,
+                                                                     axis_center_min, axis_center_max,
+                                                                     axis_range_min, axis_range_max));
+        }
+
+        return_value = g_variant_builder_end (builder);
+        g_dbus_method_invocation_return_value (invocation, g_variant_new_tuple (&return_value, 1));
+}
+
+/*
+ * Handles the 'SetDeviceCalibrationData' DBus method call, first storing the calibration
+ * data to the GSettings schema via the helper function and then applying this calibration
+ * data to the file descriptor of the opened device file.
+ */
+static void
+ggm_dbus_set_device_calibration_data (GsdGamepadsManager *manager, GVariant *parameters, GDBusMethodInvocation *invocation)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GVariant *calibration_data;
+        gchar *device_id, *device_file;
+        gint fd;
+
+        g_variant_get_child (parameters, 0, "s", &device_id);
+        g_variant_get_child (parameters, 1, "s", &device_file);
+        calibration_data = g_variant_get_child_value (parameters, 2);
+
+        ggm_store_calibration_data_for_device (manager, device_id, calibration_data);
+
+        fd = open (device_file, O_NONBLOCK);
+        if (fd < 0) {
+                g_warning ("Unable to open device file %s", device_file);
+                goto out;
+        }
+
+        apply_calibration_data_to_device (fd, calibration_data);
+
+        close (fd);
+
+out:
+        g_dbus_method_invocation_return_value (invocation, NULL);
+
+        g_variant_unref (calibration_data);
+}
+
+static void
+handle_dbus_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path,
+                         const gchar *interface_name, const gchar *method_name, GVariant *parameters,
+                         GDBusMethodInvocation *invocation, gpointer user_data)
+{
+        GsdGamepadsManager *manager = GSD_GAMEPADS_MANAGER (user_data);
+
+        if (g_strcmp0 (method_name, "GetDeviceSupportStatus") == 0)
+                ggm_dbus_get_device_support_status (manager, parameters, invocation);
+        else if (g_strcmp0 (method_name, "GetDeviceCalibrationData") == 0)
+                ggm_dbus_get_device_calibration_data (manager, parameters, invocation);
+        else if (g_strcmp0 (method_name, "SetDeviceCalibrationData") == 0)
+                ggm_dbus_set_device_calibration_data (manager, parameters, invocation);
+        else
+                g_warning ("Invalid method '%s' called on DBus", method_name);
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+        handle_dbus_method_call,
+        NULL, /* get_propert */
+        NULL, /* set_property */
+};
+
+static void
+on_bus_gotten (GObject *source_object, GAsyncResult *result, GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+        GDBusConnection *connection;
+        GDBusInterfaceInfo **infos;
+        GError *error = NULL;
+        guint i;
+
+        if (priv->bus_cancellable == NULL ||
+            g_cancellable_is_cancelled (priv->bus_cancellable)) {
+                g_warning ("Operation has been cancelled, so not retrieving session bus");
+                return;
+        }
+
+        connection = g_bus_get_finish (result, &error);
+        if (connection == NULL) {
+                g_warning ("Could not get session bus: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        priv->dbus_connection = connection;
+        infos = priv->introspection_data->interfaces;
+        for (i = 0; infos[i] != NULL; i++)
+                g_dbus_connection_register_object (connection, GSD_GAMEPADS_DBUS_PATH, infos[i],
+                                                   &interface_vtable, manager, NULL, NULL);
+}
+
+static void
+register_manager_dbus (GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+
+        priv->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+        priv->bus_cancellable = g_cancellable_new ();
+        g_assert (manager->priv->introspection_data != NULL);
+
+        g_bus_get (G_BUS_TYPE_SESSION,
+                   manager->priv->bus_cancellable,
+                   (GAsyncReadyCallback) on_bus_gotten,
+                   manager);
+}
+
+GQuark
+gsd_gamepads_manager_error_quark (void)
+{
+        static GQuark quark = 0;
+        if (!quark)
+                quark = g_quark_from_static_string ("gsd_gamepads_manager_error");
+        return quark;
+}
+
+static void
+gsd_gamepads_manager_class_init (GsdGamepadsManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_gamepads_manager_finalize;
+
+        g_type_class_add_private (klass, sizeof (GsdGamepadsManagerPrivate));
+}
+
+static void
+gsd_gamepads_manager_init (GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv;
+        priv = manager->priv = GSD_GAMEPADS_MANAGER_GET_PRIVATE (manager);
+
+        priv->soup_session = soup_session_async_new ();
+
+        priv->gudev_client = g_udev_client_new (subsystems);
+        g_signal_connect (priv->gudev_client, "uevent",
+                          G_CALLBACK (ggm_gudev_client_uevent_cb),
+                          manager);
+
+        priv->settings = g_settings_new ("org.gnome.settings-daemon.plugins.gamepads");
+
+        priv->devices_dict = NULL;
+
+        priv->update_checksum = NULL;
+        priv->local_data_checksum = NULL;
+}
+
+static void
+gsd_gamepads_manager_finalize (GObject *object)
+{
+        GsdGamepadsManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_GAMEPADS_MANAGER (object));
+
+        manager = GSD_GAMEPADS_MANAGER (object);
+
+        g_return_if_fail (manager->priv != NULL);
+
+        if (manager->priv->soup_message != NULL)
+                soup_session_cancel_message (manager->priv->soup_session, manager->priv->soup_message, SOUP_STATUS_CANCELLED);
+
+        if (manager->priv->devices_dict)
+                g_variant_unref (manager->priv->devices_dict);
+
+        if (manager->priv->update_checksum != NULL)
+                g_free (manager->priv->update_checksum);
+
+        if (manager->priv->local_data_checksum != NULL)
+                g_checksum_free (manager->priv->local_data_checksum);
+
+        if (manager->priv->bus_cancellable != NULL)
+                g_object_unref (manager->priv->bus_cancellable);
+
+        if (manager->priv->introspection_data != NULL)
+                g_dbus_node_info_unref (manager->priv->introspection_data);
+
+        if (manager->priv->dbus_connection != NULL)
+                g_object_unref (manager->priv->dbus_connection);
+
+        g_object_unref (manager->priv->gudev_client);
+        g_object_unref (manager->priv->settings);
+
+        G_OBJECT_CLASS (gsd_gamepads_manager_parent_class)->finalize (object);
+}
+
+GsdGamepadsManager *
+gsd_gamepads_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_GAMEPADS_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+                register_manager_dbus (manager_object);
+        }
+
+        return GSD_GAMEPADS_MANAGER (manager_object);
+}
+
+gboolean
+gsd_gamepads_manager_start (GsdGamepadsManager *manager,
+                            GError             **error)
+{
+        g_debug ("Starting gamepads manager");
+        gnome_settings_profile_start (NULL);
+
+        g_idle_add ((GSourceFunc) get_devices_data_checksum_cb, manager);
+
+        gnome_settings_profile_end (NULL);
+        return TRUE;
+}
+
+void
+gsd_gamepads_manager_stop (GsdGamepadsManager *manager)
+{
+        GsdGamepadsManagerPrivate *priv = manager->priv;
+
+        g_debug ("Stopping gamepads manager");
+
+        if (priv->bus_cancellable != NULL)
+                g_cancellable_cancel (manager->priv->bus_cancellable);
+}
diff --git a/plugins/gamepads/gsd-gamepads-manager.h b/plugins/gamepads/gsd-gamepads-manager.h
new file mode 100644
index 0000000..170b167
--- /dev/null
+++ b/plugins/gamepads/gsd-gamepads-manager.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Zan Dobersek <zandobersek@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GSD_GAMEPADS_MANAGER_H
+#define __GSD_GAMEPADS_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_GAMEPADS_MANAGER         (gsd_gamepads_manager_get_type ())
+#define GSD_GAMEPADS_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_GAMEPADS_MANAGER, GsdGamepadsManager))
+#define GSD_GAMEPADS_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GSD_TYPE_GAMEPADS_MANAGER, GsdGamepadsManagerClass))
+#define GSD_IS_GAMEPADS_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_GAMEPADS_MANAGER))
+#define GSD_IS_GAMEPADS_MANAGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_GAMEPADS_MANAGER))
+#define GSD_GAMEPADS_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_GAMEPADS_MANAGER, GsdGamepadsManagerClass))
+#define GSD_GAMEPADS_MANAGER_ERROR        (gsd_gamepads_manager_error_quark ())
+
+typedef struct GsdGamepadsManagerPrivate GsdGamepadsManagerPrivate;
+
+typedef struct
+{
+        GObject                     parent;
+        GsdGamepadsManagerPrivate  *priv;
+} GsdGamepadsManager;
+
+typedef struct
+{
+        GObjectClass   parent_class;
+} GsdGamepadsManagerClass;
+
+enum
+{
+        GSD_GAMEPADS_MANAGER_ERROR_FAILED
+};
+
+GType                   gsd_gamepads_manager_get_type            (void);
+GQuark                  gsd_gamepads_manager_error_quark         (void);
+
+GsdGamepadsManager *    gsd_gamepads_manager_new                 (void);
+gboolean                gsd_gamepads_manager_start               (GsdGamepadsManager *manager,
+                                                                  GError            **error);
+void                    gsd_gamepads_manager_stop                (GsdGamepadsManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_GAMEPADS_MANAGER_H */
diff --git a/plugins/gamepads/gsd-gamepads-plugin.c b/plugins/gamepads/gsd-gamepads-plugin.c
new file mode 100644
index 0000000..bb28506
--- /dev/null
+++ b/plugins/gamepads/gsd-gamepads-plugin.c
@@ -0,0 +1,110 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011 Richard Hughes <richard@hughsie.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+
+#include "gnome-settings-plugin.h"
+#include "gsd-gamepads-plugin.h"
+#include "gsd-gamepads-manager.h"
+
+struct GsdGamepadsPluginPrivate {
+        GsdGamepadsManager *manager;
+};
+
+#define GSD_GAMEPADS_PLUGIN_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), GSD_TYPE_GAMEPADS_PLUGIN, GsdGamepadsPluginPrivate))
+
+GNOME_SETTINGS_PLUGIN_REGISTER (GsdGamepadsPlugin, gsd_gamepads_plugin)
+
+static void
+gsd_gamepads_plugin_init (GsdGamepadsPlugin *plugin)
+{
+        plugin->priv = GSD_GAMEPADS_PLUGIN_GET_PRIVATE (plugin);
+
+        g_debug ("GsdGamepadsPlugin initializing");
+
+        plugin->priv->manager = gsd_gamepads_manager_new ();
+}
+
+static void
+gsd_gamepads_plugin_finalize (GObject *object)
+{
+        GsdGamepadsPlugin *plugin;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_GAMEPADS_PLUGIN (object));
+
+        g_debug ("GsdGamepadsPlugin finalizing");
+
+        plugin = GSD_GAMEPADS_PLUGIN (object);
+
+        g_return_if_fail (plugin->priv != NULL);
+
+        if (plugin->priv->manager != NULL) {
+                g_object_unref (plugin->priv->manager);
+        }
+
+        G_OBJECT_CLASS (gsd_gamepads_plugin_parent_class)->finalize (object);
+}
+
+static void
+impl_activate (GnomeSettingsPlugin *plugin)
+{
+        GError  *error;
+        gboolean ret;
+
+        g_debug ("Activating gamepads plugin");
+
+        error = NULL;
+        ret = gsd_gamepads_manager_start (GSD_GAMEPADS_PLUGIN (plugin)->priv->manager, &error);
+        if (!ret) {
+                g_warning ("Unable to start gamepads manager: %s", error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+impl_deactivate (GnomeSettingsPlugin *plugin)
+{
+        g_debug ("Deactivating gamepads plugin");
+        gsd_gamepads_manager_stop (GSD_GAMEPADS_PLUGIN (plugin)->priv->manager);
+}
+
+static void
+gsd_gamepads_plugin_class_init (GsdGamepadsPluginClass *klass)
+{
+        GObjectClass             *object_class = G_OBJECT_CLASS (klass);
+        GnomeSettingsPluginClass *plugin_class = GNOME_SETTINGS_PLUGIN_CLASS (klass);
+
+        object_class->finalize = gsd_gamepads_plugin_finalize;
+
+        plugin_class->activate = impl_activate;
+        plugin_class->deactivate = impl_deactivate;
+
+        g_type_class_add_private (klass, sizeof (GsdGamepadsPluginPrivate));
+}
+
+static void
+gsd_gamepads_plugin_class_finalize (GsdGamepadsPluginClass *klass)
+{
+}
diff --git a/plugins/gamepads/gsd-gamepads-plugin.h b/plugins/gamepads/gsd-gamepads-plugin.h
new file mode 100644
index 0000000..2a223db
--- /dev/null
+++ b/plugins/gamepads/gsd-gamepads-plugin.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Zan Dobersek <zandobersek@gmail.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GSD_GAMEPADS_PLUGIN_H__
+#define __GSD_GAMEPADS_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gmodule.h>
+
+#include "gnome-settings-plugin.h"
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_GAMEPADS_PLUGIN                (gsd_gamepads_plugin_get_type ())
+#define GSD_GAMEPADS_PLUGIN(o)                  (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_GAMEPADS_PLUGIN, GsdGamepadsPlugin))
+#define GSD_GAMEPADS_PLUGIN_CLASS(k)            (G_TYPE_CHECK_CLASS_CAST((k), GSD_TYPE_GAMEPADS_PLUGIN, GsdGamepadsPluginClass))
+#define GSD_IS_GAMEPADS_PLUGIN(o)               (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_GAMEPADS_PLUGIN))
+#define GSD_IS_GAMEPADS_PLUGIN_CLASS(k)         (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_GAMEPADS_PLUGIN))
+#define GSD_GAMEPADS_PLUGIN_GET_CLASS(o)        (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_GAMEPADS_PLUGIN, GsdGamepadsPluginClass))
+
+typedef struct GsdGamepadsPluginPrivate GsdGamepadsPluginPrivate;
+
+typedef struct
+{
+        GnomeSettingsPlugin    parent;
+        GsdGamepadsPluginPrivate *priv;
+} GsdGamepadsPlugin;
+
+typedef struct
+{
+        GnomeSettingsPluginClass parent_class;
+} GsdGamepadsPluginClass;
+
+GType   gsd_gamepads_plugin_get_type            (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gnome_settings_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GSD_GAMEPADS_PLUGIN_H__ */
