summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorBastien Nocera <hadess@hadess.net>2013-12-02 19:09:26 +0100
committerBastien Nocera <hadess@hadess.net>2013-12-06 12:00:30 +0100
commit4b2c4fd253bc49d2aad7a44013a9ca3b8131e81e (patch)
tree80d08d8663d135ad6a454bbcaccde52577a5bc85 /lib
parentef7d54646d44d6c8d7a12dda587564d84cd1aa32 (diff)
downloadgnome-bluetooth-4b2c4fd253bc49d2aad7a44013a9ca3b8131e81e.tar.gz
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
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am57
-rw-r--r--lib/bluetooth-chooser.c2
-rw-r--r--lib/bluetooth-pairing-dialog.c307
-rw-r--r--lib/bluetooth-pairing-dialog.h83
-rw-r--r--lib/bluetooth-settings-row.c300
-rw-r--r--lib/bluetooth-settings-row.h63
-rw-r--r--lib/bluetooth-settings-widget.c1692
-rw-r--r--lib/bluetooth-settings-widget.h63
-rw-r--r--lib/gnome-bluetooth.symbols7
-rw-r--r--lib/pin.c174
-rw-r--r--lib/pin.h33
-rw-r--r--lib/settings.gresource.xml6
-rw-r--r--lib/settings.ui452
-rw-r--r--lib/test-pairing-dialog.c65
-rw-r--r--lib/test-settings.c29
15 files changed, 3321 insertions, 12 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 68983ae5..7dad198e 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 465d5931..1cac781f 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 00000000..7d3e8b92
--- /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 00000000..a30f8b76
--- /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 00000000..76fd2c4e
--- /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 00000000..ed0db01d
--- /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 00000000..9f83f036
--- /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 00000000..6e943bde
--- /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 4c23aa34..70300705 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/lib/pin.c b/lib/pin.c
new file mode 100644
index 00000000..ddced642
--- /dev/null
+++ b/lib/pin.c
@@ -0,0 +1,174 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2009 Bastien Nocera <hadess@hadess.net>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <bluetooth-enums.h>
+#include <bluetooth-utils.h>
+
+#include "pin.h"
+
+#define PIN_CODE_DB "pin-code-database.xml"
+#define MAX_DIGITS_PIN_PREFIX "max:"
+
+#define TYPE_IS(x, r) { \
+ if (g_str_equal(type, x)) return r; \
+}
+
+static guint string_to_type(const char *type)
+{
+ TYPE_IS ("any", BLUETOOTH_TYPE_ANY);
+ TYPE_IS ("mouse", BLUETOOTH_TYPE_MOUSE);
+ TYPE_IS ("tablet", BLUETOOTH_TYPE_TABLET);
+ TYPE_IS ("keyboard", BLUETOOTH_TYPE_KEYBOARD);
+ TYPE_IS ("headset", BLUETOOTH_TYPE_HEADSET);
+ TYPE_IS ("headphones", BLUETOOTH_TYPE_HEADPHONES);
+ TYPE_IS ("audio", BLUETOOTH_TYPE_OTHER_AUDIO);
+ TYPE_IS ("printer", BLUETOOTH_TYPE_PRINTER);
+ TYPE_IS ("network", BLUETOOTH_TYPE_NETWORK);
+
+ g_warning ("unhandled type '%s'", type);
+ return BLUETOOTH_TYPE_ANY;
+}
+
+typedef struct {
+ char *ret_pin;
+ guint max_digits;
+ guint type;
+ const char *address;
+ const char *name;
+ gboolean confirm;
+} PinParseData;
+
+static void
+pin_db_parse_start_tag (GMarkupParseContext *ctx,
+ const gchar *element_name,
+ const gchar **attr_names,
+ const gchar **attr_values,
+ gpointer data,
+ GError **error)
+{
+ PinParseData *pdata = (PinParseData *) data;
+
+ if (pdata->ret_pin != NULL || pdata->max_digits != 0)
+ return;
+ if (g_str_equal (element_name, "device") == FALSE)
+ return;
+
+ while (*attr_names && *attr_values) {
+ if (g_str_equal (*attr_names, "type")) {
+ guint type;
+
+ type = string_to_type (*attr_values);
+ if (type != BLUETOOTH_TYPE_ANY && type != pdata->type)
+ return;
+ } else if (g_str_equal (*attr_names, "oui")) {
+ if (g_str_has_prefix (pdata->address, *attr_values) == FALSE)
+ return;
+ } else if (g_str_equal (*attr_names, "name")) {
+ if (*attr_values == NULL || pdata->name == NULL)
+ return;
+ if (strstr (pdata->name, *attr_values) == NULL)
+ return;
+ pdata->confirm = FALSE;
+ } else if (g_str_equal (*attr_names, "pin")) {
+ if (g_str_has_prefix (*attr_values, MAX_DIGITS_PIN_PREFIX) != FALSE) {
+ pdata->max_digits = strtoul (*attr_values + strlen (MAX_DIGITS_PIN_PREFIX), NULL, 0);
+ g_assert (pdata->max_digits > 0 && pdata->max_digits < PIN_NUM_DIGITS);
+ } else {
+ pdata->ret_pin = g_strdup (*attr_values);
+ }
+ return;
+ }
+
+ ++attr_names;
+ ++attr_values;
+ }
+}
+
+char *
+get_pincode_for_device (guint type,
+ const char *address,
+ const char *name,
+ guint *max_digits,
+ gboolean *confirm)
+{
+ GMarkupParseContext *ctx;
+ GMarkupParser parser = { pin_db_parse_start_tag, NULL, NULL, NULL, NULL };
+ PinParseData data;
+ char *buf;
+ gsize buf_len;
+ GError *err = NULL;
+
+ g_return_val_if_fail (address != NULL, NULL);
+
+ g_debug ("Getting pincode for device '%s' (type: %s address: %s)",
+ name ? name : "", address, bluetooth_type_to_string (type));
+
+ /* Load the PIN database and split it in lines */
+ if (!g_file_get_contents(PIN_CODE_DB, &buf, &buf_len, NULL)) {
+ char *filename;
+
+ filename = g_build_filename(PKGDATADIR, PIN_CODE_DB, NULL);
+ if (!g_file_get_contents(filename, &buf, &buf_len, NULL)) {
+ g_warning("Could not load "PIN_CODE_DB);
+ g_free (filename);
+ return NULL;
+ }
+ g_free (filename);
+ }
+
+ data.ret_pin = NULL;
+ data.max_digits = 0;
+ data.type = type;
+ data.address = address;
+ data.name = name;
+ data.confirm = TRUE;
+
+ ctx = g_markup_parse_context_new (&parser, 0, &data, NULL);
+
+ if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err)) {
+ g_warning ("Failed to parse '%s': %s\n", PIN_CODE_DB, err->message);
+ g_error_free (err);
+ }
+
+ g_markup_parse_context_free (ctx);
+ g_free (buf);
+
+ if (max_digits != NULL)
+ *max_digits = data.max_digits;
+ if (confirm != NULL)
+ *confirm = data.confirm;
+
+ g_debug ("Got pin '%s' (max digits: %d) for device '%s' (type: %s address: %s)",
+ data.ret_pin, data.max_digits,
+ name ? name : "", bluetooth_type_to_string (type), address);
+
+ return data.ret_pin;
+}
+
diff --git a/lib/pin.h b/lib/pin.h
new file mode 100644
index 00000000..815969dd
--- /dev/null
+++ b/lib/pin.h
@@ -0,0 +1,33 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2009 Bastien Nocera <hadess@hadess.net>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <glib.h>
+
+#define PIN_NUM_DIGITS 6
+
+char *get_pincode_for_device (guint type,
+ const char *address,
+ const char *name,
+ guint *max_digits,
+ gboolean *confirm);
+
diff --git a/lib/settings.gresource.xml b/lib/settings.gresource.xml
new file mode 100644
index 00000000..58813cf6
--- /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 00000000..3fe2cb2f
--- /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 00000000..1cc567fa
--- /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 00000000..bcf139c9
--- /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;
+}