diff --git a/README.md b/README.md
index 9414934..1a90f39 100644
--- a/README.md
+++ b/README.md
@@ -40,15 +40,15 @@ org.freedesktop.tuhi1.Manager
initialization is independent of the Bluetooth pairing process. A Tuhi
paired device may or may not be paired over Bluetooth.
- Method: StartPairing() -> ()
- Start listening to available devices in pairing mode for an
+ Method: StartSearch() -> ()
+ Start searching for available devices in pairing mode for an
unspecified timeout. When the timeout expires or an error occurs, a
- PairingStopped signal is sent indicating success or error.
+ SearchStopped signal is sent indicating success or error.
- Method: StopPairing() -> ()
+ Method: StopSearch() -> ()
Stop listening to available devices in pairing mode. If called after
- StartPairing() and before a PairingStopped signal has been received,
- this method triggers the PairingStopped signal. That signal indicates
+ StartSearch() and before a Searchtopped signal has been received,
+ this method triggers the SearchStopped signal. That signal indicates
success or an error.
If this method is called before StartPairing() or after the
@@ -57,31 +57,21 @@ org.freedesktop.tuhi1.Manager
Note that between callling StopPairing() and the PairingStopped signal
arriving, PairableDevice signals may still arrive.
- Method: Pair(s) -> (i)
- Pairs the given device specified by its bluetooth MAC address, i.e.
- the value of "address" in the PairableDevice signal argument.
-
- Pairing a device may take a long time, a client must use asynchronous
- method invocation to avoid DBus timeouts.
-
- Invocations of Pair() before StartPairing() has been invoked or after a
- PairingStopped() signal may result in an error.
-
- Returns: 0 on success or a negative errno on failure
-
- Signal: PairableDevice(a{sv})
+ Signal: PairableDevice(o)
Indicates that a device is available for pairing. This signal may be
- sent after a StartPairing() call and before PairingStopped(). This
+ sent after a StartSearch() call and before SearchStopped(). This
signal is sent once per available device.
- The argument is a key-value dictionary, with keys as strings and value
- as key-dependent entity.
+ When this signal is sent, a org.freedesktop.tuhi1.Device object was
+ created, the object path is the argument to this signal.
- Tuhi guarantees that the following keys are available:
- * "name" - the device name as string
- * "address" - the device's Bluetooth MAC address as string
+ A client must immediately call Pair() on that object if pairing with
+ that object is desired. See the documentation for that interface
+ for details.
- Unknown keys must be ignored by a client.
+ When the search timeout expires, the device is removed by the daemon
+ again. Note that until the device is paired, the device is not listed
+ in the managers Devices property.
Signal: PairingStopped(i)
Sent when the pairing has stopped. An argument of 0 indicates a
@@ -125,6 +115,13 @@ org.freedesktop.tuhi1.Device
upon timeout, the property is set to False.
Read-only
+ Method: Pair() -> (i)
+ Pair the device. If the device is already paired, calls to this method
+ immediately return success.
+
+ Otherwise, the device is paired and this function returns success (0)
+ or a negative errno on failure.
+
Method: Listen() -> ()
Listen for data from this device. This method starts listening for
events on the device for an unspecified timeout. When the timeout
diff --git a/tuhi.py b/tuhi.py
index fd7afaa..d8af9ff 100755
--- a/tuhi.py
+++ b/tuhi.py
@@ -75,30 +75,38 @@ class TuhiDevice(GObject.Object):
real device) with the frontend DBusServer object that exports the device
over Tuhi's DBus interface
"""
- def __init__(self, bluez_device, tuhi_dbus_device):
+ def __init__(self, bluez_device, tuhi_dbus_device, paired=True):
GObject.Object.__init__(self)
self._tuhi_dbus_device = tuhi_dbus_device
self._wacom_device = WacomDevice(bluez_device)
self._wacom_device.connect('drawing', self._on_drawing_received)
self._wacom_device.connect('done', self._on_fetching_finished, bluez_device)
self.drawings = []
- self.pairing_mode = False
+ self.paired = paired
bluez_device.connect('connected', self._on_bluez_device_connected)
bluez_device.connect('disconnected', self._on_bluez_device_disconnected)
self._bluez_device = bluez_device
+ self._tuhi_dbus_device.connect('pair-requested', self._on_pair_requested)
+
def connect_device(self):
self._bluez_device.connect_device()
def _on_bluez_device_connected(self, bluez_device):
logger.debug('{}: connected'.format(bluez_device.address))
- self._wacom_device.start(self.pairing_mode)
+ self._wacom_device.start(not self.paired)
self.pairing_mode = False
def _on_bluez_device_disconnected(self, bluez_device):
logger.debug('{}: disconnected'.format(bluez_device.address))
+ def _on_pair_requested(self, dbus_device):
+ if self.paired:
+ return
+
+ self.connect_device()
+
def _on_drawing_received(self, device, drawing):
logger.debug('Drawing received')
d = TuhiDrawing(device.name, (0, 0), drawing.timestamp)
@@ -144,9 +152,8 @@ class Tuhi(GObject.Object):
GObject.Object.__init__(self)
self.server = TuhiDBusServer()
self.server.connect('bus-name-acquired', self._on_tuhi_bus_name_acquired)
- self.server.connect('pairing-start-requested', self._on_start_pairing_requested)
- self.server.connect('pairing-stop-requested', self._on_stop_pairing_requested)
- self.server.connect('pair-device-requested', self._on_pair_device_requested)
+ self.server.connect('search-start-requested', self._on_start_search_requested)
+ self.server.connect('search-stop-requested', self._on_stop_search_requested)
self.bluez = BlueZDeviceManager()
self.bluez.connect('device-added', self._on_bluez_device_added)
self.bluez.connect('device-updated', self._on_bluez_device_updated)
@@ -155,29 +162,22 @@ class Tuhi(GObject.Object):
self.devices = {}
- self._pairing_stop_handler = None
+ self._search_stop_handler = None
def _on_tuhi_bus_name_acquired(self, dbus_server):
self.bluez.connect_to_bluez()
- def _on_start_pairing_requested(self, dbus_server, stop_handler):
- self._pairing_stop_handler = stop_handler
+ def _on_start_search_requested(self, dbus_server, stop_handler):
+ self._search_stop_handler = stop_handler
self.bluez.start_discovery(timeout=30)
- def _on_stop_pairing_requested(self, dbus_server):
+ def _on_stop_search_requested(self, dbus_server):
# If you request to stop, you get a successful stop and we ignore
# anything the server does underneath
- self._pairing_stop_handler(0)
- self._pairing_stop_handler = None
+ self._search_stop_handler(0)
+ self._search_stop_handler = None
self.bluez.stop_discovery()
- self._pairable_device_handler = None
-
- def _on_pair_device_requested(self, dbusserver, bluez_device):
- tuhi_dbus_device = self.server.create_device(bluez_device)
- d = TuhiDevice(bluez_device, tuhi_dbus_device)
- d.pairing_mode = True
- self.devices[bluez_device.address] = d
- d.connect_device()
+ self._search_device_handler = None
@classmethod
def _is_pairing_device(cls, bluez_device):
@@ -201,19 +201,21 @@ class Tuhi(GObject.Object):
def _on_bluez_discovery_started(self, manager):
# Something else may turn discovery mode on, we don't care about
# it then
- if not self._pairing_stop_handler:
+ if not self._search_stop_handler:
return
def _on_bluez_discovery_stopped(self, manager):
- if self._pairing_stop_handler is not None:
- self._pairing_stop_handler(0)
+ if self._search_stop_handler is not None:
+ self._search_stop_handler(0)
def _on_bluez_device_updated(self, manager, bluez_device):
if bluez_device.vendor_id != WACOM_COMPANY_ID:
return
if Tuhi._is_pairing_device(bluez_device):
- self.server.notify_pairable_device(bluez_device)
+ tuhi_dbus_device = self.server.create_device(bluez_device, paired=False)
+ d = TuhiDevice(bluez_device, tuhi_dbus_device, paired=False)
+ self.devices[bluez_device.address] = d
def main(args):
diff --git a/tuhi/dbusserver.py b/tuhi/dbusserver.py
index f1f9b1a..1747b72 100755
--- a/tuhi/dbusserver.py
+++ b/tuhi/dbusserver.py
@@ -11,7 +11,6 @@
# GNU General Public License for more details.
#
-import os
import logging
from gi.repository import GObject, Gio, GLib
@@ -25,25 +24,20 @@ INTROSPECTION_XML = """
-
+
-
+
-
-
-
-
-
-
+
-
+
@@ -54,6 +48,10 @@ INTROSPECTION_XML = """
+
+
+
+
@@ -80,33 +78,49 @@ class TuhiDBusDevice(GObject.Object):
Class representing a DBus object for a Tuhi device. This class only
handles the DBus bits, communication with the device is done elsewhere.
"""
- def __init__(self, device, connection):
+ __gsignals__ = {
+ "pair-requested":
+ (GObject.SIGNAL_RUN_FIRST, None, ()),
+ }
+
+ def __init__(self, device, connection, paired=True):
GObject.Object.__init__(self)
self.name = device.name
self.btaddr = device.address
self.width, self.height = 0, 0
self.drawings = []
+ self.paired = paired
objpath = device.address.replace(':', '_')
self.objpath = "{}/{}".format(BASE_PATH, objpath)
- self._register_object(connection)
+ self._connection = connection
+ self._dbusid = self._register_object(connection)
+
+ def remove(self):
+ self._connection.unregister_object(self._dbusid)
+ self._dbusid = None
def _register_object(self, connection):
introspection = Gio.DBusNodeInfo.new_for_xml(INTROSPECTION_XML)
intf = introspection.lookup_interface(INTF_DEVICE)
- Gio.DBusConnection.register_object(connection,
- self.objpath,
- intf,
- self._method_cb,
- self._property_read_cb,
- self._property_write_cb)
+ return connection.register_object(self.objpath,
+ intf,
+ self._method_cb,
+ self._property_read_cb,
+ self._property_write_cb)
def _method_cb(self, connection, sender, objpath, interface, methodname, args, invocation):
if interface != INTF_DEVICE:
return None
- if methodname == 'Listen':
+ if methodname == 'Pair':
+ # FIXME: we should cache the method invocation here, wait for a
+ # successful result from Tuhi and then return the value
+ self._pair()
+ result = GLib.Variant.new_int32(0)
+ invocation.return_value(GLib.Variant.new_tuple(result))
+ elif methodname == 'Listen':
self._listen()
invocation.return_value()
elif methodname == 'GetJSONData':
@@ -133,6 +147,9 @@ class TuhiDBusDevice(GObject.Object):
def _property_write_cb(self):
pass
+ def _pair(self):
+ self.emit('pair-requested')
+
def _listen(self):
# FIXME: start listen asynchronously
# FIXME: update property when listen finishes
@@ -155,18 +172,13 @@ class TuhiDBusServer(GObject.Object):
(GObject.SIGNAL_RUN_FIRST, None, ()),
# Signal arguments:
- # pairing_stop_handler(status)
- # to be called when the pairing process has terminated, with
+ # search_stop_handler(status)
+ # to be called when the search process has terminated, with
# an integer status code (0 == success, negative errno)
- "pairing-start-requested":
+ "search-start-requested":
(GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
- "pairing-stop-requested":
+ "search-stop-requested":
(GObject.SIGNAL_RUN_FIRST, None, ()),
- # Signal arguments:
- # address
- # string of the Bluetooth device address
- "pair-device-requested":
- (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
}
def __init__(self):
@@ -179,7 +191,7 @@ class TuhiDBusServer(GObject.Object):
self._bus_aquired,
self._bus_name_aquired,
self._bus_name_lost)
- self._is_pairing = False
+ self._is_searching = False
def _bus_aquired(self, connection, name):
introspection = Gio.DBusNodeInfo.new_for_xml(INTROSPECTION_XML)
@@ -203,104 +215,66 @@ class TuhiDBusServer(GObject.Object):
if interface != INTF_MANAGER:
return None
- if methodname == 'StartPairing':
- self._start_pairing()
+ if methodname == 'StartSearch':
+ self._start_search()
invocation.return_value()
- elif methodname == 'StopPairing':
- self._stop_pairing()
+ elif methodname == 'StopSearch':
+ self._stop_search()
invocation.return_value()
- elif methodname == 'Pair':
- result = self._pair(args[0])
- result = GLib.Variant.new_int32(result)
- invocation.return_value(GLib.Variant.new_tuple(result))
def _property_read_cb(self, connection, sender, objpath, interface, propname):
if interface != INTF_MANAGER:
return None
if propname == 'Devices':
- return GLib.Variant.new_objv([d.objpath for d in self._devices])
+ return GLib.Variant.new_objv([d.objpath for d in self._devices if d.paired])
return None
def _property_write_cb(self):
pass
- def _start_pairing(self):
- if self._is_pairing:
+ def _start_search(self):
+ if self._is_searching:
return
- self._is_pairing = True
- self.emit("pairing-start-requested", self._on_pairing_stop)
+ self._is_searching = True
+ self.emit("search-start-requested", self._on_search_stop)
- def _stop_pairing(self):
- if not self._is_pairing:
+ def _stop_search(self):
+ if not self._is_searching:
return
- self._is_pairing = False
- self.emit("pairing-stop-requested")
+ self._is_searching = False
+ self.emit("search-stop-requested")
- def _pair(self, address):
- if not self._is_pairing:
- return os.errno.ECONNREFUSED
-
- if address not in self._pairable_devices:
- return os.errno.ENODEV
-
- self.emit('pair-device-requested', self._pairable_devices[address])
-
- # FIXME: we should cache the method invocation here, wait for a
- # successful result from Tuhi and then return the value
- return 0
-
- def _on_pairing_stop(self, status):
+ def _on_search_stop(self, status):
"""
- Called by whoever handles the pairing-start-requested signal
+ Called by whoever handles the search-start-requested signal
"""
- logger.debug("Pairing has stopped")
- self._is_pairing = False
+ logger.debug("Search has stopped")
+ self._is_searching = False
status = GLib.Variant.new_int32(status)
status = GLib.Variant.new_tuple(status)
self._connection.emit_signal(None, BASE_PATH, INTF_MANAGER,
- "PairingStopped", status)
- self._pairable_devices = {}
+ "SearchStopped", status)
- def notify_pairable_device(self, device):
- """
- Notify the client that a pairable device is available.
- """
- if not self._is_pairing:
- return
+ for dev in self._devices:
+ if dev.paired:
+ continue
- logger.debug("Pairable device: {}".format(device))
-
- address = device.address
- if address in self._pairable_devices:
- return
-
- self._pairable_devices[address] = device
-
- b = GLib.VariantBuilder(GLib.VariantType.new('a{sv}'))
-
- key = GLib.Variant.new_string('name')
- value = GLib.Variant.new_variant(GLib.Variant.new_string(device.name))
- de = GLib.Variant.new_dict_entry(key, value)
- b.add_value(de)
-
- key = GLib.Variant.new_string('address')
- value = GLib.Variant.new_variant(GLib.Variant.new_string(device.address))
- de = GLib.Variant.new_dict_entry(key, value)
- b.add_value(de)
-
- array = b.end()
- self._connection.emit_signal(None, BASE_PATH, INTF_MANAGER,
- "PairableDevice",
- GLib.Variant.new_tuple(array))
+ dev.remove()
+ self._devices = [d for d in self._devices if d.paired]
def cleanup(self):
Gio.bus_unown_name(self._dbus)
- def create_device(self, device):
- dev = TuhiDBusDevice(device, self._connection)
+ def create_device(self, device, paired=True):
+ dev = TuhiDBusDevice(device, self._connection, paired)
self._devices.append(dev)
+ if not paired:
+ arg = GLib.Variant.new_object_path(dev.objpath)
+ self._connection.emit_signal(None, BASE_PATH, INTF_MANAGER,
+ "PairableDevice",
+ GLib.Variant.new_tuple(arg))
return dev