Hyprland exposes two Unix sockets for IPC: .socket.sock for sending commands and .socket2.sock for receiving
events. The event socket streams compositor events — workspace changes, window focus,
monitor hotplug — as newline-delimited messages that any process can subscribe to.
This micro-library wraps the event socket with a decorator-based API for Python scripts
that need to react to compositor state.
Hyprland generates a unique instance signature each session, so the socket paths aren't static. Both the signature and runtime directory come from environment variables:
self.signature = os.getenv("HYPRLAND_INSTANCE_SIGNATURE")
self.runtime_dir = os.getenv("XDG_RUNTIME_DIR")
self.event_sock_path = f"{self.runtime_dir}/hypr/{self.signature}/.socket2.sock"
self.cmd_sock_path = f"{self.runtime_dir}/hypr/{self.signature}/.socket.sock"The decorator doesn't transform the
function — it just appends it to a list keyed by event name. Multiple handlers can
subscribe to the same event this way:
def on(self, event_name: str) -> Callable[[EventHandler], EventHandler]:
def decorator(func: EventHandler) -> EventHandler:
self.handlers[event_name].append(func)
return func
return decoratorEvents arrive as EVENT>>DATA lines. Each matching
handler gets spawned as a concurrent task, so one slow handler doesn't block others:
raw = line.decode().strip()
if ">>" in raw:
event, data = raw.split(">>", 1)
if event in self.handlers:
for func in self.handlers[event]:
asyncio.create_task(func(self, data))A wallpaper daemon might use this to repaint when monitors are added or removed:
hyprland = Hyprland()
async def on_monitor_added(ctx: Hyprland, data: str) -> None:
# data contains monitor info from Hyprland
await repaint_wallpaper()
hyprland.run()