diff options
author | Bastien Nocera <hadess@hadess.net> | 2013-12-02 19:09:26 +0100 |
---|---|---|
committer | Bastien Nocera <hadess@hadess.net> | 2013-12-06 12:00:30 +0100 |
commit | 4b2c4fd253bc49d2aad7a44013a9ca3b8131e81e (patch) | |
tree | 80d08d8663d135ad6a454bbcaccde52577a5bc85 /lib | |
parent | ef7d54646d44d6c8d7a12dda587564d84cd1aa32 (diff) | |
download | gnome-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.am | 57 | ||||
-rw-r--r-- | lib/bluetooth-chooser.c | 2 | ||||
-rw-r--r-- | lib/bluetooth-pairing-dialog.c | 307 | ||||
-rw-r--r-- | lib/bluetooth-pairing-dialog.h | 83 | ||||
-rw-r--r-- | lib/bluetooth-settings-row.c | 300 | ||||
-rw-r--r-- | lib/bluetooth-settings-row.h | 63 | ||||
-rw-r--r-- | lib/bluetooth-settings-widget.c | 1692 | ||||
-rw-r--r-- | lib/bluetooth-settings-widget.h | 63 | ||||
-rw-r--r-- | lib/gnome-bluetooth.symbols | 7 | ||||
-rw-r--r-- | lib/pin.c | 174 | ||||
-rw-r--r-- | lib/pin.h | 33 | ||||
-rw-r--r-- | lib/settings.gresource.xml | 6 | ||||
-rw-r--r-- | lib/settings.ui | 452 | ||||
-rw-r--r-- | lib/test-pairing-dialog.c | 65 | ||||
-rw-r--r-- | lib/test-settings.c | 29 |
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 & 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; +} |