/* * Copyright 2013 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Charles Kerr */ class Desktop: Profile { private uint idle_rebuild_id = 0; private Settings settings; private Bluetooth bluetooth; private SimpleActionGroup action_group; private SimpleAction root_action; private Menu device_section; private HashTable connect_actions; protected override void dispose () { if (idle_rebuild_id != 0) { Source.remove (idle_rebuild_id); idle_rebuild_id = 0; } base.dispose (); } public Desktop (Bluetooth bluetooth, SimpleActionGroup action_group) { base ("desktop"); this.bluetooth = bluetooth; this.action_group = action_group; connect_actions = new HashTable(direct_hash, direct_equal); settings = new Settings ("com.canonical.indicator.bluetooth"); root_action = create_root_action (); // build the static actions Action[] actions = {}; actions += root_action; actions += create_enabled_action (bluetooth); actions += create_discoverable_action (bluetooth); actions += create_wizard_action (); actions += create_browse_files_action (); actions += create_send_file_action (); actions += create_show_settings_action (); foreach (var a in actions) action_group.insert (a); build_menu (); settings.changed["visible"].connect (()=> update_root_action_state()); bluetooth.notify.connect (() => update_root_action_state()); // when devices change, rebuild our device section bluetooth.devices_changed.connect (()=> { if (idle_rebuild_id == 0) idle_rebuild_id = Idle.add (() => { rebuild_device_section(); idle_rebuild_id = 0; return false; }); }); } /// /// MenuItems /// MenuItem create_device_connection_menuitem (Device device) { var action_name = @"desktop-device-$(device.id)-connected"; var item = new MenuItem (_("Connection"), @"indicator.$action_name"); item.set_attribute ("x-canonical-type", "s", "com.canonical.indicator.switch"); // if this doesn't already have an action, create one if (!connect_actions.contains (device.id)) { debug (@"creating action for $action_name"); var action = new SimpleAction.stateful (action_name, null, device.is_connected); action.activate.connect (() => action.set_state (!action.get_state().get_boolean())); action.notify["state"].connect (() => bluetooth.set_device_connected (device.id, action.get_state().get_boolean())); connect_actions.insert (device.id, action); action_group.insert (action); } else { debug (@"updating action $(device.id) state to $(device.is_connected)"); var action = connect_actions.lookup (device.id); action.set_state (device.is_connected); } return item; } void rebuild_device_section () { device_section.remove_all (); foreach (var device in bluetooth.get_devices()) { Menu submenu = new Menu (); MenuItem item; if (device.is_connectable) submenu.append_item (create_device_connection_menuitem (device)); if (device.supports_browsing) submenu.append (_("Browse files…"), @"indicator.desktop-browse-files::$(device.address)"); if (device.supports_file_transfer) submenu.append (_("Send files…"), @"indicator.desktop-send-file::$(device.address)"); switch (device.device_type) { case Device.Type.KEYBOARD: submenu.append (_("Keyboard Settings…"), "indicator.desktop-show-settings::keyboard"); break; case Device.Type.MOUSE: case Device.Type.TABLET: submenu.append (_("Mouse and Touchpad Settings…"), "indicator.desktop-show-settings::mouse"); break; case Device.Type.HEADSET: case Device.Type.HEADPHONES: case Device.Type.OTHER_AUDIO: submenu.append (_("Sound Settings…"), "indicator.desktop-show-settings::sound"); break; } /* only show the device if it's got actions that we can perform on it */ if (submenu.get_n_items () > 0) { item = new MenuItem (device.name, null); item.set_attribute_value ("icon", device.icon.serialize()); item.set_submenu (submenu); device_section.append_item (item); } } } void build_menu () { Menu section; MenuItem item; // quick toggles section section = new Menu (); item = new MenuItem ("Bluetooth", "indicator.desktop-enabled"); item.set_attribute ("x-canonical-type", "s", "com.canonical.indicator.switch"); section.append_item (item); item = new MenuItem ("Visible", "indicator.desktop-discoverable"); item.set_attribute ("x-canonical-type", "s", "com.canonical.indicator.switch"); section.append_item (item); menu.append_section (null, section); // devices section device_section = new Menu (); rebuild_device_section (); menu.append_section (null, device_section); // settings section section = new Menu (); section.append (_("Set Up New Device…"), "indicator.desktop-wizard"); section.append (_("Bluetooth Settings…"), "indicator.desktop-show-settings::bluetooth"); menu.append_section (null, section); } /// /// Action Helpers /// void spawn_command_line_async (string command) { try { Process.spawn_command_line_async (command); } catch (Error e) { warning ("unable to launch '$command': $(e.message)"); } } void show_control_center (string panel) { spawn_command_line_async ("gnome-control-center " + panel); } /// /// Actions /// Action create_enabled_action (Bluetooth bluetooth) { var action = new SimpleAction.stateful ("desktop-enabled", null, !bluetooth.blocked); action.activate.connect (() => action.set_state (!action.get_state().get_boolean())); action.notify["state"].connect (() => bluetooth.try_set_blocked (!action.get_state().get_boolean())); bluetooth.notify["blocked"].connect (() => action.set_state (!bluetooth.blocked)); return action; } Action create_discoverable_action (Bluetooth bluetooth) { var action = new SimpleAction.stateful ("desktop-discoverable", null, bluetooth.discoverable); action.set_enabled (bluetooth.powered); action.activate.connect (() => action.set_state (!action.get_state().get_boolean())); action.notify["state"].connect (() => bluetooth.try_set_discoverable (action.get_state().get_boolean())); bluetooth.notify["discoverable"].connect (() => action.set_state (bluetooth.discoverable)); bluetooth.notify["powered"].connect (() => action.set_enabled (bluetooth.powered)); return action; } Action create_wizard_action () { var action = new SimpleAction ("desktop-wizard", null); action.activate.connect (() => spawn_command_line_async ("bluetooth-wizard")); return action; } Action create_browse_files_action () { var action = new SimpleAction ("desktop-browse-files", VariantType.STRING); action.activate.connect ((action, address) => { var uri = @"obex://[$(address.get_string())]/"; var file = File.new_for_uri (uri); file.mount_enclosing_volume.begin (MountMountFlags.NONE, null, null, (obj, res) => { try { AppInfo.launch_default_for_uri (uri, null); } catch (Error e) { warning ("unable to launch '$uri': $(e.message)"); } }); }); return action; } Action create_send_file_action () { var action = new SimpleAction ("desktop-send-file", VariantType.STRING); action.activate.connect ((action, address) => { spawn_command_line_async ("bluetooth-sendto --device=$(address.get_string())"); }); return action; } Action create_show_settings_action () { var action = new SimpleAction ("desktop-show-settings", VariantType.STRING); action.activate.connect ((action, panel) => show_control_center (panel.get_string())); return action; } private Variant action_state_for_root () { bool blocked = bluetooth.blocked; bool powered = bluetooth.powered; settings.changed["visible"].connect (()=> update_root_action_state()); bool visible = powered && settings.get_boolean("visible"); string a11y; string icon_name; if (powered && !blocked) { a11y = "Bluetooth (on)"; icon_name = "bluetooth-active"; } else { a11y = "Bluetooth (off)"; icon_name = "bluetooth-disabled"; } var icon = new ThemedIcon.with_default_fallbacks (icon_name); var builder = new VariantBuilder (new VariantType ("a{sv}")); builder.add ("{sv}", "visible", new Variant ("b", visible)); builder.add ("{sv}", "accessible-desc", new Variant ("s", a11y)); builder.add ("{sv}", "icon", icon.serialize()); return builder.end (); } SimpleAction create_root_action () { return new SimpleAction.stateful ("root-desktop", null, action_state_for_root()); } void update_root_action_state () { root_action.set_state (action_state_for_root ()); } }