aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am8
-rw-r--r--src/bluetooth.vala191
-rw-r--r--src/bluez.vala288
-rw-r--r--src/desktop.vala192
-rw-r--r--src/killswitch.vala7
-rw-r--r--src/main.vala2
-rw-r--r--src/phone.vala2
-rw-r--r--src/profile.vala4
-rw-r--r--src/service.vala421
9 files changed, 489 insertions, 626 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index b5aa8f1..87f4231 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -5,6 +5,7 @@ indicator_bluetooth_service_SOURCES = \
bluetooth.vala \
bluez.vala \
desktop.vala \
+ device.vala \
main.vala \
phone.vala \
profile.vala \
@@ -15,18 +16,17 @@ indicator_bluetooth_service_VALAFLAGS = \
--ccode \
--vapidir=$(top_srcdir)/vapi/ \
--vapidir=./ \
- --pkg indicator3-0.4 \
- --pkg gnome-bluetooth-1.0 \
--pkg config \
--pkg rfkill \
--pkg posix \
--pkg glib-2.0 \
- --pkg gtk+-3.0 \
- --pkg Dbusmenu-0.4
+ --pkg gtk+-3.0
+# -w to disable warnings for vala-generated code
indicator_bluetooth_service_CFLAGS = \
-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
-DLOCALE_DIR=\"$(datadir)/locale\" \
+ -w \
$(INDICATOR_BLUETOOTH_SERVICE_CFLAGS)
indicator_bluetooth_service_LDADD = \
diff --git a/src/bluetooth.vala b/src/bluetooth.vala
index 1952d1c..98464a4 100644
--- a/src/bluetooth.vala
+++ b/src/bluetooth.vala
@@ -17,173 +17,64 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
+
/**
- * Base class for the bluetooth backend.
+ * Abstract interface for the Bluetooth backend.
*/
-public class Bluetooth: Object
+public interface Bluetooth: Object
{
- /* whether or not our system can be seen by other bluetooth devices */
- public bool discoverable { get; protected set; default = false; }
- public virtual void try_set_discoverable (bool b) {}
+ /* 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; }
- /* whether or not there are any bluetooth adapters powered up on the system */
- public bool powered { get; protected set; default = false; }
+ /* 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);
- /* whether or not bluetooth's been disabled,
- either by a software setting or physical hardware switch */
- public bool blocked { get; protected set; default = true; }
- public virtual void try_set_blocked (bool b) {
- killswitch.try_set_blocked (b);
- }
+ /* 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; }
- public class Device: Object {
- public string name { get; construct; }
- public bool supports_browsing { get; construct; }
- public bool supports_file_transfer { get; construct; }
- public Device (string name,
- bool supports_browsing,
- bool supports_file_transfer) {
- Object (name: name,
- supports_browsing: supports_browsing,
- supports_file_transfer: supports_file_transfer);
- }
- }
-
- private static uint16 get_uuid16_from_uuid (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;
- }
-
- protected static bool uuid_supports_file_transfer (string uuid)
- {
- return get_uuid16_from_uuid (uuid) == 0x1105; // OBEXObjectPush
- }
-
- protected static bool uuid_supports_browsing (string uuid)
- {
- return get_uuid16_from_uuid (uuid) == 0x1106; // OBEXFileTransfer
- }
-
- public enum DeviceType
- {
- COMPUTER,
- PHONE,
- MODEM,
- NETWORK,
- HEADSET,
- HEADPHONES,
- VIDEO,
- OTHER_AUDIO,
- JOYPAD,
- KEYPAD,
- KEYBOARD,
- TABLET,
- MOUSE,
- PRINTER,
- CAMERA
- }
-
- protected static DeviceType class_to_device_type (uint32 c)
- {
- switch ((c & 0x1f00) >> 8)
- {
- case 0x01:
- return DeviceType.COMPUTER;
+ /* 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);
- case 0x02:
- switch ((c & 0xfc) >> 2)
- {
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x05:
- return DeviceType.PHONE;
+ /* Get a list of the Device structs that we know about */
+ public abstract List<unowned Device> get_devices ();
- case 0x04:
- return DeviceType.MODEM;
- }
- break;
+ /* Emitted when one or more of the devices is added, removed, or changed */
+ public signal void devices_changed ();
- case 0x03:
- return DeviceType.NETWORK;
-
- case 0x04:
- switch ((c & 0xfc) >> 2)
- {
- case 0x01:
- case 0x02:
- return DeviceType.HEADSET;
-
- case 0x06:
- return DeviceType.HEADPHONES;
-
- case 0x0b: // vcr
- case 0x0c: // video camera
- case 0x0d: // camcorder
- return DeviceType.VIDEO;
-
- default:
- return DeviceType.OTHER_AUDIO;
- }
- //break;
-
- case 0x05:
- switch ((c & 0xc0) >> 6)
- {
- case 0x00:
- switch ((c & 0x1e) >> 2)
- {
- case 0x01:
- case 0x02:
- return DeviceType.JOYPAD;
- }
- break;
-
- case 0x01:
- return DeviceType.KEYBOARD;
-
- case 0x02:
- switch ((c & 0x1e) >> 2)
- {
- case 0x05:
- return DeviceType.TABLET;
-
- default:
- return DeviceType.MOUSE;
- }
- }
- break;
-
- case 0x06:
- if ((c & 0x80) != 0)
- return DeviceType.PRINTER;
- if ((c & 0x20) != 0)
- return DeviceType.CAMERA;
- break;
- }
-
- return 0;
- }
+ /* 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);
+}
- /***
- **** Killswitch Implementation
- ***/
+/**
+ * Base class for Bluetooth objects that use a killswitch to implement
+ * the 'discoverable' property.
+ */
+public abstract class KillswitchBluetooth: Object, Bluetooth
+{
private KillSwitch killswitch;
- public Bluetooth (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
index b0c8761..b4d7836 100644
--- a/src/bluez.vala
+++ b/src/bluez.vala
@@ -18,90 +18,295 @@
*/
/**
- * Bluetooth implementaion which uses bluez over dbus
+ * Bluetooth implementaion which uses org.bluez on DBus
*/
-public class Bluez: Bluetooth
+public class Bluez: KillswitchBluetooth
{
private org.bluez.Manager manager;
private org.bluez.Adapter default_adapter;
- private HashTable<string,Bluetooth.Device> devices;
+ private HashTable<string,org.bluez.Device> path_to_proxy;
+ private HashTable<string,uint> path_to_id;
+ private HashTable<uint,string> id_to_path;
+ private HashTable<uint,Device> id_to_device;
+ private uint next_device_id = 1;
public Bluez (KillSwitch killswitch)
{
base (killswitch);
- string default_adapter_object_path = null;
+ string adapter_path = null;
- this.devices = new HashTable<string,Bluetooth.Device>(str_hash, str_equal);
+ 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", "/");
-
manager.default_adapter_changed.connect ((object_path) => on_default_adapter_changed (object_path));
- default_adapter_object_path = manager.default_adapter ();
+ adapter_path = manager.default_adapter ();
}
catch (Error e)
- {
- critical ("%s", e.message);
- }
+ {
+ critical (@"$(e.message)");
+ }
- on_default_adapter_changed (default_adapter_object_path);
+ on_default_adapter_changed (adapter_path);
}
private void on_default_adapter_changed (string? object_path)
{
if (object_path != null) try
{
- message ("using default adapter at %s", object_path);
+ message (@"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));
- default_adapter.device_removed.connect((adapter, path) => devices.remove (path));
foreach (string device_path in default_adapter.list_devices())
add_device (device_path);
}
catch (Error e)
{
- critical ("%s", e.message);
+ critical (@"$(e.message)");
}
this.on_default_adapter_properties_changed ();
}
- private void add_device (string object_path)
+ 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 (uint16 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 (uint16 uuid16 in uuids)
+ if (uuid16 == 0x1106) // OBEXFileTransfer
+ return true;
+
+ return false;
+ }
+
+ /* headsets, audio sinks, and input devices are connectable.
+ *
+ * TODO: this duplicates the behavior of the indicator from when it used
+ * gnome-bluetooth as a backend. Are there other interfaces we care about? */
+ private DBusInterfaceInfo[] get_connectable_interfaces (DBusProxy device)
+ {
+ DBusInterfaceInfo[] connectable_interfaces = {};
+
try
{
- org.bluez.Device device = Bus.get_proxy_sync (BusType.SYSTEM, "org.bluez", object_path);
- message ("got device proxy for %s", object_path);
- var properties = device.get_properties ();
+ var iname = "org.freedesktop.DBus.Introspectable.Introspect";
+ var intro = device.call_sync (iname, null, DBusCallFlags.NONE, -1);
- Variant v = properties.lookup ("Alias");
- if (v == null)
- v = properties.lookup ("Name");
- string name = v == null ? _("Unknown") : v.get_string();
+ if ((intro != null) && (intro.n_children() > 0))
+ {
+ string xml = intro.get_child_value(0).get_string();
+ var info = new DBusNodeInfo.for_xml (xml);
+ if (info != null)
+ {
+ foreach (DBusInterfaceInfo 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)");
+ }
- bool supports_browsing = false;
- v = properties.lookup ("UUIDs");
- message ("%s", v.print(true));
+ return connectable_interfaces;
+ }
- bool supports_file_transfer = false;
+ private bool device_is_connectable (DBusProxy device)
+ {
+ var connectable_interfaces = get_connectable_interfaces (device);
+ return connectable_interfaces.length > 0;
+ }
- //protected static bool uuid_supports_file_transfer (string uuid)
- //protected static bool uuid_supports_browsing (string uuid)
+ private void device_connect (DBusProxy proxy)
+ {
+ var connection = proxy.get_connection ();
+ var object_path = proxy.get_object_path ();
- var dev = new Bluetooth.Device (name,
- supports_browsing,
- supports_file_transfer);
- devices.insert (object_path, dev);
- message ("devices.size() is %u", devices.size());
+ foreach (var i in get_connectable_interfaces (proxy))
+ {
+ try
+ {
+ debug (@"trying to connect to $object_path: $(i.name)");
+ connection.call_sync ("org.bluez",
+ object_path,
+ i.name,
+ "Connect",
+ null,
+ null,
+ DBusCallFlags.NONE,
+ -1);
+ }
+ catch (Error e)
+ {
+ debug (@"Unable to call $(i.name).Connect() on $(proxy.get_object_path()): $(e.message)");
+ }
+ }
+ }
+
+ 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)");
+ }
+ }
+ }
+
+ 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");
+ string name = v == null ? _("Unknown") : v.get_string ();
+
+ // look up the device's bus address
+ v = properties.lookup ("Address");
+ string address = v.get_string ();
+
+ // look up the device's bus address
+ Icon icon;
+ v = properties.lookup ("Icon");
+ if (v == null)
+ icon = new ThemedIcon ("unknown");
+ else
+ icon = new ThemedIcon (v.get_string());
+
+ // 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");
+ bool 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 (string 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 override void set_device_connected (uint id, bool connected)
+ {
+ var device = id_to_device.lookup (id);
+ var object_path = id_to_path.lookup (id);
+ var proxy = (object_path != null) ? path_to_proxy.lookup (object_path) : null;
+
+ if ((proxy != null) && (device != null) && (device.is_connected != connected))
+ {
+ if (connected)
+ {
+ device_connect (proxy as DBusProxy);
+ }
+ else // disconnect
+ {
+ try
+ {
+ proxy.disconnect ();
+ }
+ catch (Error e)
+ {
+ critical (@"Unable to disconnect $object_path: $(e.message)");
+ }
+ }
}
- catch (Error e)
- {
- critical ("%s", e.message);
- }
}
private void on_default_adapter_properties_changed ()
@@ -121,7 +326,7 @@ public class Bluez: Bluetooth
}
catch (Error e)
{
- critical ("%s", e.message);
+ critical (@"$(e.message)");
}
this.powered = is_powered;
@@ -136,7 +341,12 @@ public class Bluez: Bluetooth
}
catch (Error e)
{
- critical ("%s", e.message);
+ critical (@"$(e.message)");
}
}
+
+ public override List<unowned Device> get_devices ()
+ {
+ return id_to_device.get_values();
+ }
}
diff --git a/src/desktop.vala b/src/desktop.vala
index a7b8995..31fe9ac 100644
--- a/src/desktop.vala
+++ b/src/desktop.vala
@@ -19,11 +19,14 @@
class Desktop: Profile
{
+ private uint idle_rebuild_id = 0;
private Settings settings;
private Bluetooth bluetooth;
private SimpleAction root_action;
private Action[] all_actions;
+ private Menu device_section;
+ private HashTable<uint,SimpleAction> connect_actions;
public override void add_actions_to_group (SimpleActionGroup group)
{
@@ -31,29 +34,136 @@ class Desktop: Profile
group.insert (all_actions[i]);
}
+ protected override void dispose ()
+ {
+ if (idle_rebuild_id != 0)
+ {
+ Source.remove (idle_rebuild_id);
+ idle_rebuild_id = 0;
+ }
+
+ base.dispose ();
+ }
+
public Desktop (Bluetooth bluetooth)
{
base ("desktop");
this.bluetooth = bluetooth;
+ connect_actions = new HashTable<uint,SimpleAction>(direct_hash, direct_equal);
+
settings = new Settings ("com.canonical.indicator.bluetooth");
- root_action = new SimpleAction.stateful ("root-desktop", null, action_state_for_root());
+ root_action = create_root_action ();
all_actions = {};
all_actions += root_action;
all_actions += create_enabled_action (bluetooth);
all_actions += create_discoverable_action (bluetooth);
- all_actions += create_settings_action ();
all_actions += create_wizard_action ();
+ all_actions += create_browse_files_action ();
+ all_actions += create_send_file_action ();
+ all_actions += create_show_settings_action ();
+
+ build_menu ();
- bluetooth.notify.connect (() => update_root_action_state());
settings.changed["visible"].connect (()=> update_root_action_state());
+ bluetooth.notify.connect (() => update_root_action_state());
+ 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);
+ all_actions += 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");
@@ -63,12 +173,40 @@ class Desktop: Profile
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-settings");
+ 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);
@@ -89,15 +227,6 @@ class Desktop: Profile
return action;
}
- void spawn_command_line_async (string command)
- {
- try {
- Process.spawn_command_line_async (command);
- } catch (Error e) {
- warning ("unable to launch '%s': %s", command, e.message);
- }
- }
-
Action create_wizard_action ()
{
var action = new SimpleAction ("desktop-wizard", null);
@@ -105,10 +234,36 @@ class Desktop: Profile
return action;
}
- Action create_settings_action ()
+ Action create_browse_files_action ()
{
- var action = new SimpleAction ("desktop-settings", null);
- action.activate.connect (() => spawn_command_line_async ("gnome-control-center bluetooth"));
+ 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;
}
@@ -143,6 +298,11 @@ class Desktop: Profile
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 ());
diff --git a/src/killswitch.vala b/src/killswitch.vala
index cf5c6c8..fc7978c 100644
--- a/src/killswitch.vala
+++ b/src/killswitch.vala
@@ -22,8 +22,7 @@
* either by software (e.g., a session configuration setting)
* or by hardware (e.g., user disabled it via a physical switch on her laptop).
*
- * The Bluetooth class uses this as a backend for its 'blocked' property.
- * Other code can't even see this, so use Bluetooth.blocked instead. :)
+ * KillswitchBluetooth uses this as a backend for its Bluetooth.blocked property.
*/
public class KillSwitch: Object
{
@@ -50,7 +49,7 @@ public class RfKillSwitch: KillSwitch
var bwritten = Posix.write (fd, &event, sizeof(Linux.RfKillEvent));
if (bwritten == -1)
- warning ("Could not write rfkill event: %s", strerror(errno));
+ warning (@"Could not write rfkill event: $(strerror(errno))");
}
private class Entry
@@ -90,7 +89,7 @@ public class RfKillSwitch: KillSwitch
message ("fd is %d", fd);
if (fd == -1)
{
- warning (@"Can't open $path: $(strerror(errno)); KillSwitch disable");
+ warning (@"Can't open $path for use as a killswitch backend: $(strerror(errno))");
}
else
{
diff --git a/src/main.vala b/src/main.vala
index b88e94e..824e1d5 100644
--- a/src/main.vala
+++ b/src/main.vala
@@ -12,7 +12,7 @@ main (string[] args)
var bluetooth = new Bluez (new RfKillSwitch ());
// start the service
- var service = new BluetoothIndicator (bluetooth);
+ var service = new Service (bluetooth);
service.run ();
return Posix.EXIT_SUCCESS;
diff --git a/src/phone.vala b/src/phone.vala
index 8c9c816..34c10ab 100644
--- a/src/phone.vala
+++ b/src/phone.vala
@@ -48,7 +48,7 @@ class Phone: Profile
try {
Process.spawn_command_line_async ("system-settings bluetooth");
} catch (Error e) {
- warning ("unable to launch settings: %s", e.message);
+ warning (@"unable to launch settings: $(e.message)");
}
});
diff --git a/src/profile.vala b/src/profile.vala
index 6dd5f52..360722d 100644
--- a/src/profile.vala
+++ b/src/profile.vala
@@ -43,12 +43,12 @@ class Profile: Object
{
try
{
- message ("exporting '%s' on %s", name, object_path);
+ debug (@"exporting '$name' on $object_path");
connection.export_menu_model (object_path, this.root);
}
catch (Error e)
{
- critical ("%s", e.message);
+ critical (@"Unable to export menu on $object_path: $(e.message)");
}
}
}
diff --git a/src/service.vala b/src/service.vala
index 10f7f25..0cece83 100644
--- a/src/service.vala
+++ b/src/service.vala
@@ -8,27 +8,13 @@
* See http://www.gnu.org/copyleft/gpl.html the full text of the license.
*/
-public class BluetoothIndicator
+public class Service: Object
{
private MainLoop loop;
private SimpleActionGroup actions;
private HashTable<string,Profile> profiles;
- 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 (Bluetooth bluetooth)
+ public Service (Bluetooth bluetooth)
{
profiles = new HashTable<string,Profile> (str_hash, str_equal);
profiles.insert ("phone", new Phone (bluetooth));
@@ -39,107 +25,6 @@ public class BluetoothIndicator
profile.add_actions_to_group (actions);
}
- private void init_for_bus (DBusConnection bus)
- {
- this.bus = bus;
-
- ///
- ///
-
- indicator_service = new Indicator.Service ("com.canonical.indicator.bluetooth.old");
- 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 ();
- }
-
public int run ()
{
if (this.loop != null)
@@ -162,310 +47,28 @@ public class BluetoothIndicator
void on_bus_acquired (DBusConnection connection, string name)
{
- stdout.printf ("bus acquired: %s\n", name);
-
- init_for_bus (connection);
+ debug (@"bus acquired: $name");
+ var object_path = "/com/canonical/indicator/bluetooth";
try
{
- connection.export_action_group ("/com/canonical/indicator/bluetooth", this.actions);
+ connection.export_action_group (object_path, this.actions);
}
catch (Error e)
{
- critical ("%s", e.message);
+ critical (@"Unable to export actions on $object_path: $(e.message)");
}
- this.profiles.@foreach ((name,profile) => profile.export_menu (connection, @"/com/canonical/indicator/bluetooth/$name"));
+ this.profiles.for_each ((name,profile) => {
+ var path = @"/com/canonical/indicator/bluetooth/$name";
+ message (@"exporting menu '$path'");
+ profile.export_menu (connection, path);
+ });
}
void on_name_lost (DBusConnection connection, string name)
{
- stdout.printf ("name lost: %s\n", name);
+ debug (@"name lost: $name");
this.loop.quit ();
}
-
-
- 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++)
- {
-message ("alias %s uuid #%d: %s", alias, i, uuids[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);
- }
-}
-
-[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; }
- }
}