xibus

experimental pure python async D-Bus library
git clone https://git.ce9e.org/xibus.git

commit
cd6b737ca8f9204163d93457271c0b9334762658
parent
10bef3c9d7a55769299413d4ddb6ab51b3a04c63
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2026-02-16 08:55
add subscribe signal

Diffstat

M xibus/client.py 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M xibus/connection.py 15 ++++++++++++++-

2 files changed, 75 insertions, 1 deletions


diff --git a/xibus/client.py b/xibus/client.py

@@ -1,6 +1,40 @@
   -1     1 import contextlib
   -1     2 
    1     3 from .schema import Schema
    2     4 
    3     5 
   -1     6 class SignalQueue:
   -1     7     def __init__(self, queue, sender, path, iface, signal):
   -1     8         self.queue = queue
   -1     9         self.sender = sender
   -1    10         self.path = path
   -1    11         self.iface = iface
   -1    12         self.signal = signal
   -1    13 
   -1    14     @property
   -1    15     def rule(self):
   -1    16         return ','.join(
   -1    17             f"{key}='{value}'"
   -1    18             for key, value in {
   -1    19                 'type': 'signal',
   -1    20                 'sender': self.sender,
   -1    21                 'path': self.path,
   -1    22                 'interface': self.iface,
   -1    23                 'member': self.signal,
   -1    24             }.items()
   -1    25         )
   -1    26 
   -1    27     async def __aiter__(self):
   -1    28         async for msg in self.queue:
   -1    29             if (
   -1    30                 msg.sender == self.sender
   -1    31                 and msg.path == self.path
   -1    32                 and msg.iface == self.iface
   -1    33                 and msg.member == self.signal
   -1    34             ):
   -1    35                 yield msg.body
   -1    36 
   -1    37 
    4    38 class Proxy:
    5    39     def __init__(self, client, name, path=None, iface=None):
    6    40         self.client = client
@@ -9,11 +43,22 @@ class Proxy:
    9    43     async def call(self, method, params=(), sig=None):
   10    44         return await self.client.call(*self.defaults, method, params, sig)
   11    45 
   -1    46     @contextlib.asynccontextmanager
   -1    47     async def subscribe_signal(self, signal):
   -1    48         async with self.client.subscribe_signal(*self.defaults, signal) as queue:
   -1    49             yield queue
   -1    50 
   12    51 
   13    52 class Client:
   14    53     def __init__(self, con):
   15    54         self.con = con
   16    55         self.introspect_cache = {}
   -1    56         self.bus = Proxy(
   -1    57             self,
   -1    58             'org.freedesktop.DBus',
   -1    59             '/org/freedesktop/DBus',
   -1    60             'org.freedesktop.DBus',
   -1    61         )
   17    62 
   18    63     async def introspect(self, name, path):
   19    64         key = f'{name}{path}'
@@ -34,3 +79,19 @@ class Client:
   34    79             return result[0]
   35    80         elif len(m.returns) > 1:
   36    81             return result
   -1    82 
   -1    83     @contextlib.asynccontextmanager
   -1    84     async def subscribe_signal(self, name, path, iface, signal):
   -1    85         # NOTE: if we register the same match rule twice and then remove one of
   -1    86         # them, the other still exists on the bus. So we do not need any
   -1    87         # special handling on our end.
   -1    88 
   -1    89         if not name.startswith(':'):
   -1    90             name = await self.bus.call('GetNameOwner', [name], 's')
   -1    91         with self.con.signal_queue() as queue:
   -1    92             sq = SignalQueue(queue, name, path, iface, signal)
   -1    93             await self.bus.call('AddMatch', [sq.rule], 's')
   -1    94             try:
   -1    95                 yield sq
   -1    96             finally:
   -1    97                 await self.bus.call('RemoveMatch', [sq.rule], 's')

diff --git a/xibus/connection.py b/xibus/connection.py

@@ -2,6 +2,7 @@ import asyncio
    2     2 import os
    3     3 import re
    4     4 import socket
   -1     5 from contextlib import contextmanager
    5     6 
    6     7 from .message import Msg
    7     8 from .message import MsgFlag
@@ -34,6 +35,7 @@ class Connection:
   34    35         self.serial = 0
   35    36         self.send_queue = []
   36    37         self.replies = {}
   -1    38         self.signal_queues = set()
   37    39 
   38    40         if not self.loop:
   39    41             self.loop = asyncio.get_running_loop()
@@ -57,7 +59,8 @@ class Connection:
   57    59             elif msg.type == MsgType.METHOD_CALL:
   58    60                 raise NotImplementedError
   59    61             elif msg.type == MsgType.SIGNAL:
   60    -1                 raise NotImplementedError
   -1    62                 for queue in self.signal_queues:
   -1    63                     queue.put_nowait(msg)
   61    64             else:
   62    65                 raise ValueError(msg)
   63    66 
@@ -120,6 +123,16 @@ class Connection:
  120   123         self.sock.close()
  121   124         self.sock = None
  122   125 
   -1   126     @contextmanager
   -1   127     def signal_queue(self):
   -1   128         queue = asyncio.Queue()
   -1   129         self.signal_queues.add(queue)
   -1   130         try:
   -1   131             yield iter_queue(queue)
   -1   132         finally:
   -1   133             self.signal_queues.remove(queue)
   -1   134             queue.shutdown()
   -1   135 
  123   136     async def call(self, dest, path, iface, method, body, sig, flags=MsgFlag.NONE):
  124   137         if not RE_PATH.match(path):
  125   138             raise InvalidPathError(path)