Compare commits
3 Commits
53713ee0f5
...
9495dfba62
| Author | SHA1 | Date | |
|---|---|---|---|
| 9495dfba62 | |||
| 0cf1c5aeb7 | |||
| f8b352d624 |
211
bar/bar.py
211
bar/bar.py
@ -1,185 +1,56 @@
|
||||
# fabric bar.py example
|
||||
# https://github.com/Fabric-Development/fabric/blob/rewrite/examples/bar/bar.py
|
||||
import psutil
|
||||
from loguru import logger
|
||||
|
||||
from fabric import Application
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.eventbox import EventBox
|
||||
from fabric.widgets.datetime import DateTime
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.system_tray.widgets import SystemTray
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
from .river.widgets import RiverWorkspaces, RiverWorkspaceButton, RiverActiveWindow
|
||||
from .river.widgets import (
|
||||
get_river_connection,
|
||||
)
|
||||
from fabric.utils import (
|
||||
FormattedString,
|
||||
bulk_replace,
|
||||
invoke_repeater,
|
||||
get_relative_path,
|
||||
)
|
||||
from bar.modules.player import Player
|
||||
from bar.modules.vinyl import VinylButton
|
||||
|
||||
AUDIO_WIDGET = True
|
||||
|
||||
if AUDIO_WIDGET is True:
|
||||
try:
|
||||
from fabric.audio.service import Audio
|
||||
except Exception as e:
|
||||
print(e)
|
||||
AUDIO_WIDGET = False
|
||||
|
||||
|
||||
class VolumeWidget(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.audio = Audio()
|
||||
|
||||
self.progress_bar = CircularProgressBar(
|
||||
name="volume-progress-bar", pie=True, size=24
|
||||
)
|
||||
|
||||
self.event_box = EventBox(
|
||||
events="scroll",
|
||||
child=Overlay(
|
||||
child=self.progress_bar,
|
||||
overlays=Label(
|
||||
label="",
|
||||
style="margin: 0px 6px 0px 0px; font-size: 12px", # to center the icon glyph
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
self.audio.connect("notify::speaker", self.on_speaker_changed)
|
||||
self.event_box.connect("scroll-event", self.on_scroll)
|
||||
self.add(self.event_box)
|
||||
|
||||
def on_scroll(self, _, event):
|
||||
match event.direction:
|
||||
case 0:
|
||||
self.audio.speaker.volume += 8
|
||||
case 1:
|
||||
self.audio.speaker.volume -= 8
|
||||
return
|
||||
|
||||
def on_speaker_changed(self, *_):
|
||||
if not self.audio.speaker:
|
||||
return
|
||||
self.progress_bar.value = self.audio.speaker.volume / 100
|
||||
self.audio.speaker.bind(
|
||||
"volume", "value", self.progress_bar, lambda _, v: v / 100
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
class StatusBar(Window):
|
||||
def __init__(self, display: int, monitor: int = 1, with_system_tray: bool = False):
|
||||
super().__init__(
|
||||
name="bar",
|
||||
layer="top",
|
||||
anchor="left top right",
|
||||
margin="0px 0px -2px 0px",
|
||||
exclusivity="auto",
|
||||
visible=False,
|
||||
all_visible=False,
|
||||
monitor=monitor,
|
||||
)
|
||||
self.workspaces = RiverWorkspaces(
|
||||
display,
|
||||
name="workspaces",
|
||||
spacing=4,
|
||||
buttons_factory=lambda ws_id: RiverWorkspaceButton(id=ws_id, label=None),
|
||||
)
|
||||
self.date_time = DateTime(name="date-time", formatters="%d %b - %H:%M")
|
||||
self.system_tray = None
|
||||
if with_system_tray:
|
||||
self.system_tray = SystemTray(name="system-tray", spacing=4)
|
||||
|
||||
self.active_window = RiverActiveWindow(
|
||||
name="active-window",
|
||||
max_length=50,
|
||||
style="color: #ffffff; font-size: 14px; font-weight: bold;",
|
||||
)
|
||||
|
||||
self.ram_progress_bar = CircularProgressBar(
|
||||
name="ram-progress-bar", pie=True, size=24
|
||||
)
|
||||
self.cpu_progress_bar = CircularProgressBar(
|
||||
name="cpu-progress-bar", pie=True, size=24
|
||||
)
|
||||
self.progress_bars_overlay = Overlay(
|
||||
child=self.ram_progress_bar,
|
||||
overlays=[
|
||||
self.cpu_progress_bar,
|
||||
Label("", style="margin: 0px 6px 0px 0px; font-size: 12px"),
|
||||
],
|
||||
)
|
||||
self.player = Player()
|
||||
self.vinyl = VinylButton()
|
||||
|
||||
self.status_container = Box(
|
||||
name="widgets-container",
|
||||
spacing=4,
|
||||
orientation="h",
|
||||
children=self.progress_bars_overlay,
|
||||
)
|
||||
self.status_container.add(VolumeWidget()) if AUDIO_WIDGET is True else None
|
||||
|
||||
end_container_children = [
|
||||
self.vinyl,
|
||||
self.status_container,
|
||||
self.date_time,
|
||||
]
|
||||
|
||||
if self.system_tray is not None:
|
||||
end_container_children = [
|
||||
self.vinyl,
|
||||
self.status_container,
|
||||
self.system_tray,
|
||||
self.date_time,
|
||||
]
|
||||
self.children = CenterBox(
|
||||
name="bar-inner",
|
||||
start_children=Box(
|
||||
name="start-container",
|
||||
spacing=6,
|
||||
orientation="h",
|
||||
children=[
|
||||
Label(name="nixos-label", markup=""),
|
||||
self.workspaces,
|
||||
],
|
||||
),
|
||||
center_children=Box(
|
||||
name="center-container",
|
||||
spacing=4,
|
||||
orientation="h",
|
||||
children=[self.active_window],
|
||||
),
|
||||
end_children=Box(
|
||||
name="end-container",
|
||||
spacing=4,
|
||||
orientation="h",
|
||||
children=end_container_children,
|
||||
),
|
||||
)
|
||||
|
||||
invoke_repeater(1000, self.update_progress_bars)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def update_progress_bars(self):
|
||||
self.ram_progress_bar.value = psutil.virtual_memory().percent / 100
|
||||
self.cpu_progress_bar.value = psutil.cpu_percent() / 100
|
||||
return True
|
||||
from .modules.bar import StatusBar
|
||||
|
||||
|
||||
def main():
|
||||
bar = StatusBar(45)
|
||||
bar_two = StatusBar(44, monitor=2, with_system_tray=True)
|
||||
app = Application("bar", bar, bar_two)
|
||||
app.set_stylesheet_from_file(get_relative_path("bar.css"))
|
||||
tray = SystemTray(name="system-tray", spacing=4)
|
||||
|
||||
river = get_river_connection()
|
||||
|
||||
# Dummy window just to hold the event loop
|
||||
dummy = Window(visible=False)
|
||||
|
||||
# Real bar windows will be added later
|
||||
bar_windows = []
|
||||
|
||||
def spawn_bars():
|
||||
logger.info("[Bar] Spawning bars after river ready")
|
||||
outputs = river.outputs
|
||||
|
||||
if not outputs:
|
||||
logger.warning("[Bar] No outputs found — skipping bar spawn")
|
||||
return
|
||||
|
||||
output_ids = sorted(outputs.keys())
|
||||
|
||||
for i, output_id in enumerate(output_ids):
|
||||
print("i", i)
|
||||
print("output_id", output_id)
|
||||
bar = StatusBar(display=output_id, tray=tray if i == 0 else None, monitor=i)
|
||||
bar_windows.append(bar)
|
||||
|
||||
return False
|
||||
|
||||
if river.ready:
|
||||
print("river ready", river._ready)
|
||||
spawn_bars()
|
||||
else:
|
||||
river.connect("notify::ready", lambda sender, pspec: spawn_bars())
|
||||
|
||||
app = Application("bar", dummy)
|
||||
app.set_stylesheet_from_file(get_relative_path("bar.css"))
|
||||
app.run()
|
||||
|
||||
|
||||
|
||||
128
bar/modules/bar.py
Normal file
128
bar/modules/bar.py
Normal file
@ -0,0 +1,128 @@
|
||||
import psutil
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.datetime import DateTime
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from bar.modules.player import Player
|
||||
from bar.modules.vinyl import VinylButton
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
from fabric.system_tray.widgets import SystemTray
|
||||
from ..river.widgets import (
|
||||
RiverWorkspaces,
|
||||
RiverWorkspaceButton,
|
||||
RiverActiveWindow,
|
||||
get_river_connection,
|
||||
)
|
||||
from fabric.utils import (
|
||||
invoke_repeater,
|
||||
)
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
|
||||
|
||||
class StatusBar(Window):
|
||||
def __init__(
|
||||
self,
|
||||
display: int,
|
||||
tray: SystemTray | None = None,
|
||||
monitor: int = 1,
|
||||
river_service=None,
|
||||
):
|
||||
super().__init__(
|
||||
name="bar",
|
||||
layer="top",
|
||||
anchor="left top right",
|
||||
margin="0px 0px -2px 0px",
|
||||
exclusivity="auto",
|
||||
visible=False,
|
||||
all_visible=False,
|
||||
monitor=monitor,
|
||||
)
|
||||
if river_service:
|
||||
self.river = river_service
|
||||
else:
|
||||
self.river = get_river_connection()
|
||||
|
||||
self.workspaces = RiverWorkspaces(
|
||||
display,
|
||||
name="workspaces",
|
||||
spacing=4,
|
||||
buttons_factory=lambda ws_id: RiverWorkspaceButton(id=ws_id, label=None),
|
||||
river_service=self.river,
|
||||
)
|
||||
self.date_time = DateTime(name="date-time", formatters="%d %b - %H:%M")
|
||||
self.system_tray = tray
|
||||
|
||||
self.active_window = RiverActiveWindow(
|
||||
name="active-window",
|
||||
max_length=50,
|
||||
style="color: #ffffff; font-size: 14px; font-weight: bold;",
|
||||
)
|
||||
|
||||
self.ram_progress_bar = CircularProgressBar(
|
||||
name="ram-progress-bar", pie=True, size=24
|
||||
)
|
||||
self.cpu_progress_bar = CircularProgressBar(
|
||||
name="cpu-progress-bar", pie=True, size=24
|
||||
)
|
||||
|
||||
self.progress_label = Label(
|
||||
"", style="margin: 0px 6px 0px 0px; font-size: 12px"
|
||||
)
|
||||
self.progress_bars_overlay = Overlay(
|
||||
child=self.ram_progress_bar,
|
||||
overlays=[self.cpu_progress_bar, self.progress_label],
|
||||
)
|
||||
self.player = Player()
|
||||
self.vinyl = VinylButton()
|
||||
|
||||
self.status_container = Box(
|
||||
name="widgets-container",
|
||||
spacing=4,
|
||||
orientation="h",
|
||||
children=self.progress_bars_overlay,
|
||||
)
|
||||
|
||||
end_container_children = [
|
||||
self.vinyl,
|
||||
self.status_container,
|
||||
]
|
||||
|
||||
if self.system_tray:
|
||||
end_container_children.append(self.system_tray)
|
||||
|
||||
end_container_children.append(self.date_time)
|
||||
|
||||
self.children = CenterBox(
|
||||
name="bar-inner",
|
||||
start_children=Box(
|
||||
name="start-container",
|
||||
spacing=6,
|
||||
orientation="h",
|
||||
children=[
|
||||
Label(name="nixos-label", markup=""),
|
||||
self.workspaces,
|
||||
],
|
||||
),
|
||||
center_children=Box(
|
||||
name="center-container",
|
||||
spacing=4,
|
||||
orientation="h",
|
||||
children=[self.active_window],
|
||||
),
|
||||
end_children=Box(
|
||||
name="end-container",
|
||||
spacing=4,
|
||||
orientation="h",
|
||||
children=end_container_children,
|
||||
),
|
||||
)
|
||||
|
||||
invoke_repeater(1000, self.update_progress_bars)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def update_progress_bars(self):
|
||||
self.ram_progress_bar.value = psutil.virtual_memory().percent / 100
|
||||
self.cpu_progress_bar.value = psutil.cpu_percent() / 100
|
||||
return True
|
||||
48
bar/modules/volume.py
Normal file
48
bar/modules/volume.py
Normal file
@ -0,0 +1,48 @@
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
from fabric.audio.service import Audio
|
||||
from fabric.widgets.eventbox import EventBox
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.label import Label
|
||||
|
||||
|
||||
class VolumeWidget(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.audio = Audio()
|
||||
|
||||
self.progress_bar = CircularProgressBar(
|
||||
name="volume-progress-bar", pie=True, size=24
|
||||
)
|
||||
|
||||
self.event_box = EventBox(
|
||||
events="scroll",
|
||||
child=Overlay(
|
||||
child=self.progress_bar,
|
||||
overlays=Label(
|
||||
label="",
|
||||
style="margin: 0px 6px 0px 0px; font-size: 12px", # to center the icon glyph
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
self.audio.connect("notify::speaker", self.on_speaker_changed)
|
||||
self.event_box.connect("scroll-event", self.on_scroll)
|
||||
self.add(self.event_box)
|
||||
|
||||
def on_scroll(self, _, event):
|
||||
match event.direction:
|
||||
case 0:
|
||||
self.audio.speaker.volume += 8
|
||||
case 1:
|
||||
self.audio.speaker.volume -= 8
|
||||
return
|
||||
|
||||
def on_speaker_changed(self, *_):
|
||||
if not self.audio.speaker:
|
||||
return
|
||||
self.progress_bar.value = self.audio.speaker.volume / 100
|
||||
self.audio.speaker.bind(
|
||||
"volume", "value", self.progress_bar, lambda _, v: v / 100
|
||||
)
|
||||
return
|
||||
@ -16,14 +16,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pywayland.protocol_core import (
|
||||
Argument,
|
||||
ArgumentType,
|
||||
Global,
|
||||
Interface,
|
||||
Proxy,
|
||||
Resource,
|
||||
)
|
||||
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||
Proxy, Resource)
|
||||
|
||||
|
||||
class ZriverCommandCallbackV1(Interface):
|
||||
|
||||
@ -25,7 +25,7 @@ from pywayland.protocol_core import (
|
||||
Resource,
|
||||
)
|
||||
|
||||
from ..wayland import WlSeat
|
||||
from pywayland.protocol.wayland import WlSeat
|
||||
from .zriver_command_callback_v1 import ZriverCommandCallbackV1
|
||||
|
||||
|
||||
|
||||
@ -16,14 +16,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pywayland.protocol_core import (
|
||||
Argument,
|
||||
ArgumentType,
|
||||
Global,
|
||||
Interface,
|
||||
Proxy,
|
||||
Resource,
|
||||
)
|
||||
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||
Proxy, Resource)
|
||||
|
||||
|
||||
class ZriverOutputStatusV1(Interface):
|
||||
|
||||
@ -16,16 +16,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pywayland.protocol_core import (
|
||||
Argument,
|
||||
ArgumentType,
|
||||
Global,
|
||||
Interface,
|
||||
Proxy,
|
||||
Resource,
|
||||
)
|
||||
|
||||
from pywayland.protocol.wayland import WlOutput
|
||||
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||
Proxy, Resource)
|
||||
|
||||
|
||||
class ZriverSeatStatusV1(Interface):
|
||||
|
||||
@ -16,17 +16,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pywayland.protocol_core import (
|
||||
Argument,
|
||||
ArgumentType,
|
||||
Global,
|
||||
Interface,
|
||||
Proxy,
|
||||
Resource,
|
||||
)
|
||||
from pywayland.protocol.wayland import WlOutput, WlSeat
|
||||
from pywayland.protocol_core import (Argument, ArgumentType, Global, Interface,
|
||||
Proxy, Resource)
|
||||
|
||||
from pywayland.protocol.wayland import WlOutput
|
||||
from pywayland.protocol.wayland import WlSeat
|
||||
from .zriver_output_status_v1 import ZriverOutputStatusV1
|
||||
from .zriver_seat_status_v1 import ZriverSeatStatusV1
|
||||
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from loguru import logger
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, Any, Set
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from fabric.core.service import Service, Signal, Property
|
||||
from fabric.core.service import Property, Service, Signal
|
||||
from fabric.utils.helpers import idle_add
|
||||
from gi.repository import GLib
|
||||
from loguru import logger
|
||||
|
||||
# Import pywayland components - ensure these imports are correct
|
||||
from pywayland.client import Display
|
||||
from pywayland.protocol.wayland import WlOutput, WlSeat
|
||||
|
||||
from .generated.river_control_unstable_v1 import ZriverControlV1
|
||||
from .generated.river_status_unstable_v1 import ZriverStatusManagerV1
|
||||
|
||||
|
||||
@ -48,7 +50,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")
|
||||
@ -61,22 +63,20 @@ class River(Service):
|
||||
self._active_window_title = ""
|
||||
self.outputs: Dict[int, OutputInfo] = {}
|
||||
self.river_status_mgr = None
|
||||
self.river_control = None
|
||||
self.seat = None
|
||||
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 +84,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")
|
||||
|
||||
@ -105,11 +98,11 @@ class River(Service):
|
||||
"registry": registry,
|
||||
"outputs": {},
|
||||
"river_status_mgr": None,
|
||||
"river_control": None,
|
||||
"seat": None,
|
||||
"seat_status": None,
|
||||
}
|
||||
|
||||
# Set up registry handlers - using more direct approach like your example
|
||||
def handle_global(registry, name, iface, version):
|
||||
logger.debug(
|
||||
f"[RiverService] Global: {iface} (v{version}, name={name})"
|
||||
@ -119,6 +112,11 @@ class River(Service):
|
||||
name, ZriverStatusManagerV1, version
|
||||
)
|
||||
logger.info("[RiverService] Found river status manager")
|
||||
elif iface == "zriver_control_v1":
|
||||
state["river_control"] = registry.bind(
|
||||
name, ZriverControlV1, version
|
||||
)
|
||||
logger.info("[RiverService] Found river control interface")
|
||||
elif iface == "wl_output":
|
||||
output = registry.bind(name, WlOutput, version)
|
||||
state["outputs"][name] = OutputInfo(name=name, output=output)
|
||||
@ -153,6 +151,12 @@ class River(Service):
|
||||
|
||||
# Handle the window title updates through seat status
|
||||
|
||||
if not state["river_control"]:
|
||||
logger.error(
|
||||
"[RiverService] River control interface not found - falling back to riverctl"
|
||||
)
|
||||
# You could still fall back to the old riverctl method here if needed
|
||||
|
||||
def focused_view_handler(_, title):
|
||||
logger.debug(f"[RiverService] Focused view title: {title}")
|
||||
self._active_window_title = title
|
||||
@ -218,8 +222,10 @@ class River(Service):
|
||||
# Update our outputs dictionary
|
||||
self.outputs.update(state["outputs"])
|
||||
self.river_status_mgr = state["river_status_mgr"]
|
||||
self.river_control = state["river_control"]
|
||||
self.seat = state["seat"]
|
||||
self.seat_status = state.get("seat_status")
|
||||
self._display = display
|
||||
|
||||
# Mark service as ready
|
||||
idle_add(self._set_ready)
|
||||
@ -236,11 +242,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):
|
||||
@ -287,8 +295,48 @@ class River(Service):
|
||||
|
||||
return sorted(tags)
|
||||
|
||||
def run_command(self, command, *args):
|
||||
def run_command(self, command, *args, callback=None):
|
||||
"""Run a riverctl command"""
|
||||
if not self.river_control or not self.seat:
|
||||
logger.warning(
|
||||
"[RiverService] River control or seat not available, falling back to riverctl"
|
||||
)
|
||||
return self._run_command_fallback(command, *args)
|
||||
|
||||
self.river_control.add_argument(command)
|
||||
for arg in args:
|
||||
self.river_control.add_argument(str(arg))
|
||||
|
||||
# Execute the command
|
||||
command_callback = self.river_control.run_command(self.seat)
|
||||
|
||||
# Set up callback handlers
|
||||
result = {"stdout": None, "stderr": None, "success": None}
|
||||
|
||||
def handle_success(_, output):
|
||||
logger.debug(f"[RiverService] Command success: {output}")
|
||||
result["stdout"] = output
|
||||
result["success"] = True
|
||||
if callback:
|
||||
idle_add(lambda: callback(True, output, None))
|
||||
|
||||
def handle_failure(_, failure_message):
|
||||
logger.debug(f"[RiverService] Command failure: {failure_message}")
|
||||
result["stderr"] = failure_message
|
||||
result["success"] = False
|
||||
if callback:
|
||||
idle_add(lambda: callback(False, None, failure_message))
|
||||
|
||||
command_callback.dispatcher["success"] = handle_success
|
||||
command_callback.dispatcher["failure"] = handle_failure
|
||||
|
||||
if hasattr(self, "_display"):
|
||||
self._display.flush()
|
||||
|
||||
return True
|
||||
|
||||
def _run_command_fallback(self, command, *args):
|
||||
"""Fallback to riverctl"""
|
||||
import subprocess
|
||||
|
||||
cmd = ["riverctl", command] + [str(arg) for arg in args]
|
||||
@ -302,7 +350,7 @@ class River(Service):
|
||||
)
|
||||
return None
|
||||
|
||||
def toggle_focused_tag(self, tag):
|
||||
def toggle_focused_tag(self, tag, callback=None):
|
||||
"""Toggle a tag in the focused tags"""
|
||||
tag_mask = 1 << int(tag)
|
||||
self.run_command("set-focused-tags", str(tag_mask))
|
||||
self.run_command("set-focused-tags", str(tag_mask), callback=callback)
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
from loguru import logger
|
||||
from fabric.core.service import Property
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.utils.helpers import bulk_connect
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.eventbox import EventBox
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.utils.helpers import bulk_connect
|
||||
from .service import River
|
||||
|
||||
|
||||
from gi.repository import Gdk
|
||||
from loguru import logger
|
||||
|
||||
from .service import River
|
||||
|
||||
_connection: River | None = None
|
||||
|
||||
@ -76,12 +75,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 +97,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 +199,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 +217,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"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user