#!/usr/bin/env python3 from functools import partial from datetime import datetime, timezone from wl_framework.network.connection import WaylandConnection from wl_framework.protocols.base import UnsupportedProtocolError from wl_framework.protocols.foreign_toplevel import ForeignTopLevel from wl_framework.protocols.data_control import DataControl from wl_framework.protocols.idle_notify import ( IdleNotifyManager, IdleNotifier as _IdleNotifier ) from wl_framework.network.connection import WaylandDisconnected import asyncio.subprocess as subprocess import orjson class ForeignTopLevelMonitor(ForeignTopLevel): def __init__(self, *args, **kwargs): self.tlwindows = {} super().__init__(*args, **kwargs) def on_toplevel_created(self, toplevel): self.tlwindows[toplevel.obj_id] = {} def on_toplevel_synced(self, toplevel): self.tlwindows[toplevel.obj_id]["app_id"] = toplevel.app_id self.tlwindows[toplevel.obj_id]["title"] = toplevel.title self.tlwindows[toplevel.obj_id]["states"] = toplevel.states self.tlwindows[toplevel.obj_id]["outputs"] = { x.name for x in toplevel.outputs } def on_toplevel_closed(self, toplevel): del self.tlwindows[toplevel.obj_id] # not actually using this class ClipboardMonitor(DataControl): def on_new_selection(self, offer): self._print_selection(offer) self._receive(offer) def on_new_primary_selection(self, offer): self._print_selection(offer, is_primary=True) self._receive(offer, is_primary=True) # Internal def _receive(self, offer, is_primary=False): if offer is None: return for mime in ( 'text/plain;charset=utf-8', 'UTF8_STRING', ): if mime in offer.get_mime_types(): offer.receive(mime, partial(self._on_received, is_primary=is_primary)) break def _print_selection(self, offer, is_primary=False): _selection = 'primary' if is_primary else 'main' if offer is None: self.log(f"{_selection.capitalize()} selection cleared") return self.log(f"New {_selection} selection offers:") for mime_type in offer.get_mime_types(): self.log(f" {mime_type}") def _on_received(self, mime_type, data, is_primary=False): if data: data = data.decode('utf-8') self.log(f"Received {' primary' if is_primary else 'main'} selection: '{data}'") class IdleNotifier(_IdleNotifier): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.idle_start = None def on_idle(self): self.idle_start = datetime.now(tz=timezone.utc) def on_resume(self): self.idle_start = None class WlMonitor(WaylandConnection): def on_initial_sync(self, data): super().on_initial_sync(data) self.toplevels = ForeignTopLevelMonitor(self) #self.clipboard = ClipboardMonitor(self) self.idle = IdleNotifyManager(self, IdleNotifier) self.idle_notifier = self.idle.get_idle_notifier(0, self.display.seat) INTERVAL_MS = 60_000 def generate_log_entry(wl: WlMonitor()): entry = {"desktop": ""} now = datetime.now(timezone.utc) entry["date"] = now.isoformat() entry["rate"] = INTERVAL_MS if wl.idle_notifier.idle_start: entry["inactive"] = int((now.timestamp() - wl.idle_notifier.idle_start.timestamp()) * 1000) else: entry["inactive"] = 0 def arbitrary_output(window): s = window["outputs"] try: return s.pop() except KeyError: return "" entry["windows"] = [ { "title": x["title"], "program": x["app_id"], "active": "activated" in x["states"], "hidden": "minimized" in x["states"] } for x in wl.toplevels.tlwindows.values() ] return entry if __name__ == '__main__': import sys import asyncio from wl_framework.loop_integrations import AsyncIOIntegration # without this, WaylandDisconnected exceptions are thrown forever and problems occur def handler(loop, context): sys.exit(0) async def init(): asyncio.get_event_loop().set_exception_handler(handler) arbtt_importer = await subprocess.create_subprocess_exec("arbtt-import", "-a", "-t", "JSON", stdin=subprocess.PIPE) loop = AsyncIOIntegration() try: app = WlMonitor(eventloop_integration=loop) await asyncio.sleep(1) while True: entry = generate_log_entry(app) arbtt_importer.stdin.write(orjson.dumps(entry)) arbtt_importer.stdin.write(b"\n") await asyncio.sleep(INTERVAL_MS / 1000) except RuntimeError as e: print(e) sys.exit(1) try: asyncio.run(init()) except KeyboardInterrupt: print()