[gnome-bluetooth] lib: Add Settings widget



commit 4b2c4fd253bc49d2aad7a44013a9ca3b8131e81e
Author: Bastien Nocera <hadess hadess net>
Date:   Mon Dec 2 19:09:26 2013 +0100

    lib: Add Settings widget
    
    Rather than implementing the majority of the settings widget
    inside the control-center, implement it within gnome-bluetooth
    so we can cut down on the necessary amount of exported symbols
    and functions.
    
    And remove the stand-alone wizard as it duplicated functionality.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=719564

 Makefile.am                           |    4 +-
 configure.ac                          |    7 -
 lib/Makefile.am                       |   57 +-
 lib/bluetooth-chooser.c               |    2 +-
 lib/bluetooth-pairing-dialog.c        |  307 ++++++
 lib/bluetooth-pairing-dialog.h        |   83 ++
 lib/bluetooth-settings-row.c          |  300 ++++++
 lib/bluetooth-settings-row.h          |   63 ++
 lib/bluetooth-settings-widget.c       | 1692 +++++++++++++++++++++++++++++++++
 lib/bluetooth-settings-widget.h       |   63 ++
 lib/gnome-bluetooth.symbols           |    7 +
 {wizard => lib}/pin.c                 |    0
 {wizard => lib}/pin.h                 |    0
 lib/settings.gresource.xml            |    6 +
 lib/settings.ui                       |  452 +++++++++
 lib/test-pairing-dialog.c             |   65 ++
 lib/test-settings.c                   |   29 +
 po/POTFILES.in                        |    7 +-
 po/POTFILES.skip                      |    2 -
 wizard/Makefile.am                    |   47 -
 wizard/TEST-PROCEDURE.txt             |   33 -
 wizard/bluetooth-input.c              |  239 -----
 wizard/bluetooth-input.h              |   65 --
 wizard/bluetooth-wizard.1             |   29 -
 wizard/bluetooth-wizard.desktop.in.in |   14 -
 wizard/main.c                         | 1115 ----------------------
 wizard/pin-code-database.xml          |  155 ---
 wizard/test-input.c                   |   54 --
 wizard/wizard.ui                      |  540 -----------
 29 files changed, 3120 insertions(+), 2317 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 84a324c..9253148 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
-DEF_SUBDIRS = icons lib wizard sendto docs po help
+DEF_SUBDIRS = icons lib sendto docs po help
 
-SUBDIRS = icons lib wizard sendto docs po
+SUBDIRS = icons lib sendto docs po
 
 if ENABLE_DOCUMENTATION
 SUBDIRS += help
diff --git a/configure.ac b/configure.ac
index fb8a82e..f5ddcc8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -81,11 +81,6 @@ PKG_CHECK_MODULES(SENDTO,
                  gtk+-3.0
                  gio-2.0)
 
-dnl Requires for the wizard dialogue
-PKG_CHECK_MODULES(WIZARD,
-                 gmodule-export-2.0
-                 gtk+-3.0)
-
 AC_CHECK_LIBM
 AC_SUBST(LIBM)
 
@@ -117,8 +112,6 @@ AC_OUTPUT(Makefile
          gnome-bluetooth-1.0.pc
          icons/Makefile
          lib/Makefile
-         wizard/Makefile
-         wizard/bluetooth-wizard.desktop.in
          sendto/Makefile
          sendto/bluetooth-sendto.desktop.in
          docs/Makefile
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 68983ae..7dad198 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,8 +1,18 @@
 EXTRA_DIST = 
 CLEANFILES =
+BUILT_SOURCES =
 
 lib_LTLIBRARIES = libgnome-bluetooth.la
 
+resource_files = $(shell glib-compile-resources --sourcedir=$(srcdir) --generate-dependencies 
$(srcdir)/settings.gresource.xml)
+bluetooth-settings-resources.c: settings.gresource.xml $(resource_files)
+       $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-source --c-name 
bluetooth_settings $<
+bluetooth-settings-resources.h: settings.gresource.xml $(resource_files)
+       $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-header --c-name 
bluetooth_settings $<
+
+EXTRA_DIST += settings.gresource.xml
+BUILT_SOURCES += bluetooth-settings-resources.c bluetooth-settings-resources.h
+
 # will be scanned for introspection annotation, but won't be installed
 libgnome_bluetooth_c_sources =         \
        bluetooth-client.c              \
@@ -14,7 +24,12 @@ libgnome_bluetooth_c_sources =               \
        bluetooth-chooser-button.c      \
        bluetooth-chooser-combo.c       \
        bluetooth-client-glue.c         \
-       bluetooth-fdo-glue.c
+       bluetooth-fdo-glue.c            \
+       bluetooth-settings-widget.c     \
+       bluetooth-settings-resources.c  \
+       bluetooth-settings-row.c        \
+       bluetooth-pairing-dialog.c      \
+       pin.c
 
 libgnome_bluetooth_private_headers =   \
        bluetooth-client-private.h      \
@@ -22,7 +37,11 @@ libgnome_bluetooth_private_headers = \
        gnome-bluetooth-enum-types.h    \
        bluetooth-chooser-private.h     \
        bluetooth-client-glue.h         \
-       bluetooth-fdo-glue.h
+       bluetooth-fdo-glue.h            \
+       bluetooth-settings-resources.h  \
+       bluetooth-settings-row.h        \
+       bluetooth-pairing-dialog.h      \
+       pin.h
 
 # public headers don't need to be listed, are handled by _HEADERS
 libgnome_bluetooth_la_SOURCES =                        \
@@ -44,7 +63,8 @@ libgnome_bluetooth_introspect_headers =                       \
        bluetooth-chooser-combo.h                       \
        bluetooth-filter-widget.h                       \
        bluetooth-enums.h                               \
-       bluetooth-utils.h
+       bluetooth-utils.h                               \
+       bluetooth-settings-widget.h
 
 gnomebluetoothdir = $(pkgincludedir)
 gnomebluetooth_HEADERS = $(libgnome_bluetooth_introspect_headers)
@@ -54,6 +74,7 @@ AM_CFLAGS =                                           \
        $(LIBGNOMEBT_CFLAGS)                            \
        $(WARN_CFLAGS)                                  \
        $(DISABLE_DEPRECATED)                           \
+       -DPKGDATADIR="\"$(pkgdatadir)\""                \
        -DG_LOG_DOMAIN=\"Bluetooth\"
 
 -include $(INTROSPECTION_MAKEFILE)
@@ -85,16 +106,16 @@ CLEANFILES += $(gir_DATA) $(typelib_DATA)
 
 endif # HAVE_INTROSPECTION
 
-BUILT_SOURCES = bluetooth-client-glue.h \
-               bluetooth-client-glue.c \
-               bluetooth-fdo-glue.h \
-               bluetooth-fdo-glue.c \
-               gnome-bluetooth-enum-types.h \
-               gnome-bluetooth-enum-types.c
+BUILT_SOURCES += bluetooth-client-glue.h \
+                bluetooth-client-glue.c \
+                bluetooth-fdo-glue.h \
+                bluetooth-fdo-glue.c \
+                gnome-bluetooth-enum-types.h \
+                gnome-bluetooth-enum-types.c
 
 CLEANFILES += $(BUILT_SOURCES)
 
-noinst_PROGRAMS = test-client test-agent test-deviceselection test-class
+noinst_PROGRAMS = test-client test-agent test-deviceselection test-class test-settings test-pairing-dialog
 
 test_client_LDADD = libgnome-bluetooth.la $(LIBGNOMEBT_LIBS)
 
@@ -104,7 +125,21 @@ test_deviceselection_LDADD = libgnome-bluetooth.la $(LIBGNOMEBT_LIBS)
 
 test_class_LDADD = libgnome-bluetooth.la $(LIBGNOMEBT_LIBS)
 
-EXTRA_DIST += bluetooth-client.xml bluetooth-fdo.xml gnome-bluetooth.symbols
+test_settings_LDADD = libgnome-bluetooth.la $(LIBGNOMEBT_LIBS)
+
+test_pairing_dialog_LDADD = libgnome-bluetooth.la $(LIBGNOMEBT_LIBS)
+
+pin_DATA = pin-code-database.xml
+pindir = $(pkgdatadir)
+
+all: check
+
+check:
+       @if test -n $(XMLLINT) ; then \
+               xmllint --noout --valid $(srcdir)/pin-code-database.xml ;       \
+       fi
+
+EXTRA_DIST += bluetooth-client.xml bluetooth-fdo.xml gnome-bluetooth.symbols $(pin_DATA)
 
 MAINTAINERCLEANFILES = Makefile.in
 
diff --git a/lib/bluetooth-chooser.c b/lib/bluetooth-chooser.c
index 465d593..1cac781 100644
--- a/lib/bluetooth-chooser.c
+++ b/lib/bluetooth-chooser.c
@@ -53,7 +53,7 @@ enum {
        LAST_SIGNAL
 };
 
-static int selection_table_signals[LAST_SIGNAL] = { 0 };
+static guint selection_table_signals[LAST_SIGNAL] = { 0 };
 
 #define BLUETOOTH_CHOOSER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), \
                                                                        BLUETOOTH_TYPE_CHOOSER, 
BluetoothChooserPrivate))
diff --git a/lib/bluetooth-pairing-dialog.c b/lib/bluetooth-pairing-dialog.c
new file mode 100644
index 0000000..7d3e8b9
--- /dev/null
+++ b/lib/bluetooth-pairing-dialog.c
@@ -0,0 +1,307 @@
+/*
+ *
+ *  Copyright (C) 2013  Bastien Nocera <hadess hadess net>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "bluetooth-pairing-dialog.h"
+#include "bluetooth-enums.h"
+#include "gnome-bluetooth-enum-types.h"
+#include "bluetooth-settings-resources.h"
+
+#define BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE((obj), BLUETOOTH_TYPE_PAIRING_DIALOG, BluetoothPairingDialogPrivate))
+
+typedef struct _BluetoothPairingDialogPrivate BluetoothPairingDialogPrivate;
+
+struct _BluetoothPairingDialogPrivate {
+       GtkBuilder           *builder;
+       GtkWidget            *header;
+       GtkWidget            *help_label;
+       GtkWidget            *label_pin;
+       GtkWidget            *entry_pin;
+       GtkWidget            *pin_notebook;
+       GtkWidget            *done;
+       GtkWidget            *cancel;
+
+       BluetoothPairingMode  mode;
+       char                 *pin;
+};
+
+G_DEFINE_TYPE(BluetoothPairingDialog, bluetooth_pairing_dialog, GTK_TYPE_DIALOG)
+
+#define WID(s) GTK_WIDGET (gtk_builder_get_object (priv->builder, s))
+
+enum {
+       CONFIRMATION_PAGE,
+       DISPLAY_PAGE,
+       MESSAGE_PAGE
+};
+
+void
+bluetooth_pairing_dialog_set_mode (BluetoothPairingDialog *self,
+                                  BluetoothPairingMode    mode,
+                                  const char             *pin,
+                                  const char             *device_name)
+{
+       BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);
+       char *help;
+
+       priv->mode = mode;
+
+       g_clear_pointer (&priv->pin, g_free);
+       priv->pin = g_strdup (pin);
+       gtk_entry_set_text (GTK_ENTRY (priv->entry_pin), pin ? pin : "");
+       gtk_label_set_text (GTK_LABEL (priv->label_pin), pin);
+
+       switch (mode) {
+       case BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION:
+               gtk_widget_show (priv->done);
+               gtk_button_set_label (GTK_BUTTON (priv->done), _("Done"));
+               gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), CONFIRMATION_PAGE);
+               help = g_strdup_printf (_("Please confirm the PIN to use for '%s':"), device_name);
+               break;
+       case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_NORMAL:
+       case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD:
+       case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_ICADE:
+               gtk_widget_hide (priv->done);
+               gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), DISPLAY_PAGE);
+               break;
+       case BLUETOOTH_PAIRING_MODE_PIN_MATCH:
+               gtk_button_set_label (GTK_BUTTON (priv->done), _("Matches"));
+               gtk_widget_show (priv->done);
+               gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), DISPLAY_PAGE);
+               help = g_strdup_printf (_("Please confirm that the PIN displayed on '%s' matches this one:"), 
device_name);
+               break;
+       case BLUETOOTH_PAIRING_MODE_YES_NO:
+               gtk_button_set_label (GTK_BUTTON (priv->done), _("Accept"));
+               gtk_widget_show (priv->done);
+               gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), MESSAGE_PAGE);
+               help = g_strdup_printf (_("Please confirm that you want to pair with '%s'"), device_name);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       switch (mode) {
+       case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_NORMAL:
+               help = g_strdup_printf (_("Please enter the following PIN on '%s':"), device_name);
+               break;
+       case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD:
+               help = g_strdup_printf (_("Please enter the following PIN on '%s' and press “Enter” on the 
keyboard:"), device_name);
+               break;
+       case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_ICADE:
+               help = g_strdup (_("Please move the joystick of your iCade in the following directions:"));
+               break;
+       default:
+               g_assert (help);
+       }
+
+       gtk_label_set_text (GTK_LABEL (priv->help_label), help);
+       g_free (help);
+}
+
+BluetoothPairingMode
+bluetooth_pairing_dialog_get_mode (BluetoothPairingDialog *self)
+{
+       BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);
+
+       return priv->mode;
+}
+
+char *
+bluetooth_pairing_dialog_get_pin (BluetoothPairingDialog *self)
+{
+       BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);
+
+       g_assert (priv->mode == BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION);
+       g_assert (gtk_widget_is_sensitive (GTK_WIDGET (priv->done)));
+
+       return g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->entry_pin)));
+}
+
+void
+bluetooth_pairing_dialog_set_pin_entered (BluetoothPairingDialog *self,
+                                         guint                   entered)
+{
+       BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);
+       char *done;
+
+       g_assert (priv->mode == BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD);
+       g_assert (priv->pin);
+
+       if (entered > 0) {
+               gunichar invisible;
+               GString *str;
+               guint i;
+
+               invisible = gtk_entry_get_invisible_char (GTK_ENTRY (priv->entry_pin));
+
+               str = g_string_new (NULL);
+               for (i = 0; i < entered; i++)
+                       g_string_append_unichar (str, invisible);
+               if (entered < strlen (priv->pin))
+                       g_string_append (str, priv->pin + entered);
+
+               done = g_string_free (str, FALSE);
+       } else {
+               done = g_strdup (priv->pin);
+       }
+
+       gtk_label_set_text (GTK_LABEL (priv->label_pin), done);
+       g_free (done);
+}
+
+static void
+response_cb (GtkWidget *button,
+            gpointer   user_data)
+{
+       BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (user_data);
+       int response;
+
+       if (button == priv->done)
+               response = GTK_RESPONSE_ACCEPT;
+       else if (button == priv->cancel)
+               response = GTK_RESPONSE_CANCEL;
+       else
+               g_assert_not_reached ();
+
+       gtk_dialog_response (GTK_DIALOG (user_data), response);
+}
+
+static void
+text_changed_cb (GObject    *gobject,
+                GParamSpec *pspec,
+                gpointer    user_data)
+{
+       BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (user_data);
+       const char *text;
+
+       if (priv->mode != BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION)
+               return;
+
+       text = gtk_entry_get_text (GTK_ENTRY (priv->entry_pin));
+       if (!text || strlen (text) < 4)
+               gtk_widget_set_sensitive (GTK_WIDGET (priv->done), FALSE);
+       else
+               gtk_widget_set_sensitive (GTK_WIDGET (priv->done), TRUE);
+}
+
+static void
+bluetooth_pairing_dialog_init (BluetoothPairingDialog *self)
+{
+       BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);
+       GtkWidget *container, *buttonbox;
+       GError *error = NULL;
+       GtkStyleContext *context;
+
+       g_resources_register (bluetooth_settings_get_resource ());
+       priv->builder = gtk_builder_new ();
+       gtk_builder_set_translation_domain (priv->builder, GETTEXT_PACKAGE);
+       gtk_builder_add_from_resource (priv->builder,
+                                       "/org/gnome/bluetooth/settings.ui",
+                                       &error);
+       if (error != NULL) {
+               g_warning ("Could not load ui: %s", error->message);
+               g_error_free (error);
+               return;
+       }
+
+       gtk_widget_set_size_request (GTK_WIDGET (self), 380, -1);
+       gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
+       container = gtk_dialog_get_content_area (GTK_DIALOG (self));
+       buttonbox = gtk_dialog_get_action_area (GTK_DIALOG (self));
+
+       priv->header = gtk_header_bar_new ();
+       gtk_header_bar_set_custom_title (GTK_HEADER_BAR (priv->header), gtk_label_new (""));
+       priv->done = gtk_button_new_with_label (_("Accept"));
+       g_signal_connect (G_OBJECT (priv->done), "clicked",
+                         G_CALLBACK (response_cb), self);
+       gtk_header_bar_pack_end (GTK_HEADER_BAR (priv->header), priv->done);
+       priv->cancel = gtk_button_new_with_label (_("Cancel"));
+       g_signal_connect (G_OBJECT (priv->cancel), "clicked",
+                         G_CALLBACK (response_cb), self);
+       gtk_header_bar_pack_start (GTK_HEADER_BAR (priv->header), priv->cancel);
+       gtk_widget_show_all (priv->header);
+       gtk_window_set_titlebar (GTK_WINDOW (self), priv->header);
+
+       //FIXME set default button
+       gtk_widget_set_receives_default (GTK_WIDGET (priv->done), TRUE);
+
+       gtk_container_add (GTK_CONTAINER (container), WID ("pairing_dialog_box"));
+       priv->help_label = WID ("help_label");
+       priv->label_pin = WID ("label_pin");
+       priv->entry_pin = WID ("entry_pin");
+       g_signal_connect (G_OBJECT (priv->entry_pin), "notify::text",
+                         G_CALLBACK (text_changed_cb), self);
+       priv->pin_notebook = WID ("pin_notebook");
+       gtk_widget_hide (buttonbox);
+       gtk_widget_set_no_show_all (buttonbox, FALSE);
+
+       context = gtk_widget_get_style_context (priv->cancel);
+       gtk_style_context_add_class (context, "destructive-action");
+       context = gtk_widget_get_style_context (priv->done);
+       gtk_style_context_add_class (context, "suggested-action");
+       context = gtk_widget_get_style_context (priv->entry_pin);
+       gtk_style_context_add_class (context, "pin-entry");
+       context = gtk_widget_get_style_context (priv->label_pin);
+       gtk_style_context_add_class (context, "pin-label");
+}
+
+static void
+bluetooth_pairing_dialog_finalize (GObject *object)
+{
+       BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (object);
+
+       g_clear_object (&priv->builder);
+       g_clear_pointer (&priv->pin, g_free);
+
+       G_OBJECT_CLASS(bluetooth_pairing_dialog_parent_class)->finalize(object);
+}
+
+static void
+bluetooth_pairing_dialog_class_init (BluetoothPairingDialogClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+       g_type_class_add_private (klass, sizeof (BluetoothPairingDialogPrivate));
+
+       object_class->finalize = bluetooth_pairing_dialog_finalize;
+}
+
+/**
+ * bluetooth_pairing_dialog_new:
+ *
+ * Returns a new #BluetoothPairingDialog widget.
+ *
+ * Return value: A #BluetoothPairingDialog widget
+ **/
+GtkWidget *
+bluetooth_pairing_dialog_new (void)
+{
+       return g_object_new (BLUETOOTH_TYPE_PAIRING_DIALOG, NULL);
+}
diff --git a/lib/bluetooth-pairing-dialog.h b/lib/bluetooth-pairing-dialog.h
new file mode 100644
index 0000000..a30f8b7
--- /dev/null
+++ b/lib/bluetooth-pairing-dialog.h
@@ -0,0 +1,83 @@
+/*
+ *
+ *  Copyright (C) 2013 Bastien Nocera <hadess hadess net>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __BLUETOOTH_PAIRING_DIALOG_H
+#define __BLUETOOTH_PAIRING_DIALOG_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define BLUETOOTH_TYPE_PAIRING_DIALOG (bluetooth_pairing_dialog_get_type())
+#define BLUETOOTH_PAIRING_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+                               BLUETOOTH_TYPE_PAIRING_DIALOG, BluetoothPairingDialog))
+#define BLUETOOTH_PAIRING_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+                               BLUETOOTH_TYPE_PAIRING_DIALOG, BluetoothPairingDialogClass))
+#define BLUETOOTH_IS_PAIRING_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+                                               BLUETOOTH_TYPE_PAIRING_DIALOG))
+#define BLUETOOTH_IS_PAIRING_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+                                               BLUETOOTH_TYPE_PAIRING_DIALOG))
+#define BLUETOOTH_GET_PAIRING_DIALOG_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+                               BLUETOOTH_TYPE_PAIRING_DIALOG, BluetoothPairingDialogClass))
+
+/**
+ * BluetoothPairingDialog:
+ *
+ * The <structname>BluetoothPairingDialog</structname> struct contains
+ * only private fields and should not be directly accessed.
+ */
+typedef struct _BluetoothPairingDialog BluetoothPairingDialog;
+typedef struct _BluetoothPairingDialogClass BluetoothPairingDialogClass;
+
+struct _BluetoothPairingDialog {
+       GtkDialog parent;
+};
+
+struct _BluetoothPairingDialogClass {
+       GtkDialogClass parent_class;
+};
+
+typedef enum {
+       BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION,
+       BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_NORMAL,
+       BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD,
+       BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_ICADE,
+       BLUETOOTH_PAIRING_MODE_PIN_MATCH,
+       BLUETOOTH_PAIRING_MODE_YES_NO
+} BluetoothPairingMode;
+
+GType bluetooth_pairing_dialog_get_type (void);
+
+GtkWidget *bluetooth_pairing_dialog_new (void);
+
+void bluetooth_pairing_dialog_set_mode (BluetoothPairingDialog *self,
+                                       BluetoothPairingMode    mode,
+                                       const char             *pin,
+                                       const char             *name);
+BluetoothPairingMode bluetooth_pairing_dialog_get_mode (BluetoothPairingDialog *self);
+
+void bluetooth_pairing_dialog_set_pin_entered (BluetoothPairingDialog *self,
+                                              guint                   entered);
+
+char *bluetooth_pairing_dialog_get_pin (BluetoothPairingDialog *self);
+
+G_END_DECLS
+
+#endif /* __BLUETOOTH_PAIRING_DIALOG_H */
diff --git a/lib/bluetooth-settings-row.c b/lib/bluetooth-settings-row.c
new file mode 100644
index 0000000..76fd2c4
--- /dev/null
+++ b/lib/bluetooth-settings-row.c
@@ -0,0 +1,300 @@
+/*
+ *
+ *  Copyright (C) 2013  Bastien Nocera <hadess hadess net>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "bluetooth-settings-row.h"
+#include "bluetooth-enums.h"
+#include "gnome-bluetooth-enum-types.h"
+
+#define BLUETOOTH_SETTINGS_ROW_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE((obj), BLUETOOTH_TYPE_SETTINGS_ROW, BluetoothSettingsRowPrivate))
+
+typedef struct _BluetoothSettingsRowPrivate BluetoothSettingsRowPrivate;
+
+struct _BluetoothSettingsRowPrivate {
+       /* Widget */
+       GtkWidget *label;
+       GtkWidget *status;
+       GtkWidget *spinner;
+
+       /* Properties */
+       GDBusProxy *proxy;
+       gboolean paired;
+       gboolean trusted;
+       BluetoothType type;
+       gboolean connected;
+       char *name;
+       char *bdaddr;
+       gboolean legacy_pairing;
+
+       gboolean pairing;
+};
+
+enum {
+       PROP_0,
+       PROP_PROXY,
+       PROP_PAIRED,
+       PROP_TRUSTED,
+       PROP_TYPE,
+       PROP_CONNECTED,
+       PROP_NAME,
+       PROP_ADDRESS,
+       PROP_PAIRING,
+       PROP_LEGACY_PAIRING
+};
+
+G_DEFINE_TYPE(BluetoothSettingsRow, bluetooth_settings_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+label_might_change (BluetoothSettingsRow *self)
+{
+       BluetoothSettingsRowPrivate *priv = BLUETOOTH_SETTINGS_ROW_GET_PRIVATE (self);
+
+       if (!priv->paired && !priv->trusted)
+               gtk_label_set_text (GTK_LABEL (priv->status), _("Not Setup"));
+       else if (priv->connected)
+               gtk_label_set_text (GTK_LABEL (priv->status), _("Connected"));
+       else
+               gtk_label_set_text (GTK_LABEL (priv->status), _("Disconnected"));
+
+       if (priv->pairing)
+               gtk_widget_show (priv->spinner);
+       else
+               gtk_widget_show (priv->status);
+}
+
+static void
+bluetooth_settings_row_init (BluetoothSettingsRow *self)
+{
+       BluetoothSettingsRowPrivate *priv = BLUETOOTH_SETTINGS_ROW_GET_PRIVATE (self);
+       GtkWidget *box;
+
+       box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 50);
+       gtk_container_add (GTK_CONTAINER (self), box);
+
+       /* Name is already escaped */
+       priv->label = gtk_label_new ("Placeholder Name");
+       gtk_misc_set_alignment (GTK_MISC (priv->label), 0, 0.5);
+       gtk_widget_set_margin_start (priv->label, 20);
+       gtk_widget_set_margin_end (priv->label, 20);
+       gtk_widget_set_margin_top (priv->label, 6);
+       gtk_widget_set_margin_bottom (priv->label, 6);
+       gtk_box_pack_start (GTK_BOX (box), priv->label, TRUE, TRUE, 0);
+
+       /* Spinner */
+       priv->spinner = gtk_spinner_new ();
+       g_object_bind_property (priv->spinner, "visible",
+                               priv->spinner, "active", 0);
+       gtk_widget_set_margin_start (priv->spinner, 24);
+       gtk_widget_set_margin_end (priv->spinner, 24);
+       gtk_box_pack_start (GTK_BOX (box), priv->spinner, FALSE, TRUE, 0);
+       gtk_widget_set_no_show_all (priv->spinner, TRUE);
+       g_object_set_data (G_OBJECT (self), "spinner", priv->spinner);
+
+       /* Placeholder text */
+       priv->status = gtk_label_new (_("Not Setup"));
+
+       gtk_widget_set_no_show_all (priv->status, TRUE);
+       gtk_misc_set_alignment (GTK_MISC (priv->status), 1, 0.5);
+       gtk_widget_set_margin_start (priv->status, 24);
+       gtk_widget_set_margin_end (priv->status, 24);
+       gtk_box_pack_start (GTK_BOX (box), priv->status, FALSE, TRUE, 0);
+       g_object_bind_property (priv->spinner, "visible",
+                               priv->status, "visible", G_BINDING_INVERT_BOOLEAN);
+
+       gtk_widget_show (priv->status);
+       gtk_widget_show_all (GTK_WIDGET (self));
+}
+
+static void
+bluetooth_settings_row_finalize (GObject *object)
+{
+       BluetoothSettingsRowPrivate *priv = BLUETOOTH_SETTINGS_ROW_GET_PRIVATE (object);
+
+       g_clear_object (&priv->proxy);
+       g_clear_pointer (&priv->name, g_free);
+       g_clear_pointer (&priv->bdaddr, g_free);
+
+       G_OBJECT_CLASS(bluetooth_settings_row_parent_class)->finalize(object);
+}
+
+static void
+bluetooth_settings_row_get_property (GObject        *object,
+                                    guint           property_id,
+                                    GValue         *value,
+                                    GParamSpec     *pspec)
+{
+       BluetoothSettingsRowPrivate *priv = BLUETOOTH_SETTINGS_ROW_GET_PRIVATE (object);
+
+       switch (property_id) {
+       case PROP_PROXY:
+               g_value_set_object (value, priv->proxy);
+               break;
+       case PROP_PAIRED:
+               g_value_set_boolean (value, priv->paired);
+               break;
+       case PROP_TRUSTED:
+               g_value_set_boolean (value, priv->trusted);
+               break;
+       case PROP_TYPE:
+               g_value_set_flags (value, priv->type);
+               break;
+       case PROP_CONNECTED:
+               g_value_set_boolean (value, priv->connected);
+               break;
+       case PROP_NAME:
+               g_value_set_string (value, priv->name);
+               break;
+       case PROP_ADDRESS:
+               g_value_set_string (value, priv->bdaddr);
+               break;
+       case PROP_PAIRING:
+               g_value_set_boolean (value, priv->pairing);
+               break;
+       case PROP_LEGACY_PAIRING:
+               g_value_set_boolean (value, priv->legacy_pairing);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+               break;
+       }
+}
+
+static void
+bluetooth_settings_row_set_property (GObject        *object,
+                                    guint           property_id,
+                                    const GValue   *value,
+                                    GParamSpec     *pspec)
+{
+       BluetoothSettingsRow *self = BLUETOOTH_SETTINGS_ROW (object);
+       BluetoothSettingsRowPrivate *priv = BLUETOOTH_SETTINGS_ROW_GET_PRIVATE (self);
+
+       switch (property_id) {
+       case PROP_PROXY:
+               g_clear_object (&priv->proxy);
+               priv->proxy = g_value_dup_object (value);
+               break;
+       case PROP_PAIRED:
+               priv->paired = g_value_get_boolean (value);
+               label_might_change (self);
+               break;
+       case PROP_TRUSTED:
+               priv->trusted = g_value_get_boolean (value);
+               label_might_change (self);
+               break;
+       case PROP_TYPE:
+               priv->type = g_value_get_flags (value);
+               break;
+       case PROP_CONNECTED:
+               priv->connected = g_value_get_boolean (value);
+               label_might_change (self);
+               break;
+       case PROP_NAME:
+               g_free (priv->name);
+               priv->name = g_value_dup_string (value);
+               gtk_label_set_text (GTK_LABEL (priv->label), priv->name);
+               break;
+       case PROP_ADDRESS:
+               g_free (priv->bdaddr);
+               priv->bdaddr = g_value_dup_string (value);
+               break;
+       case PROP_PAIRING:
+               priv->pairing = g_value_get_boolean (value);
+               label_might_change (self);
+               break;
+       case PROP_LEGACY_PAIRING:
+               priv->legacy_pairing = g_value_get_boolean (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+               break;
+       }
+}
+
+static void
+bluetooth_settings_row_class_init (BluetoothSettingsRowClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+       g_type_class_add_private (klass, sizeof (BluetoothSettingsRowPrivate));
+
+       object_class->finalize = bluetooth_settings_row_finalize;
+       object_class->get_property = bluetooth_settings_row_get_property;
+       object_class->set_property = bluetooth_settings_row_set_property;
+
+       g_object_class_install_property (object_class, PROP_PROXY,
+                                        g_param_spec_object ("proxy", NULL,
+                                                             "The D-Bus object path of the device",
+                                                             G_TYPE_DBUS_PROXY, G_PARAM_READWRITE));
+       g_object_class_install_property (object_class, PROP_PAIRED,
+                                        g_param_spec_boolean ("paired", NULL,
+                                                             "Paired",
+                                                             FALSE, G_PARAM_READWRITE));
+       g_object_class_install_property (object_class, PROP_TRUSTED,
+                                        g_param_spec_boolean ("trusted", NULL,
+                                                             "Trusted",
+                                                             FALSE, G_PARAM_READWRITE));
+       g_object_class_install_property (object_class, PROP_TYPE,
+                                        g_param_spec_flags ("type", NULL,
+                                                             "Type",
+                                                             BLUETOOTH_TYPE_TYPE, BLUETOOTH_TYPE_ANY, 
G_PARAM_READWRITE));
+       g_object_class_install_property (object_class, PROP_CONNECTED,
+                                        g_param_spec_boolean ("connected", NULL,
+                                                             "Connected",
+                                                             FALSE, G_PARAM_READWRITE));
+       g_object_class_install_property (object_class, PROP_NAME,
+                                        g_param_spec_string ("name", NULL,
+                                                             "Name",
+                                                             "Placeholder Name", G_PARAM_READWRITE));
+       g_object_class_install_property (object_class, PROP_ADDRESS,
+                                        g_param_spec_string ("address", NULL,
+                                                             "Address",
+                                                             NULL, G_PARAM_READWRITE));
+       g_object_class_install_property (object_class, PROP_PAIRING,
+                                        g_param_spec_boolean ("pairing", NULL,
+                                                             "Pairing",
+                                                             FALSE, G_PARAM_READWRITE));
+       g_object_class_install_property (object_class, PROP_LEGACY_PAIRING,
+                                        g_param_spec_boolean ("legacy-pairing", NULL,
+                                                             "Legacy pairing",
+                                                             FALSE, G_PARAM_READWRITE));
+}
+
+/**
+ * bluetooth_settings_row_new:
+ *
+ * Returns a new #BluetoothSettingsRow widget.
+ *
+ * Return value: A #BluetoothSettingsRow widget
+ **/
+GtkWidget *
+bluetooth_settings_row_new (void)
+{
+       return g_object_new (BLUETOOTH_TYPE_SETTINGS_ROW, NULL);
+}
diff --git a/lib/bluetooth-settings-row.h b/lib/bluetooth-settings-row.h
new file mode 100644
index 0000000..ed0db01
--- /dev/null
+++ b/lib/bluetooth-settings-row.h
@@ -0,0 +1,63 @@
+/*
+ *
+ *  Copyright (C) 2013 Bastien Nocera <hadess hadess net>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __BLUETOOTH_SETTINGS_ROW_H
+#define __BLUETOOTH_SETTINGS_ROW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define BLUETOOTH_TYPE_SETTINGS_ROW (bluetooth_settings_row_get_type())
+#define BLUETOOTH_SETTINGS_ROW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+                               BLUETOOTH_TYPE_SETTINGS_ROW, BluetoothSettingsRow))
+#define BLUETOOTH_SETTINGS_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+                               BLUETOOTH_TYPE_SETTINGS_ROW, BluetoothSettingsRowClass))
+#define BLUETOOTH_IS_SETTINGS_ROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+                                               BLUETOOTH_TYPE_SETTINGS_ROW))
+#define BLUETOOTH_IS_SETTINGS_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+                                               BLUETOOTH_TYPE_SETTINGS_ROW))
+#define BLUETOOTH_GET_SETTINGS_ROW_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+                               BLUETOOTH_TYPE_SETTINGS_ROW, BluetoothSettingsRowClass))
+
+/**
+ * BluetoothSettingsRow:
+ *
+ * The <structname>BluetoothSettingsRow</structname> struct contains
+ * only private fields and should not be directly accessed.
+ */
+typedef struct _BluetoothSettingsRow BluetoothSettingsRow;
+typedef struct _BluetoothSettingsRowClass BluetoothSettingsRowClass;
+
+struct _BluetoothSettingsRow {
+       GtkListBoxRow parent;
+};
+
+struct _BluetoothSettingsRowClass {
+       GtkListBoxRowClass parent_class;
+};
+
+GType bluetooth_settings_row_get_type (void);
+
+GtkWidget *bluetooth_settings_row_new (void);
+
+G_END_DECLS
+
+#endif /* __BLUETOOTH_SETTINGS_ROW_H */
diff --git a/lib/bluetooth-settings-widget.c b/lib/bluetooth-settings-widget.c
new file mode 100644
index 0000000..9f83f03
--- /dev/null
+++ b/lib/bluetooth-settings-widget.c
@@ -0,0 +1,1692 @@
+/*
+ *
+ *  Copyright (C) 2013  Bastien Nocera <hadess hadess net>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include <glib/gi18n-lib.h>
+#include <math.h>
+
+#include "bluetooth-client.h"
+#include "bluetooth-client-private.h"
+#include "bluetooth-client-glue.h"
+#include "bluetooth-agent.h"
+#include "bluetooth-utils.h"
+#include "bluetooth-settings-widget.h"
+#include "bluetooth-settings-resources.h"
+#include "bluetooth-settings-row.h"
+#include "bluetooth-pairing-dialog.h"
+#include "pin.h"
+
+#define BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE((obj), BLUETOOTH_TYPE_SETTINGS_WIDGET, BluetoothSettingsWidgetPrivate))
+
+typedef struct _BluetoothSettingsWidgetPrivate BluetoothSettingsWidgetPrivate;
+
+struct _BluetoothSettingsWidgetPrivate {
+       GtkBuilder          *builder;
+       BluetoothClient     *client;
+       GtkTreeModel        *model;
+       gboolean             debug;
+       GCancellable        *cancellable;
+
+       /* Pairing */
+       BluetoothAgent      *agent;
+       GtkWidget           *pairing_dialog;
+
+       /* Properties */
+       GtkWidget           *properties_dialog;
+       GtkWidget           *properties_header;
+       GtkWidget           *properties_title;
+       char                *selected_bdaddr;
+       char                *selected_name;
+       char                *selected_object_path;
+
+       /* Device section */
+       GtkWidget           *device_list;
+       GtkAdjustment       *focus_adjustment;
+       GtkSizeGroup        *row_sizegroup;
+       GtkWidget           *device_revealer;
+       GtkWidget           *device_spinner;
+       GHashTable          *connecting_devices;
+
+       GtkWidget           *visible_label;
+
+       GList               *boxes;
+       GList               *boxes_reverse;
+};
+
+G_DEFINE_TYPE(BluetoothSettingsWidget, bluetooth_settings_widget, GTK_TYPE_BOX)
+
+enum {
+       PANEL_CHANGED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define WID(s) GTK_WIDGET (gtk_builder_get_object (priv->builder, s))
+
+#define KEYBOARD_PREFS         "keyboard"
+#define MOUSE_PREFS            "mouse"
+#define SOUND_PREFS            "sound"
+
+#define ICON_SIZE 128
+
+/* We'll try to connect to the device repeatedly for that
+ * amount of time before we bail out */
+#define CONNECT_TIMEOUT 3.0
+
+#define BLUEZ_SERVICE  "org.bluez"
+#define ADAPTER_IFACE  "org.bluez.Adapter1"
+
+#define AGENT_PATH "/org/gnome/bluetooth/settings"
+
+enum {
+       CONNECTING_NOTEBOOK_PAGE_SWITCH = 0,
+       CONNECTING_NOTEBOOK_PAGE_SPINNER = 1
+};
+
+static void
+set_connecting_page (BluetoothSettingsWidget *self,
+                    int               page)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+
+       if (page == CONNECTING_NOTEBOOK_PAGE_SPINNER)
+               gtk_spinner_start (GTK_SPINNER (WID ("connecting_spinner")));
+       gtk_notebook_set_current_page (GTK_NOTEBOOK (WID ("connecting_notebook")), page);
+       if (page == CONNECTING_NOTEBOOK_PAGE_SWITCH)
+               gtk_spinner_start (GTK_SPINNER (WID ("connecting_spinner")));
+}
+
+static void
+remove_connecting (BluetoothSettingsWidget *self,
+                  const char       *bdaddr)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       g_hash_table_remove (priv->connecting_devices, bdaddr);
+}
+
+static void
+add_connecting (BluetoothSettingsWidget *self,
+               const char       *bdaddr)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       g_hash_table_insert (priv->connecting_devices,
+                            g_strdup (bdaddr),
+                            GINT_TO_POINTER (1));
+}
+
+static gboolean
+is_connecting (BluetoothSettingsWidget *self,
+              const char       *bdaddr)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       return GPOINTER_TO_INT (g_hash_table_lookup (priv->connecting_devices,
+                                                    bdaddr));
+}
+
+typedef struct {
+       char             *bdaddr;
+       BluetoothSettingsWidget *self;
+} ConnectData;
+
+static void
+connect_done (GObject      *source_object,
+             GAsyncResult *res,
+             gpointer      user_data)
+{
+       BluetoothSettingsWidget *self;
+       BluetoothSettingsWidgetPrivate *priv;
+       gboolean success;
+       GError *error = NULL;
+       ConnectData *data = (ConnectData *) user_data;
+
+       success = bluetooth_client_connect_service_finish (BLUETOOTH_CLIENT (source_object),
+                                                          res, &error);
+       if (!success && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+               goto out;
+
+       self = data->self;
+       priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+
+       /* Check whether the same device is now selected, and update the UI */
+       if (g_strcmp0 (priv->selected_bdaddr, data->bdaddr) == 0) {
+               GtkSwitch *button;
+
+               button = GTK_SWITCH (WID ("switch_connection"));
+               /* Reset the switch if it failed */
+               if (success == FALSE)
+                       gtk_switch_set_active (button, !gtk_switch_get_active (button));
+               set_connecting_page (self, CONNECTING_NOTEBOOK_PAGE_SWITCH);
+       }
+
+       remove_connecting (self, data->bdaddr);
+
+       //FIXME show an error if it failed?
+
+out:
+       g_clear_error (&error);
+       g_free (data->bdaddr);
+       g_free (data);
+}
+
+static void
+setup_pairing_dialog (BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GtkWidget *toplevel;
+
+       g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy);
+       priv->pairing_dialog = bluetooth_pairing_dialog_new ();
+       toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+       gtk_window_set_transient_for (GTK_WINDOW (priv->pairing_dialog), GTK_WINDOW (toplevel));
+       gtk_window_set_modal (GTK_WINDOW (priv->pairing_dialog), TRUE);
+}
+
+static gboolean
+get_properties_for_device (GDBusProxy     *device,
+                          char          **name,
+                          char          **bdaddr,
+                          BluetoothType  *type)
+{
+       GVariant *value;
+
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       value = g_dbus_proxy_get_cached_property (device, "Name");
+       *name = g_variant_dup_string (value, NULL);
+       g_variant_unref (value);
+
+       if (bdaddr) {
+               value = g_dbus_proxy_get_cached_property (device, "Address");
+               *bdaddr = g_variant_dup_string (value, NULL);
+               g_variant_unref (value);
+       }
+
+       if (type) {
+               value = g_dbus_proxy_get_cached_property (device, "Class");
+               *type = bluetooth_class_to_type (g_variant_get_uint32 (value));
+               g_variant_unref (value);
+       }
+
+       return TRUE;
+}
+
+static char *
+get_random_pincode (guint num_digits)
+{
+       if (num_digits == 0)
+               num_digits = PIN_NUM_DIGITS;
+       return g_strdup_printf ("%d", g_random_int_range (pow (10, num_digits - 1),
+                                                         pow (10, num_digits)));
+}
+
+static char *
+get_icade_pincode (char **pin_display_str)
+{
+       GString *pin, *pin_display;
+       guint i;
+       static char *arrows[] = {
+               NULL,
+               "⬆", /* up = 1    */
+               "⬇", /* down = 2  */
+               "⬅", /* left = 3  */
+               "➡"  /* right = 4 */
+       };
+
+       pin = g_string_new (NULL);
+       pin_display = g_string_new (NULL);
+
+       for (i = 0; i < PIN_NUM_DIGITS; i++) {
+               int r;
+               char *c;
+
+               r = g_random_int_range (1, 5);
+
+               c = g_strdup_printf ("%d", r);
+               g_string_append (pin, c);
+               g_free (c);
+
+               g_string_append (pin_display, arrows[r]);
+       }
+       g_string_append (pin_display, "❍");
+
+       *pin_display_str = g_string_free (pin_display, FALSE);
+       return g_string_free (pin, FALSE);
+}
+
+static void
+display_cb (GtkDialog *dialog,
+           int        response,
+           gpointer   user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       GDBusMethodInvocation *invocation;
+
+       invocation = g_object_get_data (G_OBJECT (dialog), "invocation");
+
+       g_dbus_method_invocation_return_dbus_error (invocation,
+                                                   "org.bluez.Error.Canceled",
+                                                   "User cancelled pairing");
+
+       g_object_set_data (G_OBJECT (priv->pairing_dialog), "invocation", NULL);
+}
+
+static void
+enter_pin_cb (GtkDialog *dialog,
+             int        response,
+             gpointer   user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       GDBusMethodInvocation *invocation;
+
+       invocation = g_object_get_data (G_OBJECT (dialog), "invocation");
+
+       if (response == GTK_RESPONSE_ACCEPT) {
+               const char *name;
+               char *pin;
+               BluetoothPairingMode mode;
+
+               g_dbus_method_invocation_return_value (invocation, NULL);
+
+               mode = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (dialog), "mode"));
+               name = g_object_get_data (G_OBJECT (dialog), "name");
+               pin = bluetooth_pairing_dialog_get_pin (BLUETOOTH_PAIRING_DIALOG (dialog));
+               g_dbus_method_invocation_return_value (invocation,
+                                                      g_variant_new ("(s)", pin));
+               bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog),
+                                                  mode, pin, name);
+               g_free (pin);
+               g_signal_connect (G_OBJECT (priv->pairing_dialog), "response",
+                                 G_CALLBACK (display_cb), user_data);
+       } else {
+               g_dbus_method_invocation_return_dbus_error (invocation,
+                                                           "org.bluez.Error.Canceled",
+                                                           "User cancelled pairing");
+       }
+
+       g_object_set_data (G_OBJECT (priv->pairing_dialog), "invocation", NULL);
+       g_object_set_data (G_OBJECT (priv->pairing_dialog), "mode", NULL);
+       g_object_set_data (G_OBJECT (priv->pairing_dialog), "name", NULL);
+}
+
+static void
+pincode_callback (GDBusMethodInvocation *invocation,
+                 GDBusProxy            *device,
+                 gpointer               user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       BluetoothType type;
+       char *name, *bdaddr;
+       guint max_digits;
+       gboolean confirm_pin = TRUE;
+       char *default_pin;
+       char *display_pin = NULL;
+       BluetoothPairingMode mode;
+
+       g_debug ("pincode_callback (%s)", g_dbus_proxy_get_object_path (device));
+
+       if (!get_properties_for_device (device, &name, &bdaddr, &type)) {
+               char *msg;
+
+               msg = g_strdup_printf ("Missing information for %s", g_dbus_proxy_get_object_path (device));
+               g_dbus_method_invocation_return_dbus_error (invocation, "org.bluez.Error.Rejected", msg);
+               g_free (msg);
+               return;
+       }
+
+       default_pin = get_pincode_for_device (type, bdaddr, name, &max_digits, &confirm_pin);
+       if (g_strcmp0 (default_pin, "KEYBOARD") == 0) {
+               mode = BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD;
+               g_free (default_pin);
+               default_pin = get_random_pincode (max_digits);
+               display_pin = g_strdup_printf ("%s⏎", default_pin);
+       } else if (g_strcmp0 (default_pin, "ICADE") == 0) {
+               mode = BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_ICADE;
+               confirm_pin = FALSE;
+               g_free (default_pin);
+               default_pin = get_icade_pincode (&display_pin);
+       } else if (default_pin == NULL) {
+               mode = BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_NORMAL;
+               confirm_pin = TRUE;
+               default_pin = get_random_pincode (0);
+       } else if (g_strcmp0 (default_pin, "NULL") == 0) {
+               g_assert_not_reached ();
+       } else {
+               mode = BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_NORMAL;
+               confirm_pin = TRUE;
+       }
+
+       setup_pairing_dialog (BLUETOOTH_SETTINGS_WIDGET (user_data));
+
+       g_object_set_data (G_OBJECT (priv->pairing_dialog), "invocation", invocation);
+       g_object_set_data_full (G_OBJECT (priv->pairing_dialog), "name", g_strdup (name), g_free);
+       g_object_set_data (G_OBJECT (priv->pairing_dialog), "mode", GUINT_TO_POINTER (mode));
+
+       if (confirm_pin) {
+               bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog),
+                                                  BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION,
+                                                  default_pin,
+                                                  name);
+               g_signal_connect (G_OBJECT (priv->pairing_dialog), "response",
+                                 G_CALLBACK (enter_pin_cb), user_data);
+       } else {
+               bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog),
+                                                  mode, display_pin, name);
+               g_signal_connect (G_OBJECT (priv->pairing_dialog), "response",
+                                 G_CALLBACK (display_cb), user_data);
+       }
+
+       gtk_widget_show (priv->pairing_dialog);
+}
+
+static void
+display_callback (GDBusMethodInvocation *invocation,
+                 GDBusProxy            *device,
+                 guint                  pin,
+                 guint                  entered,
+                 gpointer               user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       char *pin_str, *name;
+
+       g_debug ("display_callback (%s, %i, %i)", g_dbus_proxy_get_object_path (device), pin, entered);
+
+       if (priv->pairing_dialog == NULL ||
+           bluetooth_pairing_dialog_get_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog)) != 
BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD)
+               setup_pairing_dialog (BLUETOOTH_SETTINGS_WIDGET (user_data));
+
+       pin_str = g_strdup_printf ("%06d", pin);
+       get_properties_for_device (device, &name, NULL, NULL);
+       bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog),
+                                          BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD,
+                                          pin_str,
+                                          name);
+       bluetooth_pairing_dialog_set_pin_entered (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog),
+                                                 entered);
+       g_free (pin_str);
+       g_free (name);
+}
+
+static void
+display_pincode_callback (GDBusMethodInvocation *invocation,
+                         GDBusProxy            *device,
+                         const char            *pincode,
+                         gpointer               user_data)
+{
+       g_debug ("display_pincode_callback (%s, %s)", g_dbus_proxy_get_object_path (device), pincode);
+
+       /* Reject all the calls here, so that we'll get asked about the
+        * pincode instead of being told the pincode */
+       g_dbus_method_invocation_return_dbus_error (invocation,
+                                                   "org.bluez.Error.Rejected",
+                                                   "Rejected bluetoothd generated PIN code");
+}
+
+static gboolean
+cancel_callback (GDBusMethodInvocation *invocation,
+                gpointer               user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       GList *l, *children;
+
+       g_debug ("cancel_callback ()");
+
+       g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy);
+
+       children = gtk_container_get_children (GTK_CONTAINER (priv->device_list));
+       for (l = children; l != NULL; l = l->next)
+               g_object_set (l->data, "pairing", FALSE, NULL);
+       g_list_free (children);
+
+       g_dbus_method_invocation_return_value (invocation, NULL);
+
+       return TRUE;
+}
+
+static void
+confirm_cb (GtkDialog *dialog,
+           int        response,
+           gpointer   user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       GDBusMethodInvocation *invocation;
+
+       invocation = g_object_get_data (G_OBJECT (dialog), "invocation");
+       if (response == GTK_RESPONSE_ACCEPT) {
+               g_dbus_method_invocation_return_value (invocation, NULL);
+       } else {
+               g_dbus_method_invocation_return_dbus_error (invocation,
+                                                           "org.bluez.Error.Canceled",
+                                                           "User cancelled pairing");
+       }
+       g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy);
+}
+
+static void
+confirm_callback (GDBusMethodInvocation *invocation,
+                 GDBusProxy            *device,
+                 guint                  pin,
+                 gpointer               user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       char *name, *pin_str;
+
+       g_debug ("confirm_callback (%s, %i)", g_dbus_proxy_get_object_path (device), pin);
+
+       setup_pairing_dialog (BLUETOOTH_SETTINGS_WIDGET (user_data));
+
+       pin_str = g_strdup_printf ("%06d", pin);
+       get_properties_for_device (device, &name, NULL, NULL);
+       bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog),
+                                          BLUETOOTH_PAIRING_MODE_PIN_MATCH,
+                                          pin_str, name);
+
+       g_signal_connect (G_OBJECT (priv->pairing_dialog), "response",
+                         G_CALLBACK (confirm_cb), user_data);
+       g_object_set_data (G_OBJECT (priv->pairing_dialog), "invocation", invocation);
+
+       gtk_widget_show (priv->pairing_dialog);
+
+       g_free (pin_str);
+       g_free (name);
+}
+
+static void
+authorize_callback (GDBusMethodInvocation *invocation,
+                   GDBusProxy            *device,
+                   gpointer               user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       char *name;
+
+       g_debug ("authorize_callback (%s)", g_dbus_proxy_get_object_path (device));
+
+       setup_pairing_dialog (BLUETOOTH_SETTINGS_WIDGET (user_data));
+       get_properties_for_device (device, &name, NULL, NULL);
+       bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog),
+                                          BLUETOOTH_PAIRING_MODE_YES_NO,
+                                          NULL, name);
+
+       g_signal_connect (G_OBJECT (priv->pairing_dialog), "response",
+                         G_CALLBACK (confirm_cb), user_data);
+       g_object_set_data (G_OBJECT (priv->pairing_dialog), "invocation", invocation);
+
+       gtk_widget_show (priv->pairing_dialog);
+
+       g_free (name);
+}
+
+static void
+authorize_service_callback (GDBusMethodInvocation *invocation,
+                           GDBusProxy            *device,
+                           const char            *uuid,
+                           gpointer               user_data)
+{
+       char *msg;
+
+       g_debug ("authorize_service_callback (%s, %s)", g_dbus_proxy_get_object_path (device), uuid);
+
+       msg = g_strdup_printf ("Rejecting service auth (%s) for %s",
+                              uuid, g_dbus_proxy_get_object_path (device));
+       g_dbus_method_invocation_return_dbus_error (invocation, "org.bluez.Error.Rejected", msg);
+       g_free (msg);
+}
+
+static void
+turn_off_pairing (BluetoothSettingsWidget *self,
+                 const char              *object_path)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GList *l, *children;
+
+       children = gtk_container_get_children (GTK_CONTAINER (priv->device_list));
+       for (l = children; l != NULL; l = l->next) {
+               GDBusProxy *proxy;
+
+               g_object_get (l->data, "proxy", &proxy, NULL);
+               if (g_strcmp0 (g_dbus_proxy_get_object_path (proxy), object_path) == 0) {
+                       g_object_set (l->data, "pairing", FALSE, NULL);
+                       g_object_unref (proxy);
+                       break;
+               }
+               g_object_unref (proxy);
+       }
+       g_list_free (children);
+}
+
+typedef struct {
+       BluetoothSettingsWidget *self;
+       char *device;
+       GTimer *timer;
+       guint timeout_id;
+} SetupConnectData;
+
+static void connect_callback (GObject      *source_object,
+                             GAsyncResult *res,
+                             gpointer      user_data);
+
+static gboolean
+connect_timeout_cb (gpointer user_data)
+{
+       SetupConnectData *data = (SetupConnectData *) user_data;
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (data->self);
+
+       bluetooth_client_connect_service (priv->client, data->device, TRUE, NULL, connect_callback, data);
+       data->timeout_id = 0;
+
+       return G_SOURCE_REMOVE;
+}
+
+static void
+connect_callback (GObject      *source_object,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+       SetupConnectData *data = (SetupConnectData *) user_data;
+       GError *error = NULL;
+       gboolean success;
+
+       success = bluetooth_client_connect_service_finish (BLUETOOTH_CLIENT (source_object), res, &error);
+
+       if (success == FALSE) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       g_error_free (error);
+                       goto bail;
+               } else if (g_timer_elapsed (data->timer, NULL) < CONNECT_TIMEOUT) {
+                       g_assert (data->timeout_id == 0);
+                       data->timeout_id = g_timeout_add (500, connect_timeout_cb, data);
+                       g_error_free (error);
+                       return;
+               }
+               g_debug ("Failed to connect to device %s", data->device);
+       }
+
+       turn_off_pairing (user_data, data->device);
+
+bail:
+       if (data->timeout_id > 0)
+               g_source_remove (data->timeout_id);
+
+       g_timer_destroy (data->timer);
+       g_free (data);
+}
+
+static void
+create_callback (GObject      *source_object,
+                GAsyncResult *res,
+                gpointer      user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       SetupConnectData *data;
+       GError *error = NULL;
+       gboolean ret;
+       char *path;
+
+       ret = bluetooth_client_setup_device_finish (BLUETOOTH_CLIENT (source_object),
+                                                   res, &path, &error);
+
+       g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy);
+
+       /* Create failed */
+       if (ret == FALSE) {
+               //char *text;
+
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+                       g_error_free (error);
+                       g_free (path);
+                       return;
+               }
+
+               turn_off_pairing (user_data, path);
+
+               //FIXME show an error?
+
+               /* translators:
+                * The '%s' is the device name, for example:
+                * Setting up 'Sony Bluetooth Headset' failed
+                */
+               //text = g_strdup_printf(_("Setting up '%s' failed"), target_name);
+
+               g_warning ("Setting up %s failed: %s", path, error->message);
+
+               //gtk_label_set_markup(GTK_LABEL(label_summary), text);
+               //g_free (text);
+
+               g_error_free (error);
+               g_free (path);
+               return;
+       }
+
+       priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+
+       bluetooth_client_set_trusted (BLUETOOTH_CLIENT (source_object), path, TRUE);
+
+       data = g_new0 (SetupConnectData, 1);
+       data->self = user_data;
+       data->device = path;
+       data->timer = g_timer_new ();
+
+       bluetooth_client_connect_service (BLUETOOTH_CLIENT (source_object),
+                                         path, TRUE, priv->cancellable, connect_callback, data);
+       //gtk_assistant_set_current_page (window_assistant, PAGE_FINISHING);
+}
+
+static void
+start_pairing (BluetoothSettingsWidget *self,
+              GtkListBoxRow           *row)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GDBusProxy *proxy;
+       gboolean pair = TRUE;
+       BluetoothType type;
+       char *bdaddr, *name;
+       gboolean legacy_pairing;
+
+       g_object_set (G_OBJECT (row), "pairing", TRUE, NULL);
+       g_object_get (G_OBJECT (row),
+                     "proxy", &proxy,
+                     "type", &type,
+                     "address", &bdaddr,
+                     "name", &name,
+                     "legacy-pairing", &legacy_pairing,
+                     NULL);
+
+       if (legacy_pairing) {
+               const char *pincode;
+
+               pincode = get_pincode_for_device (type,
+                                                 bdaddr,
+                                                 name,
+                                                 NULL,
+                                                 NULL);
+               if (g_strcmp0 (pincode, "NULL") == 0)
+                       pair = FALSE;
+       }
+
+       g_debug ("About to setup %s (legacy pairing: %d pair: %d)",
+                g_dbus_proxy_get_object_path (proxy),
+                legacy_pairing, pair);
+
+       bluetooth_client_setup_device (priv->client,
+                                      g_dbus_proxy_get_object_path (proxy),
+                                      pair,
+                                      priv->cancellable,
+                                      (GAsyncReadyCallback) create_callback,
+                                      self);
+       g_object_unref (proxy);
+}
+
+static void
+switch_connected_active_changed (GtkSwitch               *button,
+                                GParamSpec              *spec,
+                                BluetoothSettingsWidget *self)
+{
+       ConnectData *data;
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+
+       if (is_connecting (self, priv->selected_bdaddr))
+               return;
+
+       data = g_new0 (ConnectData, 1);
+       data->bdaddr = g_strdup (priv->selected_bdaddr);
+       data->self = self;
+
+       bluetooth_client_connect_service (priv->client,
+                                         priv->selected_object_path,
+                                         gtk_switch_get_active (button),
+                                         priv->cancellable,
+                                         connect_done,
+                                         data);
+
+       add_connecting (self, data->bdaddr);
+       set_connecting_page (self, CONNECTING_NOTEBOOK_PAGE_SPINNER);
+}
+
+static void
+update_properties (BluetoothSettingsWidget *self,
+                  GDBusProxy       *proxy)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GtkSwitch *button;
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       gboolean ret;
+       BluetoothType type;
+       gboolean connected, paired;
+       char **uuids, *bdaddr, *name, *icon;
+       guint i;
+
+       model = bluetooth_client_get_device_model (priv->client);
+       g_assert (model);
+
+       ret = gtk_tree_model_get_iter_first (model, &iter);
+       while (ret) {
+               GDBusProxy *p;
+
+               gtk_tree_model_get (model, &iter,
+                                   BLUETOOTH_COLUMN_PROXY, &p,
+                                   -1);
+
+               if (g_strcmp0 (g_dbus_proxy_get_object_path (proxy),
+                              g_dbus_proxy_get_object_path (p)) == 0) {
+                       g_object_unref (p);
+                       break;
+               }
+
+               g_object_unref (p);
+
+               ret = gtk_tree_model_iter_next (model, &iter);
+       }
+
+       /* This means we've found the device */
+       g_assert (ret);
+
+       gtk_tree_model_get (model, &iter,
+                           BLUETOOTH_COLUMN_ADDRESS, &bdaddr,
+                           BLUETOOTH_COLUMN_NAME, &name,
+                           BLUETOOTH_COLUMN_ICON, &icon,
+                           BLUETOOTH_COLUMN_PAIRED, &paired,
+                           BLUETOOTH_COLUMN_CONNECTED, &connected,
+                           BLUETOOTH_COLUMN_UUIDS, &uuids,
+                           BLUETOOTH_COLUMN_TYPE, &type,
+                           -1);
+       if (priv->debug)
+               bluetooth_client_dump_device (model, &iter);
+       g_object_unref (model);
+
+       g_free (priv->selected_object_path);
+       priv->selected_object_path = g_strdup (g_dbus_proxy_get_object_path (proxy));
+
+       /* Hide all the buttons now, and show them again if we need to */
+       gtk_widget_hide (WID ("keyboard_button"));
+       gtk_widget_hide (WID ("sound_button"));
+       gtk_widget_hide (WID ("mouse_button"));
+       gtk_widget_hide (WID ("send_button"));
+
+       /* Name */
+       gtk_label_set_text (GTK_LABEL (priv->properties_title), name);
+       g_free (priv->selected_name);
+       priv->selected_name = name;
+
+       /* Icon */
+       gtk_image_set_from_icon_name (GTK_IMAGE (WID ("image")), icon, GTK_ICON_SIZE_DIALOG);
+
+       /* Connection */
+       button = GTK_SWITCH (WID ("switch_connection"));
+       g_signal_handlers_block_by_func (button, switch_connected_active_changed, self);
+
+       if (is_connecting (self, bdaddr)) {
+               gtk_switch_set_active (button, TRUE);
+               set_connecting_page (self, CONNECTING_NOTEBOOK_PAGE_SPINNER);
+       } else {
+               gtk_switch_set_active (button, connected);
+               set_connecting_page (self, CONNECTING_NOTEBOOK_PAGE_SWITCH);
+       }
+
+       g_signal_handlers_unblock_by_func (button, switch_connected_active_changed, self);
+
+       /* Paired */
+       gtk_label_set_text (GTK_LABEL (WID ("paired_label")),
+                           paired ? _("Yes") : _("No"));
+
+       /* UUIDs */
+       gtk_widget_set_sensitive (GTK_WIDGET (button),
+                                 bluetooth_client_get_connectable ((const char **) uuids));
+       for (i = 0; uuids && uuids[i] != NULL; i++) {
+               if (g_str_equal (uuids[i], "OBEXObjectPush")) {
+                       gtk_widget_show (WID ("send_button"));
+                       break;
+               }
+       }
+
+       /* Type */
+       gtk_label_set_text (GTK_LABEL (WID ("type_label")), bluetooth_type_to_string (type));
+       switch (type) {
+       case BLUETOOTH_TYPE_KEYBOARD:
+               gtk_widget_show (WID ("keyboard_button"));
+               break;
+       case BLUETOOTH_TYPE_MOUSE:
+       case BLUETOOTH_TYPE_TABLET:
+               gtk_widget_show (WID ("mouse_button"));
+               break;
+       case BLUETOOTH_TYPE_HEADSET:
+       case BLUETOOTH_TYPE_HEADPHONES:
+       case BLUETOOTH_TYPE_OTHER_AUDIO:
+               gtk_widget_show (WID ("sound_button"));
+       default:
+               /* others? */
+               ;
+       }
+
+       /* Address */
+       gtk_label_set_text (GTK_LABEL (WID ("address_label")), bdaddr);
+
+       g_free (priv->selected_bdaddr);
+       priv->selected_bdaddr = bdaddr;
+
+       g_free (icon);
+       g_strfreev (uuids);
+}
+
+static void
+switch_panel (BluetoothSettingsWidget *self,
+             const char       *panel)
+{
+       g_signal_emit (G_OBJECT (self),
+                      signals[PANEL_CHANGED],
+                      0, panel);
+}
+
+static gboolean
+keyboard_callback (GtkButton        *button,
+                  BluetoothSettingsWidget *self)
+{
+       switch_panel (self, KEYBOARD_PREFS);
+       return TRUE;
+}
+
+static gboolean
+mouse_callback (GtkButton        *button,
+               BluetoothSettingsWidget *self)
+{
+       switch_panel (self, MOUSE_PREFS);
+       return TRUE;
+}
+
+static gboolean
+sound_callback (GtkButton        *button,
+               BluetoothSettingsWidget *self)
+{
+       switch_panel (self, SOUND_PREFS);
+       return TRUE;
+}
+
+static void
+send_callback (GtkButton        *button,
+              BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       bluetooth_send_to_address (priv->selected_bdaddr, priv->selected_name);
+}
+
+/* Visibility/Discoverable */
+static void
+update_visibility (BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       char *name, *label;
+
+       g_object_get (G_OBJECT (priv->client), "default-adapter-name", &name, NULL);
+       /* translators: %s is the name of the computer, for example:
+        * Now visible as “Bastien Nocera's Computer” */
+       label = g_strdup_printf (_("Now visible as “%s”"), name);
+       g_free (name);
+       gtk_label_set_text (GTK_LABEL (priv->visible_label), label);
+       g_free (label);
+}
+
+static void
+name_changed (BluetoothClient  *client,
+             GParamSpec       *spec,
+             BluetoothSettingsWidget *self)
+{
+       update_visibility (self);
+}
+
+static gboolean
+show_confirm_dialog (BluetoothSettingsWidget *self,
+                    const char *name)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GtkWidget *dialog;
+       gint response;
+
+       dialog = gtk_message_dialog_new (GTK_WINDOW (priv->properties_dialog), GTK_DIALOG_MODAL,
+                                        GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
+                                        _("Remove '%s' from the list of devices?"), name);
+       g_object_set (G_OBJECT (dialog), "secondary-text",
+                     _("If you remove the device, you will have to set it up again before next use."),
+                     NULL);
+
+       gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL);
+       gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Remove"), GTK_RESPONSE_ACCEPT);
+
+       response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+       gtk_widget_destroy (dialog);
+
+       if (response == GTK_RESPONSE_ACCEPT)
+               return TRUE;
+
+       return FALSE;
+}
+
+static gboolean
+remove_selected_device (BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       char *adapter;
+       GDBusProxy *adapter_proxy;
+       GError *error = NULL;
+       GVariant *ret;
+
+       g_object_get (G_OBJECT (priv->client), "default-adapter", &adapter, NULL);
+       adapter_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                                      G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | 
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+                                                      NULL,
+                                                      BLUEZ_SERVICE,
+                                                      adapter,
+                                                      ADAPTER_IFACE,
+                                                      NULL,
+                                                      &error);
+       g_free (adapter);
+       if (adapter_proxy == NULL) {
+               g_warning ("Failed to create a GDBusProxy for the default adapter: %s", error->message);
+               g_error_free (error);
+               return FALSE;
+       }
+
+       //FIXME use adapter1_call_remove_device_sync()
+       ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (adapter_proxy),
+                                     "RemoveDevice",
+                                     g_variant_new ("(o)", priv->selected_object_path),
+                                     G_DBUS_CALL_FLAGS_NONE,
+                                     -1,
+                                     NULL,
+                                     &error);
+
+       if (ret == NULL) {
+               g_warning ("Failed to remove device '%s': %s",
+                          priv->selected_object_path, error->message);
+               g_error_free (error);
+       } else {
+               g_variant_unref (ret);
+       }
+
+       g_object_unref (adapter_proxy);
+
+       return (ret != NULL);
+}
+
+static void
+delete_clicked (GtkButton        *button,
+               BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       if (show_confirm_dialog (self, priv->selected_name) != FALSE) {
+               remove_selected_device (self);
+               gtk_widget_hide (priv->properties_dialog);
+       }
+}
+
+static void
+default_adapter_changed (BluetoothClient  *client,
+                        GParamSpec       *spec,
+                        BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       char *default_adapter;
+
+       g_debug ("Default adapter changed");
+
+       g_object_get (priv->client, "default-adapter", &default_adapter, NULL);
+
+       g_object_set (G_OBJECT (client), "default-adapter-discovering", default_adapter != NULL, NULL);
+
+       /* FIXME: This should turn off automatically when
+        * the settings panel goes away */
+       g_object_set (G_OBJECT (client), "default-adapter-discoverable", default_adapter != NULL, NULL);
+}
+
+static gint
+device_sort_func (gconstpointer a, gconstpointer b, gpointer data)
+{
+       GObject *row_a = (GObject*)a;
+       GObject *row_b = (GObject*)b;
+       gboolean setup_a, setup_b;
+       gboolean paired_a, paired_b;
+       gboolean trusted_a, trusted_b;
+       gboolean connected_a, connected_b;
+       char *name_a, *name_b;
+       int ret;
+
+       g_object_get (row_a,
+                     "paired", &paired_a,
+                     "trusted", &trusted_a,
+                     "connected", &connected_a,
+                     "name", &name_a,
+                     NULL);
+       g_object_get (row_b,
+                     "paired", &paired_b,
+                     "trusted", &trusted_b,
+                     "connected", &connected_b,
+                     "name", &name_b,
+                     NULL);
+
+       /* First, paired or trusted devices (setup devices) */
+       setup_a = paired_a || trusted_a;
+       setup_b = paired_b || trusted_b;
+       if (setup_a != setup_b) {
+               if (setup_a)
+                       ret = -1;
+               else
+                       ret = 1;
+               goto out;
+       }
+
+       /* Then connected ones */
+       if (connected_a != connected_b) {
+               if (connected_a)
+                       ret = -1;
+               else
+                       ret = 1;
+               goto out;
+       }
+
+       /* And all being equal, alphabetically */
+       ret = g_utf8_collate (name_a, name_b);
+
+out:
+       g_free (name_a);
+       g_free (name_b);
+       return ret;
+}
+
+static void
+update_header_func (GtkListBoxRow  *row,
+                   GtkListBoxRow  *before,
+                   gpointer    user_data)
+{
+       GtkWidget *current;
+
+       if (before == NULL)
+               return;
+
+       current = gtk_list_box_row_get_header (row);
+       if (current == NULL) {
+               current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+               gtk_widget_show (current);
+               gtk_list_box_row_set_header (row, current);
+       }
+}
+
+static gboolean
+keynav_failed (GtkWidget *list, GtkDirectionType direction, BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+  GtkWidget *next_list = NULL;
+  GList *item, *boxes_list;
+  gdouble value, lower, upper, page;
+
+  /* Find the list in the list of GtkListBoxes */
+  if (direction == GTK_DIR_DOWN)
+    boxes_list = priv->boxes;
+  else
+    boxes_list = priv->boxes_reverse;
+
+  item = g_list_find (boxes_list, list);
+  g_assert (item);
+  item = item->next;
+  while (1)
+    {
+      if (item == NULL)
+        item = boxes_list;
+
+      /* Avoid looping */
+      if (item->data == list)
+        break;
+
+      if (gtk_widget_is_visible (item->data))
+        {
+          next_list = item->data;
+          break;
+        }
+
+    item = item->next;
+  }
+
+  if (next_list)
+    {
+      gtk_widget_child_focus (next_list, direction);
+      return TRUE;
+    }
+
+  value = gtk_adjustment_get_value (priv->focus_adjustment);
+  lower = gtk_adjustment_get_lower (priv->focus_adjustment);
+  upper = gtk_adjustment_get_upper (priv->focus_adjustment);
+  page  = gtk_adjustment_get_page_size (priv->focus_adjustment);
+
+  if (direction == GTK_DIR_UP && value > lower)
+    {
+      gtk_adjustment_set_value (priv->focus_adjustment, lower);
+      return TRUE;
+    }
+  else if (direction == GTK_DIR_DOWN && value < upper - page)
+    {
+      gtk_adjustment_set_value (priv->focus_adjustment, upper - page);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+activate_row (BluetoothSettingsWidget *self,
+              GtkListBoxRow    *row)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GtkWidget *w;
+       GtkWidget *toplevel;
+       gboolean paired, trusted, is_setup;
+
+       g_object_get (G_OBJECT (row),
+                     "paired", &paired,
+                     "trusted", &trusted,
+                     NULL);
+       is_setup = paired || trusted;
+
+       if (is_setup) {
+               GDBusProxy *proxy;
+
+               //FIXME pass the row
+               //FIXME add UUIDs to the row
+               //FIXME add icon to the row
+               g_object_get (G_OBJECT (proxy), "proxy", &proxy, NULL);
+               update_properties (self, proxy);
+               g_object_unref (proxy);
+
+               w = priv->properties_dialog;
+               toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+               gtk_window_set_transient_for (GTK_WINDOW (w), GTK_WINDOW (toplevel));
+               gtk_window_set_modal (GTK_WINDOW (w), TRUE);
+               gtk_window_present (GTK_WINDOW (w));
+       } else {
+               char *name;
+
+               g_object_get (G_OBJECT (row), "name", &name, NULL);
+               g_debug ("Start pairing '%s'", name);
+               g_free (name);
+
+               start_pairing (self, row);
+       }
+}
+
+static void
+add_device_section (BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GtkWidget *vbox;
+       GtkWidget *widget, *box, *hbox, *spinner;
+       GtkWidget *frame;
+       gchar *s;
+
+       vbox = WID ("vbox_bluetooth");
+
+       box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+       gtk_widget_set_margin_start (box, 50);
+       gtk_widget_set_margin_end (box, 50);
+       gtk_widget_set_margin_top (box, 6);
+       gtk_widget_set_margin_bottom (box, 24);
+       gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, TRUE, 0);
+
+       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+       gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, TRUE, 0);
+
+       s = g_markup_printf_escaped ("<b>%s</b>", _("Devices"));
+       widget = gtk_label_new (s);
+       g_free (s);
+       gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+       gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+       gtk_widget_set_margin_start (widget, 6);
+       gtk_widget_set_margin_end (widget, 6);
+       gtk_widget_set_margin_bottom (widget, 6);
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, TRUE, 0);
+
+       priv->device_spinner = spinner = gtk_spinner_new ();
+       g_object_bind_property (G_OBJECT (priv->client), "default-adapter-discovering",
+                               G_OBJECT (priv->device_spinner), "active",
+                               G_BINDING_SYNC_CREATE);
+       gtk_widget_set_margin_bottom (spinner, 6);
+       gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, TRUE, 0);
+
+       priv->device_list = widget = gtk_list_box_new ();
+       priv->boxes_reverse = g_list_prepend (priv->boxes_reverse, priv->device_list);
+       g_signal_connect (widget, "keynav-failed", G_CALLBACK (keynav_failed), self);
+       gtk_list_box_set_selection_mode (GTK_LIST_BOX (widget), GTK_SELECTION_NONE);
+       gtk_list_box_set_header_func (GTK_LIST_BOX (widget),
+                                     update_header_func,
+                                     NULL, NULL);
+       gtk_list_box_set_sort_func (GTK_LIST_BOX (widget),
+                                   (GtkListBoxSortFunc)device_sort_func, NULL, NULL);
+       g_signal_connect_swapped (widget, "row-activated",
+                                 G_CALLBACK (activate_row), self);
+
+       priv->device_revealer = gtk_revealer_new ();
+
+       frame = gtk_frame_new (NULL);
+       gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+       gtk_container_add (GTK_CONTAINER (frame), widget);
+       gtk_container_add (GTK_CONTAINER (priv->device_revealer), frame);
+       gtk_box_pack_start (GTK_BOX (box), priv->device_revealer, FALSE, TRUE, 0);
+
+       gtk_widget_show_all (box);
+}
+
+static void
+on_content_size_changed (GtkWidget *widget, GtkAllocation *allocation, gpointer data)
+{
+       GtkWidget *box;
+
+       box = gtk_widget_get_parent (gtk_widget_get_parent (widget));
+       if (allocation->height < 490) {
+               gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
+                                               GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+       } else {
+               gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
+                                               GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+               gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (box), 490);
+       }
+}
+
+static gboolean
+is_interesting_device (GtkTreeModel *model,
+                      GtkTreeIter  *iter)
+{
+       GtkTreeIter parent_iter;
+       gboolean is_default;
+
+       /* Not a child */
+       if (gtk_tree_model_iter_parent (model, &parent_iter, iter) == FALSE)
+               return FALSE;
+
+       /* Not the default adapter */
+       gtk_tree_model_get (model, &parent_iter,
+                           BLUETOOTH_COLUMN_DEFAULT, &is_default,
+                           -1);
+       return is_default;
+}
+
+static void
+row_inserted_cb (GtkTreeModel *tree_model,
+                GtkTreePath  *path,
+                GtkTreeIter  *iter,
+                gpointer      user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       GDBusProxy *proxy;
+       char *name, *bdaddr;
+       BluetoothType type;
+       gboolean paired, trusted, connected, legacy_pairing;
+       GtkWidget *row;
+
+       if (is_interesting_device (tree_model, iter) == FALSE) {
+               gtk_tree_model_get (tree_model, iter,
+                                   BLUETOOTH_COLUMN_NAME, &name,
+                                   -1);
+               g_debug ("Not adding device '%s'", name);
+               g_free (name);
+               return;
+       }
+
+       gtk_tree_model_get (tree_model, iter,
+                           BLUETOOTH_COLUMN_PROXY, &proxy,
+                           BLUETOOTH_COLUMN_NAME, &name,
+                           BLUETOOTH_COLUMN_PAIRED, &paired,
+                           BLUETOOTH_COLUMN_TRUSTED, &trusted,
+                           BLUETOOTH_COLUMN_CONNECTED, &connected,
+                           BLUETOOTH_COLUMN_ADDRESS, &bdaddr,
+                           BLUETOOTH_COLUMN_TYPE, &type,
+                           BLUETOOTH_COLUMN_LEGACYPAIRING, &legacy_pairing,
+                           -1);
+
+       g_debug ("Adding device %s (%s)", name, g_dbus_proxy_get_object_path (proxy));
+
+       row = g_object_new (BLUETOOTH_TYPE_SETTINGS_ROW,
+                           "proxy", proxy,
+                           "paired", paired,
+                           "trusted", trusted,
+                           "type", type,
+                           "connected", connected,
+                           "name", name,
+                           "address", bdaddr,
+                           "legacy-pairing", legacy_pairing,
+                           NULL);
+       g_object_set_data_full (G_OBJECT (row), "object-path", g_strdup (g_dbus_proxy_get_object_path 
(proxy)), g_free);
+
+       gtk_container_add (GTK_CONTAINER (priv->device_list), row);
+       gtk_size_group_add_widget (priv->row_sizegroup, row);
+
+       g_object_unref (proxy);
+       g_free (name);
+       g_free (bdaddr);
+
+       gtk_revealer_set_reveal_child (GTK_REVEALER (priv->device_revealer), TRUE);
+}
+
+static void
+row_changed_cb (GtkTreeModel *tree_model,
+               GtkTreePath  *path,
+               GtkTreeIter  *iter,
+               gpointer      user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       GDBusProxy *proxy;
+       GList *l, *children;
+       const char *object_path;
+
+       if (is_interesting_device (tree_model, iter) == FALSE) {
+               char *name;
+
+               gtk_tree_model_get (tree_model, iter,
+                                   BLUETOOTH_COLUMN_NAME, &name,
+                                   -1);
+               g_debug ("Not interested in device '%s'", name);
+               g_free (name);
+               return;
+       }
+
+       gtk_tree_model_get (tree_model, iter,
+                           BLUETOOTH_COLUMN_PROXY, &proxy,
+                           -1);
+       object_path = g_dbus_proxy_get_object_path (proxy);
+
+       children = gtk_container_get_children (GTK_CONTAINER (priv->device_list));
+       for (l = children; l != NULL; l = l->next) {
+               const char *path;
+
+               path = g_object_get_data (G_OBJECT (l->data), "object-path");
+               if (g_str_equal (object_path, path)) {
+                       char *name, *bdaddr;
+                       BluetoothType type;
+                       gboolean paired, trusted, connected, legacy_pairing;
+
+                       gtk_tree_model_get (tree_model, iter,
+                                           BLUETOOTH_COLUMN_NAME, &name,
+                                           BLUETOOTH_COLUMN_PAIRED, &paired,
+                                           BLUETOOTH_COLUMN_TRUSTED, &trusted,
+                                           BLUETOOTH_COLUMN_CONNECTED, &connected,
+                                           BLUETOOTH_COLUMN_ADDRESS, &bdaddr,
+                                           BLUETOOTH_COLUMN_TYPE, &type,
+                                           BLUETOOTH_COLUMN_LEGACYPAIRING, &legacy_pairing,
+                                           -1);
+
+                       g_object_set (G_OBJECT (l->data),
+                                     "paired", paired,
+                                     "trusted", trusted,
+                                     "type", type,
+                                     "connected", connected,
+                                     "name", name,
+                                     "legacy-pairing", legacy_pairing,
+                                     NULL);
+                       break;
+               }
+       }
+       g_list_free (children);
+       g_object_unref (proxy);
+}
+
+static void
+device_removed_cb (BluetoothClient *client,
+                  const char      *object_path,
+                  gpointer         user_data)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data);
+       GList *children, *l;
+
+       children = gtk_container_get_children (GTK_CONTAINER (priv->device_list));
+       for (l = children; l != NULL; l = l->next) {
+               const char *path;
+
+               path = g_object_get_data (G_OBJECT (l->data), "object-path");
+               if (g_str_equal (path, object_path)) {
+                       char *name;
+
+                       g_object_get (G_OBJECT (l->data), "name", &name, NULL);
+                       g_debug ("Removing device '%s'", name);
+                       g_free (name);
+
+                       gtk_widget_destroy (GTK_WIDGET (l->data));
+                       return;
+               }
+       }
+
+       g_debug ("Didn't find a row to remove for tree path %s", object_path);
+}
+
+static void
+setup_properties_dialog (BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GtkWidget *container, *buttonbox, *header, *done;
+       GtkStyleContext *context;
+
+       priv->properties_dialog = gtk_dialog_new ();
+       gtk_widget_set_size_request (priv->properties_dialog, 380, -1);
+       gtk_window_set_resizable (GTK_WINDOW (priv->properties_dialog), FALSE);
+       container = gtk_dialog_get_content_area (GTK_DIALOG (priv->properties_dialog));
+       buttonbox = gtk_dialog_get_action_area (GTK_DIALOG (priv->properties_dialog));
+       priv->properties_header = header = gtk_header_bar_new ();
+       done = gtk_button_new_with_label (_("Done"));
+       gtk_header_bar_pack_end (GTK_HEADER_BAR (header), done);
+       gtk_widget_show_all (header);
+       priv->properties_title = gtk_label_new ("");
+       gtk_header_bar_set_custom_title (GTK_HEADER_BAR (header), priv->properties_title);
+       gtk_window_set_titlebar (GTK_WINDOW (priv->properties_dialog), header);
+       gtk_container_add (GTK_CONTAINER (container), WID ("properties_vbox"));
+       gtk_widget_hide (buttonbox);
+       gtk_widget_set_no_show_all (buttonbox, FALSE);
+
+       g_signal_connect (G_OBJECT (priv->properties_dialog), "delete-event",
+                         G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+       g_signal_connect_swapped (G_OBJECT (done), "clicked",
+                                 G_CALLBACK (gtk_widget_hide_on_delete), priv->properties_dialog);
+       g_signal_connect (G_OBJECT (WID ("delete_button")), "clicked",
+                         G_CALLBACK (delete_clicked), self);
+       g_signal_connect (G_OBJECT (WID ("mouse_button")), "clicked",
+                         G_CALLBACK (mouse_callback), self);
+       g_signal_connect (G_OBJECT (WID ("keyboard_button")), "clicked",
+                         G_CALLBACK (keyboard_callback), self);
+       g_signal_connect (G_OBJECT (WID ("sound_button")), "clicked",
+                         G_CALLBACK (sound_callback), self);
+       g_signal_connect (G_OBJECT (WID ("send_button")), "clicked",
+                         G_CALLBACK (send_callback), self);
+       g_signal_connect (G_OBJECT (WID ("switch_connection")), "notify::active",
+                         G_CALLBACK (switch_connected_active_changed), self);
+
+       /* Styling */
+       gtk_image_set_pixel_size (GTK_IMAGE (WID ("image")), ICON_SIZE);
+
+       context = gtk_widget_get_style_context (WID ("delete_button"));
+       gtk_style_context_add_class (context, "destructive-action");
+       context = gtk_widget_get_style_context (done);
+       gtk_style_context_add_class (context, "suggested-action");
+       context = gtk_widget_get_style_context (priv->properties_title);
+       gtk_style_context_add_class (context, "title");
+}
+
+static void
+setup_pairing_agent (BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+
+       priv->agent = bluetooth_agent_new ();
+       if (bluetooth_agent_register (priv->agent) == FALSE)
+               return;
+
+       g_object_add_weak_pointer (G_OBJECT (priv->agent), (gpointer *) (&priv->agent));
+
+       bluetooth_agent_set_pincode_func (priv->agent, pincode_callback, self);
+       bluetooth_agent_set_display_func (priv->agent, display_callback, self);
+       bluetooth_agent_set_display_pincode_func (priv->agent, display_pincode_callback, self);
+       bluetooth_agent_set_cancel_func (priv->agent, cancel_callback, self);
+       bluetooth_agent_set_confirm_func (priv->agent, confirm_callback, self);
+       bluetooth_agent_set_authorize_func (priv->agent, authorize_callback, self);
+       bluetooth_agent_set_authorize_service_func (priv->agent, authorize_service_callback, self);
+
+       bluetooth_agent_setup (priv->agent, AGENT_PATH);
+}
+
+static void
+bluetooth_settings_widget_init (BluetoothSettingsWidget *self)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self);
+       GtkWidget *widget, *box;
+       GError *error = NULL;
+
+       priv->cancellable = g_cancellable_new ();
+       priv->debug = g_getenv ("BLUETOOTH_DEBUG") != NULL;
+
+       g_resources_register (bluetooth_settings_get_resource ());
+       priv->builder = gtk_builder_new ();
+       gtk_builder_set_translation_domain (priv->builder, GETTEXT_PACKAGE);
+       gtk_builder_add_from_resource (priv->builder,
+                                       "/org/gnome/bluetooth/settings.ui",
+                                       &error);
+       if (error != NULL) {
+               g_warning ("Could not load ui: %s", error->message);
+               g_error_free (error);
+               return;
+       }
+
+       widget = WID ("vbox_bluetooth");
+
+       priv->connecting_devices = g_hash_table_new_full (g_str_hash,
+                                                               g_str_equal,
+                                                               (GDestroyNotify) g_free,
+                                                               NULL);
+
+       setup_pairing_agent (self);
+       priv->client = bluetooth_client_new ();
+       g_signal_connect (G_OBJECT (priv->client), "notify::default-adapter-name",
+                         G_CALLBACK (name_changed), self);
+       priv->model = bluetooth_client_get_model (priv->client);
+       g_signal_connect (priv->model, "row-changed",
+                         G_CALLBACK (row_changed_cb), self);
+       g_signal_connect (priv->model, "row-inserted",
+                         G_CALLBACK (row_inserted_cb), self);
+       g_signal_connect (priv->client, "device-removed",
+                         G_CALLBACK (device_removed_cb), self);
+       g_signal_connect (G_OBJECT (priv->client), "notify::default-adapter",
+                         G_CALLBACK (default_adapter_changed), self);
+       default_adapter_changed (priv->client, NULL, self);
+
+       priv->row_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+
+       add_device_section (self);
+
+       priv->boxes = g_list_copy (priv->boxes_reverse);
+       priv->boxes = g_list_reverse (priv->boxes);
+
+       box = gtk_scrolled_window_new (NULL, NULL);
+       gtk_widget_set_hexpand (box, TRUE);
+       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
+                                       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+       g_signal_connect (widget, "size-allocate",
+                         G_CALLBACK (on_content_size_changed), NULL);
+       gtk_widget_show (box);
+       gtk_container_add (GTK_CONTAINER (self), box);
+       gtk_container_add (GTK_CONTAINER (box), widget);
+
+       priv->focus_adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (box));
+       gtk_container_set_focus_vadjustment (GTK_CONTAINER (widget), priv->focus_adjustment);
+
+       /* Discoverable label */
+       priv->visible_label = gtk_label_new ("Now visible as “Foobar”");
+       gtk_label_set_use_markup (GTK_LABEL (priv->visible_label), TRUE);
+       gtk_misc_set_alignment (GTK_MISC (priv->visible_label), 0.5, 0.5);
+       gtk_box_pack_start (GTK_BOX (widget), priv->visible_label, FALSE, TRUE, 0);
+       update_visibility (self);
+
+       setup_properties_dialog (self);
+
+       gtk_widget_show_all (GTK_WIDGET (self));
+}
+
+static void
+bluetooth_settings_widget_finalize (GObject *object)
+{
+       BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (object);
+
+       g_clear_pointer (&priv->properties_dialog, gtk_widget_destroy);
+       g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy);
+
+       /* See default_adapter_changed () */
+       if (priv->client)
+               g_object_set (G_OBJECT (priv->client), "default-adapter-discoverable", FALSE, NULL);
+
+       g_cancellable_cancel (priv->cancellable);
+       g_clear_object (&priv->cancellable);
+
+       g_clear_object (&priv->model);
+       g_clear_object (&priv->client);
+       g_clear_object (&priv->builder);
+
+       g_clear_pointer (&priv->connecting_devices, g_hash_table_destroy);
+       g_clear_pointer (&priv->selected_name, g_free);
+       g_clear_pointer (&priv->selected_object_path, g_free);
+
+       G_OBJECT_CLASS(bluetooth_settings_widget_parent_class)->finalize(object);
+}
+
+static void
+bluetooth_settings_widget_class_init (BluetoothSettingsWidgetClass *klass)
+{
+       bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+       g_type_class_add_private (klass, sizeof (BluetoothSettingsWidgetPrivate));
+
+       G_OBJECT_CLASS (klass)->finalize = bluetooth_settings_widget_finalize;
+
+       /**
+        * BluetoothSettingsWidget::panel-changed:
+        * @chooser: a #BluetoothSettingsWidget widget which received the signal
+        * @panel: the new panel that the Settings application should now open
+        *
+        * The #BluetoothChooser::selected-device-changed signal is launched when a
+        * link to another settings panel is clicked.
+        **/
+       signals[PANEL_CHANGED] =
+               g_signal_new ("panel-changed",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__STRING,
+                             G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+/**
+ * bluetooth_settings_widget_new:
+ *
+ * Returns a new #BluetoothSettingsWidget widget.
+ *
+ * Return value: A #BluetoothSettingsWidget widget
+ **/
+GtkWidget *
+bluetooth_settings_widget_new (void)
+{
+       return g_object_new (BLUETOOTH_TYPE_SETTINGS_WIDGET, NULL);
+}
diff --git a/lib/bluetooth-settings-widget.h b/lib/bluetooth-settings-widget.h
new file mode 100644
index 0000000..6e943bd
--- /dev/null
+++ b/lib/bluetooth-settings-widget.h
@@ -0,0 +1,63 @@
+/*
+ *
+ *  Copyright (C) 2013 Bastien Nocera <hadess hadess net>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __BLUETOOTH_SETTINGS_WIDGET_H
+#define __BLUETOOTH_SETTINGS_WIDGET_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define BLUETOOTH_TYPE_SETTINGS_WIDGET (bluetooth_settings_widget_get_type())
+#define BLUETOOTH_SETTINGS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+                               BLUETOOTH_TYPE_SETTINGS_WIDGET, BluetoothSettingsWidget))
+#define BLUETOOTH_SETTINGS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+                               BLUETOOTH_TYPE_SETTINGS_WIDGET, BluetoothSettingsWidgetClass))
+#define BLUETOOTH_IS_SETTINGS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+                                               BLUETOOTH_TYPE_SETTINGS_WIDGET))
+#define BLUETOOTH_IS_SETTINGS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+                                               BLUETOOTH_TYPE_SETTINGS_WIDGET))
+#define BLUETOOTH_GET_SETTINGS_WIDGET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+                               BLUETOOTH_TYPE_SETTINGS_WIDGET, BluetoothSettingsWidgetClass))
+
+/**
+ * BluetoothSettingsWidget:
+ *
+ * The <structname>BluetoothSettingsWidget</structname> struct contains
+ * only private fields and should not be directly accessed.
+ */
+typedef struct _BluetoothSettingsWidget BluetoothSettingsWidget;
+typedef struct _BluetoothSettingsWidgetClass BluetoothSettingsWidgetClass;
+
+struct _BluetoothSettingsWidget {
+       GtkBox parent;
+};
+
+struct _BluetoothSettingsWidgetClass {
+       GtkBoxClass parent_class;
+};
+
+GType bluetooth_settings_widget_get_type (void);
+
+GtkWidget *bluetooth_settings_widget_new (void);
+
+G_END_DECLS
+
+#endif /* __BLUETOOTH_SETTINGS_WIDGET_H */
diff --git a/lib/gnome-bluetooth.symbols b/lib/gnome-bluetooth.symbols
index 4c23aa3..7030070 100644
--- a/lib/gnome-bluetooth.symbols
+++ b/lib/gnome-bluetooth.symbols
@@ -57,3 +57,10 @@ bluetooth_agent_set_display_func
 bluetooth_agent_set_display_pincode_func
 bluetooth_agent_set_authorize_service_func
 bluetooth_agent_setup
+bluetooth_settings_widget_get_type
+bluetooth_settings_widget_new
+bluetooth_pairing_dialog_new
+bluetooth_pairing_dialog_get_type
+bluetooth_pairing_dialog_set_mode
+bluetooth_pairing_dialog_get_mode
+bluetooth_pairing_dialog_set_pin_entered
diff --git a/wizard/pin.c b/lib/pin.c
similarity index 100%
rename from wizard/pin.c
rename to lib/pin.c
diff --git a/wizard/pin.h b/lib/pin.h
similarity index 100%
rename from wizard/pin.h
rename to lib/pin.h
diff --git a/lib/settings.gresource.xml b/lib/settings.gresource.xml
new file mode 100644
index 0000000..58813cf
--- /dev/null
+++ b/lib/settings.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/bluetooth">
+    <file preprocess="xml-stripblanks">settings.ui</file>
+  </gresource>
+</gresources>
diff --git a/lib/settings.ui b/lib/settings.ui
new file mode 100644
index 0000000..3fe2cb2
--- /dev/null
+++ b/lib/settings.ui
@@ -0,0 +1,452 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.0 on Thu Dec  5 15:20:37 2013 -->
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkBox" id="pairing_dialog_box">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkLabel" id="help_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Please enter the following PIN on 'Foobar':</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkNotebook" id="pin_notebook">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="show_tabs">False</property>
+        <property name="show_border">False</property>
+        <child>
+          <object class="GtkAspectFrame" id="aspectframe1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label_xalign">0</property>
+            <property name="shadow_type">none</property>
+            <child>
+              <object class="GtkEntry" id="entry_pin">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="max_length">6</property>
+                <property name="width_chars">6</property>
+                <property name="input_purpose">digits</property>
+                <property name="input_hints">GTK_INPUT_HINT_NO_SPELLCHECK | GTK_INPUT_HINT_NONE</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label_pin">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label">123456</property>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label_placeholder">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label"></property>
+          </object>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+  </object>
+  <object class="GtkBox" id="properties_vbox">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_left">12</property>
+    <property name="margin_right">12</property>
+    <property name="margin_top">12</property>
+    <property name="margin_bottom">12</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_top">16</property>
+            <property name="margin_bottom">16</property>
+            <property name="stock">gtk-missing-image</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">18</property>
+            <property name="homogeneous">True</property>
+            <child>
+              <object class="GtkLabel" id="connection_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">Connection</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkNotebook" id="connecting_notebook">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="show_tabs">False</property>
+                <property name="show_border">False</property>
+                <child>
+                  <object class="GtkSwitch" id="switch_connection">
+                    <property name="use_action_appearance">False</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="halign">start</property>
+                  </object>
+                </child>
+                <child type="tab">
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">page 1</property>
+                  </object>
+                  <packing>
+                    <property name="tab_fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSpinner" id="connecting_spinner">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child type="tab">
+                  <object class="GtkLabel" id="label9">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">page 2</property>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                    <property name="tab_fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child type="tab">
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">18</property>
+            <property name="homogeneous">True</property>
+            <child>
+              <object class="GtkLabel" id="label4">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">Paired</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="paired_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label">Yes</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box4">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">18</property>
+            <property name="homogeneous">True</property>
+            <child>
+              <object class="GtkLabel" id="label5">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">Type</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="type_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label">Keyboard</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box5">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">18</property>
+            <property name="homogeneous">True</property>
+            <child>
+              <object class="GtkLabel" id="label6">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">Address</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="address_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label">00:00:00:00:00</property>
+                <property name="selectable">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+    <child>
+      <object class="GtkBox" id="vbox3">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_bottom">6</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkButton" id="mouse_button">
+            <property name="label" translatable="yes">_Mouse &amp; Touchpad Settings</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="sound_button">
+            <property name="label" translatable="yes">_Sound Settings</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="keyboard_button">
+            <property name="label" translatable="yes">_Keyboard Settings</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="send_button">
+            <property name="label" translatable="yes">Send _Files…</property>
+            <property name="use_action_appearance">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="delete_button">
+            <property name="label" translatable="yes">_Remove Device</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="pack_type">end</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="label7">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="position">3</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkBox" id="vbox_bluetooth">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_top">12</property>
+    <property name="border_width">12</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">3</property>
+  </object>
+  <object class="GtkSizeGroup" id="sizegroup1">
+    <property name="mode">both</property>
+    <widgets>
+      <widget name="entry_pin"/>
+      <widget name="label_pin"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/lib/test-pairing-dialog.c b/lib/test-pairing-dialog.c
new file mode 100644
index 0000000..1cc567f
--- /dev/null
+++ b/lib/test-pairing-dialog.c
@@ -0,0 +1,65 @@
+#include "bluetooth-pairing-dialog.h"
+
+static const char *
+response_to_str (int response)
+{
+       switch (response) {
+       case GTK_RESPONSE_ACCEPT:
+               return "accept";
+       case GTK_RESPONSE_CANCEL:
+               return "cancel";
+       case GTK_RESPONSE_DELETE_EVENT:
+               return "delete-event";
+       default:
+               g_message ("response %d unhandled", response);
+               g_assert_not_reached ();
+       }
+}
+
+static void
+response_cb (GtkDialog *dialog,
+            int        response,
+            gpointer   user_data)
+{
+       g_message ("Received response '%d' (%s)",
+                  response, response_to_str (response));
+
+       if (response == GTK_RESPONSE_CANCEL ||
+           response == GTK_RESPONSE_DELETE_EVENT) {
+               if (response != GTK_RESPONSE_DELETE_EVENT)
+                       gtk_widget_destroy (GTK_WIDGET (dialog));
+               gtk_main_quit ();
+               return;
+       }
+
+       if (bluetooth_pairing_dialog_get_mode (BLUETOOTH_PAIRING_DIALOG (user_data)) == 
BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION) {
+               bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (user_data),
+                                                  BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_NORMAL,
+                                                  "234567",
+                                                  "My device");
+       } else {
+               gtk_widget_destroy (GTK_WIDGET (dialog));
+               gtk_main_quit ();
+       }
+}
+
+int main (int argc, char **argv)
+{
+       GtkWidget *window;
+
+       gtk_init (&argc, &argv);
+
+       window = bluetooth_pairing_dialog_new ();
+       bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (window),
+                                          BLUETOOTH_PAIRING_MODE_YES_NO,
+                                          NULL,
+                                          "My device");
+       g_signal_connect (G_OBJECT (window), "response",
+                         G_CALLBACK (response_cb), window);
+
+       gtk_widget_show_all (window);
+
+       gtk_main ();
+
+       return 0;
+}
diff --git a/lib/test-settings.c b/lib/test-settings.c
new file mode 100644
index 0000000..bcf139c
--- /dev/null
+++ b/lib/test-settings.c
@@ -0,0 +1,29 @@
+#include "bluetooth-settings-widget.h"
+
+static gboolean
+delete_event_cb (GtkWidget *widget,
+                GdkEvent  *event,
+                gpointer   user_data)
+{
+       gtk_main_quit ();
+       return FALSE;
+}
+
+int main (int argc, char **argv)
+{
+       GtkWidget *window, *widget;
+
+       gtk_init (&argc, &argv);
+
+       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+       g_signal_connect (G_OBJECT (window), "delete-event",
+                         G_CALLBACK (delete_event_cb), NULL);
+       widget = bluetooth_settings_widget_new ();
+       gtk_container_add (GTK_CONTAINER (window), widget);
+
+       gtk_widget_show_all (window);
+
+       gtk_main ();
+
+       return 0;
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 894a19b..316b646 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -4,8 +4,9 @@ lib/bluetooth-chooser-combo.c
 lib/bluetooth-chooser.c
 lib/bluetooth-filter-widget.c
 lib/bluetooth-utils.c
-wizard/main.c
-[type: gettext/glade] wizard/wizard.ui
-wizard/bluetooth-wizard.desktop.in.in
+lib/bluetooth-pairing-dialog.c
+lib/bluetooth-settings-row.c
+lib/bluetooth-settings-widget.c
+lib/settings.ui
 sendto/main.c
 sendto/bluetooth-sendto.desktop.in.in
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 03eba56..9fcf378 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1,3 +1 @@
 sendto/bluetooth-sendto.desktop.in
-wizard/bluetooth-wizard.desktop.in
-wizard/main.c


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]