multiple monitor support

This commit is contained in:
2025-05-06 13:09:40 +02:00
parent 53713ee0f5
commit f8b352d624
5 changed files with 263 additions and 211 deletions

View File

@@ -12,6 +12,10 @@ from fabric.utils.helpers import idle_add
from pywayland.client import Display
from pywayland.protocol.wayland import WlOutput, WlSeat
from .generated.river_status_unstable_v1 import ZriverStatusManagerV1
from gi.repository import (
Gio,
GLib,
)
@dataclass
@@ -48,7 +52,7 @@ class River(Service):
return self._active_window_title
@Signal
def ready(self):
def ready_signal(self):
return self.notify("ready")
@Signal("event", flags="detailed")
@@ -65,18 +69,15 @@ class River(Service):
self.seat_status = None
# Start the connection in a separate thread
self.river_thread = threading.Thread(
target=self._river_connection_task, daemon=True, name="river-status-service"
self.river_thread = GLib.Thread.new(
"river-status-service", self._river_connection_task
)
self.river_thread.start()
def _river_connection_task(self):
"""Main thread that connects to River and listens for events"""
try:
# Create a new display connection - THIS IS WHERE THE ERROR OCCURS
logger.info("[RiverService] Starting connection to River")
# Let's add some more diagnostic info to help troubleshoot
logger.debug(
f"[RiverService] XDG_RUNTIME_DIR={os.environ.get('XDG_RUNTIME_DIR', 'Not set')}"
)
@@ -84,14 +85,7 @@ class River(Service):
f"[RiverService] WAYLAND_DISPLAY={os.environ.get('WAYLAND_DISPLAY', 'Not set')}"
)
# Create the display connection
# with Display() as display:
# registry = display.get_registry()
# logger.debug("[RiverService] Registry obtained")
# Discover globals
display = Display("wayland-1")
display = Display()
display.connect()
logger.debug("[RiverService] Display connection created")
@@ -236,11 +230,13 @@ class River(Service):
logger.error(traceback.format_exc())
return True
def _set_ready(self):
"""Set the service as ready (called on main thread via idle_add)"""
self._ready = True
logger.info("[RiverService] Service ready")
self.ready.emit()
self.ready_signal.emit()
return False # Don't repeat
def _emit_view_tags(self, output_id, tags):

View File

@@ -76,12 +76,16 @@ class RiverWorkspaceButton(Button):
class RiverWorkspaces(EventBox):
def __init__(self, output_id=None, max_tags=9, **kwargs):
def __init__(self, output_id, river_service=None, max_tags=9, **kwargs):
super().__init__(events="scroll")
self.service = get_river_connection()
self._box = Box(**kwargs)
self.children = self._box
if river_service:
self.river = river_service
else:
self.river = get_river_connection()
# Store output_id as received
self.output_id = output_id
@@ -94,33 +98,33 @@ class RiverWorkspaces(EventBox):
self._box.add(btn)
# Connect to service events
self.service.connect("event::focused_tags", self.on_focus_change_general)
self.service.connect("event::view_tags", self.on_view_change_general)
self.service.connect("event::urgent_tags", self.on_urgent_change_general)
self.service.connect("event::output_removed", self.on_output_removed)
self.river.connect("event::focused_tags", self.on_focus_change_general)
self.river.connect("event::view_tags", self.on_view_change_general)
self.river.connect("event::urgent_tags", self.on_urgent_change_general)
self.river.connect("event::output_removed", self.on_output_removed)
# Initial setup when service is ready
if self.service.ready:
if self.river.ready:
self.on_ready(None)
else:
self.service.connect("event::ready", self.on_ready)
self.river.connect("event::ready", self.on_ready)
self.connect("scroll-event", self.on_scroll)
def on_ready(self, _):
"""Initialize widget state when service is ready"""
logger.debug(
f"[RiverWorkspaces] Service ready, outputs: {list(self.service.outputs.keys())}"
f"[RiverWorkspaces] Service ready, outputs: {list(self.river.outputs.keys())}"
)
# If no output_id was specified, take the first one
if self.output_id is None and self.service.outputs:
self.output_id = next(iter(self.service.outputs.keys()))
if self.output_id is None and self.river.outputs:
self.output_id = next(iter(self.river.outputs.keys()))
logger.info(f"[RiverWorkspaces] Selected output {self.output_id}")
# Initialize state from selected output
if self.output_id is not None and self.output_id in self.service.outputs:
output_info = self.service.outputs[self.output_id]
if self.output_id is not None and self.output_id in self.river.outputs:
output_info = self.river.outputs[self.output_id]
# Initialize buttons with current state
# Access fields directly on the OutputInfo dataclass
@@ -196,13 +200,13 @@ class RiverWorkspaces(EventBox):
logger.info(f"[RiverWorkspaces] Our output {self.output_id} was removed")
# Try to find another output
if self.service.outputs:
self.output_id = next(iter(self.service.outputs.keys()))
if self.river.outputs:
self.output_id = next(iter(self.river.outputs.keys()))
logger.info(f"[RiverWorkspaces] Switching to output {self.output_id}")
# Update state for new output
if self.output_id in self.service.outputs:
output_info = self.service.outputs[self.output_id]
if self.output_id in self.river.outputs:
output_info = self.river.outputs[self.output_id]
# Access fields directly on the OutputInfo dataclass
focused_tags = output_info.tags_focused
view_tags = output_info.tags_view
@@ -214,41 +218,46 @@ class RiverWorkspaces(EventBox):
def on_workspace_click(self, btn):
"""Handle workspace button click"""
logger.info(f"[RiverWorkspaces] Clicked on workspace {btn.id}")
self.service.toggle_focused_tag(btn.id)
self.river.toggle_focused_tag(btn.id)
def on_scroll(self, _, event):
"""Handle scroll events"""
direction = event.direction
if direction == Gdk.ScrollDirection.DOWN:
logger.info("[RiverWorkspaces] Scroll down - focusing next view")
self.service.run_command("focus-view", "next")
self.river.run_command("focus-view", "next")
elif direction == Gdk.ScrollDirection.UP:
logger.info("[RiverWorkspaces] Scroll up - focusing previous view")
self.service.run_command("focus-view", "previous")
self.river.run_command("focus-view", "previous")
class RiverActiveWindow(Label):
"""Widget to display the currently active window's title"""
def __init__(self, max_length=None, ellipsize="end", **kwargs):
def __init__(self, max_length=None, ellipsize="end", river_service=None, **kwargs):
super().__init__(**kwargs)
self.service = get_river_connection()
if river_service:
self.river = river_service
else:
self.river = get_river_connection()
self.max_length = max_length
self.ellipsize = ellipsize
# Set initial state
if self.service.ready:
if self.river.ready:
self.on_ready(None)
else:
self.service.connect("event::ready", self.on_ready)
self.river.connect("event::ready", self.on_ready)
# Connect to active window changes
self.service.connect("event::active_window", self.on_active_window_changed)
self.river.connect("event::active_window", self.on_active_window_changed)
def on_ready(self, _):
"""Initialize widget when service is ready"""
logger.debug("[RiverActiveWindow] Service ready")
self.update_title(self.service.active_window)
self.update_title(self.river.active_window)
def on_active_window_changed(self, _, event):
"""Update widget when active window changes"""