/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2009 Bastien Nocera * Copyright (C) 2009, Intel Corporation. * * * 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 * * Written by: Joshua Lock * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth-client.h" #include "bluetooth-client-private.h" #include "bluetooth-chooser.h" #include "bluetooth-chooser-private.h" #include "bluetooth-powerswitch.h" #include "bluetooth-plugin-manager.h" #include "bluetooth-filter-widget.h" #include "bluetooth-agent.h" #include "gnome-bluetooth-enum-types.h" #include "pin.h" #include "mux-cell-renderer-text.h" #include "mux-banner.h" #include "koto-cell-renderer-pixbuf.h" #include "moblin-panel.h" G_DEFINE_TYPE (MoblinPanel, moblin_panel, GTK_TYPE_HBOX) #define MOBLIN_PANEL_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), MOBLIN_TYPE_PANEL, MoblinPanelPrivate)) struct _MoblinPanelPrivate { /* Bluetooth objects for interacting with BlueZ */ BluetoothPowerswitch *powerswitch; BluetoothClient *client; BluetoothAgent *agent; GtkTreeModel *chooser_model; /* Page widgets that need to be "globally" accessible */ GtkWidget *power_switch; guint visible_countdown; /* seconds */ GtkWidget *visible_label; GtkWidget *visible_button; GtkWidget *notebook; GtkWidget *label_pin_help; GtkWidget *label_pin; GtkWidget *label_ssp_pin_help; GtkWidget *label_ssp_pin; GtkWidget *summary_frame; GtkWidget *extra_config_label; GtkWidget *extra_config_vbox; GtkWidget *label_failure; GtkWidget *chooser; GtkWidget *display; GtkWidget *send_button; GtkWidget *add_new_button; /* Widgets for use in lazy-loading the pin options dialog from the GtkBuilder UI file */ GtkWidget *matches_button; GtkWidget *does_not_match_button; GtkWidget *spinner; /* Widgets for use in lazy-loading the pin options dialog from the GtkBuilder UI file */ GtkWidget *pin_dialog; GtkWidget *entry_custom; GtkWidget *radio_auto; GtkWidget *radio_0000; GtkWidget *radio_1111; GtkWidget *radio_1234; GtkWidget *radio_custom; /* State tracking variables */ gchar *pincode; gchar *user_pincode; gchar *target_pincode; gchar *target_name; gchar *target_address; gboolean target_ssp; gboolean automatic_pincode; gboolean create_started; gboolean complete; BluetoothType target_type; gboolean connecting; gboolean display_called; guint update_visible_source_id; }; #define CONNECT_TIMEOUT 3.0 #define DISCOVER_TIMEOUT (2 * 60) #define AGENT_PATH "/org/bluez/agent/moblin" typedef struct { char *path; GTimer *timer; MoblinPanel *self; } ConnectData; typedef enum { PAGE_DEVICES, PAGE_ADD, PAGE_SETUP, PAGE_SSP_SETUP, PAGE_CONNECTING, PAGE_SUMMARY, PAGE_FAILURE } MoblinPages; enum { STATUS_CONNECTING, REQUEST_FOCUS, LAST_SIGNAL }; static guint _signals[LAST_SIGNAL] = {0, }; /* Forward declarations of methods */ static void set_current_page (MoblinPanel *self, MoblinPages page); static void create_callback (BluetoothClient *client, const gchar *path, const GError *error, gpointer user_data); static void update_random_pincode (MoblinPanel *self); static void create_selected_device (MoblinPanel *self); static void power_switch_toggled_cb (MxGtkLightSwitch *light_switch, gboolean state, gpointer user_data) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE(user_data); if (state == TRUE) { bluetooth_powerswitch_set_state (priv->powerswitch, POWERSWITCH_STATE_ON); } else { bluetooth_powerswitch_set_state (priv->powerswitch, POWERSWITCH_STATE_OFF); } } static char * totem_time_to_string_text (gint64 msecs) { char *secs, *mins, *hours, *string; int sec, min, hour, _time; _time = (int) (msecs / 1000); sec = _time % 60; _time = _time - sec; min = (_time % (60*60)) / 60; _time = _time - (min * 60); hour = _time / (60*60); hours = g_strdup_printf (ngettext ("%d hour", "%d hours", hour), hour); mins = g_strdup_printf (ngettext ("%d minute", "%d minutes", min), min); secs = g_strdup_printf (ngettext ("%d second", "%d seconds", sec), sec); if (hour > 0) { /* hour:minutes:seconds */ string = g_strdup_printf (_("%s %s %s"), hours, mins, secs); } else if (min > 0) { if (sec) { /* minutes:seconds */ string = g_strdup_printf (_("%s %s"), mins, secs); } else { /* minutes */ string = g_strdup_printf (_("%s"), mins); } } else if (sec > 0) { /* seconds */ string = g_strdup_printf (_("%s"), secs); } else { /* 0 seconds */ string = g_strdup (_("0 seconds")); } g_free (hours); g_free (mins); g_free (secs); return string; } static gboolean update_visible (gpointer data) { MoblinPanel *panel = MOBLIN_PANEL (data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (panel); char *label, *time; if (priv->visible_countdown > 0) { gtk_widget_hide (priv->visible_button); time = totem_time_to_string_text (priv->visible_countdown * 1000); label = g_strdup_printf (_("Your computer is visible on " "Bluetooth for %s."), time); gtk_label_set_text (GTK_LABEL (priv->visible_label), label); g_free (label); g_free (time); priv->visible_countdown -= 5; return TRUE; } else { bluetooth_client_set_discoverable (priv->client, FALSE, 0); gtk_widget_show (priv->visible_button); gtk_label_set_text (GTK_LABEL (priv->visible_label), ""); priv->update_visible_source_id = 0; return FALSE; } } static void make_discoverable (MoblinPanel *panel) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (panel); if (bluetooth_powerswitch_get_state (priv->powerswitch) != POWERSWITCH_STATE_ON) return; bluetooth_client_set_discoverable (priv->client, TRUE, DISCOVER_TIMEOUT); priv->visible_countdown = DISCOVER_TIMEOUT; update_visible (panel); priv->update_visible_source_id = g_timeout_add_seconds (5, update_visible, panel); } static void visible_button_cb (GtkButton *button, gpointer user_data) { make_discoverable (MOBLIN_PANEL (user_data)); } static void enable_send_file (MoblinPanel *self) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); BluetoothChooser *chooser = BLUETOOTH_CHOOSER (priv->display); GValue value = {0, }; gchar *name = NULL; gboolean enabled = FALSE; name = bluetooth_chooser_get_selected_device_name (chooser); if (name != NULL) { guint i; const char **uuids; g_free (name); bluetooth_chooser_get_selected_device_info (chooser, "uuids", &value); uuids = (const char **) g_value_get_boxed (&value); if (uuids != NULL) { for (i = 0; uuids[i] != NULL; i++) if (g_str_equal (uuids[i], "OBEXObjectPush")) { enabled = TRUE; break; } g_value_unset (&value); } } gtk_widget_set_sensitive (priv->send_button, enabled); } static void start_user_share (MoblinPanel *self) { char *argv[] = { LIBEXECDIR "/gnome-user-share", NULL}; GError *error = NULL; if (!g_spawn_async (NULL, argv, NULL, 0, NULL, NULL, NULL, &error)) { g_message ("Cannot start gnome-user-share: %s", error->message); g_error_free (error); } } static void cancel_visible_updates (MoblinPanel *self) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); if (priv->update_visible_source_id != 0) { g_source_remove (priv->update_visible_source_id); gtk_label_set_text (GTK_LABEL (priv->visible_label), ""); priv->update_visible_source_id = 0; } } static void powerswitch_state_changed_cb (BluetoothPowerswitch *powerswitch, PowerswitchState state, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); g_signal_handlers_block_by_func (priv->power_switch, power_switch_toggled_cb, user_data); if (state == POWERSWITCH_STATE_OFF) { cancel_visible_updates (self); mx_gtk_light_switch_set_active (MX_GTK_LIGHT_SWITCH (priv->power_switch), FALSE); gtk_widget_set_sensitive (priv->power_switch, TRUE); gtk_widget_set_sensitive (priv->visible_button, FALSE); gtk_widget_set_sensitive (priv->add_new_button, FALSE); gtk_widget_set_sensitive (priv->send_button, FALSE); } else if (state == POWERSWITCH_STATE_ON) { mx_gtk_light_switch_set_active (MX_GTK_LIGHT_SWITCH (priv->power_switch), TRUE); gtk_widget_set_sensitive (priv->power_switch, TRUE); gtk_widget_show (priv->visible_button); gtk_widget_set_sensitive (priv->visible_button, TRUE); gtk_widget_set_sensitive (priv->add_new_button, TRUE); enable_send_file (self); start_user_share (self); } else if (state == POWERSWITCH_STATE_NO_ADAPTER) { cancel_visible_updates (self); gtk_widget_set_sensitive (priv->power_switch, FALSE); gtk_widget_set_sensitive (priv->visible_button, FALSE); gtk_widget_set_sensitive (priv->add_new_button, FALSE); gtk_widget_set_sensitive (priv->send_button, FALSE); } else { g_assert_not_reached (); } g_signal_handlers_unblock_by_func (priv->power_switch, power_switch_toggled_cb, user_data); } static void selected_device_changed_cb (BluetoothChooser *chooser, const char *address, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); enable_send_file (MOBLIN_PANEL (self)); } static void set_frame_title (GtkFrame *frame, gchar *title) { GtkWidget *frame_title; gchar *label = NULL; label = g_strdup_printf ("%s", title); frame_title = gtk_frame_get_label_widget (frame); gtk_label_set_markup (GTK_LABEL (frame_title), label); g_free (label); gtk_widget_show (frame_title); } static void send_file_button_clicked_cb (GtkButton *button, gpointer user_data) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (user_data); BluetoothChooser *chooser = BLUETOOTH_CHOOSER (priv->display); GPtrArray *a; GError *err = NULL; guint i; char *address, *name; address = bluetooth_chooser_get_selected_device (chooser); name = bluetooth_chooser_get_selected_device_name (chooser); a = g_ptr_array_new (); g_ptr_array_add (a, "bluetooth-sendto"); if (address != NULL) { char *s; s = g_strdup_printf ("--device=%s", address); g_ptr_array_add (a, s); } if (address != NULL && name != NULL) { char *s; s = g_strdup_printf ("--name=%s", name); g_ptr_array_add (a, s); } g_ptr_array_add (a, NULL); g_free (address); g_free (name); if (g_spawn_async(NULL, (char **) a->pdata, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &err) == FALSE) { g_printerr("Couldn't execute command: %s\n", err->message); g_error_free (err); } for (i = 1; a->pdata[i] != NULL; i++) g_free (a->pdata[i]); g_ptr_array_free (a, TRUE); } static gboolean entry_custom_event (GtkWidget *entry, GdkEventKey *event) { if (event->length == 0) return FALSE; if ((event->keyval >= GDK_0 && event->keyval <= GDK_9) || (event->keyval >= GDK_KP_0 && event->keyval <= GDK_KP_9)) return FALSE; return TRUE; } static void entry_custom_changed (GtkWidget *entry, gpointer user_data) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (user_data); priv->user_pincode = g_strdup (gtk_entry_get_text(GTK_ENTRY(entry))); gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->pin_dialog), GTK_RESPONSE_ACCEPT, gtk_entry_get_text_length (GTK_ENTRY (entry)) >= 1); } static void toggle_set_sensitive (GtkWidget *button, gpointer user_data) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (user_data); gboolean active; active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); gtk_widget_set_sensitive(priv->entry_custom, active); /* When selecting another PIN, make sure the "Close" button is sensitive */ if (!active) gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->pin_dialog), GTK_RESPONSE_ACCEPT, TRUE); } static void set_user_pincode (GtkWidget *button, gpointer user_data) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (user_data); GtkEntry *entry; const char *pin; if (! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) return; pin = g_object_get_data (G_OBJECT (button), "pin"); entry = g_object_get_data (G_OBJECT (button), "entry"); if (entry != NULL) { g_free (priv->user_pincode); priv->user_pincode = g_strdup (gtk_entry_get_text(entry)); gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->pin_dialog), GTK_RESPONSE_ACCEPT, gtk_entry_get_text_length (entry) >= 1); } else if (pin != NULL) { g_free (priv->user_pincode); if (*pin == '\0') priv->user_pincode = NULL; else priv->user_pincode = g_strdup (pin); } } static void pin_options_button_clicked_cb (GtkButton *button, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); GtkWidget *radio; gchar *oldpin; GtkBuilder *builder; GError *err = NULL; if (priv->pin_dialog == NULL) { builder = gtk_builder_new (); if (gtk_builder_add_from_file (builder, "pin.ui", NULL) == 0) { if (gtk_builder_add_from_file (builder, PKGDATADIR "/pin.ui", &err) == 0) { g_warning ("Could not load PIN dialog UI form %s: %s", PKGDATADIR "/pin.ui", err->message); g_error_free (err); } } priv->pin_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "pin_dialog")); priv->radio_auto = GTK_WIDGET (gtk_builder_get_object (builder, "radio_auto")); g_signal_connect (priv->radio_auto, "toggled", G_CALLBACK (set_user_pincode), self); priv->radio_0000 = GTK_WIDGET (gtk_builder_get_object (builder, "radio_0000")); g_signal_connect (priv->radio_0000, "toggled", G_CALLBACK (set_user_pincode), self); priv->radio_1111 = GTK_WIDGET (gtk_builder_get_object (builder, "radio_1111")); g_signal_connect (priv->radio_1111, "toggled", G_CALLBACK (set_user_pincode), self); priv->radio_1234 = GTK_WIDGET (gtk_builder_get_object (builder, "radio_1234")); g_signal_connect (priv->radio_1234, "toggled", G_CALLBACK (set_user_pincode), self); priv->radio_custom = GTK_WIDGET (gtk_builder_get_object (builder, "radio_custom")); g_signal_connect (priv->radio_custom, "toggled", G_CALLBACK (set_user_pincode), self); g_signal_connect (priv->radio_custom, "toggled", G_CALLBACK (toggle_set_sensitive), self); priv->entry_custom = GTK_WIDGET (gtk_builder_get_object (builder, "entry_custom")); g_signal_connect (priv->entry_custom, "key-press-event", G_CALLBACK (entry_custom_event), self); g_signal_connect (priv->entry_custom, "changed", G_CALLBACK (entry_custom_changed), self); g_object_set_data (G_OBJECT (priv->radio_auto), "pin", ""); g_object_set_data (G_OBJECT (priv->radio_0000), "pin", "0000"); g_object_set_data (G_OBJECT (priv->radio_1111), "pin", "1111"); g_object_set_data (G_OBJECT (priv->radio_1234), "pin", "1234"); g_object_set_data (G_OBJECT (priv->radio_custom), "entry", priv->entry_custom); } oldpin = g_strdup (priv->user_pincode); gtk_window_present (GTK_WINDOW (priv->pin_dialog)); if (priv->user_pincode == NULL) { radio = priv->radio_auto; } else if (g_str_equal (priv->user_pincode, "0000")) { radio = priv->radio_0000; } else if (g_str_equal (priv->user_pincode, "1111")) { radio = priv->radio_1111; } else if (g_str_equal (priv->user_pincode, "1234")) { radio = priv->radio_1234; } else { radio = priv->radio_custom; gtk_entry_set_text (GTK_ENTRY (priv->entry_custom), priv->user_pincode); } gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); if (gtk_dialog_run (GTK_DIALOG (priv->pin_dialog)) != GTK_RESPONSE_OK) { g_free (priv->user_pincode); priv->user_pincode = oldpin; } else { g_free (oldpin); } gtk_widget_hide (priv->pin_dialog); g_signal_emit (self, _signals[REQUEST_FOCUS], 0); } /* * This helper function forces the currently selected row in the chooser to be * the same row which contains the activated cell. */ static void ensure_selection (BluetoothChooser *chooser, const gchar *path) { GtkTreeView *view; GtkTreePath *tree_path; view = GTK_TREE_VIEW (bluetooth_chooser_get_treeview (chooser)); /* Set selection */ tree_path = gtk_tree_path_new_from_string (path); gtk_tree_view_set_cursor (view, tree_path, NULL, FALSE); gtk_tree_path_free (tree_path); } static void remove_clicked_cb (GtkCellRenderer *cell, const gchar *path, gpointer user_data) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (user_data); BluetoothChooser *chooser = BLUETOOTH_CHOOSER (priv->display); const gchar *address = NULL; GValue value = { 0, }; ensure_selection (chooser, path); /* Get address */ g_assert (bluetooth_chooser_get_selected_device_info (chooser, "address", &value)); address = g_value_get_string (&value); if (bluetooth_chooser_remove_selected_device (chooser) != FALSE && address) { bluetooth_plugin_manager_device_deleted (address); } g_value_unset (&value); } static void mount_finish_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; char *uri; if (g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error) == FALSE) { g_message ("Failed to mount OBEX volume: %s", error->message); g_error_free (error); return; } uri = g_file_get_uri (G_FILE (source_object)); if (gtk_show_uri (NULL, uri, GDK_CURRENT_TIME, &error) == FALSE) { g_message ("Failed to open %s: %s", uri, error->message); g_error_free (error); } g_free (uri); } static void browse_clicked (GtkCellRenderer *renderer, const gchar *path, gpointer user_data) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (user_data); BluetoothChooser *chooser = BLUETOOTH_CHOOSER (priv->display); const gchar *address = NULL; GValue value = { 0, }; ensure_selection (chooser, path); /* Get address */ if (bluetooth_chooser_get_selected_device_info (chooser, "address", &value)) { address = g_value_get_string (&value); } if (address) { char *uri; GFile *file; uri = g_strdup_printf ("obex://[%s]/", address); file = g_file_new_for_uri (uri); g_free (uri); g_file_mount_enclosing_volume (file, G_MOUNT_MOUNT_NONE, NULL, NULL, mount_finish_cb, NULL); g_object_unref (file); } g_value_unset (&value); } static void connect_callback (BluetoothClient *client, gboolean success, gpointer user_data) { ConnectData *data = (ConnectData *)user_data; if (success == FALSE && g_timer_elapsed (data->timer, NULL) < CONNECT_TIMEOUT) { if (bluetooth_client_connect_service (client, data->path, connect_callback, data) != FALSE) return; } if (success == FALSE) g_message ("Failed to connect to device %s", data->path); g_timer_destroy (data->timer); g_free (data->path); g_object_unref (data->self); g_free (data); } static void disconnect_callback (BluetoothClient *client, gboolean success, gpointer user_data) { ConnectData *data = (ConnectData *)user_data; gboolean ret; if (success == FALSE && g_timer_elapsed (data->timer, NULL) < CONNECT_TIMEOUT) { ret = bluetooth_client_disconnect_service (client, data->path, disconnect_callback, data); if (ret != FALSE) return; } if (success == FALSE) g_message ("Failed to disconnect device %s", data->path); g_timer_destroy (data->timer); g_free (data->path); g_object_unref (data->self); g_free (data); } static void set_failure_message (MoblinPanel *self, gchar *device) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); gchar *str; str = g_strdup_printf (_("Pairing with %s failed."), device); gtk_label_set_text (GTK_LABEL (priv->label_failure), str); g_free (str); } static void set_ssp_pin_message (MoblinPanel *self, gchar *msg) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); gtk_label_set_text (GTK_LABEL (priv->label_ssp_pin_help), msg); } static void set_ssp_pin_label (MoblinPanel *self, gchar *pin) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); gchar *str; str = g_strdup_printf (" %s ", pin); gtk_label_set_markup (GTK_LABEL (priv->label_ssp_pin), str); g_free (str); } static void set_pin_message (MoblinPanel *self, gchar *msg) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); gtk_label_set_text (GTK_LABEL (priv->label_pin_help), msg); } static void set_pin_label (MoblinPanel *self, gchar *pin) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); gchar *str; str = g_strdup_printf (" %s ", pin); gtk_label_set_markup (GTK_LABEL (priv->label_pin), str); g_free (str); } static gboolean cancel_callback (DBusGMethodInvocation *context, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); priv->create_started = FALSE; set_current_page (self, PAGE_FAILURE); set_failure_message (self, priv->target_name); dbus_g_method_return(context); return TRUE; } static void connect_device (const gchar *device_path, MoblinPanel *self) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); ConnectData *data; data = g_new0 (ConnectData, 1); data->path = g_strdup (device_path); data->timer = g_timer_new (); data->self = g_object_ref (self); if (bluetooth_client_connect_service (priv->client, device_path, connect_callback, data) == FALSE) { g_timer_destroy (data->timer); g_free (data->path); g_object_unref (data->self); g_free (data); } } static void disconnect_device (const gchar *device_path, MoblinPanel *self) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); ConnectData *data; gboolean ret; data = g_new0 (ConnectData, 1); data->path = g_strdup (device_path); data->timer = g_timer_new (); data->self = g_object_ref (self); ret = bluetooth_client_disconnect_service (priv->client, device_path, disconnect_callback, data); if (ret == FALSE) { g_timer_destroy (data->timer); g_free (data->path); g_object_unref (data->self); g_free (data); } } static void container_remove_all (GtkContainer *container) { GList *list; GList *iter; list = gtk_container_get_children (container); for (iter = list; iter; iter = iter->next) gtk_container_remove (container, GTK_WIDGET (iter->data)); g_list_free (list); } static void set_current_page (MoblinPanel *self, MoblinPages page) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); if (page == PAGE_ADD) { g_object_set (BLUETOOTH_CHOOSER (priv->chooser), "device-category-filter", BLUETOOTH_CATEGORY_NOT_PAIRED_OR_TRUSTED, NULL); bluetooth_chooser_start_discovery (BLUETOOTH_CHOOSER (priv->chooser)); } else { bluetooth_chooser_stop_discovery (BLUETOOTH_CHOOSER (priv->chooser)); } if (page == PAGE_CONNECTING) gtk_spinner_start (GTK_SPINNER (priv->spinner)); else gtk_spinner_stop (GTK_SPINNER (priv->spinner)); if ((page == PAGE_SETUP || page == PAGE_SSP_SETUP || page == PAGE_CONNECTING) && (priv->create_started == FALSE)) { create_selected_device (self); } if (page == PAGE_SETUP) { priv->complete = FALSE; if (priv->automatic_pincode == FALSE && priv->target_ssp == FALSE) { gchar *text; if (priv->target_type == BLUETOOTH_TYPE_KEYBOARD) { text = g_strdup_printf (_("Please enter the following PIN on '%s' and press “Enter” on the keyboard:"), priv->target_name); } else { text = g_strdup_printf (_("Please enter the following PIN on '%s':"), priv->target_name); } set_pin_message (self, text); set_pin_label (self, priv->pincode); } else { g_assert_not_reached (); } } if (page == PAGE_SSP_SETUP && priv->display_called == FALSE) { priv->complete = FALSE; gtk_widget_show (priv->matches_button); gtk_widget_show (priv->does_not_match_button); } else { gtk_widget_hide (priv->matches_button); gtk_widget_hide (priv->does_not_match_button); } if (page == PAGE_SUMMARY) { GValue value = { 0, }; const char **uuids; GList *widgets; char *title; title = g_strdup_printf (_("Successfully set up new device '%s'"), priv->target_name); set_frame_title (GTK_FRAME (priv->summary_frame), title); g_free (title); if (bluetooth_chooser_get_selected_device_info (BLUETOOTH_CHOOSER (priv->chooser), "uuids", &value)) uuids = g_value_get_boxed (&value); else uuids = NULL; widgets = bluetooth_plugin_manager_get_widgets (priv->target_address, uuids); if (widgets) { GList *iter; for (iter = widgets; iter; iter = iter->next) gtk_box_pack_start (GTK_BOX (priv->extra_config_vbox), GTK_WIDGET (iter->data), FALSE, TRUE, 0); g_list_free (widgets); gtk_widget_show (priv->extra_config_label); gtk_widget_show_all (priv->extra_config_vbox); } else { gtk_widget_hide (priv->extra_config_label); gtk_widget_hide (priv->extra_config_vbox); } g_value_unset (&value); } if (page == PAGE_DEVICES) { /* Clean up old state */ update_random_pincode (self); priv->target_ssp = FALSE; priv->target_type = BLUETOOTH_TYPE_ANY; priv->display_called = FALSE; g_free (priv->target_address); priv->target_address = NULL; g_free (priv->target_name); priv->target_name = NULL; container_remove_all (GTK_CONTAINER (priv->extra_config_vbox)); g_object_set (priv->chooser, "device-type-filter", BLUETOOTH_TYPE_ANY, NULL); } gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), page); } static void create_callback (BluetoothClient *client, const gchar *path, const GError *error, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); priv->create_started = FALSE; if (path == NULL) { set_failure_message (self, priv->target_name); set_current_page (self, PAGE_FAILURE); return; } bluetooth_client_set_trusted (client, path, TRUE); connect_device (path, self); set_current_page (self, PAGE_SUMMARY); } static void create_selected_device (MoblinPanel *self) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); const gchar *path = AGENT_PATH; gchar *pin_ret; /* Set the filter on the selector, so we can use it to get more * info later, in page_summary */ g_object_set (priv->chooser, "device-category-filter", BLUETOOTH_CATEGORY_ALL, NULL); pin_ret = get_pincode_for_device (priv->target_type, priv->target_address, priv->target_name, NULL); if (pin_ret != NULL && g_str_equal (pin_ret, "NULL")) path = NULL; g_free (pin_ret); g_object_ref (priv->agent); bluetooth_client_create_device (priv->client, priv->target_address, path, create_callback, self); priv->create_started = TRUE; } static void pair_clicked (GtkCellRenderer *renderer, const gchar *path, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); GValue value = { 0, }; gchar *name; gchar *address = NULL; guint legacy_pairing; BluetoothType type; BluetoothChooser *chooser = BLUETOOTH_CHOOSER (priv->chooser); ensure_selection (BLUETOOTH_CHOOSER (priv->chooser), path); name = bluetooth_chooser_get_selected_device_name (chooser); type = bluetooth_chooser_get_selected_device_type (chooser); if (bluetooth_chooser_get_selected_device_info (chooser, "address", &value) != FALSE) { address = g_value_dup_string (&value); g_value_unset (&value); } if (bluetooth_chooser_get_selected_device_info (chooser, "legacypairing", &value) != FALSE) { legacy_pairing = g_value_get_int (&value); g_value_unset (&value); if (legacy_pairing == -1) legacy_pairing = TRUE; } else { legacy_pairing = TRUE; } g_free (priv->target_address); priv->target_address = address; g_free (priv->target_name); priv->target_name = name; priv->target_type = type; priv->target_ssp = !legacy_pairing; priv->automatic_pincode = FALSE; g_free (priv->pincode); priv->pincode = NULL; if (priv->user_pincode != NULL && *priv->user_pincode != '\0') { priv->pincode = g_strdup (priv->user_pincode); priv->automatic_pincode = TRUE; } else if (address != NULL) { guint max_digits; priv->pincode = get_pincode_for_device (priv->target_type, priv->target_address, priv->target_name, &max_digits); if (priv->pincode == NULL) { /* Truncate the default pincode if the device doesn't like long * PIN codes */ if (max_digits != PIN_NUM_DIGITS && max_digits > 0) priv->pincode = g_strndup(priv->target_pincode, max_digits); else priv->pincode = g_strdup(priv->target_pincode); } else if (priv->target_ssp == FALSE) { priv->automatic_pincode = TRUE; } } if (priv->target_ssp != FALSE || priv->automatic_pincode != FALSE) { set_current_page (self, PAGE_CONNECTING); } else { set_current_page (self, PAGE_SETUP); } } static void connect_clicked (GtkCellRenderer *renderer, const gchar *path, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); BluetoothChooser *chooser = BLUETOOTH_CHOOSER (priv->display); GValue value = { 0, }; DBusGProxy *device; const gchar *device_path = NULL; gboolean connected; ensure_selection (chooser, path); bluetooth_chooser_get_selected_device_info (BLUETOOTH_CHOOSER (priv->display), "proxy", &value); device = g_value_get_object (&value); device_path = dbus_g_proxy_get_path (device); g_value_unset (&value); connected = bluetooth_chooser_get_selected_device_is_connected (chooser); if (connected) { disconnect_device (device_path, self); } else { connect_device (device_path, self); } } static void remove_to_icon (GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gboolean paired, trusted; gtk_tree_model_get (model, iter, BLUETOOTH_COLUMN_PAIRED, &paired, BLUETOOTH_COLUMN_TRUSTED, &trusted, -1); if (paired || trusted) { g_object_set (cell, "icon-name", GTK_STOCK_CLEAR, NULL); } else { g_object_set (cell, "icon-name", NULL, NULL); } } static void pair_to_text (GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gboolean paired, trusted; gtk_tree_model_get (model, iter, BLUETOOTH_COLUMN_PAIRED, &paired, BLUETOOTH_COLUMN_TRUSTED, &trusted, -1); if (!paired && !trusted) { g_object_set (cell, "markup", _("Pair"), NULL); } } static void connect_to_text (GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gboolean paired, trusted, connected; GHashTable *table; gtk_tree_model_get (model, iter, BLUETOOTH_COLUMN_PAIRED, &paired, BLUETOOTH_COLUMN_CONNECTED, &connected, BLUETOOTH_COLUMN_TRUSTED, &trusted, BLUETOOTH_COLUMN_SERVICES, &table, -1); if (table) { if ((paired || trusted) && connected == FALSE) { g_object_set (cell, "text", _("Connect"), NULL); } else { g_object_set (cell, "text", _("Disconnect"), NULL); } g_hash_table_unref (table); } else { g_object_set (cell, "text", _(""), NULL); } } static void browse_to_text (GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gboolean found = FALSE; guint i; char **uuids = NULL; gtk_tree_model_get (model, iter, BLUETOOTH_COLUMN_UUIDS, &uuids, -1); if (uuids != NULL) { for (i = 0; uuids[i] != NULL; i++) if (g_str_equal (uuids[i], "OBEXFileTransfer")) { g_object_set (cell, "text", _("Browse"), NULL); found = TRUE; break; } g_strfreev (uuids); } if (found == FALSE) g_object_set (cell, "text", "", NULL); } static void set_scanning_view (GtkButton *button, MoblinPanel *self) { set_current_page (self, PAGE_ADD); } static void set_device_view (GtkButton *button, MoblinPanel *self) { set_current_page (self, PAGE_DEVICES); } static void determine_connecting (const gchar *key, gpointer value, gpointer user_data) { BluetoothStatus status = GPOINTER_TO_INT (value); BluetoothStatus *other_status = user_data; if (status == BLUETOOTH_STATUS_CONNECTING) (*other_status) = status; } static void have_connecting_device (MoblinPanel *self) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); GHashTable *states = NULL; GtkTreeIter iter; BluetoothStatus status = BLUETOOTH_STATUS_INVALID; gboolean connecting = FALSE; if (gtk_tree_model_get_iter_first (priv->chooser_model, &iter) == TRUE) { gtk_tree_model_get(priv->chooser_model, &iter, BLUETOOTH_COLUMN_SERVICES, &states, -1); if (states) { g_hash_table_foreach (states, (GHFunc) determine_connecting, &status); g_hash_table_unref (states); } if (status == BLUETOOTH_STATUS_CONNECTING) connecting = TRUE; if (connecting != priv->connecting) { priv->connecting = connecting; g_signal_emit (self, _signals[STATUS_CONNECTING], 0, priv->connecting); } } } static void model_changed_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { have_connecting_device (MOBLIN_PANEL (user_data)); } static void row_deleted_cb (GtkTreeModel *model, GtkTreePath *path, gpointer user_data) { have_connecting_device (MOBLIN_PANEL (user_data)); } static void does_not_match_cb (GtkButton *button, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); DBusGMethodInvocation *context; GError *error = NULL; set_current_page (self, PAGE_FAILURE); set_failure_message (self, priv->target_name); context = g_object_get_data (G_OBJECT (button), "context"); g_error_new (AGENT_ERROR, AGENT_ERROR_REJECT, "Agent callback cancelled"); dbus_g_method_return (context, error); g_object_set_data (G_OBJECT (priv->does_not_match_button), "context", NULL); g_object_set_data (G_OBJECT (priv->matches_button), "context", NULL); } static void matches_cb (GtkButton *button, gpointer user_data) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (user_data); DBusGMethodInvocation *context; context = g_object_get_data (G_OBJECT (button), "context"); gtk_widget_set_sensitive (priv->does_not_match_button, FALSE); gtk_widget_set_sensitive (priv->matches_button, FALSE); dbus_g_method_return (context, ""); g_object_set_data (G_OBJECT (priv->does_not_match_button), "context", NULL); g_object_set_data (G_OBJECT (priv->matches_button), "context", NULL); } static gboolean confirm_callback (DBusGMethodInvocation *context, DBusGProxy *device, guint pin, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); gchar *text, *label; priv->target_ssp = TRUE; set_current_page (self, PAGE_SSP_SETUP); text = g_strdup_printf ("%d", pin); label = g_strdup_printf (_("Please confirm that the PIN displayed on '%s' matches this one."), priv->target_name); set_ssp_pin_message (self, label); set_ssp_pin_label (self, text); gtk_widget_set_sensitive (priv->does_not_match_button, TRUE); gtk_widget_set_sensitive (priv->matches_button, TRUE); g_object_set_data (G_OBJECT (priv->does_not_match_button), "context", context); g_object_set_data (G_OBJECT (priv->matches_button), "context", context); return TRUE; } static gboolean display_callback (DBusGMethodInvocation *context, DBusGProxy *device, guint pin, guint entered, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); gchar *text, *code, *done; priv->display_called = TRUE; priv->target_ssp = TRUE; set_current_page (self, PAGE_SSP_SETUP); code = g_strdup_printf ("%d", pin); if (entered > 0) { GtkEntry *entry; gunichar invisible; GString *str; guint i; entry = GTK_ENTRY (gtk_entry_new ()); invisible = gtk_entry_get_invisible_char (entry); g_object_unref (entry); str = g_string_new (NULL); for (i = 0; i < entered; i++) g_string_append_unichar (str, invisible); if (entered < strlen (code)) g_string_append (str, code + entered); done = g_string_free (str, FALSE); } else { done = g_strdup (""); } text = g_strdup_printf("%s%s", done, code + entered); set_ssp_pin_message (self, _("Please enter the following PIN:")); set_ssp_pin_label (self, text); dbus_g_method_return (context); return TRUE; } static GtkWidget * create_failure_page (MoblinPanel *self) { MoblinPanelPrivate *priv; GtkWidget *page; GtkWidget *page_title; GtkWidget *vbox, *hbox; GtkWidget *back_button; priv = MOBLIN_PANEL_GET_PRIVATE (self); page = mx_gtk_frame_new (); page_title = gtk_label_new (""); gtk_frame_set_label_widget (GTK_FRAME (page), page_title); set_frame_title (GTK_FRAME (page), _("Device setup failed")); gtk_widget_show (page); vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (page), vbox); priv->label_failure = gtk_label_new (""); gtk_misc_set_alignment (GTK_MISC (priv->label_failure), 0.0, 0.5); gtk_widget_show (priv->label_failure); gtk_box_pack_start (GTK_BOX (vbox), priv->label_failure, FALSE, FALSE, 6); hbox = gtk_hbox_new (FALSE, 6); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 6); back_button = gtk_button_new_with_label (_("Back to devices")); gtk_widget_show (back_button); gtk_box_pack_start (GTK_BOX (hbox), back_button, FALSE, FALSE, 6); g_signal_connect (back_button, "clicked", G_CALLBACK (set_device_view), self); return page; } static GtkWidget * create_summary_page (MoblinPanel *self) { MoblinPanelPrivate *priv; GtkWidget *page; GtkWidget *vbox, *hbox; GtkWidget *w; priv = MOBLIN_PANEL_GET_PRIVATE (self); page = mx_gtk_frame_new (); w = gtk_label_new (NULL); gtk_frame_set_label_widget (GTK_FRAME (page), w); gtk_widget_show (page); priv->summary_frame = page; vbox = gtk_vbox_new (FALSE, 12); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (page), vbox); priv->extra_config_label = gtk_label_new (_("Select the additional services you want to use with your device:")); gtk_misc_set_alignment (GTK_MISC (priv->extra_config_label), 0.0, 0.5); gtk_widget_show (priv->extra_config_label); gtk_box_pack_start (GTK_BOX (vbox), priv->extra_config_label, FALSE, FALSE, 6); priv->extra_config_vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (priv->extra_config_vbox); gtk_box_pack_start (GTK_BOX (vbox), priv->extra_config_vbox, TRUE, TRUE, 6); hbox = gtk_hbox_new (FALSE, 6); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 6); w = gtk_button_new_with_label (_("Done")); gtk_widget_show (w); gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 6); g_signal_connect (w, "clicked", G_CALLBACK (set_device_view), self); return page; } static GtkWidget * create_setup_page (MoblinPanel *self) { MoblinPanelPrivate *priv; GtkWidget *page, *page_title; GtkWidget *vbox, *hbox; GtkWidget *back_button; priv = MOBLIN_PANEL_GET_PRIVATE (self); page = mx_gtk_frame_new (); page_title = gtk_label_new (""); gtk_frame_set_label_widget (GTK_FRAME (page), page_title); set_frame_title (GTK_FRAME (page), _("Device setup")); gtk_widget_show (page); vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (page), vbox); priv->label_pin_help = gtk_label_new (""); gtk_widget_show (priv->label_pin_help); gtk_box_pack_start (GTK_BOX (vbox), priv->label_pin_help, FALSE, FALSE, 6); priv->label_pin = gtk_label_new (""); gtk_widget_show (priv->label_pin); gtk_box_pack_start (GTK_BOX (vbox), priv->label_pin, FALSE, FALSE, 6); hbox = gtk_hbox_new (FALSE, 6); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 6); back_button = gtk_button_new_with_label (_("Back to devices")); gtk_widget_show (back_button); gtk_box_pack_start (GTK_BOX (hbox), back_button, FALSE, FALSE, 6); g_signal_connect (back_button, "clicked", G_CALLBACK (set_device_view), self); return page; } static GtkWidget * create_ssp_setup_page (MoblinPanel *self) { MoblinPanelPrivate *priv; GtkWidget *page, *page_title; GtkWidget *vbox, *hbox; GtkWidget *back_button; priv = MOBLIN_PANEL_GET_PRIVATE (self); page = mx_gtk_frame_new (); page_title = gtk_label_new (""); gtk_frame_set_label_widget (GTK_FRAME (page), page_title); set_frame_title (GTK_FRAME (page), _("Device setup")); gtk_widget_show (page); vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (page), vbox); priv->label_ssp_pin_help = gtk_label_new (""); gtk_widget_show (priv->label_ssp_pin_help); gtk_box_pack_start (GTK_BOX (vbox), priv->label_ssp_pin_help, FALSE, FALSE, 6); priv->label_ssp_pin = gtk_label_new (""); gtk_widget_show (priv->label_ssp_pin); gtk_box_pack_start (GTK_BOX (vbox), priv->label_ssp_pin, FALSE, FALSE, 6); hbox = gtk_hbox_new (FALSE, 6); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 6); priv->matches_button = gtk_button_new_with_label (_("Matches")); gtk_widget_show (priv->matches_button); g_signal_connect (priv->matches_button, "clicked", G_CALLBACK (matches_cb), self); gtk_box_pack_start (GTK_BOX (hbox), priv->matches_button, FALSE, FALSE, 6); priv->does_not_match_button = gtk_button_new_with_label (_("Does not match")); gtk_widget_show (priv->does_not_match_button); g_signal_connect (priv->does_not_match_button, "clicked", G_CALLBACK (does_not_match_cb), self); gtk_box_pack_start (GTK_BOX (hbox), priv->does_not_match_button, FALSE, FALSE, 6); hbox = gtk_hbox_new (FALSE, 6); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 6); back_button = gtk_button_new_with_label (_("Back to devices")); gtk_widget_show (back_button); gtk_box_pack_start (GTK_BOX (hbox), back_button, FALSE, FALSE, 6); g_signal_connect (back_button, "clicked", G_CALLBACK (set_device_view), self); return page; } static GtkWidget * create_add_page (MoblinPanel *self) { MoblinPanelPrivate *priv; GtkWidget *page; GtkWidget *vbox, *hbox; GtkWidget *filter; GtkWidget *frame_vbox; GtkWidget *banner; GtkWidget *frame; GtkWidget *alignment; GtkWidget *back_button; GtkWidget *pin_button; GtkWidget *tree_view; GtkTreeViewColumn *type_column; GtkCellRenderer *cell; priv = MOBLIN_PANEL_GET_PRIVATE (self); page = gtk_hbox_new (FALSE, 8); gtk_container_set_border_width (GTK_CONTAINER (page), 8); gtk_widget_show (page); /* Add child widgetry */ vbox = gtk_vbox_new (FALSE, 4); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (page), vbox, TRUE, TRUE, 0); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_widget_show (frame); gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); frame_vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (frame_vbox); gtk_container_add (GTK_CONTAINER (frame), frame_vbox); banner = mux_banner_new (_("Devices")); gtk_widget_show (banner); gtk_box_pack_start (GTK_BOX (frame_vbox), banner, FALSE, FALSE, 0); /* BluetoothChooser adds its own alignment on the left, so balance it out... */ alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0); gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 12, 0, 12); gtk_widget_show (alignment); gtk_box_pack_start (GTK_BOX (frame_vbox), alignment, TRUE, TRUE, 0); /* Device list */ priv->chooser = g_object_new (BLUETOOTH_TYPE_CHOOSER, "has-internal-device-filter", FALSE, "show-device-category", FALSE, "show-searching", TRUE, "device-category-filter", BLUETOOTH_CATEGORY_NOT_PAIRED_OR_TRUSTED, NULL); tree_view = bluetooth_chooser_get_treeview (BLUETOOTH_CHOOSER (priv->chooser)); g_object_set (tree_view, "enable-grid-lines", TRUE, "headers-visible", FALSE, NULL); type_column = bluetooth_chooser_get_type_column (BLUETOOTH_CHOOSER (priv->chooser)); /* Add the pair button */ cell = mux_cell_renderer_text_new (); gtk_tree_view_column_pack_start (type_column, cell, FALSE); gtk_tree_view_column_set_cell_data_func (type_column, cell, pair_to_text, self, NULL); g_signal_connect (cell, "activated", G_CALLBACK (pair_clicked), self); gtk_widget_show (priv->chooser); gtk_container_add (GTK_CONTAINER (alignment), priv->chooser); /* Back button */ back_button = gtk_button_new_with_label (_("Back to devices")); gtk_widget_show (back_button); g_signal_connect (back_button, "clicked", G_CALLBACK (set_device_view), self); gtk_box_pack_start (GTK_BOX (vbox), back_button, FALSE, FALSE, 0); /* Right column */ frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_widget_show (frame); gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0); frame_vbox = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (frame_vbox), 0); gtk_widget_show (frame_vbox); gtk_container_add (GTK_CONTAINER (frame), frame_vbox); banner = mux_banner_new (_("Settings")); gtk_widget_show (banner); gtk_box_pack_start (GTK_BOX (frame_vbox), banner, FALSE, FALSE, 0); vbox = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (frame_vbox), vbox); hbox = gtk_hbox_new (FALSE, 4); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); /* Filter combo */ filter = bluetooth_filter_widget_new (); gtk_widget_show (filter); bluetooth_filter_widget_set_title (BLUETOOTH_FILTER_WIDGET (filter), _("Only show:")); bluetooth_filter_widget_bind_filter (BLUETOOTH_FILTER_WIDGET (filter), BLUETOOTH_CHOOSER (priv->chooser)); gtk_box_pack_start (GTK_BOX (vbox), filter, FALSE, FALSE, 0); /* Button for PIN options file */ pin_button = gtk_button_new_with_label (_("PIN options")); gtk_widget_show (pin_button); g_signal_connect (pin_button, "clicked", G_CALLBACK (pin_options_button_clicked_cb), self); gtk_box_pack_start (GTK_BOX (vbox), pin_button, FALSE, FALSE, 0); return page; } static GtkWidget * create_devices_page (MoblinPanel *self) { MoblinPanelPrivate *priv; GtkWidget *page; GtkWidget *banner; GtkWidget *alignment, *vbox, *vbox2, *hbox; GtkWidget *frame; GtkWidget *power_label; GtkTreeViewColumn *type_column; GtkCellRenderer *cell; GtkWidget *tree_view; GtkSizeGroup *size; priv = MOBLIN_PANEL_GET_PRIVATE (self); page = gtk_hbox_new (FALSE, 8); gtk_container_set_border_width (GTK_CONTAINER (page), 8); gtk_widget_show (page); size = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_widget_show (frame); vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (frame), vbox); gtk_box_pack_start (GTK_BOX (page), frame, TRUE, TRUE, 0); banner = mux_banner_new (_("Devices")); gtk_widget_show (banner); gtk_size_group_add_widget (size, banner); gtk_box_pack_start (GTK_BOX (vbox), banner, FALSE, FALSE, 0); /* Add new button */ priv->add_new_button = gtk_button_new_with_label (_("Add a new device")); gtk_widget_show (priv->add_new_button); g_signal_connect (priv->add_new_button, "clicked", G_CALLBACK (set_scanning_view), self); gtk_box_pack_end (GTK_BOX (banner), priv->add_new_button, FALSE, FALSE, 0); /* BluetoothChooser adds its own alignment on the left, so balance it out... */ alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0); gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 12, 0, 12); gtk_widget_show (alignment); gtk_box_pack_start (GTK_BOX (vbox), alignment, TRUE, TRUE, 0); /* Device list */ priv->display = g_object_new (BLUETOOTH_TYPE_CHOOSER, "has-internal-device-filter", FALSE, "show-device-category", FALSE, "show-searching", FALSE, "device-category-filter", BLUETOOTH_CATEGORY_PAIRED_OR_TRUSTED, NULL); type_column = bluetooth_chooser_get_type_column (BLUETOOTH_CHOOSER (priv->display)); if (!priv->chooser_model) { priv->chooser_model = bluetooth_chooser_get_model (BLUETOOTH_CHOOSER (priv->display)); g_signal_connect (priv->chooser_model, "row-changed", G_CALLBACK (model_changed_cb), self); g_signal_connect (priv->chooser_model, "row-deleted", G_CALLBACK (row_deleted_cb), self); g_signal_connect (priv->chooser_model, "row-inserted", G_CALLBACK (model_changed_cb), self); } tree_view = bluetooth_chooser_get_treeview (BLUETOOTH_CHOOSER (priv->display)); g_object_set (tree_view, "enable-grid-lines", TRUE, "headers-visible", FALSE, NULL); /* Add the browse button */ cell = mux_cell_renderer_text_new (); g_object_set (cell, "underline", PANGO_UNDERLINE_SINGLE, NULL); gtk_tree_view_column_pack_start (type_column, cell, FALSE); gtk_tree_view_column_set_cell_data_func (type_column, cell, browse_to_text, self, NULL); g_signal_connect (cell, "activated", G_CALLBACK (browse_clicked), self); /* Add the connect button */ cell = mux_cell_renderer_text_new (); g_object_set (cell, "underline", PANGO_UNDERLINE_SINGLE, NULL); gtk_tree_view_column_pack_start (type_column, cell, FALSE); gtk_tree_view_column_set_cell_data_func (type_column, cell, connect_to_text, self, NULL); g_signal_connect (cell, "activated", G_CALLBACK (connect_clicked), self); /* Add the remove button */ cell = koto_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_end (type_column, cell, FALSE); gtk_tree_view_column_set_cell_data_func (type_column, cell, remove_to_icon, self, NULL); g_signal_connect (cell, "activated", G_CALLBACK (remove_clicked_cb), self); gtk_widget_show (priv->display); gtk_container_add (GTK_CONTAINER (alignment), priv->display); /* Right column */ frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_widget_show (frame); vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (frame), vbox); gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0); banner = mux_banner_new (_("Settings")); gtk_size_group_add_widget (size, banner); gtk_widget_show (banner); gtk_box_pack_start (GTK_BOX (vbox), banner, FALSE, FALSE, 0); vbox2 = gtk_vbox_new (FALSE, 4); gtk_container_set_border_width (GTK_CONTAINER (vbox2), 12); gtk_widget_show (vbox2); gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0); hbox = gtk_hbox_new (FALSE, 4); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 4); /* Power switch */ /* Translators: This string appears next to a toggle switch which controls * enabling/disabling Bluetooth radio's on the device. Akin to the power * switches in the Network UI of MeeGo */ power_label = gtk_label_new (_("Bluetooth")); gtk_widget_show (power_label); gtk_box_pack_start (GTK_BOX (hbox), power_label, FALSE, FALSE, 4); priv->power_switch = mx_gtk_light_switch_new (); g_signal_connect (priv->powerswitch, "state-changed", G_CALLBACK (powerswitch_state_changed_cb), self); g_signal_connect (priv->power_switch, "switch-flipped", G_CALLBACK (power_switch_toggled_cb), self); gtk_widget_show (priv->power_switch); gtk_box_pack_start (GTK_BOX (hbox), priv->power_switch, FALSE, FALSE, 4); priv->visible_label = gtk_label_new (NULL); gtk_widget_set_size_request (priv->visible_label, 200, -1); gtk_misc_set_alignment (GTK_MISC (priv->visible_label), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (priv->visible_label), TRUE); gtk_widget_show (priv->visible_label); gtk_box_pack_start (GTK_BOX (vbox2), priv->visible_label, FALSE, FALSE, 4); priv->visible_button = gtk_button_new_with_label (_("Make visible on Bluetooth")); g_signal_connect (priv->visible_button, "clicked", G_CALLBACK (visible_button_cb), self); gtk_widget_show (priv->visible_button); gtk_box_pack_start (GTK_BOX (vbox2), priv->visible_button, FALSE, FALSE, 4); /* Button for Send file */ priv->send_button = gtk_button_new_with_label (_("Send file from your computer")); gtk_widget_show (priv->send_button); g_signal_connect (priv->send_button, "clicked", G_CALLBACK (send_file_button_clicked_cb), self); g_signal_connect (priv->display, "selected-device-changed", G_CALLBACK (selected_device_changed_cb), self); gtk_box_pack_start (GTK_BOX (vbox2), priv->send_button, FALSE, FALSE, 4); powerswitch_state_changed_cb (priv->powerswitch, bluetooth_powerswitch_get_state (priv->powerswitch), self); return page; } static GtkWidget * create_connecting_page (MoblinPanel *self) { MoblinPanelPrivate *priv; GtkWidget *page, *page_title; GtkWidget *vbox, *hbox; GtkWidget *back_button; priv = MOBLIN_PANEL_GET_PRIVATE (self); page = mx_gtk_frame_new (); page_title = gtk_label_new (""); gtk_frame_set_label_widget (GTK_FRAME (page), page_title); //set_frame_title (GTK_FRAME (page), _("Connecting")); gtk_widget_show (page); vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (vbox); gtk_container_add (GTK_CONTAINER (page), vbox); priv->spinner = gtk_spinner_new (); gtk_widget_set_size_request (priv->spinner, 150, 150); gtk_widget_show (priv->spinner); gtk_box_pack_start (GTK_BOX (vbox), priv->spinner, FALSE, FALSE, 6); hbox = gtk_hbox_new (FALSE, 6); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 6); back_button = gtk_button_new_with_label (_("Back to devices")); gtk_widget_show (back_button); gtk_box_pack_start (GTK_BOX (hbox), back_button, FALSE, FALSE, 6); g_signal_connect (back_button, "clicked", G_CALLBACK (set_device_view), self); return page; } static gboolean pincode_callback (DBusGMethodInvocation *context, DBusGProxy *device, gpointer user_data) { MoblinPanel *self = MOBLIN_PANEL (user_data); MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); priv->target_ssp = FALSE; /* Only show the pincode page if the pincode isn't automatic */ if (priv->automatic_pincode == FALSE) set_current_page (self, PAGE_SETUP); dbus_g_method_return (context, priv->pincode); return TRUE; } static void update_random_pincode (MoblinPanel *self) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (self); priv->target_pincode = g_strdup_printf ("%d", g_random_int_range (pow (10, PIN_NUM_DIGITS - 1), pow (10, PIN_NUM_DIGITS) - 1)); priv->automatic_pincode = FALSE; } static void moblin_panel_init (MoblinPanel *self) { MoblinPanelPrivate *priv; GtkWidget *devices_page, *add_page, *setup_page, *ssp_setup_page, *failure_page, *connecting_page; GtkWidget *page; priv = MOBLIN_PANEL_GET_PRIVATE (self); priv->pin_dialog = NULL; priv->target_address = NULL; priv->target_name = NULL; priv->target_pincode = NULL; priv->target_type = BLUETOOTH_TYPE_ANY; priv->target_ssp = FALSE; priv->create_started = FALSE; priv->display_called = FALSE; priv->user_pincode = NULL; priv->automatic_pincode = FALSE; priv->pincode = NULL; priv->connecting = FALSE; priv->update_visible_source_id = 0; update_random_pincode (self); priv->client = bluetooth_client_new (); priv->agent = bluetooth_agent_new (); bluetooth_agent_set_pincode_func (priv->agent, pincode_callback, self); bluetooth_agent_set_display_func (priv->agent, display_callback, self); bluetooth_agent_set_cancel_func (priv->agent, cancel_callback, self); bluetooth_agent_set_confirm_func (priv->agent, confirm_callback, self); bluetooth_agent_setup (priv->agent, AGENT_PATH); priv->powerswitch = bluetooth_powerswitch_new (); bluetooth_plugin_manager_init (); gtk_box_set_homogeneous (GTK_BOX (self), FALSE); gtk_box_set_spacing (GTK_BOX (self), 0); priv->notebook = gtk_notebook_new (); gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), FALSE); gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), FALSE); devices_page = create_devices_page (self); gtk_widget_show (devices_page); gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), devices_page, NULL); add_page = create_add_page (self); gtk_widget_show (add_page); gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), add_page, NULL); setup_page = create_setup_page (self); gtk_widget_show (setup_page); gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), setup_page, NULL); ssp_setup_page = create_ssp_setup_page (self); gtk_widget_show (ssp_setup_page); gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), ssp_setup_page, NULL); connecting_page = create_connecting_page (self); gtk_widget_show (connecting_page); gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), connecting_page, NULL); page = create_summary_page (self); gtk_widget_show (page); gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), page, NULL); failure_page = create_failure_page (self); gtk_widget_show (failure_page); gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), failure_page, NULL); set_current_page (self, PAGE_DEVICES); gtk_widget_show (priv->notebook); gtk_box_pack_start (GTK_BOX (self), priv->notebook, TRUE, TRUE, 0); } static void moblin_panel_dispose (GObject *object) { MoblinPanelPrivate *priv = MOBLIN_PANEL_GET_PRIVATE (object); bluetooth_plugin_manager_cleanup (); if (priv->powerswitch != NULL) { g_object_unref (priv->powerswitch); priv->powerswitch = NULL; } if (priv->agent != NULL) { g_object_unref (priv->agent); priv->agent = NULL; } if (priv->client != NULL) { g_object_unref (priv->client); priv->client = NULL; } G_OBJECT_CLASS (moblin_panel_parent_class)->dispose (object); } static void moblin_panel_class_init (MoblinPanelClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (MoblinPanelPrivate)); obj_class->dispose = moblin_panel_dispose; _signals[STATUS_CONNECTING] = g_signal_new ("status-connecting", MOBLIN_TYPE_PANEL, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MoblinPanelClass, status_connecting), NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); _signals[REQUEST_FOCUS] = g_signal_new ("request-focus", MOBLIN_TYPE_PANEL, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } /** * moblin_panel_new: * * Return value: A #MoblinPanel widget **/ GtkWidget * moblin_panel_new (void) { return g_object_new (MOBLIN_TYPE_PANEL, NULL); } /** * * moblin_panel_hidden: * * @self: A #MoblinPanel widget **/ void moblin_panel_hidden (MoblinPanel *self) { set_current_page (self, PAGE_DEVICES); } void moblin_panel_shown (MoblinPanel *self) { set_current_page (self, PAGE_DEVICES); make_discoverable (self); }