This commit is contained in:
2025-05-01 13:53:39 +02:00
commit 0fbf25d214
47 changed files with 7539 additions and 0 deletions

117
bar/river/service.py Normal file
View File

@@ -0,0 +1,117 @@
import logging
from dataclasses import dataclass, field
from typing import Dict, List
from fabric.core.service import Service, Signal, Property
from pywayland.client import Display
from pywayland.protocol.wayland import WlOutput, WlSeat
from ..generated.river_status_unstable_v1 import (
ZriverStatusManagerV1,
ZriverOutputStatusV1,
ZriverSeatStatusV1,
)
logger = logging.getLogger(__name__)
@dataclass
class OutputState:
id: int
output: WlOutput
status: ZriverOutputStatusV1 = None
focused_tags: List[int] = field(default_factory=list)
view_tags: List[int] = field(default_factory=list)
class River(Service):
@Property(bool, "readable", "is-ready", default_value=False)
def ready(self) -> bool:
return self._ready
@Signal
def ready(self):
return self.notify("ready")
@Signal("event", flags="detailed")
def event(self, event: object): ...
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._ready = False
self.display = None
self.registry = None
self.outputs: Dict[int, OutputState] = {}
self.status_mgr = None
self.seat = None
self.seat_status: ZriverSeatStatusV1 = None
self.ready.emit()
def on_start(self):
print("✅ River.on_start called")
logger.info("[RiverService] Starting River service...")
self.display = Display()
self.display.connect()
self.registry = self.display.get_registry()
self.registry.dispatcher["global"] = self._on_global
self.registry.dispatcher["global_remove"] = lambda *_: None
self.display.roundtrip()
if self.seat and self.status_mgr:
self.seat_status = self.status_mgr.get_river_seat_status(self.seat)
for id, output_state in self.outputs.items():
status = self.status_mgr.get_river_output_status(output_state.output)
output_state.status = status
status.dispatcher["focused_tags"] = self._make_focused_handler(id)
status.dispatcher["view_tags"] = self._make_view_handler(id)
self.display.roundtrip()
self._ready = True
self.ready.emit()
logger.info("[RiverService] Ready. Monitoring tags.")
def on_tick(self):
# Periodic poll
self.display.roundtrip()
def _on_global(self, registry, name, interface, version):
if interface == "wl_output":
output = registry.bind(name, WlOutput, version)
self.outputs[name] = OutputState(id=name, output=output)
elif interface == "wl_seat":
self.seat = registry.bind(name, WlSeat, version)
elif interface == "zriver_status_manager_v1":
self.status_mgr = registry.bind(name, ZriverStatusManagerV1, version)
def _make_focused_handler(self, output_id):
def handler(_, bitfield):
tags = self._decode_bitfield(bitfield)
self.outputs[output_id].focused_tags = tags
logger.debug(f"[RiverService] Output {output_id} focused: {tags}")
self.emit(f"event::focused_tags::{output_id}", tags)
return handler
def _make_view_handler(self, output_id):
def handler(_, array):
tags = self._decode_array_bitfields(array)
self.outputs[output_id].view_tags = tags
logger.debug(f"[RiverService] Output {output_id} views: {tags}")
self.emit(f"event::view_tags::{output_id}", tags)
return handler
def _decode_bitfield(self, bits: int) -> List[int]:
return [i for i in range(32) if bits & (1 << i)]
def _decode_array_bitfields(self, array) -> List[int]:
tags = set()
for bits in array:
tags.update(self._decode_bitfield(bits))
return sorted(tags)