Skip to content

Commit 77a527a

Browse files
author
Tony Crisci
committed
expose aio.Connection.subscribe
1 parent 4418260 commit 77a527a

File tree

6 files changed

+53
-33
lines changed

6 files changed

+53
-33
lines changed

i3ipc/aio/connection.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ def __init__(self, socket_path: Optional[str] = None, auto_reconnect: bool = Fal
247247
self._socket_path = socket_path
248248
self._auto_reconnect = auto_reconnect
249249
self._pubsub = _AIOPubSub(self)
250-
self._subscriptions = 0
250+
self._subscriptions = set()
251251
self._main_future = None
252252
self._reconnect_future = None
253253

@@ -355,7 +355,7 @@ async def connect(self) -> 'Connection':
355355
self._sub_fd = self._sub_socket.fileno()
356356
self._loop.add_reader(self._sub_fd, self._message_reader)
357357

358-
await self._subscribe(self._subscriptions, force=True)
358+
await self.subscribe(list(self._subscriptions), force=True)
359359

360360
return self
361361

@@ -419,25 +419,45 @@ async def _message(self, message_type: MessageType, payload: str = '') -> bytes:
419419

420420
return message
421421

422-
async def _subscribe(self, events: Union[EventType, int], force=False):
422+
async def subscribe(self, events: Union[List[Event], List[str]], force: bool = False):
423+
"""Send a ``SUBSCRIBE`` command to the ipc subscription connection and
424+
await the result. To attach event handlers, use :func:`Connection.on()
425+
<i3ipc.aio.Connection.on()>`. Calling this is only needed if you want
426+
to be notified when events will start coming in.
427+
428+
:ivar events: A list of events to subscribe to. Currently you cannot
429+
subscribe to detailed events.
430+
:vartype events: list(:class:`Event <i3ipc.Event>`) or list(str)
431+
:ivar force: If ``False``, the message will not be sent if this
432+
connection is already subscribed to the event.
433+
:vartype force: bool
434+
"""
423435
if not events:
424436
return
425437

426-
if type(events) is int:
427-
events = EventType(events)
438+
if type(events) is not list:
439+
raise TypeError('events must be a list of events')
440+
441+
subscriptions = set()
442+
443+
for e in events:
444+
e = Event(e)
445+
if e not in Event._subscribable_events:
446+
correct_event = str.split(e.value, '::')[0].upper()
447+
raise ValueError(
448+
f'only nondetailed events are subscribable (use Event.{correct_event})')
449+
subscriptions.add(e)
428450

429451
if not force:
430-
new_subscriptions = EventType(self._subscriptions ^ events.value)
431-
else:
432-
new_subscriptions = events
452+
subscriptions = subscriptions.difference(self._subscriptions)
453+
if not subscriptions:
454+
return
433455

434-
if not new_subscriptions:
435-
return
456+
self._subscriptions.update(subscriptions)
457+
458+
payload = json.dumps([s.value for s in subscriptions])
436459

437-
self._subscriptions |= new_subscriptions.value
438-
event_list = new_subscriptions.to_list()
439-
await self._loop.sock_sendall(self._sub_socket,
440-
_pack(MessageType.SUBSCRIBE, json.dumps(event_list)))
460+
await self._loop.sock_sendall(self._sub_socket, _pack(MessageType.SUBSCRIBE, payload))
441461

442462
def on(self, event: Union[Event, str], handler: Callable[['Connection', IpcBaseEvent], None]):
443463
"""Subscribe to the event and call the handler when it is emitted by
@@ -456,9 +476,8 @@ def on(self, event: Union[Event, str], handler: Callable[['Connection', IpcBaseE
456476
if event.count('::') > 0:
457477
[event, __] = event.split('::')
458478

459-
event_type = EventType.from_string(event)
460479
self._pubsub.subscribe(event, handler)
461-
asyncio.ensure_future(self._subscribe(event_type))
480+
asyncio.ensure_future(self.subscribe([event]))
462481

463482
def off(self, handler: Callable[['Connection', IpcBaseEvent], None]):
464483
"""Unsubscribe the handler from being called on ipc events.

i3ipc/events.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ class Event(Enum):
4242
SHUTDOWN_EXIT = 'shutdown::exit'
4343

4444

45+
Event._subscribable_events = [
46+
Event.WORKSPACE, Event.OUTPUT, Event.MODE, Event.WINDOW, Event.BARCONFIG_UPDATE, Event.BINDING,
47+
Event.SHUTDOWN, Event.TICK
48+
]
49+
50+
4551
class WorkspaceEvent(IpcBaseEvent):
4652
"""Sent when the user switches to a different workspace, when a new
4753
workspace is initialized or when a workspace is removed (because the last

test/aio/ipctest.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from random import random
99
import asyncio
1010

11-
from Xlib import display
1211
from .window import Window
1312

1413

@@ -64,11 +63,9 @@ async def command_checked(self, cmd):
6463

6564
return result
6665

67-
async def open_window(self):
68-
d = display.Display()
69-
window = Window(d)
66+
def open_window(self):
67+
window = Window()
7068
window.run()
71-
await self.i3_conn.command('nop')
7269
return window.window.id
7370

7471
async def fresh_workspace(self):

test/aio/test_get_marks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
class TestGetMarks(IpcTest):
77
@pytest.mark.asyncio
88
async def test_get_marks(self, i3):
9-
await self.open_window()
9+
self.open_window()
1010
await self.command_checked('mark a')
1111
await self.command_checked('mark --add b')
12-
await self.open_window()
12+
self.open_window()
1313
await self.command_checked('mark "(╯°□°)╯︵ ┻━┻"')
1414

1515
marks = await i3.get_marks()

test/aio/test_leaves.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ class TestLeaves(IpcTest):
77
@pytest.mark.asyncio
88
async def test_workspace_leaves(self, i3):
99
ws_name = await self.fresh_workspace()
10-
con1 = await self.open_window()
10+
con1 = self.open_window()
1111
await self.command_checked(f'[id={con1}] floating enable')
12-
await self.open_window()
13-
await self.open_window()
12+
self.open_window()
13+
self.open_window()
1414

1515
tree = await i3.get_tree()
1616
ws = [w for w in tree.workspaces() if w.name == ws_name][0]

test/aio/test_window.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .ipctest import IpcTest
22

33
import pytest
4-
import asyncio
4+
from i3ipc import Event
55

66

77
class TestWindow(IpcTest):
@@ -14,12 +14,10 @@ def on_window(self, i3, e):
1414
@pytest.mark.asyncio
1515
async def test_window_event(self, i3):
1616
await self.fresh_workspace()
17-
i3.on('window', self.on_window)
17+
await i3.subscribe([Event.WINDOW])
18+
i3.on(Event.WINDOW, self.on_window)
1819

19-
def open_window():
20-
asyncio.ensure_future(self.open_window())
21-
22-
i3._loop.call_later(0.1, open_window)
20+
self.open_window()
2321

2422
await i3.main()
2523

@@ -28,7 +26,7 @@ def open_window():
2826
@pytest.mark.asyncio
2927
async def test_marks(self, i3):
3028
await self.fresh_workspace()
31-
await self.open_window()
29+
self.open_window()
3230
await i3.command('mark foo')
3331
tree = await i3.get_tree()
3432
assert 'foo' in tree.find_focused().marks

0 commit comments

Comments
 (0)