/* * * Copyright (C) 2013 Bastien Nocera * * 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 #endif #include #include #include #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-settings-obexpush.h" #include "bluetooth-pairing-dialog.h" #include "pin.h" #define BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE(obj) \ (bluetooth_settings_widget_get_instance_private (obj)) typedef struct _BluetoothSettingsWidgetPrivate BluetoothSettingsWidgetPrivate; struct _BluetoothSettingsWidgetPrivate { GtkBuilder *builder; GtkWidget *child_box; BluetoothClient *client; GtkTreeModel *model; gboolean debug; GCancellable *cancellable; /* Pairing */ BluetoothAgent *agent; GtkWidget *pairing_dialog; GHashTable *pairing_devices; /* key=object-path, value=boolean */ /* Properties */ GtkWidget *properties_dialog; char *selected_bdaddr; char *selected_name; char *selected_object_path; /* Device section */ GtkWidget *device_list; GtkAdjustment *focus_adjustment; GtkSizeGroup *row_sizegroup; GtkWidget *device_stack; GtkWidget *device_spinner; GHashTable *connecting_devices; /* key=bdaddr, value=boolean */ /* Hack to work-around: * http://thread.gmane.org/gmane.linux.bluez.kernel/41471 */ GHashTable *devices_type; /* key=bdaddr, value=guint32 */ /* Sharing section */ GtkWidget *visible_label; gboolean has_console; GDBusProxy *session_proxy; }; G_DEFINE_TYPE_WITH_PRIVATE(BluetoothSettingsWidget, bluetooth_settings_widget, GTK_TYPE_BOX) enum { PANEL_CHANGED, ADAPTER_STATUS_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" #define GNOME_SESSION_DBUS_NAME "org.gnome.SessionManager" #define GNOME_SESSION_DBUS_OBJECT "/org/gnome/SessionManager" #define GNOME_SESSION_DBUS_INTERFACE "org.gnome.SessionManager" #define FILLER_PAGE "filler-page" #define DEVICES_PAGE "devices-page" 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_stop (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")); /* Ensure the switch position is in the correct place. */ gtk_switch_set_active (button, gtk_switch_get_state (button)); if (success == FALSE) { g_debug ("Connection failed to %s: %s", data->bdaddr, error->message); } set_connecting_page (self, CONNECTING_NOTEBOOK_PAGE_SWITCH); } g_clear_error (&error); 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 add_device_type (BluetoothSettingsWidget *self, const char *bdaddr, BluetoothType type) { BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self); BluetoothType t; t = GPOINTER_TO_UINT (g_hash_table_lookup (priv->devices_type, bdaddr)); if (t == 0 || t == BLUETOOTH_TYPE_ANY) { g_hash_table_insert (priv->devices_type, g_strdup (bdaddr), GUINT_TO_POINTER (type)); g_debug ("Saving device type %s for %s", bluetooth_type_to_string (type), bdaddr); } } 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 (BluetoothSettingsWidget *self, GDBusProxy *device, char **name, char **ret_bdaddr, BluetoothType *type) { BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self); GVariant *value; char *bdaddr; g_return_val_if_fail (name != NULL, FALSE); value = g_dbus_proxy_get_cached_property (device, "Name"); if (value == NULL) { /* What?! */ return FALSE; } *name = g_variant_dup_string (value, NULL); g_variant_unref (value); value = g_dbus_proxy_get_cached_property (device, "Address"); bdaddr = g_variant_dup_string (value, NULL); g_variant_unref (value); if (ret_bdaddr) *ret_bdaddr = g_strdup (bdaddr); if (type) { value = g_dbus_proxy_get_cached_property (device, "Class"); if (value != NULL) { *type = bluetooth_class_to_type (g_variant_get_uint32 (value)); g_variant_unref (value); } else { *type = GPOINTER_TO_UINT (g_hash_table_lookup (priv->devices_type, bdaddr)); if (*type == 0) *type = BLUETOOTH_TYPE_ANY; } } g_free (bdaddr); 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); g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy); } 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; 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)); if (bluetooth_pairing_dialog_get_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog)) == BLUETOOTH_PAIRING_MODE_PIN_QUERY) { g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy); return; } 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_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy); return; } 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 confirm_remote_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) { GDBusProxy *device; const char *pin; pin = g_object_get_data (G_OBJECT (invocation), "pin"); device = g_object_get_data (G_OBJECT (invocation), "device"); bluetooth_client_set_trusted (priv->client, g_dbus_proxy_get_object_path (device), TRUE); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", pin)); } else { g_dbus_method_invocation_return_dbus_error (invocation, "org.bluez.Error.Rejected", "Pairing refused from settings panel"); } g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy); } static void pincode_callback (GDBusMethodInvocation *invocation, GDBusProxy *device, gpointer user_data) { BluetoothSettingsWidget *self = 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; gboolean remote_initiated; g_debug ("pincode_callback (%s)", g_dbus_proxy_get_object_path (device)); if (!get_properties_for_device (self, 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; } remote_initiated = !GPOINTER_TO_UINT (g_hash_table_lookup (priv->pairing_devices, g_dbus_proxy_get_object_path (device))); 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; display_pin = g_strdup (default_pin); } setup_pairing_dialog (BLUETOOTH_SETTINGS_WIDGET (user_data)); 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) { g_object_set_data (G_OBJECT (priv->pairing_dialog), "invocation", invocation); if (remote_initiated) { bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog), BLUETOOTH_PAIRING_MODE_PIN_QUERY, default_pin, name); } else { 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 if (!remote_initiated) { bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog), mode, display_pin, name); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", default_pin)); g_signal_connect (G_OBJECT (priv->pairing_dialog), "response", G_CALLBACK (display_cb), user_data); } else { bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog), BLUETOOTH_PAIRING_MODE_YES_NO, default_pin, name); g_object_set_data_full (G_OBJECT (invocation), "pin", g_strdup (default_pin), g_free); g_object_set_data_full (G_OBJECT (invocation), "device", g_object_ref (device), g_object_unref); g_object_set_data (G_OBJECT (priv->pairing_dialog), "invocation", invocation); g_signal_connect (G_OBJECT (priv->pairing_dialog), "response", G_CALLBACK (confirm_remote_pin_cb), user_data); } g_free (name); g_free (bdaddr); g_free (default_pin); g_free (display_pin); gtk_widget_show (priv->pairing_dialog); } static void display_callback (GDBusMethodInvocation *invocation, GDBusProxy *device, guint pin, guint entered, gpointer user_data) { BluetoothSettingsWidget *self = 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 (self, 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); gtk_widget_show (priv->pairing_dialog); } static void display_pincode_callback (GDBusMethodInvocation *invocation, GDBusProxy *device, const char *pincode, gpointer user_data) { BluetoothSettingsWidget *self = user_data; BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data); BluetoothType type; char *display_pin = NULL; char *name, *bdaddr; char *db_pin; g_debug ("display_pincode_callback (%s, %s)", g_dbus_proxy_get_object_path (device), pincode); if (!get_properties_for_device (self, 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; } /* Verify PIN code validity */ db_pin = get_pincode_for_device (type, bdaddr, name, NULL, NULL); if (g_strcmp0 (db_pin, "KEYBOARD") == 0) { /* Should work, follow through */ } else if (g_strcmp0 (db_pin, "ICADE") == 0) { char *msg; msg = g_strdup_printf ("Generated pincode for %s when it shouldn't have", name); g_dbus_method_invocation_return_dbus_error (invocation, "org.bluez.Error.Rejected", msg); g_free (msg); goto bail; } else if (g_strcmp0 (db_pin, "0000") == 0) { g_debug ("Ignoring generated keyboard PIN '%s', should get 0000 soon", pincode); g_dbus_method_invocation_return_value (invocation, NULL); goto bail; } else if (g_strcmp0 (db_pin, "NULL") == 0) { char *msg; msg = g_strdup_printf ("Attempting pairing for %s that doesn't support pairing", name); g_dbus_method_invocation_return_dbus_error (invocation, "org.bluez.Error.Rejected", msg); g_free (msg); goto bail; } setup_pairing_dialog (BLUETOOTH_SETTINGS_WIDGET (user_data)); display_pin = g_strdup_printf ("%s⏎", pincode); bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog), BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD, display_pin, name); gtk_widget_show (priv->pairing_dialog); g_dbus_method_invocation_return_value (invocation, NULL); bail: g_free (db_pin); g_free (display_pin); g_free (bdaddr); g_free (name); } static void passkey_callback (GDBusMethodInvocation *invocation, GDBusProxy *device, gpointer data) { g_warning ("RequestPasskey(): not implemented"); g_dbus_method_invocation_return_dbus_error (invocation, "org.bluez.Error.Rejected", "RequestPasskey not implemented"); } 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) { BluetoothSettingsWidget *self = 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 (self, 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) { BluetoothSettingsWidget *self = 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 (self, 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_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) { GDBusProxy *device; device = g_object_get_data (G_OBJECT (invocation), "device"); bluetooth_client_set_trusted (priv->client, g_dbus_proxy_get_object_path (device), TRUE); g_dbus_method_invocation_return_value (invocation, NULL); } else { char *msg; msg = g_strdup_printf ("Rejecting service auth (HID): not paired or trusted"); g_dbus_method_invocation_return_dbus_error (invocation, "org.bluez.Error.Rejected", msg); g_free (msg); } g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy); } static void authorize_service_callback (GDBusMethodInvocation *invocation, GDBusProxy *device, const char *uuid, gpointer user_data) { BluetoothSettingsWidget *self = user_data; BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data); char *msg; GVariant *value; gboolean paired, trusted; g_debug ("authorize_service_callback (%s, %s)", g_dbus_proxy_get_object_path (device), uuid); value = g_dbus_proxy_get_cached_property (device, "Paired"); paired = g_variant_get_boolean (value); g_variant_unref (value); value = g_dbus_proxy_get_cached_property (device, "Trusted"); trusted = g_variant_get_boolean (value); g_variant_unref (value); /* Device was paired, initiated from the remote device, * so we didn't get the opportunity to set the trusted bit */ if (paired && !trusted) { bluetooth_client_set_trusted (priv->client, g_dbus_proxy_get_object_path (device), TRUE); g_dbus_method_invocation_return_value (invocation, NULL); return; } if (g_strcmp0 (bluetooth_uuid_to_string (uuid), "HumanInterfaceDeviceService") != 0) { msg = g_strdup_printf ("Rejecting service auth (%s) for %s: not HID", uuid, g_dbus_proxy_get_object_path (device)); g_dbus_method_invocation_return_dbus_error (invocation, "org.bluez.Error.Rejected", msg); g_free (msg); return; } /* We shouldn't get asked, but shizzle happens */ if (paired || trusted) { g_dbus_method_invocation_return_value (invocation, NULL); } else { char *name; setup_pairing_dialog (BLUETOOTH_SETTINGS_WIDGET (user_data)); get_properties_for_device (self, device, &name, NULL, NULL); bluetooth_pairing_dialog_set_mode (BLUETOOTH_PAIRING_DIALOG (priv->pairing_dialog), BLUETOOTH_PAIRING_MODE_CONFIRM_AUTH, NULL, name); g_signal_connect (G_OBJECT (priv->pairing_dialog), "response", G_CALLBACK (authorize_service_cb), user_data); g_object_set_data_full (G_OBJECT (invocation), "device", g_object_ref (device), g_object_unref); g_object_set_data (G_OBJECT (priv->pairing_dialog), "invocation", invocation); gtk_widget_show (priv->pairing_dialog); g_free (name); } } 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 (data->self, 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); /* Create failed */ if (ret == FALSE) { //char *text; char *dbus_error; 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); dbus_error = g_dbus_error_get_remote_error (error); if (g_strcmp0 (dbus_error, "org.bluez.Error.AuthenticationCanceled") != 0) { //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_free (dbus_error); g_error_free (error); g_free (path); return; } priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (user_data); g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy); g_hash_table_remove (priv->pairing_devices, path); 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); static void device_name_appeared (GObject *gobject, GParamSpec *pspec, gpointer user_data) { char *name; g_object_get (G_OBJECT (gobject), "name", &name, NULL); if (!name) return; g_debug ("Pairing device name is now '%s'", name); start_pairing (user_data, GTK_LIST_BOX_ROW (gobject)); g_free (name); g_signal_handlers_disconnect_by_func (gobject, device_name_appeared, user_data); } 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; const char *pincode; 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 (name == NULL) { g_debug ("No name yet, will start pairing later"); g_signal_connect (G_OBJECT (row), "notify::name", G_CALLBACK (device_name_appeared), self); g_object_unref (proxy); g_free (bdaddr); g_free (name); return; } g_debug ("Starting pairing for '%s'", name); /* Legacy pairing might not have been detected yet, * so don't check for it */ 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); g_hash_table_insert (priv->pairing_devices, g_strdup (g_dbus_proxy_get_object_path (proxy)), GINT_TO_POINTER (1)); 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 gboolean switch_connected_state_set (GtkSwitch *button, gboolean state, BluetoothSettingsWidget *self) { ConnectData *data; BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self); if (gtk_switch_get_state (button) == state) return TRUE; if (is_connecting (self, priv->selected_bdaddr)) return TRUE; 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); return TRUE; } 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_window_set_title (GTK_WINDOW (priv->properties_dialog), 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")); gtk_switch_set_state (button, connected); if (is_connecting (self, bdaddr)) { set_connecting_page (self, CONNECTING_NOTEBOOK_PAGE_SPINNER); } else { set_connecting_page (self, CONNECTING_NOTEBOOK_PAGE_SWITCH); } /* 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; g_object_get (G_OBJECT (priv->client), "default-adapter-name", &name, NULL); if (name != NULL) { char *label, *path, *uri; path = lookup_download_dir (); uri = g_filename_to_uri (path, NULL, NULL); g_free (path); /* translators: first %s is the name of the computer, for example: * Visible as “Bastien Nocera’s Computer” followed by the * location of the Downloads folder.*/ label = g_strdup_printf (_("Visible as “%s” and available for Bluetooth file transfers. Transferred files are placed in the Downloads folder."), name, uri); g_free (uri); g_free (name); gtk_label_set_markup (GTK_LABEL (priv->visible_label), label); g_free (label); } gtk_widget_set_visible (priv->visible_label, name != NULL); } 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); GDBusProxy *adapter_proxy; GError *error = NULL; GVariant *ret; g_debug ("About to call RemoveDevice for %s", priv->selected_object_path); adapter_proxy = _bluetooth_client_get_default_adapter (priv->client); if (adapter_proxy == NULL) { g_warning ("Failed to get a GDBusProxy for the default adapter"); 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_object_get (priv->client, "default-adapter", &default_adapter, NULL); g_debug ("Default adapter changed to: %s", default_adapter ? default_adapter : "(none)"); 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); g_signal_emit (G_OBJECT (self), signals[ADAPTER_STATUS_CHANGED], 0); } 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; } if (name_a == NULL) { ret = 1; goto out; } if (name_b == NULL) { 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); GList *children, *item; children = gtk_container_get_children (GTK_CONTAINER (priv->device_list)); if (direction == GTK_DIR_DOWN) { item = children; } else { item = g_list_last (children); } gtk_widget_child_focus (item->data, direction); g_list_free (children); return TRUE; } 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 (row), "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 { 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, *label; gchar *s; vbox = WID ("vbox_bluetooth"); box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_set_margin_top (box, 6); gtk_widget_set_margin_bottom (box, 24); gtk_box_pack_start (GTK_BOX (vbox), box, TRUE, TRUE, 0); priv->child_box = box; hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, TRUE, 0); s = g_markup_printf_escaped ("%s", _("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_end (widget, 6); gtk_widget_set_margin_bottom (widget, 12); gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, TRUE, 0); /* Discoverable spinner */ 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, 12); gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, TRUE, 0); /* Discoverable label placeholder, the real name is set in update_visibility(). * If you ever see this string during normal use, please file a bug. */ priv->visible_label = WID ("explanation-label"); gtk_label_set_use_markup (GTK_LABEL (priv->visible_label), TRUE); update_visibility (self); priv->device_list = widget = gtk_list_box_new (); 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_stack = gtk_stack_new (); gtk_stack_set_homogeneous (GTK_STACK (priv->device_stack), FALSE); label = gtk_label_new (_("Searching for devices…")); gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label"); gtk_stack_add_named (GTK_STACK (priv->device_stack), label, FILLER_PAGE); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (frame), widget); gtk_stack_add_named (GTK_STACK (priv->device_stack), frame, DEVICES_PAGE); gtk_box_pack_start (GTK_BOX (box), priv->device_stack, TRUE, TRUE, 0); gtk_widget_show_all (box); } 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) { BluetoothSettingsWidget *self = 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)); add_device_type (self, bdaddr, type); 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_stack_set_transition_type (GTK_STACK (priv->device_stack), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN); gtk_container_child_set (GTK_CONTAINER (WID ("vbox_bluetooth")), priv->child_box, "expand", FALSE, NULL); gtk_stack_set_visible_child_name (GTK_STACK (priv->device_stack), DEVICES_PAGE); } static void row_changed_cb (GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { BluetoothSettingsWidget *self = 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); add_device_type (self, bdaddr, type); g_object_set (G_OBJECT (l->data), "paired", paired, "trusted", trusted, "type", type, "connected", connected, "name", name, "legacy-pairing", legacy_pairing, NULL); /* Update the properties if necessary */ if (g_strcmp0 (priv->selected_object_path, object_path) == 0) update_properties (user_data, proxy); 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; gboolean found = FALSE; 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)); found = TRUE; break; } } if (found) { if (gtk_container_get_children (GTK_CONTAINER (priv->device_list)) == NULL) { gtk_stack_set_transition_type (GTK_STACK (priv->device_stack), GTK_STACK_TRANSITION_TYPE_NONE); gtk_container_child_set (GTK_CONTAINER (WID ("vbox_bluetooth")), priv->child_box, "expand", TRUE, NULL); gtk_stack_set_visible_child_name (GTK_STACK (priv->device_stack), FILLER_PAGE); } } else { 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; GtkStyleContext *context; priv->properties_dialog = g_object_new (GTK_TYPE_DIALOG, "use-header-bar", TRUE, NULL); 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)); gtk_container_add (GTK_CONTAINER (container), WID ("properties_vbox")); g_signal_connect (G_OBJECT (priv->properties_dialog), "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); 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")), "state-set", G_CALLBACK (switch_connected_state_set), 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"); } static void setup_pairing_agent (BluetoothSettingsWidget *self) { BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self); priv->agent = bluetooth_agent_new (AGENT_PATH); if (bluetooth_agent_register (priv->agent) == FALSE) { g_clear_object (&priv->agent); return; } bluetooth_agent_set_pincode_func (priv->agent, pincode_callback, self); bluetooth_agent_set_passkey_func (priv->agent, passkey_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); } static void session_properties_changed_cb (GDBusProxy *session, GVariant *changed, char **invalidated, BluetoothSettingsWidget *self) { BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self); GVariant *v; v = g_variant_lookup_value (changed, "SessionIsActive", G_VARIANT_TYPE_BOOLEAN); if (v) { priv->has_console = g_variant_get_boolean (v); g_debug ("Received session is active change: now %s", priv->has_console ? "active" : "inactive"); g_variant_unref (v); if (priv->has_console) obex_agent_up (); else obex_agent_down (); } } static gboolean is_session_active (BluetoothSettingsWidget *self) { BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self); GVariant *variant; gboolean is_session_active = FALSE; variant = g_dbus_proxy_get_cached_property (priv->session_proxy, "SessionIsActive"); if (variant) { is_session_active = g_variant_get_boolean (variant); g_variant_unref (variant); } return is_session_active; } static void setup_obex (BluetoothSettingsWidget *self) { BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self); GError *error = NULL; priv->session_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL, GNOME_SESSION_DBUS_NAME, GNOME_SESSION_DBUS_OBJECT, GNOME_SESSION_DBUS_INTERFACE, NULL, &error); if (priv->session_proxy == NULL) { g_warning ("Failed to get session proxy: %s", error->message); g_error_free (error); return; } g_signal_connect (priv->session_proxy, "g-properties-changed", G_CALLBACK (session_properties_changed_cb), self); priv->has_console = is_session_active (self); if (priv->has_console) obex_agent_up (); } static void bluetooth_settings_widget_init (BluetoothSettingsWidget *self) { BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (self); GtkWidget *widget; 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 ("scrolledwindow1"); priv->connecting_devices = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL); priv->pairing_devices = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL); priv->devices_type = 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); g_signal_connect (G_OBJECT (priv->client), "notify::default-adapter-powered", 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); gtk_widget_set_hexpand (widget, TRUE); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (widget), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (self), widget); setup_properties_dialog (self); gtk_widget_show_all (GTK_WIDGET (self)); setup_obex (self); } static void bluetooth_settings_widget_finalize (GObject *object) { BluetoothSettingsWidget *widget = BLUETOOTH_SETTINGS_WIDGET(object); BluetoothSettingsWidgetPrivate *priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (widget); g_clear_object (&priv->agent); g_clear_pointer (&priv->properties_dialog, gtk_widget_destroy); g_clear_pointer (&priv->pairing_dialog, gtk_widget_destroy); g_clear_object (&priv->session_proxy); obex_agent_down (); /* See default_adapter_changed () */ /* FIXME: This is blocking */ 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->devices_type, g_hash_table_destroy); g_clear_pointer (&priv->connecting_devices, g_hash_table_destroy); g_clear_pointer (&priv->pairing_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"); bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); 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::panel-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); /** * BluetoothSettingsWidget::adapter-status-changed: * @chooser: a #BluetoothSettingsWidget widget which received the signal * * The #BluetoothChooser::adapter-status-changed signal is launched when the status * of the adapter changes (powered, available, etc.). **/ signals[ADAPTER_STATUS_CHANGED] = g_signal_new ("adapter-status-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); } /** * 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); } /** * bluetooth_settings_widget_get_default_adapter_powered: * @widget: a #BluetoothSettingsWidget widget. * * Return value: Whether the default Bluetooth adapter is powered. **/ gboolean bluetooth_settings_widget_get_default_adapter_powered (BluetoothSettingsWidget *widget) { BluetoothSettingsWidgetPrivate *priv; gboolean ret; g_return_val_if_fail (BLUETOOTH_IS_SETTINGS_WIDGET (widget), FALSE); priv = BLUETOOTH_SETTINGS_WIDGET_GET_PRIVATE (widget); g_object_get (G_OBJECT (priv->client), "default-adapter-powered", &ret, NULL); return ret; }