squash
This commit is contained in:
233
bar/services/wlr/protocol/windows.py
Normal file
233
bar/services/wlr/protocol/windows.py
Normal file
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import pywayland
|
||||
from pywayland.client import Display
|
||||
from pywayland.protocol.wayland import WlOutput, WlSeat
|
||||
|
||||
# Import the protocol interfaces from your files
|
||||
from wlr_foreign_toplevel_management_unstable_v1.zwlr_foreign_toplevel_manager_v1 import (
|
||||
ZwlrForeignToplevelManagerV1,
|
||||
)
|
||||
from wlr_foreign_toplevel_management_unstable_v1.zwlr_foreign_toplevel_handle_v1 import (
|
||||
ZwlrForeignToplevelHandleV1,
|
||||
)
|
||||
|
||||
|
||||
class Window:
|
||||
"""Represents a toplevel window in the compositor."""
|
||||
|
||||
def __init__(self, handle: ZwlrForeignToplevelHandleV1):
|
||||
self.handle = handle
|
||||
self.title: str = "Unknown"
|
||||
self.app_id: str = "Unknown"
|
||||
self.states: List[str] = []
|
||||
self.outputs: List[WlOutput] = []
|
||||
self.parent: Optional["Window"] = None
|
||||
self.closed = False
|
||||
|
||||
def __str__(self) -> str:
|
||||
state_str = (
|
||||
", ".join([ZwlrForeignToplevelHandleV1.state(s).name for s in self.states])
|
||||
if self.states
|
||||
else "normal"
|
||||
)
|
||||
return (
|
||||
f"Window(title='{self.title}', app_id='{self.app_id}', state={state_str})"
|
||||
)
|
||||
|
||||
|
||||
class WaylandWindowManager:
|
||||
"""Manages Wayland windows using the foreign toplevel protocol."""
|
||||
|
||||
def __init__(self):
|
||||
self.display = Display()
|
||||
self.windows: Dict[ZwlrForeignToplevelHandleV1, Window] = {}
|
||||
self.manager = None
|
||||
self.running = False
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Connect to the Wayland display and bind to the toplevel manager."""
|
||||
try:
|
||||
self.display.connect()
|
||||
print("Connected to Wayland display")
|
||||
|
||||
# Get the registry to find the foreign toplevel manager
|
||||
registry = self.display.get_registry()
|
||||
registry.dispatcher["global"] = self._registry_global_handler
|
||||
|
||||
# Roundtrip to process registry events
|
||||
self.display.roundtrip()
|
||||
|
||||
if not self.manager:
|
||||
print(
|
||||
"Foreign toplevel manager not found. Is wlr-foreign-toplevel-management protocol supported?"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to connect: {e}")
|
||||
return False
|
||||
|
||||
def _registry_global_handler(self, registry, id, interface, version):
|
||||
"""Handle registry global objects."""
|
||||
if interface == ZwlrForeignToplevelManagerV1.name:
|
||||
print(f"Found foreign toplevel manager (id={id}, version={version})")
|
||||
self.manager = registry.bind(
|
||||
id, ZwlrForeignToplevelManagerV1, min(version, 3)
|
||||
)
|
||||
self.manager.dispatcher["toplevel"] = self._handle_toplevel
|
||||
self.manager.dispatcher["finished"] = self._handle_manager_finished
|
||||
|
||||
def _handle_toplevel(self, manager, toplevel):
|
||||
"""Handle a new toplevel window."""
|
||||
window = Window(toplevel)
|
||||
self.windows[toplevel] = window
|
||||
print(window)
|
||||
|
||||
# Setup event dispatchers for the toplevel
|
||||
toplevel.dispatcher["title"] = self._handle_title
|
||||
toplevel.dispatcher["app_id"] = self._handle_app_id
|
||||
toplevel.dispatcher["state"] = self._handle_state
|
||||
toplevel.dispatcher["done"] = self._handle_done
|
||||
toplevel.dispatcher["closed"] = self._handle_closed
|
||||
toplevel.dispatcher["output_enter"] = self._handle_output_enter
|
||||
toplevel.dispatcher["output_leave"] = self._handle_output_leave
|
||||
|
||||
def _handle_title(self, toplevel, title):
|
||||
"""Handle toplevel title changes."""
|
||||
window = self.windows.get(toplevel)
|
||||
if window:
|
||||
window.title = title
|
||||
|
||||
def _handle_app_id(self, toplevel, app_id):
|
||||
"""Handle toplevel app_id changes."""
|
||||
window = self.windows.get(toplevel)
|
||||
if window:
|
||||
window.app_id = app_id
|
||||
|
||||
def _handle_state(self, toplevel, states):
|
||||
"""Handle toplevel state changes."""
|
||||
window = self.windows.get(toplevel)
|
||||
if window:
|
||||
window.states = states
|
||||
|
||||
def _handle_done(self, toplevel):
|
||||
"""Handle toplevel done event."""
|
||||
window = self.windows.get(toplevel)
|
||||
if window and not window.closed:
|
||||
print(f"Window updated: {window}")
|
||||
|
||||
def _handle_closed(self, toplevel):
|
||||
"""Handle toplevel closed event."""
|
||||
window = self.windows.get(toplevel)
|
||||
if window:
|
||||
window.closed = True
|
||||
print(f"Window closed: {window}")
|
||||
# Clean up the toplevel object
|
||||
toplevel.destroy()
|
||||
# Remove from our dictionary
|
||||
del self.windows[toplevel]
|
||||
|
||||
def _handle_output_enter(self, toplevel, output):
|
||||
"""Handle toplevel entering an output."""
|
||||
window = self.windows.get(toplevel)
|
||||
if window and output not in window.outputs:
|
||||
window.outputs.append(output)
|
||||
|
||||
def _handle_output_leave(self, toplevel, output):
|
||||
"""Handle toplevel leaving an output."""
|
||||
window = self.windows.get(toplevel)
|
||||
if window and output in window.outputs:
|
||||
window.outputs.remove(output)
|
||||
|
||||
def _handle_parent(self, toplevel, parent):
|
||||
"""Handle toplevel parent changes."""
|
||||
window = self.windows.get(toplevel)
|
||||
if window:
|
||||
if parent is None:
|
||||
window.parent = None
|
||||
else:
|
||||
parent_window = self.windows.get(parent)
|
||||
if parent_window:
|
||||
window.parent = parent_window
|
||||
|
||||
def _handle_manager_finished(self, manager):
|
||||
"""Handle manager finished event."""
|
||||
print("Foreign toplevel manager finished")
|
||||
self.running = False
|
||||
|
||||
def get_windows(self) -> List[Window]:
|
||||
"""Get all currently active windows."""
|
||||
# Filter out closed windows
|
||||
active_windows = [
|
||||
window for window in self.windows.values() if not window.closed
|
||||
]
|
||||
return active_windows
|
||||
|
||||
def run(self):
|
||||
"""Run the event loop to receive window updates."""
|
||||
self.running = True
|
||||
print("Listening for window events (press Ctrl+C to exit)...")
|
||||
|
||||
try:
|
||||
while self.running:
|
||||
self.display.dispatch(block=True)
|
||||
except KeyboardInterrupt:
|
||||
print("\nExiting...")
|
||||
finally:
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up resources."""
|
||||
print("cleanup")
|
||||
if self.manager:
|
||||
self.manager.stop()
|
||||
|
||||
# Destroy all toplevel handles
|
||||
for toplevel, window in list(self.windows.items()):
|
||||
if not window.closed:
|
||||
toplevel.destroy()
|
||||
|
||||
# Disconnect from display
|
||||
if self.display:
|
||||
self.display.disconnect()
|
||||
|
||||
self.running = False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
manager = WaylandWindowManager()
|
||||
|
||||
if not manager.connect():
|
||||
return 1
|
||||
|
||||
# # Run for a short time to collect initial windows
|
||||
for _ in range(1):
|
||||
manager.display.dispatch(block=True)
|
||||
|
||||
# Print all windows
|
||||
windows = manager.get_windows()
|
||||
print("\nActive windows:")
|
||||
if windows:
|
||||
for i, window in enumerate(windows, 1):
|
||||
print(f"{i}. {window}")
|
||||
else:
|
||||
print("No windows found")
|
||||
|
||||
# # Option to keep monitoring window events
|
||||
# if len(sys.argv) > 1 and sys.argv[1] == "--monitor":
|
||||
# manager.run()
|
||||
# else:
|
||||
manager.cleanup()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user