aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCharles Kerr <charles.kerr@canonical.com>2013-08-10 04:26:27 +0000
committerTarmac <>2013-08-10 04:26:27 +0000
commitf28653eb1a4a310987fffc44334b93da3f547d41 (patch)
tree82a72bfbb7eeca76766d73ae20f1152c5b8392c3 /src
parent0391025f72e9e5fed972b40e63087635d50c9234 (diff)
parenta0616908a817d6fad47c29ae703fc8a2ea379af6 (diff)
downloadayatana-indicator-bluetooth-f28653eb1a4a310987fffc44334b93da3f547d41.tar.gz
ayatana-indicator-bluetooth-f28653eb1a4a310987fffc44334b93da3f547d41.tar.bz2
ayatana-indicator-bluetooth-f28653eb1a4a310987fffc44334b93da3f547d41.zip
Add phone profile. Export menus & actions using gio. Drops the gtk, dbusmenu, and libindicator build dependencies. Drops runtime dependency on gnome-blueooth in the phone profile.
Approved by Ted Gould, PS Jenkins bot, Mathieu Trudel-Lapierre.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am55
-rw-r--r--src/bluetooth.vala80
-rw-r--r--src/bluez.vala397
-rw-r--r--src/config.vapi3
-rw-r--r--src/desktop.vala286
-rw-r--r--src/device.vala156
-rw-r--r--src/gnome-bluetooth-1.0.vapi82
-rw-r--r--src/indicator-bluetooth-service.vala443
-rw-r--r--src/indicator-bluetooth.vala142
-rw-r--r--src/indicator3-0.4.vapi145
-rw-r--r--src/killswitch.vala167
-rw-r--r--src/libido3-0.1.vapi10
-rw-r--r--src/main.vala34
-rw-r--r--src/org-bluez.vala293
-rw-r--r--src/phone.vala76
-rw-r--r--src/profile.vala159
-rw-r--r--src/service.vala108
17 files changed, 1777 insertions, 859 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 6bbb623..2fc41bb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,49 +1,36 @@
pkglibexec_PROGRAMS = indicator-bluetooth-service
-indicatordir = $(INDICATORDIR)
-indicator_LTLIBRARIES = libbluetooth.la
-indicator_bluetooth_service_SOURCES = \
- config.vapi \
- indicator3-0.4.vapi \
- gnome-bluetooth-1.0.vapi \
- indicator-bluetooth-service.vala
+indicator_bluetooth_service_SOURCES = \
+ org-bluez.vala \
+ bluetooth.vala \
+ bluez.vala \
+ desktop.vala \
+ device.vala \
+ main.vala \
+ phone.vala \
+ profile.vala \
+ killswitch.vala \
+ service.vala
indicator_bluetooth_service_VALAFLAGS = \
+ --ccode \
+ --vapidir=$(top_srcdir)/vapi/ \
+ --vapidir=./ \
+ --pkg config \
+ --pkg rfkill \
--pkg posix \
--pkg glib-2.0 \
- --pkg gtk+-3.0 \
- --pkg Dbusmenu-0.4
+ --pkg gio-2.0
+# -w to disable warnings for vala-generated code
indicator_bluetooth_service_CFLAGS = \
-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
-DLOCALE_DIR=\"$(datadir)/locale\" \
- $(INDICATOR_BLUETOOTH_SERVICE_CFLAGS)
+ -w \
+ $(SERVICE_DEPS_CFLAGS)
indicator_bluetooth_service_LDADD = \
- $(INDICATOR_BLUETOOTH_SERVICE_LIBS)
-
-libbluetooth_la_SOURCES = \
- config.vapi \
- indicator3-0.4.vapi \
- indicator-bluetooth.vala \
- libido3-0.1.vapi
-
-libbluetooth_la_VALAFLAGS = \
- --pkg posix \
- --pkg glib-2.0 \
- --pkg gtk+-3.0 \
- --pkg Dbusmenu-0.4 \
- --pkg DbusmenuGtk3-0.4
-
-libbluetooth_la_CFLAGS = \
- -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
- $(INDICATOR_BLUETOOTH_CFLAGS)
-
-libbluetooth_la_LIBADD = \
- $(INDICATOR_BLUETOOTH_LIBS)
-
-libbluetooth_la_LDFLAGS = \
- -module -avoid-version
+ $(SERVICE_DEPS_LIBS)
CLEANFILES = \
$(patsubst %.vala,%.c,$(filter %.vala, $(SOURCES))) \
diff --git a/src/bluetooth.vala b/src/bluetooth.vala
new file mode 100644
index 0000000..98464a4
--- /dev/null
+++ b/src/bluetooth.vala
@@ -0,0 +1,80 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+
+/**
+ * Abstract interface for the Bluetooth backend.
+ */
+public interface Bluetooth: Object
+{
+ /* True if there are any bluetooth adapters powered up on the system.
+ In short, whether or not this system's bluetooth is "on". */
+ public abstract bool powered { get; protected set; }
+
+ /* True if our system can be seen by other bluetooth devices */
+ public abstract bool discoverable { get; protected set; }
+ public abstract void try_set_discoverable (bool discoverable);
+
+ /* True if bluetooth's blocked. This can be soft-blocked by software and
+ * hard-blocked physically, eg by a laptop's network killswitch */
+ public abstract bool blocked { get; protected set; }
+
+ /* Try to block/unblock bluetooth. This can fail if it's overridden
+ by the system, eg by a laptop's network killswitch */
+ public abstract void try_set_blocked (bool b);
+
+ /* Get a list of the Device structs that we know about */
+ public abstract List<unowned Device> get_devices ();
+
+ /* Emitted when one or more of the devices is added, removed, or changed */
+ public signal void devices_changed ();
+
+ /* Try to connect/disconnect a particular device.
+ The device_key argument comes from the Device struct */
+ public abstract void set_device_connected (uint device_key, bool connected);
+}
+
+
+
+/**
+ * Base class for Bluetooth objects that use a killswitch to implement
+ * the 'discoverable' property.
+ */
+public abstract class KillswitchBluetooth: Object, Bluetooth
+{
+ private KillSwitch killswitch;
+
+ public KillswitchBluetooth (KillSwitch killswitch)
+ {
+ // always sync our 'blocked' property with the one in killswitch
+ this.killswitch = killswitch;
+ blocked = killswitch.blocked;
+ killswitch.notify["blocked"].connect (() => blocked = killswitch.blocked );
+ }
+
+ public bool powered { get; protected set; default = false; }
+ public bool discoverable { get; protected set; default = false; }
+ public bool blocked { get; protected set; default = true; }
+ public void try_set_blocked (bool b) { killswitch.try_set_blocked (b); }
+
+ // empty implementations
+ public abstract void try_set_discoverable (bool b);
+ public abstract List<unowned Device> get_devices ();
+ public abstract void set_device_connected (uint device_key, bool connected);
+}
diff --git a/src/bluez.vala b/src/bluez.vala
new file mode 100644
index 0000000..9baacd5
--- /dev/null
+++ b/src/bluez.vala
@@ -0,0 +1,397 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ * Robert Ancell <robert.ancell@canonical.com>
+ */
+
+
+/**
+ * Bluetooth implementaion which uses org.bluez on DBus
+ */
+public class Bluez: KillswitchBluetooth
+{
+ uint next_device_id = 1;
+ org.bluez.Manager manager;
+ org.bluez.Adapter default_adapter;
+
+ /* maps an org.bluez.Device's object_path to the org.bluez.Device proxy */
+ HashTable<string,org.bluez.Device> path_to_proxy;
+
+ /* maps an org.bluez.Device's object_path to our arbitrary unique id */
+ HashTable<string,uint> path_to_id;
+
+ /* maps our arbitrary unique id to an org.bluez.Device's object path */
+ HashTable<uint,string> id_to_path;
+
+ /* maps our arbitrary unique id to a Bluetooth.Device struct for public consumption */
+ HashTable<uint,Device> id_to_device;
+
+ public Bluez (KillSwitch killswitch)
+ {
+ base (killswitch);
+
+ string adapter_path = null;
+
+ id_to_path = new HashTable<uint,string> (direct_hash, direct_equal);
+ id_to_device = new HashTable<uint,Device> (direct_hash, direct_equal);
+ path_to_id = new HashTable<string,uint> (str_hash, str_equal);
+ path_to_proxy = new HashTable<string,org.bluez.Device> (str_hash, str_equal);
+
+ try
+ {
+ manager = Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", "/");
+
+ // get the current default adapter & watch for future default adapters
+ adapter_path = manager.default_adapter ();
+ manager.default_adapter_changed.connect ((object_path)
+ => on_default_adapter_changed (object_path));
+ }
+ catch (Error e)
+ {
+ critical (@"$(e.message)");
+ }
+
+ on_default_adapter_changed (adapter_path);
+ }
+
+ private void on_default_adapter_changed (string? object_path)
+ {
+ if (object_path != null) try
+ {
+ debug (@"using default adapter at $object_path");
+ default_adapter = Bus.get_proxy_sync (BusType.SYSTEM,
+ "org.bluez",
+ object_path);
+
+ default_adapter.property_changed.connect (()
+ => on_default_adapter_properties_changed ());
+
+ default_adapter.device_removed.connect ((adapter, path) => {
+ var id = path_to_id.lookup (path);
+ path_to_id.remove (path);
+ id_to_path.remove (id);
+ id_to_device.remove (id);
+ devices_changed ();
+ });
+
+ default_adapter.device_created.connect ((adapter, path)
+ => add_device (path));
+
+ foreach (var device_path in default_adapter.list_devices ())
+ add_device (device_path);
+ }
+ catch (Error e)
+ {
+ critical (@"$(e.message)");
+ }
+
+ on_default_adapter_properties_changed ();
+ }
+
+ private void on_default_adapter_properties_changed ()
+ {
+ bool is_discoverable = false;
+ bool is_powered = false;
+
+ if (default_adapter != null) try
+ {
+ var properties = default_adapter.get_properties ();
+
+ var v = properties.lookup ("Discoverable");
+ is_discoverable = (v != null) && v.get_boolean ();
+
+ v = properties.lookup ("Powered");
+ is_powered = (v != null) && v.get_boolean ();
+ }
+ catch (Error e)
+ {
+ critical (@"$(e.message)");
+ }
+
+ powered = is_powered;
+ discoverable = is_discoverable;
+ }
+
+ ////
+ //// bluetooth device UUIDs
+ ////
+
+ private static uint16 get_uuid16_from_uuid_string (string uuid)
+ {
+ uint16 uuid16;
+
+ string[] tokens = uuid.split ("-", 1);
+ if (tokens.length > 0)
+ uuid16 = (uint16) uint64.parse ("0x"+tokens[0]);
+ else
+ uuid16 = 0;
+
+ return uuid16;
+ }
+
+ // A device supports file transfer if OBEXObjectPush is in its uuid list
+ private bool device_supports_file_transfer (uint16[] uuids)
+ {
+ foreach (var uuid16 in uuids)
+ if (uuid16 == 0x1105) // OBEXObjectPush
+ return true;
+
+ return false;
+ }
+
+ // A device supports browsing if OBEXFileTransfer is in its uuid list
+ private bool device_supports_browsing (uint16[] uuids)
+ {
+ foreach (var uuid16 in uuids)
+ if (uuid16 == 0x1106) // OBEXFileTransfer
+ return true;
+
+ return false;
+ }
+
+ ////
+ //// Connectable Interfaces
+ ////
+
+ /* Headsets, Audio Sinks, and Input devices are connectable.
+ *
+ * This continues the behavior of the old gnome-bluetooth indicator.
+ * But are there other interfaces we care about? */
+ private DBusInterfaceInfo[] get_connectable_interfaces (DBusProxy device)
+ {
+ DBusInterfaceInfo[] connectable_interfaces = {};
+
+ try
+ {
+ var iname = "org.freedesktop.DBus.Introspectable.Introspect";
+ var intro = device.call_sync (iname, null, DBusCallFlags.NONE, -1);
+
+ if ((intro != null) && (intro.n_children() > 0))
+ {
+ var xml = intro.get_child_value(0).get_string();
+ var info = new DBusNodeInfo.for_xml (xml);
+ if (info != null)
+ {
+ foreach (var i in info.interfaces)
+ {
+ if ((i.name == "org.bluez.AudioSink") ||
+ (i.name == "org.bluez.Headset") ||
+ (i.name == "org.bluez.Input"))
+ {
+ connectable_interfaces += i;
+ }
+ }
+ }
+ }
+ }
+ catch (Error e)
+ {
+ critical (@"$(e.message)");
+ }
+
+ return connectable_interfaces;
+ }
+
+ private bool device_is_connectable (DBusProxy device)
+ {
+ return get_connectable_interfaces (device).length > 0;
+ }
+
+ // call "Connect" on the specified interface
+ private void device_connect_on_interface (DBusProxy proxy,
+ string interface_name)
+ {
+ var bus = proxy.get_connection ();
+ var object_path = proxy.get_object_path ();
+
+ debug (@"trying to connect to $object_path: $(interface_name)");
+
+ try
+ {
+ bus.call_sync ("org.bluez", object_path, interface_name,
+ "Connect", null, null, DBusCallFlags.NONE, -1);
+ }
+ catch (Error e)
+ {
+ debug (@"$object_path $interface_name.Connect() failed: $(e.message)");
+ }
+ }
+
+ private void device_connect (org.bluez.Device device)
+ {
+ DBusProxy proxy = device as DBusProxy;
+
+ // call "Connect" on all the interfaces that support it
+ foreach (var i in get_connectable_interfaces (proxy))
+ device_connect_on_interface (proxy, i.name);
+ }
+
+ private void device_disconnect (org.bluez.Device device)
+ {
+ try
+ {
+ device.disconnect ();
+ }
+ catch (Error e)
+ {
+ var object_path = (device as DBusProxy).get_object_path ();
+ critical (@"Unable to disconnect $object_path: $(e.message)");
+ }
+ }
+
+ ////
+ //// Device Upkeep
+ ////
+
+ private void add_device (string object_path)
+ {
+ if (!path_to_proxy.contains (object_path))
+ {
+ try
+ {
+ org.bluez.Device device = Bus.get_proxy_sync (BusType.SYSTEM,
+ "org.bluez",
+ object_path);
+ path_to_proxy.insert (object_path, device);
+ device.property_changed.connect(() => update_device (device));
+ update_device (device);
+ }
+ catch (Error e)
+ {
+ critical (@"$(e.message)");
+ }
+ }
+ }
+
+ /* Update our public Device struct from the org.bluez.Device's properties.
+ *
+ * This is called when we first walk through bluez' Devices on startup,
+ * when the org.bluez.Adapter gets a new device,
+ * and when a device's properties change s.t. we need to rebuild the proxy.
+ */
+ private void update_device (org.bluez.Device device_proxy)
+ {
+ HashTable<string, GLib.Variant> properties;
+
+ try {
+ properties = device_proxy.get_properties ();
+ } catch (Error e) {
+ critical (@"$(e.message)");
+ return;
+ }
+
+ // look up our id for this device.
+ // if we don't have one yet, create one.
+ var object_path = (device_proxy as DBusProxy).get_object_path();
+ var id = path_to_id.lookup (object_path);
+ if (id == 0)
+ {
+ id = next_device_id ++;
+ id_to_path.insert (id, object_path);
+ path_to_id.insert (object_path, id);
+ }
+
+ // look up the device's type
+ Device.Type type;
+ var v = properties.lookup ("Class");
+ if (v == null)
+ type = Device.Type.OTHER;
+ else
+ type = Device.class_to_device_type (v.get_uint32());
+
+ // look up the device's human-readable name
+ v = properties.lookup ("Alias");
+ if (v == null)
+ v = properties.lookup ("Name");
+ var name = v == null ? _("Unknown") : v.get_string ();
+
+ // look up the device's bus address
+ v = properties.lookup ("Address");
+ var address = v.get_string ();
+
+ // look up the device's bus address
+ v = properties.lookup ("Icon");
+ var icon = new ThemedIcon (v != null ? v.get_string() : "unknown");
+
+ // derive a Connectable flag for this device
+ var is_connectable = device_is_connectable (device_proxy as DBusProxy);
+
+ // look up the device's Connected flag
+ v = properties.lookup ("Connected");
+ var is_connected = (v != null) && v.get_boolean ();
+
+ // derive the uuid-related attributes we care about
+ v = properties.lookup ("UUIDs");
+ string[] uuid_strings = v.dup_strv ();
+ uint16[] uuids = {};
+ foreach (var s in uuid_strings)
+ uuids += get_uuid16_from_uuid_string (s);
+ var supports_browsing = device_supports_browsing (uuids);
+ var supports_file_transfer = device_supports_file_transfer (uuids);
+
+ // update our lookup table with these new attributes
+ id_to_device.insert (id, new Device (id,
+ type,
+ name,
+ address,
+ icon,
+ is_connectable,
+ is_connected,
+ supports_browsing,
+ supports_file_transfer));
+
+ devices_changed ();
+ }
+
+ ////
+ //// Public API
+ ////
+
+ public override void set_device_connected (uint id, bool connected)
+ {
+ var device = id_to_device.lookup (id);
+ var path = id_to_path.lookup (id);
+ var proxy = (path != null) ? path_to_proxy.lookup (path) : null;
+
+ if ((proxy != null)
+ && (device != null)
+ && (device.is_connected != connected))
+ {
+ if (connected)
+ device_connect (proxy);
+ else
+ device_disconnect (proxy);
+ }
+ }
+
+ public override void try_set_discoverable (bool b)
+ {
+ if (discoverable != b) try
+ {
+ default_adapter.set_property ("Discoverable", new Variant.boolean (b));
+ }
+ catch (Error e)
+ {
+ critical (@"$(e.message)");
+ }
+ }
+
+ public override List<unowned Device> get_devices ()
+ {
+ return id_to_device.get_values();
+ }
+}
diff --git a/src/config.vapi b/src/config.vapi
deleted file mode 100644
index a1bb094..0000000
--- a/src/config.vapi
+++ /dev/null
@@ -1,3 +0,0 @@
-public const string VERSION;
-public const string GETTEXT_PACKAGE;
-public const string LOCALE_DIR;
diff --git a/src/desktop.vala b/src/desktop.vala
new file mode 100644
index 0000000..4a10db8
--- /dev/null
+++ b/src/desktop.vala
@@ -0,0 +1,286 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+class Desktop: Profile
+{
+ private uint idle_rebuild_id = 0;
+ private Settings settings;
+ private SimpleActionGroup action_group;
+
+ private Menu device_section;
+ private HashTable<uint,SimpleAction> 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)
+ {
+ const string profile_name = "desktop";
+
+ base (bluetooth, profile_name);
+
+ this.action_group = action_group;
+
+ connect_actions = new HashTable<uint,SimpleAction>(direct_hash, direct_equal);
+
+ settings = new Settings ("com.canonical.indicator.bluetooth");
+
+ // 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 ();
+
+ // know when to show the indicator & when to hide it
+ settings.changed["visible"].connect (()=> update_visibility());
+ bluetooth.notify.connect (() => update_visibility());
+ update_visibility ();
+
+ // 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;
+ });
+ });
+ }
+
+ void update_visibility ()
+ {
+ visible = bluetooth.powered && !bluetooth.blocked && settings.get_boolean("visible");
+ }
+
+ ///
+ /// MenuItems
+ ///
+
+ MenuItem create_device_connection_menuitem (Device device)
+ {
+ var id = device.id;
+ var action_name = @"desktop-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 (id))
+ {
+ debug (@"creating action for $action_name");
+ var a = new SimpleAction.stateful (action_name,
+ null,
+ device.is_connected);
+
+ a.activate.connect (()
+ => a.set_state (!a.get_state().get_boolean()));
+
+ a.notify["state"].connect (()
+ => bluetooth.set_device_connected (id, a.get_state().get_boolean()));
+
+ connect_actions.insert (device.id, a);
+ action_group.insert (a);
+ }
+ 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 ();
+ section.append_item (create_enabled_menuitem ());
+ 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);
+ }
+
+ ///
+ /// Actions
+ ///
+
+ void show_settings (string panel)
+ {
+ spawn_command_line_async ("gnome-control-center " + panel);
+ }
+
+ Action create_discoverable_action (Bluetooth bluetooth)
+ {
+ var action = new SimpleAction.stateful ("desktop-discoverable",
+ null,
+ bluetooth.discoverable);
+
+ 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));
+
+ action.set_enabled (bluetooth.powered);
+ 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) => {
+ var cmd = @"bluetooth-sendto --device=$(address.get_string())";
+ spawn_command_line_async (cmd);
+ });
+
+ return action;
+ }
+
+ Action create_show_settings_action ()
+ {
+ var action = new SimpleAction ("desktop-show-settings", VariantType.STRING);
+
+ action.activate.connect ((action, panel)
+ => show_settings (panel.get_string()));
+
+ return action;
+ }
+}
diff --git a/src/device.vala b/src/device.vala
new file mode 100644
index 0000000..2d665b8
--- /dev/null
+++ b/src/device.vala
@@ -0,0 +1,156 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+public class Device: Object
+{
+ public enum Type
+ {
+ OTHER,
+ COMPUTER,
+ PHONE,
+ MODEM,
+ NETWORK,
+ HEADSET,
+ HEADPHONES,
+ VIDEO,
+ OTHER_AUDIO,
+ JOYPAD,
+ KEYPAD,
+ KEYBOARD,
+ TABLET,
+ MOUSE,
+ PRINTER,
+ CAMERA
+ }
+
+ public Type device_type { get; construct; }
+ public uint id { get; construct; }
+ public string name { get; construct; }
+ public string address { get; construct; }
+ public Icon icon { get; construct; }
+ public bool is_connectable { get; construct; }
+ public bool is_connected { get; construct; }
+ public bool supports_browsing { get; construct; }
+ public bool supports_file_transfer { get; construct; }
+ public string print() {
+ return @"{id:$id, name:$name, address:$address, icon:$(icon.to_string()), device_type:$device_type, is_connectable:$is_connectable, is_connected:$is_connected, supports_browsing:$supports_browsing, supports_file_transfer:$supports_file_transfer}";
+ }
+
+ public Device (uint id,
+ Type device_type,
+ string name,
+ string address,
+ Icon icon,
+ bool is_connectable,
+ bool is_connected,
+ bool supports_browsing,
+ bool supports_file_transfer)
+ {
+ Object (id: id,
+ device_type: device_type,
+ name: name,
+ address: address,
+ icon: icon,
+ is_connectable: is_connectable,
+ is_connected: is_connected,
+ supports_browsing: supports_browsing,
+ supports_file_transfer: supports_file_transfer);
+ }
+
+ public static Type class_to_device_type (uint32 c)
+ {
+ switch ((c & 0x1f00) >> 8)
+ {
+ case 0x01:
+ return Type.COMPUTER;
+
+ case 0x02:
+ switch ((c & 0xfc) >> 2)
+ {
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x05:
+ return Type.PHONE;
+
+ case 0x04:
+ return Type.MODEM;
+ }
+ break;
+
+ case 0x03:
+ return Type.NETWORK;
+
+ case 0x04:
+ switch ((c & 0xfc) >> 2)
+ {
+ case 0x01:
+ case 0x02:
+ return Type.HEADSET;
+
+ case 0x06:
+ return Type.HEADPHONES;
+
+ case 0x0b: // vcr
+ case 0x0c: // video camera
+ case 0x0d: // camcorder
+ return Type.VIDEO;
+
+ default:
+ return Type.OTHER_AUDIO;
+ }
+
+ case 0x05:
+ switch ((c & 0xc0) >> 6)
+ {
+ case 0x00:
+ switch ((c & 0x1e) >> 2)
+ {
+ case 0x01:
+ case 0x02:
+ return Type.JOYPAD;
+ }
+ break;
+
+ case 0x01:
+ return Type.KEYBOARD;
+
+ case 0x02:
+ switch ((c & 0x1e) >> 2)
+ {
+ case 0x05:
+ return Type.TABLET;
+
+ default:
+ return Type.MOUSE;
+ }
+ }
+ break;
+
+ case 0x06:
+ if ((c & 0x80) != 0)
+ return Type.PRINTER;
+ if ((c & 0x20) != 0)
+ return Type.CAMERA;
+ break;
+ }
+
+ return 0;
+ }
+}
diff --git a/src/gnome-bluetooth-1.0.vapi b/src/gnome-bluetooth-1.0.vapi
deleted file mode 100644
index 6d6aa09..0000000
--- a/src/gnome-bluetooth-1.0.vapi
+++ /dev/null
@@ -1,82 +0,0 @@
-[CCode (cprefix = "Bluetooth", lower_case_cprefix = "bluetooth_")]
-namespace GnomeBluetooth
-{
-
-[CCode (cheader_filename = "bluetooth-client.h")]
-public class Client : GLib.Object
-{
- public Client ();
- public Gtk.TreeModel model { get; }
- public Gtk.TreeModel adapter_model { get; }
- public Gtk.TreeModel device_model { get; }
- [CCode (finish_function = "bluetooth_client_connect_service_finish")]
- public async bool connect_service (string device, bool connect, GLib.Cancellable? cancellable = null) throws GLib.Error;
-}
-
-[CCode (cheader_filename = "bluetooth-enums.h", cprefix = "BLUETOOTH_COLUMN_")]
-public enum Column
-{
- PROXY,
- ADDRESS,
- ALIAS,
- NAME,
- TYPE,
- ICON,
- DEFAULT,
- PAIRED,
- TRUSTED,
- CONNECTED,
- DISCOVERABLE,
- DISCOVERING,
- LEGACYPAIRING,
- POWERED,
- SERVICES,
- UUIDS
-}
-
-[CCode (cheader_filename = "bluetooth-enums.h", cprefix = "BLUETOOTH_TYPE_")]
-public enum Type
-{
- ANY,
- PHONE,
- MODEM,
- COMPUTER,
- NETWORK,
- HEADSET,
- HEADPHONES,
- OTHER_AUDIO,
- KEYBOARD,
- MOUSE,
- CAMERA,
- PRINTER,
- JOYPAD,
- TABLET,
- VIDEO
-}
-
-[CCode (cheader_filename = "bluetooth-utils.h")]
-public void browse_address (GLib.Object? object, string address, uint timestamp, GLib.AsyncReadyCallback? callback);
-
-[CCode (cheader_filename = "bluetooth-utils.h")]
-public void send_to_address (string address, string alias);
-
-[CCode (cheader_filename = "bluetooth-killswitch.h", cprefix = "BLUETOOTH_KILLSWITCH_STATE_")]
-public enum KillswitchState
-{
- NO_ADAPTER,
- SOFT_BLOCKED,
- UNBLOCKED,
- HARD_BLOCKED
-}
-
-[CCode (cheader_filename = "bluetooth-killswitch.h")]
-public class Killswitch : GLib.Object
-{
- public Killswitch ();
- public signal void state_changed (KillswitchState state);
- public bool has_killswitches ();
- public KillswitchState state { get; set; }
- public unowned string state_to_string ();
-}
-
-}
diff --git a/src/indicator-bluetooth-service.vala b/src/indicator-bluetooth-service.vala
deleted file mode 100644
index ac84f33..0000000
--- a/src/indicator-bluetooth-service.vala
+++ /dev/null
@@ -1,443 +0,0 @@
-/*
- * Copyright (C) 2012-2013 Canonical Ltd.
- * Author: Robert Ancell <robert.ancell@canonical.com>
- *
- * 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 of the License.
- * See http://www.gnu.org/copyleft/gpl.html the full text of the license.
- */
-
-public class BluetoothIndicator
-{
- private Settings settings;
- private DBusConnection bus;
- private Indicator.Service indicator_service;
- private Dbusmenu.Server menu_server;
- private BluetoothService bluetooth_service;
- private GnomeBluetooth.Client client;
- private GnomeBluetooth.Killswitch killswitch;
- private bool updating_killswitch = false;
- private Dbusmenu.Menuitem enable_item;
- private Dbusmenu.Menuitem visible_item;
- private bool updating_visible = false;
- private Dbusmenu.Menuitem devices_separator;
- private List<BluetoothMenuItem> device_items;
- private Dbusmenu.Menuitem menu;
-
- public BluetoothIndicator () throws Error
- {
- settings = new Settings ("com.canonical.indicator.bluetooth");
- settings.changed.connect ((key) =>
- {
- if (key == "visible")
- update_visible ();
- });
-
- bus = Bus.get_sync (BusType.SESSION);
-
- indicator_service = new Indicator.Service ("com.canonical.indicator.bluetooth");
- menu_server = new Dbusmenu.Server ("/com/canonical/indicator/bluetooth/menu");
-
- bluetooth_service = new BluetoothService ();
- bus.register_object ("/com/canonical/indicator/bluetooth/service", bluetooth_service);
-
- killswitch = new GnomeBluetooth.Killswitch ();
- killswitch.state_changed.connect (killswitch_state_changed_cb);
-
- client = new GnomeBluetooth.Client ();
-
- menu = new Dbusmenu.Menuitem ();
- menu_server.set_root (menu);
-
- enable_item = new Dbusmenu.Menuitem ();
- enable_item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Bluetooth"));
- enable_item.property_set (Dbusmenu.MENUITEM_PROP_TYPE, "x-canonical-switch");
- enable_item.item_activated.connect (() =>
- {
- if (updating_killswitch)
- return;
- if (killswitch.state == GnomeBluetooth.KillswitchState.UNBLOCKED)
- killswitch.state = GnomeBluetooth.KillswitchState.SOFT_BLOCKED;
- else
- killswitch.state = GnomeBluetooth.KillswitchState.UNBLOCKED;
- });
- menu.child_append (enable_item);
-
- visible_item = new Dbusmenu.Menuitem ();
- visible_item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Visible"));
- visible_item.property_set (Dbusmenu.MENUITEM_PROP_TYPE, "x-canonical-switch");
- bool discoverable;
- client.get ("default-adapter-discoverable", out discoverable);
- visible_item.property_set_int (Dbusmenu.MENUITEM_PROP_TOGGLE_STATE, discoverable ? Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED : Dbusmenu.MENUITEM_TOGGLE_STATE_UNCHECKED);
- client.notify["default-adapter-discoverable"].connect (() =>
- {
- updating_visible = true;
- bool is_discoverable;
- client.get ("default-adapter-discoverable", out is_discoverable);
- visible_item.property_set_int (Dbusmenu.MENUITEM_PROP_TOGGLE_STATE, is_discoverable ? Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED : Dbusmenu.MENUITEM_TOGGLE_STATE_UNCHECKED);
- updating_visible = false;
- });
- visible_item.item_activated.connect (() =>
- {
- if (updating_visible)
- return;
- client.set ("default-adapter-discoverable", visible_item.property_get_int (Dbusmenu.MENUITEM_PROP_TOGGLE_STATE) != Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED);
- });
- menu.child_append (visible_item);
-
- devices_separator = new Dbusmenu.Menuitem ();
- devices_separator.property_set (Dbusmenu.MENUITEM_PROP_TYPE, Dbusmenu.CLIENT_TYPES_SEPARATOR);
- menu.child_append (devices_separator);
-
- device_items = new List<BluetoothMenuItem> ();
-
- client.model.row_inserted.connect (device_changed_cb);
- client.model.row_changed.connect (device_changed_cb);
- client.model.row_deleted.connect (device_removed_cb);
- Gtk.TreeIter iter;
- var have_iter = client.model.get_iter_first (out iter);
- while (have_iter)
- {
- Gtk.TreeIter child_iter;
- var have_child_iter = client.model.iter_children (out child_iter, iter);
- while (have_child_iter)
- {
- device_changed_cb (null, child_iter);
- have_child_iter = client.model.iter_next (ref child_iter);
- }
- have_iter = client.model.iter_next (ref iter);
- }
-
- var sep = new Dbusmenu.Menuitem ();
- sep.property_set (Dbusmenu.MENUITEM_PROP_TYPE, Dbusmenu.CLIENT_TYPES_SEPARATOR);
- menu.child_append (sep);
-
- var item = new Dbusmenu.Menuitem ();
- item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Set Up New Device…"));
- item.item_activated.connect (() => { set_up_new_device (); });
- menu.child_append (item);
-
- item = new Dbusmenu.Menuitem ();
- item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Bluetooth Settings…"));
- item.item_activated.connect (() => { show_control_center ("bluetooth"); });
- menu.child_append (item);
-
- killswitch_state_changed_cb (killswitch.state);
-
- client.adapter_model.row_inserted.connect (update_visible);
- client.adapter_model.row_deleted.connect (update_visible);
- update_visible ();
- }
-
- private BluetoothMenuItem? find_menu_item (string address)
- {
- foreach (var item in device_items)
- if (item.address == address)
- return item;
-
- return null;
- }
-
- private void device_changed_cb (Gtk.TreePath? path, Gtk.TreeIter iter)
- {
- /* Ignore adapters */
- Gtk.TreeIter parent_iter;
- if (!client.model.iter_parent (out parent_iter, iter))
- return;
-
- DBusProxy proxy;
- string address;
- string alias;
- GnomeBluetooth.Type type;
- string icon;
- bool connected;
- HashTable services;
- string[] uuids;
- client.model.get (iter,
- GnomeBluetooth.Column.PROXY, out proxy,
- GnomeBluetooth.Column.ADDRESS, out address,
- GnomeBluetooth.Column.ALIAS, out alias,
- GnomeBluetooth.Column.TYPE, out type,
- GnomeBluetooth.Column.ICON, out icon,
- GnomeBluetooth.Column.CONNECTED, out connected,
- GnomeBluetooth.Column.SERVICES, out services,
- GnomeBluetooth.Column.UUIDS, out uuids);
-
- /* Skip if haven't actually got any information yet */
- if (proxy == null)
- return;
-
- /* Find or create menu item */
- var item = find_menu_item (address);
- if (item == null)
- {
- item = new BluetoothMenuItem (client, address);
- item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, killswitch.state == GnomeBluetooth.KillswitchState.UNBLOCKED);
- var last_item = devices_separator as Dbusmenu.Menuitem;
- if (device_items != null)
- last_item = device_items.last ().data;
- device_items.append (item);
- menu.child_add_position (item, last_item.get_position (menu) + 1);
- }
-
- item.update (type, proxy, alias, icon, connected, services, uuids);
- }
-
- private void update_visible ()
- {
- bluetooth_service._visible = client.adapter_model.iter_n_children (null) > 0 && settings.get_boolean ("visible");
- var builder = new VariantBuilder (VariantType.ARRAY);
- builder.add ("{sv}", "Visible", new Variant.boolean (bluetooth_service._visible));
- try
- {
- var properties = new Variant ("(sa{sv}as)", "com.canonical.indicator.bluetooth.service", builder, null);
- bus.emit_signal (null,
- "/com/canonical/indicator/bluetooth/service",
- "org.freedesktop.DBus.Properties",
- "PropertiesChanged",
- properties);
- }
- catch (Error e)
- {
- warning ("Failed to emit signal: %s", e.message);
- }
- }
-
- private void device_removed_cb (Gtk.TreePath path)
- {
- Gtk.TreeIter iter;
- if (!client.model.get_iter (out iter, path))
- return;
-
- string address;
- client.model.get (iter, GnomeBluetooth.Column.ADDRESS, out address);
-
- var item = find_menu_item (address);
- if (item == null)
- return;
-
- device_items.remove (item);
- menu.child_delete (item);
- }
-
- private void killswitch_state_changed_cb (GnomeBluetooth.KillswitchState state)
- {
- updating_killswitch = true;
-
- var enabled = state == GnomeBluetooth.KillswitchState.UNBLOCKED;
-
- bluetooth_service._icon_name = enabled ? "bluetooth-active" : "bluetooth-disabled";
- bluetooth_service._accessible_description = enabled ? _("Bluetooth: On") : _("Bluetooth: Off");
-
- var builder = new VariantBuilder (VariantType.ARRAY);
- builder.add ("{sv}", "IconName", new Variant.string (bluetooth_service._icon_name));
- builder.add ("{sv}", "AccessibleDescription", new Variant.string (bluetooth_service._accessible_description));
- try
- {
- var properties = new Variant ("(sa{sv}as)", "com.canonical.indicator.bluetooth.service", builder, null);
- bus.emit_signal (null,
- "/com/canonical/indicator/bluetooth/service",
- "org.freedesktop.DBus.Properties",
- "PropertiesChanged",
- properties);
- }
- catch (Error e)
- {
- warning ("Failed to emit signal: %s", e.message);
- }
-
- enable_item.property_set_int (Dbusmenu.MENUITEM_PROP_TOGGLE_STATE, enabled ? Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED : Dbusmenu.MENUITEM_TOGGLE_STATE_UNCHECKED);
-
- /* Disable devices when locked */
- visible_item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, enabled);
- devices_separator.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, enabled);
- foreach (var item in device_items)
- item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, enabled && item.get_children () != null);
-
- updating_killswitch = false;
- }
-}
-
-private class BluetoothMenuItem : Dbusmenu.Menuitem
-{
- private GnomeBluetooth.Client client;
- public string address;
- private Dbusmenu.Menuitem? connect_item = null;
- private bool make_submenu = false;
-
- public BluetoothMenuItem (GnomeBluetooth.Client client, string address)
- {
- this.client = client;
- this.address = address;
- }
-
- public void update (GnomeBluetooth.Type type, DBusProxy proxy, string alias, string icon, bool connected, HashTable? services, string[] uuids)
- {
- property_set (Dbusmenu.MENUITEM_PROP_LABEL, alias);
- property_set (Dbusmenu.MENUITEM_PROP_ICON_NAME, icon);
- if (connect_item != null)
- connect_item.property_set_int (Dbusmenu.MENUITEM_PROP_TOGGLE_STATE, connected ? Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED : Dbusmenu.MENUITEM_TOGGLE_STATE_UNCHECKED);
-
- /* FIXME: Not sure if the GUI elements below can change over time */
- if (make_submenu)
- return;
- make_submenu = true;
-
- if (services != null)
- {
- connect_item = new Dbusmenu.Menuitem ();
- connect_item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Connection"));
- connect_item.property_set (Dbusmenu.MENUITEM_PROP_TYPE, "x-canonical-switch");
- connect_item.property_set_int (Dbusmenu.MENUITEM_PROP_TOGGLE_STATE, connected ? Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED : Dbusmenu.MENUITEM_TOGGLE_STATE_UNCHECKED);
- connect_item.item_activated.connect (() => { connect_service (proxy.get_object_path (), connect_item.property_get_int (Dbusmenu.MENUITEM_PROP_TOGGLE_STATE) != Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED); });
- child_append (connect_item);
- }
-
- var can_send = false;
- var can_browse = false;
- if (uuids != null)
- {
- for (var i = 0; uuids[i] != null; i++)
- {
- if (uuids[i] == "OBEXObjectPush")
- can_send = true;
- if (uuids[i] == "OBEXFileTransfer")
- can_browse = true;
- }
- }
-
- if (can_send)
- {
- var send_item = new Dbusmenu.Menuitem ();
- send_item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Send files…"));
- send_item.item_activated.connect (() => { GnomeBluetooth.send_to_address (address, alias); });
- child_append (send_item);
- }
-
- if (can_browse)
- {
- var browse_item = new Dbusmenu.Menuitem ();
- browse_item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Browse files…"));
- browse_item.item_activated.connect (() => { GnomeBluetooth.browse_address (null, address, Gdk.CURRENT_TIME, null); });
- child_append (browse_item);
- }
-
- switch (type)
- {
- case GnomeBluetooth.Type.KEYBOARD:
- var keyboard_item = new Dbusmenu.Menuitem ();
- keyboard_item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Keyboard Settings…"));
- keyboard_item.item_activated.connect (() => { show_control_center ("keyboard"); });
- child_append (keyboard_item);
- break;
-
- case GnomeBluetooth.Type.MOUSE:
- case GnomeBluetooth.Type.TABLET:
- var mouse_item = new Dbusmenu.Menuitem ();
- mouse_item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Mouse and Touchpad Settings…"));
- mouse_item.item_activated.connect (() => { show_control_center ("mouse"); });
- child_append (mouse_item);
- break;
-
- case GnomeBluetooth.Type.HEADSET:
- case GnomeBluetooth.Type.HEADPHONES:
- case GnomeBluetooth.Type.OTHER_AUDIO:
- var sound_item = new Dbusmenu.Menuitem ();
- sound_item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Sound Settings…"));
- sound_item.item_activated.connect (() => { show_control_center ("sound"); });
- child_append (sound_item);
- break;
- }
-
- property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, get_children () != null);
- }
-
- private void connect_service (string device, bool connect)
- {
- client.connect_service.begin (device, connect, null, (object, result) =>
- {
- var connected = false;
- try
- {
- connected = client.connect_service.end (result);
- }
- catch (Error e)
- {
- warning ("Failed to connect service: %s", e.message);
- }
- });
- }
-}
-
-private void set_up_new_device ()
-{
- try
- {
- Process.spawn_command_line_async ("bluetooth-wizard");
- }
- catch (GLib.SpawnError e)
- {
- warning ("Failed to open bluetooth-wizard: %s", e.message);
- }
-}
-
-private void show_control_center (string panel)
-{
- try
- {
- Process.spawn_command_line_async ("gnome-control-center %s".printf (panel));
- }
- catch (GLib.SpawnError e)
- {
- warning ("Failed to open control center: %s", e.message);
- }
-}
-
-public static int main (string[] args)
-{
- Intl.setlocale (LocaleCategory.ALL, "");
- Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
- Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
- Intl.textdomain (GETTEXT_PACKAGE);
-
- var loop = new MainLoop ();
-
- BluetoothIndicator indicator;
- try
- {
- indicator = new BluetoothIndicator ();
- }
- catch (Error e)
- {
- warning ("Failed to start bluetooth indicator service: %s", e.message);
- return Posix.EXIT_FAILURE;
- }
- // FIXMEindicator.shutdown.connect (() => { loop.quit (); });
-
- loop.run ();
-
- indicator = null;
-
- return Posix.EXIT_SUCCESS;
-}
-
-[DBus (name = "com.canonical.indicator.bluetooth.service")]
-private class BluetoothService : Object
-{
- internal bool _visible = false;
- public bool visible
- {
- get { return _visible; }
- }
- internal string _icon_name = "bluetooth-active";
- public string icon_name
- {
- get { return _icon_name; }
- }
- internal string _accessible_description = _("Bluetooth");
- public string accessible_description
- {
- get { return _accessible_description; }
- }
-}
diff --git a/src/indicator-bluetooth.vala b/src/indicator-bluetooth.vala
deleted file mode 100644
index bfbbc9f..0000000
--- a/src/indicator-bluetooth.vala
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2012-2013 Canonical Ltd.
- * Author: Robert Ancell <robert.ancell@canonical.com>
- *
- * 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 of the License.
- * See http://www.gnu.org/copyleft/gpl.html the full text of the license.
- */
-
-public class BluetoothIndicator : Indicator.Object
-{
- private Indicator.ServiceManager service;
- private Gtk.Image image;
- private DbusmenuGtk.Menu menu;
- private BluetoothService proxy;
- private string accessible_description = _("Bluetooth: On");
-
- construct
- {
- service = new Indicator.ServiceManager ("com.canonical.indicator.bluetooth");
- service.connection_change.connect (connection_change_cb);
- menu = new DbusmenuGtk.Menu ("com.canonical.indicator.bluetooth", "/com/canonical/indicator/bluetooth/menu");
- image = Indicator.image_helper ("bluetooth-active");
- image.visible = true;
-
- var menu_client = menu.get_client ();
- menu_client.add_type_handler_full ("x-canonical-switch", new_switch_cb);
-
- /* Hide until ready */
- set_visible (false);
- }
-
- private bool new_switch_cb (Dbusmenu.Menuitem newitem, Dbusmenu.Menuitem parent, Dbusmenu.Client client)
- {
- var item = new Switch (newitem);
- (client as DbusmenuGtk.Client).newitem_base (newitem, item, parent);
- return true;
- }
-
- public override unowned Gtk.Image get_image ()
- {
- return image;
- }
-
- public override unowned Gtk.Menu get_menu ()
- {
- return menu;
- }
-
- public override unowned string get_accessible_desc ()
- {
- return accessible_description;
- }
-
- private void connection_change_cb (bool connected)
- {
- if (!connected)
- return;
-
- // FIXME: Set proxy to null on disconnect?
- // FIXME: Use Cancellable to cancel existing connection
- if (proxy == null)
- {
- Bus.get_proxy.begin<BluetoothService> (BusType.SESSION,
- "com.canonical.indicator.bluetooth",
- "/com/canonical/indicator/bluetooth/service",
- DBusProxyFlags.NONE, null, (object, result) =>
- {
- try
- {
- proxy = Bus.get_proxy.end (result);
- proxy.g_properties_changed.connect (server_properties_changed_cb);
- server_properties_changed_cb ();
- }
- catch (IOError e)
- {
- warning ("Failed to connect to bluetooth service: %s", e.message);
- }
- });
- }
- }
-
- private void server_properties_changed_cb ()
- {
- set_visible (proxy.visible);
- Indicator.image_helper_update (image, proxy.icon_name);
- accessible_description = proxy.accessible_description;
- }
-}
-
-public class Switch : Ido.SwitchMenuItem
-{
- public Dbusmenu.Menuitem menuitem;
- public new Gtk.Label label;
- private bool updating_switch = false;
-
- public Switch (Dbusmenu.Menuitem menuitem)
- {
- this.menuitem = menuitem;
- label = new Gtk.Label ("");
- label.visible = true;
- content_area.add (label);
-
- /* Be the first listener to the activate signal so we can stop it
- * emitting when we change the state. Without this you get feedback loops */
- activate.connect (() =>
- {
- if (updating_switch)
- Signal.stop_emission_by_name (this, "activate");
- });
-
- menuitem.property_changed.connect ((mi, prop, value) => { update (); });
- update ();
- }
-
- private void update ()
- {
- updating_switch = true;
- label.label = menuitem.property_get (Dbusmenu.MENUITEM_PROP_LABEL);
- active = menuitem.property_get_int (Dbusmenu.MENUITEM_PROP_TOGGLE_STATE) == Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED;
- updating_switch = false;
- }
-}
-
-[DBus (name = "com.canonical.indicator.bluetooth.service")]
-public interface BluetoothService : DBusProxy
-{
- public abstract bool visible { owned get; }
- public abstract string icon_name { owned get; }
- public abstract string accessible_description { owned get; }
-}
-
-public static string get_version ()
-{
- return Indicator.VERSION;
-}
-
-public static GLib.Type get_type ()
-{
- return typeof (BluetoothIndicator);
-}
diff --git a/src/indicator3-0.4.vapi b/src/indicator3-0.4.vapi
deleted file mode 100644
index 44204cd..0000000
--- a/src/indicator3-0.4.vapi
+++ /dev/null
@@ -1,145 +0,0 @@
-/* indicator-0.4.vapi generated by vapigen, do not modify. */
-
-namespace Indicator {
- [CCode (cheader_filename = "libindicator/indicator-desktop-shortcuts.h", type_check_function = "INDICATOR_IS_DESKTOP_SHORTCUTS", type_id = "indicator_desktop_shortcuts_get_type")]
- public class DesktopShortcuts : GLib.Object {
- [CCode (has_construct_function = false)]
- public DesktopShortcuts (string file, string identity);
- public unowned string get_nicks ();
- public bool nick_exec (string nick);
- public unowned string nick_get_name (string nick);
- public string desktop_file { construct; }
- [NoAccessorMethod]
- public string identity { owned get; construct; }
- }
- [CCode (cheader_filename = "libindicator/indicator-object.h", type_check_function = "INDICATOR_IS_OBJECT", type_id = "indicator_object_get_type ()")]
- public class Object : GLib.Object {
- [CCode (has_construct_function = false)]
- public Object ();
- public bool check_environment (string env);
- [NoWrapper]
- public virtual void entry_activate (Indicator.ObjectEntry entry, uint timestamp);
- [NoWrapper]
- public virtual void entry_activate_window (Indicator.ObjectEntry entry, uint windowid, uint timestamp);
- [NoWrapper]
- public virtual void entry_being_removed (Indicator.ObjectEntry entry);
- [NoWrapper]
- public virtual void entry_close (Indicator.ObjectEntry entry, uint timestamp);
- [NoWrapper]
- public virtual void entry_was_added (Indicator.ObjectEntry entry);
- [CCode (has_construct_function = false)]
- public Object.from_file (string file);
- [NoWrapper]
- public virtual signal unowned string get_accessible_desc ();
- public virtual GLib.List<weak Indicator.ObjectEntry> get_entries ();
- public unowned string[] get_environment ();
- [NoWrapper]
- public virtual unowned Gtk.Image get_image ();
- [NoWrapper]
- public virtual unowned Gtk.Label get_label ();
- public virtual uint get_location (Indicator.ObjectEntry entry);
- [NoWrapper]
- public virtual unowned Gtk.Menu get_menu ();
- [NoWrapper]
- public virtual unowned string get_name_hint ();
- public virtual bool get_show_now (Indicator.ObjectEntry entry);
- public void set_environment (string[] env);
- public void set_visible (bool visible);
- [NoAccessorMethod]
- public bool indicator_object_default_visibility { get; set; }
- public virtual signal void accessible_desc_update (Indicator.ObjectEntry entry);
- public virtual signal void entry_added (Indicator.ObjectEntry entry);
- public virtual signal void entry_moved (Indicator.ObjectEntry entry, uint old_pos, uint new_pos);
- public virtual signal void entry_removed (Indicator.ObjectEntry entry);
- public virtual signal void entry_scrolled (Indicator.ObjectEntry entry, uint delta, Indicator.ScrollDirection direction);
- public virtual signal void menu_show (Indicator.ObjectEntry entry, uint timestamp);
- public virtual signal void secondary_activate (Indicator.ObjectEntry entry, uint timestamp);
- public virtual signal void show_now_changed (Indicator.ObjectEntry entry, bool show_now_state);
- }
- [CCode (cheader_filename = "libindicator/indicator-object.h")]
- [Compact]
- public class ObjectEntry {
- public weak string accessible_desc;
- public weak Gtk.Image image;
- public weak Gtk.Label label;
- public weak Gtk.Menu menu;
- public weak string name_hint;
- public weak Indicator.Object parent_object;
- public static void activate (Indicator.Object io, Indicator.ObjectEntry entry, uint timestamp);
- public static void activate_window (Indicator.Object io, Indicator.ObjectEntry entry, uint windowid, uint timestamp);
- public static void close (Indicator.Object io, Indicator.ObjectEntry entry, uint timestamp);
- }
- [CCode (cheader_filename = "libindicator/indicator-service.h", type_check_function = "INDICATOR_IS_SERVICE", type_id = "indicator_service_get_type")]
- public class Service : GLib.Object {
- [CCode (has_construct_function = false)]
- public Service (string name);
- [CCode (cname = "indicator_service_new_version", has_construct_function = false)]
- public Service.with_version (string name, uint version);
- [NoAccessorMethod]
- public string name { owned get; set; }
- [NoAccessorMethod]
- public uint version { get; set; }
- public virtual signal void shutdown ();
- }
- [CCode (cheader_filename = "libindicator/indicator-service-manager.h", type_check_function = "INDICATOR_IS_SERVICE_MANAGER", type_id = "indicator_service_manager_get_type")]
- public class ServiceManager : GLib.Object {
- [CCode (has_construct_function = false)]
- public ServiceManager (string dbus_name);
- public bool connected ();
- public void set_refresh (uint time_in_ms);
- [CCode (cname = "indicator_service_manager_new_version", has_construct_function = false)]
- public ServiceManager.with_version (string dbus_name, uint version);
- [NoAccessorMethod]
- public string name { owned get; set; }
- [NoAccessorMethod]
- public uint version { get; set; }
- public virtual signal void connection_change (bool connected);
- }
- [CCode (cheader_filename = "libindicator/indicator-object.h", cprefix = "INDICATOR_OBJECT_SCROLL_", has_type_id = false)]
- public enum ScrollDirection {
- UP,
- DOWN,
- LEFT,
- RIGHT
- }
- [CCode (cheader_filename = "libindicator/indicator.h", has_target = false)]
- public delegate GLib.Type get_type_t ();
- [CCode (cheader_filename = "libindicator/indicator.h", has_target = false)]
- public delegate unowned string get_version_t ();
- [CCode (cheader_filename = "libindicator/indicator.h")]
- public const string GET_TYPE_S;
- [CCode (cheader_filename = "libindicator/indicator.h")]
- public const string GET_VERSION_S;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_DEFAULT_VISIBILITY;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_SIGNAL_ENTRY_ADDED;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_SIGNAL_ENTRY_MOVED;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_SIGNAL_ENTRY_REMOVED;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_SIGNAL_ENTRY_SCROLLED;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_SIGNAL_MENU_SHOW;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_SIGNAL_SECONDARY_ACTIVATE;
- [CCode (cheader_filename = "libindicator/indicator-gobject.h")]
- public const string OBJECT_SIGNAL_SHOW_NOW_CHANGED;
- [CCode (cheader_filename = "libindicator/indicator-service-manager.h")]
- public const string SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE;
- [CCode (cheader_filename = "libindicator/indicator-service.h")]
- public const string SERVICE_SIGNAL_SHUTDOWN;
- [CCode (cheader_filename = "libindicator/indicator.h")]
- public const int SET_VERSION;
- [CCode (cheader_filename = "libindicator/indicator.h")]
- public const string VERSION;
- [CCode (cheader_filename = "libindicator/indicator.h", cname = "get_version")]
- public static unowned string get_version ();
- [CCode (cheader_filename = "libindicator/indicator-image-helper.h")]
- public static unowned Gtk.Image image_helper (string name);
- [CCode (cheader_filename = "libindicator/indicator-image-helper.h")]
- public static void image_helper_update (Gtk.Image image, string name);
-} \ No newline at end of file
diff --git a/src/killswitch.vala b/src/killswitch.vala
new file mode 100644
index 0000000..08ee0cc
--- /dev/null
+++ b/src/killswitch.vala
@@ -0,0 +1,167 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+/**
+ * Monitors whether or not bluetooth is blocked,
+ * either by software (e.g., a session configuration setting)
+ * or by hardware (e.g., user disabled it via a physical switch on her laptop).
+ *
+ * KillSwitchBluetooth uses this as the impl for its Bluetooth.blocked property
+ */
+public interface KillSwitch: Object
+{
+ public abstract bool blocked { get; protected set; }
+
+ /* Try to block/unblock bluetooth.
+ * This can fail if the requested state is overruled by a hardware block. */
+ public abstract void try_set_blocked (bool blocked);
+}
+
+/**
+ * KillSwitch impementation for Linux using /dev/rfkill
+ */
+public class RfKillSwitch: KillSwitch, Object
+{
+ public bool blocked { get; protected set; default = false; }
+
+ public void try_set_blocked (bool blocked)
+ {
+ return_if_fail (this.blocked != blocked);
+
+ // Try to soft-block all the bluetooth devices
+ var event = Linux.RfKillEvent() {
+ op = Linux.RfKillOp.CHANGE_ALL,
+ type = Linux.RfKillType.BLUETOOTH,
+ soft = (uint8)blocked
+ };
+
+ /* Write this request to rfkill.
+ * Don't update this object's "blocked" property here --
+ * We'll get the update when on_channel_event() reads it below */
+ var bwritten = Posix.write (fd, &event, sizeof(Linux.RfKillEvent));
+ if (bwritten == -1)
+ warning (@"Could not write rfkill event: $(strerror(errno))");
+ }
+
+ /* represents an entry that we've read from the rfkill file */
+ private class Entry
+ {
+ public uint32 idx;
+ public Linux.RfKillType type;
+ public bool soft;
+ public bool hard;
+ }
+
+ private HashTable<uint32,Entry> entries;
+ private int fd = -1;
+ private IOChannel channel;
+ private uint watch;
+
+ protected override void dispose ()
+ {
+ if (watch != 0)
+ {
+ Source.remove (watch);
+ watch = 0;
+ }
+
+ if (fd != -1)
+ {
+ Posix.close (fd);
+ fd = -1;
+ }
+
+ base.dispose ();
+ }
+
+ public RfKillSwitch ()
+ {
+ entries = new HashTable<uint32,Entry>(direct_hash, direct_equal);
+
+ var path = "/dev/rfkill";
+ fd = Posix.open (path, Posix.O_RDWR | Posix.O_NONBLOCK );
+ if (fd == -1)
+ {
+ warning (@"Can't open $path for use as a killswitch backend: $(strerror(errno))");
+ }
+ else
+ {
+ // read everything that's already there, then watch for more
+ while (read_event());
+ channel = new IOChannel.unix_new (fd);
+ watch = channel.add_watch (IOCondition.IN, on_channel_event);
+ }
+ }
+
+ private bool on_channel_event (IOChannel source, IOCondition condition)
+ {
+ read_event ();
+ return true;
+ }
+
+ private bool read_event ()
+ {
+ assert (fd != -1);
+
+ var event = Linux.RfKillEvent();
+ var n = sizeof (Linux.RfKillEvent);
+ var bytesread = Posix.read (fd, &event, n);
+
+ if (bytesread == n)
+ {
+ process_event (event);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void process_event (Linux.RfKillEvent event)
+ {
+ // we only want things that affect bluetooth
+ if ((event.type != Linux.RfKillType.ALL) &&
+ (event.type != Linux.RfKillType.BLUETOOTH))
+ return;
+
+ switch (event.op)
+ {
+ case Linux.RfKillOp.CHANGE:
+ case Linux.RfKillOp.ADD:
+ Entry entry = new Entry ();
+ entry.idx = event.idx;
+ entry.type = event.type;
+ entry.soft = event.soft != 0;
+ entry.hard = event.hard != 0;
+ entries.insert (entry.idx, entry);
+ break;
+
+ case Linux.RfKillOp.DEL:
+ entries.remove (event.idx);
+ break;
+ }
+
+ /* update our blocked property.
+ it should be true if any bluetooth entry is hard- or soft-blocked */
+ var b = false;
+ foreach (var entry in entries.get_values ())
+ if ((b = (entry.soft || entry.hard)))
+ break;
+ blocked = b;
+ }
+}
diff --git a/src/libido3-0.1.vapi b/src/libido3-0.1.vapi
deleted file mode 100644
index e6a8953..0000000
--- a/src/libido3-0.1.vapi
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Ido
-{
- [CCode (cheader_filename = "libido/idoswitchmenuitem.h")]
- public class SwitchMenuItem : Gtk.CheckMenuItem
- {
- [CCode (has_construct_function = false)]
- public SwitchMenuItem ();
- public Gtk.Container content_area { get; }
- }
-}
diff --git a/src/main.vala b/src/main.vala
new file mode 100644
index 0000000..caee3b3
--- /dev/null
+++ b/src/main.vala
@@ -0,0 +1,34 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+public static int main (string[] args)
+{
+ // set up i18n
+ Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
+ Intl.setlocale (LocaleCategory.ALL, "");
+ Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.GNOMELOCALEDIR);
+ Intl.textdomain (Config.GETTEXT_PACKAGE);
+
+ // create the backend
+ var bluetooth = new Bluez (new RfKillSwitch ());
+
+ // start the service
+ var service = new Service (bluetooth);
+ return service.run ();
+}
diff --git a/src/org-bluez.vala b/src/org-bluez.vala
new file mode 100644
index 0000000..49c8e4d
--- /dev/null
+++ b/src/org-bluez.vala
@@ -0,0 +1,293 @@
+/* Generated by vala-dbus-binding-tool 0.4.0. Do not modify! */
+/* Generated with: vala-dbus-binding-tool --gdbus --directory=. */
+using GLib;
+
+namespace org {
+
+ namespace bluez {
+
+ [DBus (name = "org.bluez.Manager", timeout = 120000)]
+ public interface Manager : GLib.Object {
+
+ [DBus (name = "GetProperties")]
+ public abstract GLib.HashTable<string, GLib.Variant> get_properties() throws DBusError, IOError;
+
+ [DBus (name = "DefaultAdapter")]
+ public abstract GLib.ObjectPath default_adapter() throws DBusError, IOError;
+
+ [DBus (name = "FindAdapter")]
+ public abstract GLib.ObjectPath find_adapter(string pattern) throws DBusError, IOError;
+
+ [DBus (name = "ListAdapters")]
+ public abstract GLib.ObjectPath[] list_adapters() throws DBusError, IOError;
+
+ [DBus (name = "PropertyChanged")]
+ public signal void property_changed(string name, GLib.Variant value);
+
+ [DBus (name = "AdapterAdded")]
+ public signal void adapter_added(GLib.ObjectPath adapter);
+
+ [DBus (name = "AdapterRemoved")]
+ public signal void adapter_removed(GLib.ObjectPath adapter);
+
+ [DBus (name = "DefaultAdapterChanged")]
+ public signal void default_adapter_changed(GLib.ObjectPath adapter);
+ }
+
+ [DBus (name = "org.bluez.Manager", timeout = 120000)]
+ public interface ManagerSync : GLib.Object {
+
+ [DBus (name = "GetProperties")]
+ public abstract GLib.HashTable<string, GLib.Variant> get_properties() throws DBusError, IOError;
+
+ [DBus (name = "DefaultAdapter")]
+ public abstract GLib.ObjectPath default_adapter() throws DBusError, IOError;
+
+ [DBus (name = "FindAdapter")]
+ public abstract GLib.ObjectPath find_adapter(string pattern) throws DBusError, IOError;
+
+ [DBus (name = "ListAdapters")]
+ public abstract GLib.ObjectPath[] list_adapters() throws DBusError, IOError;
+
+ [DBus (name = "PropertyChanged")]
+ public signal void property_changed(string name, GLib.Variant value);
+
+ [DBus (name = "AdapterAdded")]
+ public signal void adapter_added(GLib.ObjectPath adapter);
+
+ [DBus (name = "AdapterRemoved")]
+ public signal void adapter_removed(GLib.ObjectPath adapter);
+
+ [DBus (name = "DefaultAdapterChanged")]
+ public signal void default_adapter_changed(GLib.ObjectPath adapter);
+ }
+
+ [DBus (name = "org.bluez.Adapter", timeout = 120000)]
+ public interface Adapter : GLib.Object {
+
+ [DBus (name = "GetProperties")]
+ public abstract GLib.HashTable<string, GLib.Variant> get_properties() throws DBusError, IOError;
+
+ [DBus (name = "SetProperty")]
+ public abstract void set_property(string name, GLib.Variant value) throws DBusError, IOError;
+
+ [DBus (name = "RequestSession")]
+ public abstract void request_session() throws DBusError, IOError;
+
+ [DBus (name = "ReleaseSession")]
+ public abstract void release_session() throws DBusError, IOError;
+
+ [DBus (name = "StartDiscovery")]
+ public abstract void start_discovery() throws DBusError, IOError;
+
+ [DBus (name = "StopDiscovery")]
+ public abstract void stop_discovery() throws DBusError, IOError;
+
+ [DBus (name = "ListDevices")]
+ public abstract GLib.ObjectPath[] list_devices() throws DBusError, IOError;
+
+ [DBus (name = "CreateDevice")]
+ public abstract GLib.ObjectPath create_device(string address) throws DBusError, IOError;
+
+ [DBus (name = "CreatePairedDevice")]
+ public abstract GLib.ObjectPath create_paired_device(string address, GLib.ObjectPath agent, string capability) throws DBusError, IOError;
+
+ [DBus (name = "CancelDeviceCreation")]
+ public abstract void cancel_device_creation(string address) throws DBusError, IOError;
+
+ [DBus (name = "RemoveDevice")]
+ public abstract void remove_device(GLib.ObjectPath device) throws DBusError, IOError;
+
+ [DBus (name = "FindDevice")]
+ public abstract GLib.ObjectPath find_device(string address) throws DBusError, IOError;
+
+ [DBus (name = "RegisterAgent")]
+ public abstract void register_agent(GLib.ObjectPath agent, string capability) throws DBusError, IOError;
+
+ [DBus (name = "UnregisterAgent")]
+ public abstract void unregister_agent(GLib.ObjectPath agent) throws DBusError, IOError;
+
+ [DBus (name = "PropertyChanged")]
+ public signal void property_changed(string name, GLib.Variant value);
+
+ [DBus (name = "DeviceCreated")]
+ public signal void device_created(GLib.ObjectPath device);
+
+ [DBus (name = "DeviceRemoved")]
+ public signal void device_removed(GLib.ObjectPath device);
+
+ [DBus (name = "DeviceFound")]
+ public signal void device_found(string address, GLib.HashTable<string, GLib.Variant> values);
+
+ [DBus (name = "DeviceDisappeared")]
+ public signal void device_disappeared(string address);
+ }
+
+ [DBus (name = "org.bluez.Adapter", timeout = 120000)]
+ public interface AdapterSync : GLib.Object {
+
+ [DBus (name = "GetProperties")]
+ public abstract GLib.HashTable<string, GLib.Variant> get_properties() throws DBusError, IOError;
+
+ [DBus (name = "SetProperty")]
+ public abstract void set_property(string name, GLib.Variant value) throws DBusError, IOError;
+
+ [DBus (name = "RequestSession")]
+ public abstract void request_session() throws DBusError, IOError;
+
+ [DBus (name = "ReleaseSession")]
+ public abstract void release_session() throws DBusError, IOError;
+
+ [DBus (name = "StartDiscovery")]
+ public abstract void start_discovery() throws DBusError, IOError;
+
+ [DBus (name = "StopDiscovery")]
+ public abstract void stop_discovery() throws DBusError, IOError;
+
+ [DBus (name = "ListDevices")]
+ public abstract GLib.ObjectPath[] list_devices() throws DBusError, IOError;
+
+ [DBus (name = "CreateDevice")]
+ public abstract GLib.ObjectPath create_device(string address) throws DBusError, IOError;
+
+ [DBus (name = "CreatePairedDevice")]
+ public abstract GLib.ObjectPath create_paired_device(string address, GLib.ObjectPath agent, string capability) throws DBusError, IOError;
+
+ [DBus (name = "CancelDeviceCreation")]
+ public abstract void cancel_device_creation(string address) throws DBusError, IOError;
+
+ [DBus (name = "RemoveDevice")]
+ public abstract void remove_device(GLib.ObjectPath device) throws DBusError, IOError;
+
+ [DBus (name = "FindDevice")]
+ public abstract GLib.ObjectPath find_device(string address) throws DBusError, IOError;
+
+ [DBus (name = "RegisterAgent")]
+ public abstract void register_agent(GLib.ObjectPath agent, string capability) throws DBusError, IOError;
+
+ [DBus (name = "UnregisterAgent")]
+ public abstract void unregister_agent(GLib.ObjectPath agent) throws DBusError, IOError;
+
+ [DBus (name = "PropertyChanged")]
+ public signal void property_changed(string name, GLib.Variant value);
+
+ [DBus (name = "DeviceCreated")]
+ public signal void device_created(GLib.ObjectPath device);
+
+ [DBus (name = "DeviceRemoved")]
+ public signal void device_removed(GLib.ObjectPath device);
+
+ [DBus (name = "DeviceFound")]
+ public signal void device_found(string address, GLib.HashTable<string, GLib.Variant> values);
+
+ [DBus (name = "DeviceDisappeared")]
+ public signal void device_disappeared(string address);
+ }
+
+ [DBus (name = "org.bluez.Agent", timeout = 120000)]
+ public interface Agent : GLib.Object {
+
+ [DBus (name = "Authorize")]
+ public abstract void authorize(GLib.ObjectPath device, string uuid) throws DBusError, IOError;
+
+ [DBus (name = "RequestPinCode")]
+ public abstract string request_pin_code(GLib.ObjectPath device) throws DBusError, IOError;
+
+ [DBus (name = "DisplayPasskey")]
+ public abstract void display_passkey(GLib.ObjectPath device, uint passkey) throws DBusError, IOError;
+
+ [DBus (name = "Release")]
+ public abstract void release() throws DBusError, IOError;
+
+ [DBus (name = "Cancel")]
+ public abstract void cancel() throws DBusError, IOError;
+
+ [DBus (name = "RequestConfirmation")]
+ public abstract void request_confirmation(GLib.ObjectPath device, uint passkey) throws DBusError, IOError;
+
+ [DBus (name = "ConfirmModeChange")]
+ public abstract void confirm_mode_change(string mode) throws DBusError, IOError;
+
+ [DBus (name = "RequestPasskey")]
+ public abstract uint request_passkey(GLib.ObjectPath device) throws DBusError, IOError;
+ }
+
+ [DBus (name = "org.bluez.Agent", timeout = 120000)]
+ public interface AgentSync : GLib.Object {
+
+ [DBus (name = "Authorize")]
+ public abstract void authorize(GLib.ObjectPath device, string uuid) throws DBusError, IOError;
+
+ [DBus (name = "RequestPinCode")]
+ public abstract string request_pin_code(GLib.ObjectPath device) throws DBusError, IOError;
+
+ [DBus (name = "DisplayPasskey")]
+ public abstract void display_passkey(GLib.ObjectPath device, uint passkey) throws DBusError, IOError;
+
+ [DBus (name = "Release")]
+ public abstract void release() throws DBusError, IOError;
+
+ [DBus (name = "Cancel")]
+ public abstract void cancel() throws DBusError, IOError;
+
+ [DBus (name = "RequestConfirmation")]
+ public abstract void request_confirmation(GLib.ObjectPath device, uint passkey) throws DBusError, IOError;
+
+ [DBus (name = "ConfirmModeChange")]
+ public abstract void confirm_mode_change(string mode) throws DBusError, IOError;
+
+ [DBus (name = "RequestPasskey")]
+ public abstract uint request_passkey(GLib.ObjectPath device) throws DBusError, IOError;
+ }
+
+ [DBus (name = "org.bluez.Device", timeout = 120000)]
+ public interface Device : GLib.Object {
+
+ [DBus (name = "GetProperties")]
+ public abstract GLib.HashTable<string, GLib.Variant> get_properties() throws DBusError, IOError;
+
+ [DBus (name = "SetProperty")]
+ public abstract void set_property(string name, GLib.Variant value) throws DBusError, IOError;
+
+ [DBus (name = "DiscoverServices")]
+ public abstract GLib.HashTable<uint, string> discover_services(string pattern) throws DBusError, IOError;
+
+ [DBus (name = "CancelDiscovery")]
+ public abstract void cancel_discovery() throws DBusError, IOError;
+
+ [DBus (name = "Disconnect")]
+ public abstract void disconnect() throws DBusError, IOError;
+
+ [DBus (name = "PropertyChanged")]
+ public signal void property_changed(string name, GLib.Variant value);
+
+ [DBus (name = "DisconnectRequested")]
+ public signal void disconnect_requested();
+ }
+
+ [DBus (name = "org.bluez.Device", timeout = 120000)]
+ public interface DeviceSync : GLib.Object {
+
+ [DBus (name = "GetProperties")]
+ public abstract GLib.HashTable<string, GLib.Variant> get_properties() throws DBusError, IOError;
+
+ [DBus (name = "SetProperty")]
+ public abstract void set_property(string name, GLib.Variant value) throws DBusError, IOError;
+
+ [DBus (name = "DiscoverServices")]
+ public abstract GLib.HashTable<uint, string> discover_services(string pattern) throws DBusError, IOError;
+
+ [DBus (name = "CancelDiscovery")]
+ public abstract void cancel_discovery() throws DBusError, IOError;
+
+ [DBus (name = "Disconnect")]
+ public abstract void disconnect() throws DBusError, IOError;
+
+ [DBus (name = "PropertyChanged")]
+ public signal void property_changed(string name, GLib.Variant value);
+
+ [DBus (name = "DisconnectRequested")]
+ public signal void disconnect_requested();
+ }
+ }
+}
diff --git a/src/phone.vala b/src/phone.vala
new file mode 100644
index 0000000..43ca554
--- /dev/null
+++ b/src/phone.vala
@@ -0,0 +1,76 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+class Phone: Profile
+{
+ SimpleActionGroup action_group;
+
+ public Phone (Bluetooth bluetooth, SimpleActionGroup action_group)
+ {
+ const string profile_name = "phone";
+ base (bluetooth, profile_name);
+
+ this.bluetooth = bluetooth;
+ this.action_group = action_group;
+
+ // build the static actions
+ Action[] actions = {};
+ actions += root_action;
+ actions += create_enabled_action (bluetooth);
+ actions += create_settings_action ();
+ foreach (var a in actions)
+ action_group.insert (a);
+
+ var section = new Menu ();
+ section.append_item (create_enabled_menuitem ());
+ section.append (_("Bluetooth settings…"),
+ "indicator.phone-show-settings::bluetooth");
+ menu.append_section (null, section);
+
+ // know when to show the indicator & when to hide it
+ bluetooth.notify.connect (() => update_visibility());
+ update_visibility ();
+
+ bluetooth.notify.connect (() => update_root_action_state());
+ }
+
+ void update_visibility ()
+ {
+ visible = bluetooth.powered && !bluetooth.blocked;
+ }
+
+ ///
+ /// Actions
+ ///
+
+ void show_settings (string panel)
+ {
+ spawn_command_line_async ("system-settings " + panel);
+ }
+
+ Action create_settings_action ()
+ {
+ var action = new SimpleAction ("phone-show-settings", VariantType.STRING);
+
+ action.activate.connect ((action, panel)
+ => show_settings (panel.get_string()));
+
+ return action;
+ }
+}
diff --git a/src/profile.vala b/src/profile.vala
new file mode 100644
index 0000000..c288b4e
--- /dev/null
+++ b/src/profile.vala
@@ -0,0 +1,159 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+class Profile: Object
+{
+ protected Bluetooth bluetooth;
+ protected string profile_name;
+ protected Menu root;
+ protected Menu menu;
+ private uint menu_export_id;
+ protected SimpleAction root_action;
+
+ protected bool visible { get; set; default = true; }
+
+ public Profile (Bluetooth bluetooth, string profile_name)
+ {
+ this.bluetooth = bluetooth;
+ this.profile_name = profile_name;
+
+ root_action = new SimpleAction.stateful (@"root-$profile_name",
+ null,
+ action_state_for_root());
+ notify["visible"].connect (() => update_root_action_state());
+
+ menu = new Menu ();
+
+ var item = create_root_menuitem ();
+ item.set_submenu (menu);
+
+ root = new Menu ();
+ root.append_item (item);
+ }
+
+ public void export_menu (DBusConnection connection, string object_path)
+ {
+ try
+ {
+ debug (@"exporting menu '$profile_name'");
+ menu_export_id = connection.export_menu_model (object_path, root);
+ }
+ catch (Error e)
+ {
+ critical (@"Unable to export menu on $object_path: $(e.message)");
+ }
+ }
+
+ public void unexport_menu (DBusConnection connection)
+ {
+ if (menu_export_id != 0)
+ {
+ debug (@"unexporting menu '$profile_name'");
+ connection.unexport_menu_model (menu_export_id);
+ menu_export_id = 0;
+ }
+ }
+
+ protected void spawn_command_line_async (string command)
+ {
+ try {
+ Process.spawn_command_line_async (command);
+ } catch (Error e) {
+ warning (@"Unable to launch '$command': $(e.message)");
+ }
+ }
+
+ ///
+ /// Menu Items
+ ///
+
+ protected MenuItem create_enabled_menuitem ()
+ {
+ var item = new MenuItem ("Bluetooth", "indicator.bluetooth-enabled");
+
+ item.set_attribute ("x-canonical-type", "s",
+ "com.canonical.indicator.switch");
+
+ return item;
+ }
+
+ private MenuItem create_root_menuitem ()
+ {
+ var item = new MenuItem (null, @"indicator.root-$profile_name");
+
+ item.set_attribute ("x-canonical-type", "s",
+ "com.canonical.indicator.root");
+
+ return item;
+ }
+
+ ///
+ /// Actions
+ ///
+
+ protected Action create_enabled_action (Bluetooth bluetooth)
+ {
+ var action = new SimpleAction.stateful ("bluetooth-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;
+ }
+
+ protected void update_root_action_state ()
+ {
+ root_action.set_state (action_state_for_root ());
+ }
+
+ protected Variant action_state_for_root ()
+ {
+ var blocked = bluetooth.blocked;
+ var powered = bluetooth.powered;
+
+ 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.boolean (visible));
+ builder.add ("{sv}", "accessible-desc", new Variant.string (a11y));
+ builder.add ("{sv}", "icon", icon.serialize());
+ return builder.end ();
+ }
+}
diff --git a/src/service.vala b/src/service.vala
new file mode 100644
index 0000000..20fdd1e
--- /dev/null
+++ b/src/service.vala
@@ -0,0 +1,108 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ * Robert Ancell <robert.ancell@canonical.com>
+ */
+
+/**
+ * Boilerplate class to own the name on the bus,
+ * to create the profiles, and to export them on the bus.
+ */
+public class Service: Object
+{
+ private MainLoop loop;
+ private SimpleActionGroup actions;
+ private HashTable<string,Profile> profiles;
+ private DBusConnection connection;
+ private uint exported_action_id;
+ private const string OBJECT_PATH = "/com/canonical/indicator/bluetooth";
+
+ private void unexport ()
+ {
+ if (connection != null)
+ {
+ profiles.for_each ((name, profile)
+ => profile.unexport_menu (connection));
+
+ if (exported_action_id != 0)
+ {
+ debug (@"unexporting action group '$(OBJECT_PATH)'");
+ connection.unexport_action_group (exported_action_id);
+ exported_action_id = 0;
+ }
+ }
+ }
+
+ public Service (Bluetooth bluetooth)
+ {
+ actions = new SimpleActionGroup ();
+
+ profiles = new HashTable<string,Profile> (str_hash, str_equal);
+ profiles.insert ("phone", new Phone (bluetooth, actions));
+ profiles.insert ("desktop", new Desktop (bluetooth, actions));
+ }
+
+ public int run ()
+ {
+ if (loop != null)
+ {
+ warning ("service is already running");
+ return Posix.EXIT_FAILURE;
+ }
+
+ var own_name_id = Bus.own_name (BusType.SESSION,
+ "com.canonical.indicator.bluetooth",
+ BusNameOwnerFlags.NONE,
+ on_bus_acquired,
+ null,
+ on_name_lost);
+
+ loop = new MainLoop (null, false);
+ loop.run ();
+
+ // cleanup
+ unexport ();
+ Bus.unown_name (own_name_id);
+ return Posix.EXIT_SUCCESS;
+ }
+
+ void on_bus_acquired (DBusConnection connection, string name)
+ {
+ debug (@"bus acquired: $name");
+ this.connection = connection;
+
+ try
+ {
+ debug (@"exporting action group '$(OBJECT_PATH)'");
+ exported_action_id = connection.export_action_group (OBJECT_PATH,
+ actions);
+ }
+ catch (Error e)
+ {
+ critical (@"Unable to export actions on $OBJECT_PATH: $(e.message)");
+ }
+
+ profiles.for_each ((name, profile)
+ => profile.export_menu (connection, @"$OBJECT_PATH/$name"));
+ }
+
+ void on_name_lost (DBusConnection connection, string name)
+ {
+ debug (@"name lost: $name");
+ loop.quit ();
+ }
+}