diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2013-08-10 04:26:27 +0000 |
---|---|---|
committer | Tarmac <> | 2013-08-10 04:26:27 +0000 |
commit | f28653eb1a4a310987fffc44334b93da3f547d41 (patch) | |
tree | 82a72bfbb7eeca76766d73ae20f1152c5b8392c3 | |
parent | 0391025f72e9e5fed972b40e63087635d50c9234 (diff) | |
parent | a0616908a817d6fad47c29ae703fc8a2ea379af6 (diff) | |
download | ayatana-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.
-rw-r--r-- | Makefile.am | 7 | ||||
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | configure.ac | 71 | ||||
-rw-r--r-- | data/Makefile.am | 8 | ||||
-rw-r--r-- | data/com.canonical.indicator.bluetooth | 10 | ||||
-rw-r--r-- | debian/control | 9 | ||||
-rw-r--r-- | po/POTFILES.in | 15 | ||||
-rw-r--r-- | po/indicator-bluetooth.pot | 72 | ||||
-rw-r--r-- | src/Makefile.am | 55 | ||||
-rw-r--r-- | src/bluetooth.vala | 80 | ||||
-rw-r--r-- | src/bluez.vala | 397 | ||||
-rw-r--r-- | src/config.vapi | 3 | ||||
-rw-r--r-- | src/desktop.vala | 286 | ||||
-rw-r--r-- | src/device.vala | 156 | ||||
-rw-r--r-- | src/indicator-bluetooth-service.vala | 443 | ||||
-rw-r--r-- | src/indicator-bluetooth.vala | 142 | ||||
-rw-r--r-- | src/indicator3-0.4.vapi | 145 | ||||
-rw-r--r-- | src/killswitch.vala | 167 | ||||
-rw-r--r-- | src/libido3-0.1.vapi | 10 | ||||
-rw-r--r-- | src/main.vala | 34 | ||||
-rw-r--r-- | src/org-bluez.vala | 293 | ||||
-rw-r--r-- | src/phone.vala | 76 | ||||
-rw-r--r-- | src/profile.vala | 159 | ||||
-rw-r--r-- | src/service.vala | 108 | ||||
-rw-r--r-- | vapi/config.vapi | 8 | ||||
-rw-r--r-- | vapi/gnome-bluetooth-1.0.vapi (renamed from src/gnome-bluetooth-1.0.vapi) | 0 | ||||
-rw-r--r-- | vapi/rfkill.vapi | 44 |
27 files changed, 1903 insertions, 901 deletions
diff --git a/Makefile.am b/Makefile.am index 4fa38ad..ef681b3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,10 @@ SUBDIRS = data po src -EXTRA_DIST = autogen.sh NEWS +EXTRA_DIST = \ + autogen.sh \ + NEWS \ + vapi/config.vapi \ + vapi/gnome-bluetooth-1.0.vapi \ + vapi/rfkill.vapi DISTCHECK_CONFIGURE_FLAGS = --enable-localinstall @@ -1,3 +1,9 @@ +Overview of changes in indicator-bluetooth 13.10.0 + + * Add phone profile + * Remove dbusmenu, gtk, libindicator dependencies + * Remove gnome-bluetooth runtime dependency in phone profile + Overview of changes in indicator-bluetooth 0.0.6 * Add "Set Up New Device" menu item diff --git a/configure.ac b/configure.ac index b411913..6eba857 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,6 @@ -AC_INIT(indicator-bluetooth, 0.0.6) +AC_INIT([indicator-bluetooth],[13.10.0]) AM_INIT_AUTOMAKE([1.11 no-dist-gzip dist-xz foreign]) +AC_CONFIG_HEADERS([config.h]) AM_SILENT_RULES([yes]) AM_MAINTAINER_MODE @@ -13,54 +14,40 @@ dnl ########################################################################### dnl Dependencies dnl ########################################################################### -PKG_CHECK_MODULES(INDICATOR_BLUETOOTH_SERVICE, [ - glib-2.0 - gnome-bluetooth-1.0 - indicator3-0.4 - dbusmenu-gtk3-0.4 -]) - -PKG_CHECK_MODULES(INDICATOR_BLUETOOTH, [ - glib-2.0 - gtk+-3.0 - indicator3-0.4 - dbusmenu-gtk3-0.4 - libido3-0.1 -]) +GLIB_REQUIRED_VERSION=2.36 +GIO_REQUIRED_VERSION=2.36 -dnl ########################################################################### -dnl Internationalization -dnl ########################################################################### +PKG_CHECK_MODULES(SERVICE_DEPS, [glib-2.0 >= $GLIB_REQUIRED_VERSION + gio-unix-2.0 >= $GIO_REQUIRED_VERSION]) -IT_PROG_INTLTOOL([0.35.0]) -AC_SUBST(GETTEXT_PACKAGE, indicator-bluetooth) +dnl ############################## +dnl # Custom Junk +dnl ############################## -with_localinstall="no" -AC_ARG_ENABLE(localinstall, AS_HELP_STRING([--enable-localinstall], [install all of the files localy instead of system directories (for distcheck)]), with_localinstall=$enableval, with_localinstall=no) +AC_DEFUN([AC_DEFINE_PATH], [ + test "x$prefix" = xNONE && prefix="$ac_default_prefix" + test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + ac_define_path=`eval echo [$]$2` + ac_define_path=`eval echo [$]ac_define_path` + $1="$ac_define_path" + AC_SUBST($1) + ifelse($3, , + AC_DEFINE_UNQUOTED($1, "$ac_define_path"), + AC_DEFINE_UNQUOTED($1, "$ac_define_path", $3)) +]) -dnl ########################################################################### -dnl Indicator Info -dnl ########################################################################### +dnl ########################### +dnl # Internationalization +dnl ########################### -AS_IF([test "x$with_localinstall" = "xyes"], - [ - INDICATORDIR="${libdir}/indicators3/7/" - ], - [ - INDICATORDIR=`$PKG_CONFIG --variable=indicatordir indicator3-0.4` - ]) -AC_SUBST(INDICATORDIR) +IT_PROG_INTLTOOL([0.41.0]) -dnl ########################################################################### -dnl DBus Service Info -dnl ########################################################################### +GETTEXT_PACKAGE=indicator-bluetooth +AC_SUBST(GETTEXT_PACKAGE) +AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [Name of the default get text domain]) +AC_DEFINE_PATH(GNOMELOCALEDIR, "${datadir}/locale", [locale directory]) -if test "x$with_localinstall" = "xyes"; then - DBUSSERVICEDIR="${datadir}/dbus-1/services/" -else - DBUSSERVICEDIR=`$PKG_CONFIG --variable=session_bus_services_dir dbus-1` -fi -AC_SUBST(DBUSSERVICEDIR) +AM_GLIB_GNU_GETTEXT dnl ########################################################################### dnl Files to generate diff --git a/data/Makefile.am b/data/Makefile.am index 6f16e85..fc9beee 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -1,4 +1,10 @@ -dbus_servicesdir = $(DBUSSERVICEDIR) + +# the indicator bus file +indicatorsdir = $(datadir)/unity/indicators +dist_indicators_DATA = com.canonical.indicator.bluetooth + +# the dbus service file +dbus_servicesdir = $(datadir)/dbus-1/services dbus_services_DATA = indicator-bluetooth.service %.service: %.service.in diff --git a/data/com.canonical.indicator.bluetooth b/data/com.canonical.indicator.bluetooth new file mode 100644 index 0000000..746157a --- /dev/null +++ b/data/com.canonical.indicator.bluetooth @@ -0,0 +1,10 @@ +[Indicator Service] +Name=indicator-bluetooth +ObjectPath=/com/canonical/indicator/bluetooth +Position=70 + +[desktop] +ObjectPath=/com/canonical/indicator/bluetooth/desktop + +[phone] +ObjectPath=/com/canonical/indicator/bluetooth/phone diff --git a/debian/control b/debian/control index 07bc74d..baffbf4 100644 --- a/debian/control +++ b/debian/control @@ -6,12 +6,7 @@ Build-Depends: debhelper (>= 9.0), dh-autoreconf, dh-translations, gnome-common, - libdbusmenu-gtk3-dev, libglib2.0-dev, - libgnome-bluetooth-dev, - libgtk-3-dev, - libido3-0.1-dev, - libindicator3-dev, valac (>=0.18), Standards-Version: 3.9.4 Homepage: https://launchpad.net/indicator-bluetooth @@ -25,7 +20,9 @@ Package: indicator-bluetooth Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, - gnome-bluetooth, + bluez (>= 4.36), + ubuntu-system-settings | gnome-control-center, + ubuntu-system-settings | gnome-bluetooth, Replaces: gnome-bluetooth (<< 3.6.1-0ubuntu2) Breaks: gnome-bluetooth (<< 3.6.1-0ubuntu2) Description: System bluetooth indicator. diff --git a/po/POTFILES.in b/po/POTFILES.in index 634553a..c09e954 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -2,5 +2,16 @@ # Please keep this file in alphabetical order. [encoding: UTF-8] data/com.canonical.indicator.bluetooth.gschema.xml.in -src/indicator-bluetooth.vala -src/indicator-bluetooth-service.vala +src/bluetooth.vala +src/bluez.c +src/bluez.vala +src/desktop.c +src/desktop.vala +src/device.vala +src/killswitch.vala +src/main.vala +src/org-bluez.vala +src/phone.c +src/phone.vala +src/profile.vala +src/service.vala diff --git a/po/indicator-bluetooth.pot b/po/indicator-bluetooth.pot deleted file mode 100644 index 9559462..0000000 --- a/po/indicator-bluetooth.pot +++ /dev/null @@ -1,72 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-02-08 11:56+1300\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" -"Language-Team: LANGUAGE <LL@li.org>\n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: ../data/com.canonical.indicator.bluetooth.gschema.xml.in.h:1 -msgid "Whether or not to show the bluetooth indicator in the menu bar." -msgstr "" - -#: ../src/indicator-bluetooth.vala:17 -#: ../src/indicator-bluetooth-service.vala:231 -msgid "Bluetooth: On" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:54 -#: ../src/indicator-bluetooth-service.vala:438 -msgid "Bluetooth" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:68 -msgid "Visible" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:117 -msgid "Set Up New Device…" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:122 -msgid "Bluetooth Settings…" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:231 -msgid "Bluetooth: Off" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:290 -msgid "Connection" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:313 -msgid "Send files…" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:321 -msgid "Browse files…" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:330 -msgid "Keyboard Settings…" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:338 -msgid "Mouse and Touchpad Settings…" -msgstr "" - -#: ../src/indicator-bluetooth-service.vala:347 -msgid "Sound Settings…" -msgstr "" 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/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 (); + } +} diff --git a/vapi/config.vapi b/vapi/config.vapi new file mode 100644 index 0000000..15c4c88 --- /dev/null +++ b/vapi/config.vapi @@ -0,0 +1,8 @@ +[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "../config.h")] +namespace Config { + public const string GETTEXT_PACKAGE; + public const string GNOMELOCALEDIR; + public const string PKGDATADIR; + public const string PACKAGE_NAME; + public const string PACKAGE_VERSION; +} diff --git a/src/gnome-bluetooth-1.0.vapi b/vapi/gnome-bluetooth-1.0.vapi index 6d6aa09..6d6aa09 100644 --- a/src/gnome-bluetooth-1.0.vapi +++ b/vapi/gnome-bluetooth-1.0.vapi diff --git a/vapi/rfkill.vapi b/vapi/rfkill.vapi new file mode 100644 index 0000000..7bfd16d --- /dev/null +++ b/vapi/rfkill.vapi @@ -0,0 +1,44 @@ +// (C) Michael 'Mickey' Lauer <mickey@vanille-media.de> +// LGPL2 +// scheduled for inclusion in linux.vapi + +namespace Linux +{ + /* + * RfKill + */ + [CCode (cname = "struct rfkill_event", cheader_filename = "linux/rfkill.h")] + public struct RfKillEvent { + public uint32 idx; + public RfKillType type; + public RfKillOp op; + public uint8 soft; + public uint8 hard; + } + + [CCode (cname = "guint8", cprefix = "RFKILL_OP_", cheader_filename = "linux/rfkill.h")] + public enum RfKillOp { + ADD, + DEL, + CHANGE, + CHANGE_ALL + } + + [CCode (cname = "guint8", cprefix = "RFKILL_STATE_", cheader_filename = "linux/rfkill.h")] + public enum RfKillState { + SOFT_BLOCKED, + UNBLOCKED, + HARD_BLOCKED + } + + [CCode (cname = "guint8", cprefix = "RFKILL_TYPE_", cheader_filename = "linux/rfkill.h")] + public enum RfKillType { + ALL, + WLAN, + BLUETOOTH, + UWB, + WIMAX, + WWAN + } + +} /* namespace Linux */ |