Compare commits
15 Commits
53713ee0f5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| df2bef7685 | |||
| 5d08a48b6c | |||
| 82b0cf7aaa | |||
| e4744bab81 | |||
| 872dbfc792 | |||
| 64781af68f | |||
| 0ebfbdb3a9 | |||
| bf3920ad35 | |||
| 72c76c9fda | |||
| 743e1ed0c5 | |||
| f28dd0b6a2 | |||
| 0b8190ae8b | |||
| 9495dfba62 | |||
| 0cf1c5aeb7 | |||
| f8b352d624 |
225
bar/bar.css
225
bar/bar.css
@@ -1,225 +0,0 @@
|
|||||||
/* Fabric bar.css
|
|
||||||
* https://github.com/Fabric-Development/fabric/blob/rewrite/examples/bar/bar.css
|
|
||||||
* Modified with Camellia Theme: https://github.com/camellia-theme/camellia
|
|
||||||
*/
|
|
||||||
/* we can use webcss variables, fabric compiles that to gtk css.
|
|
||||||
global variables can be stored in :vars */
|
|
||||||
:vars {
|
|
||||||
/* Base colors from Camellia theme */
|
|
||||||
--background: #17181C; /* BG */
|
|
||||||
--mid-bg: #1E1F24; /* Mid BG */
|
|
||||||
--light-bg: #26272B; /* Light BG */
|
|
||||||
--dark-grey: #333438; /* Dark Grey */
|
|
||||||
--light-grey: #8F9093; /* Light Grey */
|
|
||||||
--dark-fg: #B0B1B4; /* Dark FG */
|
|
||||||
--mid-fg: #CBCCCE; /* Mid FG */
|
|
||||||
--foreground: #E4E5E7; /* FG */
|
|
||||||
|
|
||||||
/* Accent colors from Camellia theme */
|
|
||||||
--pink: #FA3867; /* Pink */
|
|
||||||
--orange: #F3872F; /* Orange */
|
|
||||||
--gold: #FEBD16; /* Gold */
|
|
||||||
--lime: #3FD43B; /* Lime */
|
|
||||||
--turquoise: #47E7CE; /* Turquoise */
|
|
||||||
--blue: #53ADE1; /* Blue */
|
|
||||||
--violet: #AD60FF; /* Violet */
|
|
||||||
--red: #FC3F2C; /* Red */
|
|
||||||
|
|
||||||
/* Functional variables */
|
|
||||||
--window-bg: alpha(var(--background), 0.9);
|
|
||||||
--module-bg: alpha(var(--mid-bg), 0.8);
|
|
||||||
--border-color: var(--light-bg);
|
|
||||||
--ws-active: var(--pink);
|
|
||||||
--ws-inactive: var(--blue);
|
|
||||||
--ws-empty: var(--dark-grey);
|
|
||||||
--ws-hover: var(--turquoise);
|
|
||||||
--ws-urgent: var(--red);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* unset so we can style everything from the ground up. */
|
|
||||||
* {
|
|
||||||
all: unset;
|
|
||||||
color: var(--foreground);
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: "Jost*", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-size: 400% 400%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bar-inner {
|
|
||||||
padding: 4px;
|
|
||||||
border-bottom: solid 2px;
|
|
||||||
border-color: var(--border-color);
|
|
||||||
background-color: var(--window-bg);
|
|
||||||
min-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces {
|
|
||||||
padding: 6px;
|
|
||||||
min-width: 0px;
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button {
|
|
||||||
padding: 0px 8px;
|
|
||||||
transition: padding 0.05s steps(8);
|
|
||||||
background-color: var(--foreground);
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button>label {
|
|
||||||
font-size: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces button.hover {
|
|
||||||
background-color: var(--ws-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces button.urgent {
|
|
||||||
background-color: var(--ws-urgent);
|
|
||||||
color: var(--foreground);
|
|
||||||
font-weight: bold;
|
|
||||||
animation: urgent-blink 1s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes urgent-blink {
|
|
||||||
0% { opacity: 1.0; }
|
|
||||||
50% { opacity: 0.5; }
|
|
||||||
100% { opacity: 1.0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button.empty {
|
|
||||||
background-color: var(--ws-empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workspaces>button.active {
|
|
||||||
padding: 0px 32px;
|
|
||||||
background-color: var(--ws-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
#center-container {
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.active-window {
|
|
||||||
color: var(--foreground);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#date-time,
|
|
||||||
menu>menuitem>label,
|
|
||||||
#date-time>label,
|
|
||||||
/* system tray */
|
|
||||||
#system-tray {
|
|
||||||
padding: 2px 4px;
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* menu and menu items (written for the system tray) */
|
|
||||||
menu {
|
|
||||||
border: solid 2px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border-color: var(--border-color);
|
|
||||||
background-color: var(--window-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem {
|
|
||||||
border-radius: 0px;
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
padding: 6px;
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem:first-child {
|
|
||||||
margin-top: 1px;
|
|
||||||
border-radius: 8px 8px 0px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem:last-child {
|
|
||||||
margin-bottom: 1px;
|
|
||||||
border-radius: 0px 0px 8px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu>menuitem:hover {
|
|
||||||
background-color: var(--pink);
|
|
||||||
}
|
|
||||||
|
|
||||||
#cpu-progress-bar,
|
|
||||||
#ram-progress-bar,
|
|
||||||
#volume-progress-bar {
|
|
||||||
color: transparent;
|
|
||||||
background-color: transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
#cpu-progress-bar {
|
|
||||||
border: solid 0px alpha(var(--violet), 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ram-progress-bar,
|
|
||||||
#volume-progress-bar {
|
|
||||||
border: solid 0px var(--blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
#widgets-container {
|
|
||||||
background-color: var(--module-bg);
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nixos-label {
|
|
||||||
color: var(--blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
tooltip {
|
|
||||||
border: solid 2px;
|
|
||||||
border-color: var(--border-color);
|
|
||||||
background-color: var(--window-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
tooltip>* {
|
|
||||||
padding: 2px 4px
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Vinyl button styling */
|
|
||||||
#vinyl-button {
|
|
||||||
padding: 0px 8px;
|
|
||||||
transition: padding 0.05s steps(8);
|
|
||||||
background-color: rgba(180, 180, 180, 0.2);
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Active state styling */
|
|
||||||
.active #vinyl-button {
|
|
||||||
background-color: rgba(108, 158, 175, 0.7);
|
|
||||||
padding: 0px 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Icon styling */
|
|
||||||
#vinyl-icon {
|
|
||||||
color: #555555;
|
|
||||||
min-width: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Label styling */
|
|
||||||
#vinyl-label {
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Active state changes for icon and label */
|
|
||||||
.active #vinyl-icon,
|
|
||||||
.active #vinyl-label {
|
|
||||||
color: var(--pink);
|
|
||||||
padding: 0px 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hover effect */
|
|
||||||
#vinyl-button:hover {
|
|
||||||
background-color: rgba(180, 180, 180, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.active #vinyl-button:hover {
|
|
||||||
background-color: rgba(108, 158, 175, 0.9);
|
|
||||||
}
|
|
||||||
52
bar/config.py
Normal file
52
bar/config.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import yaml
|
||||||
|
import os
|
||||||
|
from platformdirs import user_config_dir
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
APP_NAME = "makku_bar"
|
||||||
|
|
||||||
|
XDG_CONFIG_HOME = user_config_dir(appname=APP_NAME)
|
||||||
|
XDG_CONFIG_FILE = os.path.join(XDG_CONFIG_HOME, "config.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(config_path=XDG_CONFIG_FILE):
|
||||||
|
"""Loads configuration from a YAML file."""
|
||||||
|
if config_path is None:
|
||||||
|
print("No configuration file path provided or found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(config_path, "r") as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
return config
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Configuration file not found at {config_path}")
|
||||||
|
return None
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
print(f"Error parsing YAML file '{config_path}': {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred loading config file '{config_path}': {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_args():
|
||||||
|
parser = argparse.ArgumentParser(description="makku_bar")
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--config",
|
||||||
|
help="Path to a custom configuration file.",
|
||||||
|
type=str,
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args.config
|
||||||
|
|
||||||
|
|
||||||
|
app_config = load_config() if not load_args() else load_config(load_args())
|
||||||
|
|
||||||
|
if app_config is None:
|
||||||
|
raise Exception("Config file missing")
|
||||||
|
|
||||||
|
VINYL = app_config.get("vinyl", {"enable": False})
|
||||||
55
bar/main.py
Normal file
55
bar/main.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from fabric import Application
|
||||||
|
from fabric.system_tray.widgets import SystemTray
|
||||||
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
|
from fabric.river.widgets import (
|
||||||
|
get_river_connection,
|
||||||
|
)
|
||||||
|
from fabric.utils import (
|
||||||
|
get_relative_path,
|
||||||
|
)
|
||||||
|
from .modules.bar import StatusBar
|
||||||
|
from .modules.window_fuzzy import FuzzyWindowFinder
|
||||||
|
|
||||||
|
|
||||||
|
tray = SystemTray(name="system-tray", spacing=4)
|
||||||
|
river = get_river_connection()
|
||||||
|
|
||||||
|
dummy = Window(visible=False)
|
||||||
|
finder = FuzzyWindowFinder()
|
||||||
|
|
||||||
|
bar_windows = []
|
||||||
|
|
||||||
|
app = Application("bar", dummy, finder)
|
||||||
|
app.set_stylesheet_from_file(get_relative_path("styles/main.css"))
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
bar = StatusBar(display=output_id, tray=tray if i == 0 else None, monitor=i)
|
||||||
|
bar_windows.append(bar)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if river.ready:
|
||||||
|
spawn_bars()
|
||||||
|
else:
|
||||||
|
river.connect("notify::ready", lambda sender, pspec: spawn_bars())
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,80 +1,35 @@
|
|||||||
# fabric bar.py example
|
|
||||||
# https://github.com/Fabric-Development/fabric/blob/rewrite/examples/bar/bar.py
|
|
||||||
import psutil
|
import psutil
|
||||||
from fabric import Application
|
|
||||||
from fabric.widgets.box import Box
|
from fabric.widgets.box import Box
|
||||||
from fabric.widgets.label import Label
|
from fabric.widgets.label import Label
|
||||||
from fabric.widgets.overlay import Overlay
|
from fabric.widgets.overlay import Overlay
|
||||||
from fabric.widgets.eventbox import EventBox
|
|
||||||
from fabric.widgets.datetime import DateTime
|
from fabric.widgets.datetime import DateTime
|
||||||
from fabric.widgets.centerbox import CenterBox
|
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 fabric.utils import (
|
|
||||||
FormattedString,
|
|
||||||
bulk_replace,
|
|
||||||
invoke_repeater,
|
|
||||||
get_relative_path,
|
|
||||||
)
|
|
||||||
from bar.modules.player import Player
|
from bar.modules.player import Player
|
||||||
from bar.modules.vinyl import VinylButton
|
from bar.modules.vinyl import VinylButton
|
||||||
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
|
from fabric.system_tray.widgets import SystemTray
|
||||||
|
from fabric.river.widgets import (
|
||||||
|
RiverWorkspaces,
|
||||||
|
RiverWorkspaceButton,
|
||||||
|
RiverActiveWindow,
|
||||||
|
get_river_connection,
|
||||||
|
)
|
||||||
|
from fabric.utils import (
|
||||||
|
invoke_repeater,
|
||||||
|
)
|
||||||
|
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||||
|
|
||||||
AUDIO_WIDGET = True
|
from bar.config import VINYL
|
||||||
|
|
||||||
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):
|
class StatusBar(Window):
|
||||||
def __init__(self, display: int, monitor: int = 1, with_system_tray: bool = False):
|
def __init__(
|
||||||
|
self,
|
||||||
|
display: int,
|
||||||
|
tray: SystemTray | None = None,
|
||||||
|
monitor: int = 1,
|
||||||
|
river_service=None,
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name="bar",
|
name="bar",
|
||||||
layer="top",
|
layer="top",
|
||||||
@@ -85,16 +40,20 @@ class StatusBar(Window):
|
|||||||
all_visible=False,
|
all_visible=False,
|
||||||
monitor=monitor,
|
monitor=monitor,
|
||||||
)
|
)
|
||||||
|
if river_service:
|
||||||
|
self.river = river_service
|
||||||
|
else:
|
||||||
|
self.river = get_river_connection()
|
||||||
|
|
||||||
self.workspaces = RiverWorkspaces(
|
self.workspaces = RiverWorkspaces(
|
||||||
display,
|
display,
|
||||||
name="workspaces",
|
name="workspaces",
|
||||||
spacing=4,
|
spacing=4,
|
||||||
buttons_factory=lambda ws_id: RiverWorkspaceButton(id=ws_id, label=None),
|
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.date_time = DateTime(name="date-time", formatters="%d %b - %H:%M")
|
||||||
self.system_tray = None
|
self.system_tray = tray
|
||||||
if with_system_tray:
|
|
||||||
self.system_tray = SystemTray(name="system-tray", spacing=4)
|
|
||||||
|
|
||||||
self.active_window = RiverActiveWindow(
|
self.active_window = RiverActiveWindow(
|
||||||
name="active-window",
|
name="active-window",
|
||||||
@@ -108,15 +67,18 @@ class StatusBar(Window):
|
|||||||
self.cpu_progress_bar = CircularProgressBar(
|
self.cpu_progress_bar = CircularProgressBar(
|
||||||
name="cpu-progress-bar", pie=True, size=24
|
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(
|
self.progress_bars_overlay = Overlay(
|
||||||
child=self.ram_progress_bar,
|
child=self.ram_progress_bar,
|
||||||
overlays=[
|
overlays=[self.cpu_progress_bar, self.progress_label],
|
||||||
self.cpu_progress_bar,
|
|
||||||
Label("", style="margin: 0px 6px 0px 0px; font-size: 12px"),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
self.player = Player()
|
self.player = Player()
|
||||||
self.vinyl = VinylButton()
|
self.vinyl = None
|
||||||
|
if VINYL["enable"]:
|
||||||
|
self.vinyl = VinylButton()
|
||||||
|
|
||||||
self.status_container = Box(
|
self.status_container = Box(
|
||||||
name="widgets-container",
|
name="widgets-container",
|
||||||
@@ -124,21 +86,18 @@ class StatusBar(Window):
|
|||||||
orientation="h",
|
orientation="h",
|
||||||
children=self.progress_bars_overlay,
|
children=self.progress_bars_overlay,
|
||||||
)
|
)
|
||||||
self.status_container.add(VolumeWidget()) if AUDIO_WIDGET is True else None
|
|
||||||
|
|
||||||
end_container_children = [
|
end_container_children = []
|
||||||
self.vinyl,
|
|
||||||
self.status_container,
|
if self.vinyl:
|
||||||
self.date_time,
|
end_container_children.append(self.vinyl)
|
||||||
]
|
|
||||||
|
end_container_children.append(self.status_container)
|
||||||
|
if self.system_tray:
|
||||||
|
end_container_children.append(self.system_tray)
|
||||||
|
|
||||||
|
end_container_children.append(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(
|
self.children = CenterBox(
|
||||||
name="bar-inner",
|
name="bar-inner",
|
||||||
start_children=Box(
|
start_children=Box(
|
||||||
@@ -172,16 +131,3 @@ class StatusBar(Window):
|
|||||||
self.ram_progress_bar.value = psutil.virtual_memory().percent / 100
|
self.ram_progress_bar.value = psutil.virtual_memory().percent / 100
|
||||||
self.cpu_progress_bar.value = psutil.cpu_percent() / 100
|
self.cpu_progress_bar.value = psutil.cpu_percent() / 100
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
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"))
|
|
||||||
|
|
||||||
app.run()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -25,10 +25,14 @@ class VinylButton(Box):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
active_command="""pw-link alsa_input.pci-0000_12_00.6.analog-stereo:capture_FL alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX0
|
active_command=[
|
||||||
pw-link alsa_input.pci-0000_12_00.6.analog-stereo:capture_FR alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX1""",
|
"pw-link alsa_input.pci-0000_12_00.6.analog-stereo:capture_FL alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX0",
|
||||||
inactive_command="""pw-link -d alsa_input.pci-0000_12_00.6.analog-stereo:capture_FL alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX0
|
"pw-link alsa_input.pci-0000_12_00.6.analog-stereo:capture_FR alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX1",
|
||||||
pw-link -d alsa_input.pci-0000_12_00.6.analog-stereo:capture_FR alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX1 """,
|
],
|
||||||
|
inactive_command=[
|
||||||
|
"pw-link -d alsa_input.pci-0000_12_00.6.analog-stereo:capture_FL alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX0",
|
||||||
|
"pw-link -d alsa_input.pci-0000_12_00.6.analog-stereo:capture_FR alsa_output.usb-BEHRINGER_UMC1820_A71E9E3E-00.multichannel-output:playback_AUX1 ",
|
||||||
|
],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -80,13 +84,15 @@ pw-link -d alsa_input.pci-0000_12_00.6.analog-stereo:capture_FR alsa_output.usb-
|
|||||||
def _execute_active_command(self):
|
def _execute_active_command(self):
|
||||||
"""Execute shell command when button is activated"""
|
"""Execute shell command when button is activated"""
|
||||||
try:
|
try:
|
||||||
subprocess.Popen(self._active_command, shell=True)
|
for cmd in self._active_command:
|
||||||
|
subprocess.Popen(cmd, shell=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error executing active command: {e}")
|
print(f"Error executing active command: {e}")
|
||||||
|
|
||||||
def _execute_inactive_command(self):
|
def _execute_inactive_command(self):
|
||||||
"""Execute shell command when button is deactivated"""
|
"""Execute shell command when button is deactivated"""
|
||||||
try:
|
try:
|
||||||
subprocess.Popen(self._inactive_command, shell=True)
|
for cmd in self._inactive_command:
|
||||||
|
subprocess.Popen(cmd, shell=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error executing inactive command: {e}")
|
print(f"Error executing inactive command: {e}")
|
||||||
|
|||||||
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
|
||||||
75
bar/modules/window_fuzzy.py
Normal file
75
bar/modules/window_fuzzy.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import operator
|
||||||
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.label import Label
|
||||||
|
from fabric.widgets.entry import Entry
|
||||||
|
from fabric.utils import idle_add
|
||||||
|
from gi.repository import Gdk
|
||||||
|
|
||||||
|
|
||||||
|
class FuzzyWindowFinder(Window):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
monitor: int = 0,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
name="finder",
|
||||||
|
anchor="center",
|
||||||
|
monitor=monitor,
|
||||||
|
keyboard_mode="on-demand",
|
||||||
|
type="popup",
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._all_windows = ["Test", "Uwu", "Tidal"]
|
||||||
|
|
||||||
|
self.viewport = Box(name="viewport", spacing=4, orientation="v")
|
||||||
|
|
||||||
|
self.search_entry = Entry(
|
||||||
|
name="search-entry",
|
||||||
|
placeholder="Search Windows...",
|
||||||
|
h_expand=True,
|
||||||
|
editable=True,
|
||||||
|
notify_text=self.notify_text,
|
||||||
|
on_activate=lambda entry, *_: self.on_search_entry_activate(
|
||||||
|
entry.get_text()
|
||||||
|
),
|
||||||
|
on_key_press_event=self.on_search_entry_key_press,
|
||||||
|
)
|
||||||
|
self.picker_box = Box(
|
||||||
|
name="picker-box",
|
||||||
|
spacing=4,
|
||||||
|
orientation="v",
|
||||||
|
children=[self.search_entry, self.viewport],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add(self.picker_box)
|
||||||
|
self.arrange_viewport("")
|
||||||
|
|
||||||
|
def notify_text(self, entry, *_):
|
||||||
|
text = entry.get_text()
|
||||||
|
self.arrange_viewport(text) # Update list on typing
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
def on_search_entry_key_press(self, widget, event):
|
||||||
|
# if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right):
|
||||||
|
# self.move_selection_2d(event.keyval)
|
||||||
|
# return True
|
||||||
|
print(event.keyval)
|
||||||
|
if event.keyval in [Gdk.KEY_Escape, 103]:
|
||||||
|
self.hide()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def on_search_entry_activate(self, text):
|
||||||
|
print(f"activate {text}")
|
||||||
|
|
||||||
|
def arrange_viewport(self, query: str = ""):
|
||||||
|
self.viewport.children = [] # Clear previous entries
|
||||||
|
|
||||||
|
filtered = [w for w in self._all_windows if query.lower() in w.lower()]
|
||||||
|
|
||||||
|
for window in filtered:
|
||||||
|
self.viewport.add(
|
||||||
|
Box(name="slot-box", orientation="h", children=[Label(label=window)])
|
||||||
|
)
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from .service import River
|
|
||||||
|
|
||||||
__all__ = ["River"]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright 2020 The River Developers
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
|
||||||
# copyright notice and this permission notice appear in all copies.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
from .zriver_command_callback_v1 import ZriverCommandCallbackV1 # noqa: F401
|
|
||||||
from .zriver_control_v1 import ZriverControlV1 # noqa: F401
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright 2020 The River Developers
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
|
||||||
# copyright notice and this permission notice appear in all copies.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverCommandCallbackV1(Interface):
|
|
||||||
"""Callback object
|
|
||||||
|
|
||||||
This object is created by the run_command request. Exactly one of the
|
|
||||||
success or failure events will be sent. This object will be destroyed by
|
|
||||||
the compositor after one of the events is sent.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "zriver_command_callback_v1"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverCommandCallbackV1Proxy(Proxy[ZriverCommandCallbackV1]):
|
|
||||||
interface = ZriverCommandCallbackV1
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverCommandCallbackV1Resource(Resource):
|
|
||||||
interface = ZriverCommandCallbackV1
|
|
||||||
|
|
||||||
@ZriverCommandCallbackV1.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def success(self, output: str) -> None:
|
|
||||||
"""Command successful
|
|
||||||
|
|
||||||
Sent when the command has been successfully received and executed by
|
|
||||||
the compositor. Some commands may produce output, in which case the
|
|
||||||
output argument will be a non-empty string.
|
|
||||||
|
|
||||||
:param output:
|
|
||||||
the output of the command
|
|
||||||
:type output:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(0, output)
|
|
||||||
|
|
||||||
@ZriverCommandCallbackV1.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def failure(self, failure_message: str) -> None:
|
|
||||||
"""Command failed
|
|
||||||
|
|
||||||
Sent when the command could not be carried out. This could be due to
|
|
||||||
sending a non-existent command, no command, not enough arguments, too
|
|
||||||
many arguments, invalid arguments, etc.
|
|
||||||
|
|
||||||
:param failure_message:
|
|
||||||
a message explaining why failure occurred
|
|
||||||
:type failure_message:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(1, failure_message)
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverCommandCallbackV1Global(Global):
|
|
||||||
interface = ZriverCommandCallbackV1
|
|
||||||
|
|
||||||
|
|
||||||
ZriverCommandCallbackV1._gen_c()
|
|
||||||
ZriverCommandCallbackV1.proxy_class = ZriverCommandCallbackV1Proxy
|
|
||||||
ZriverCommandCallbackV1.resource_class = ZriverCommandCallbackV1Resource
|
|
||||||
ZriverCommandCallbackV1.global_class = ZriverCommandCallbackV1Global
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright 2020 The River Developers
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
|
||||||
# copyright notice and this permission notice appear in all copies.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..wayland import WlSeat
|
|
||||||
from .zriver_command_callback_v1 import ZriverCommandCallbackV1
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverControlV1(Interface):
|
|
||||||
"""Run compositor commands
|
|
||||||
|
|
||||||
This interface allows clients to run compositor commands and receive a
|
|
||||||
success/failure response with output or a failure message respectively.
|
|
||||||
|
|
||||||
Each command is built up in a series of add_argument requests and executed
|
|
||||||
with a run_command request. The first argument is the command to be run.
|
|
||||||
|
|
||||||
A complete list of commands should be made available in the man page of the
|
|
||||||
compositor.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "zriver_control_v1"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverControlV1Proxy(Proxy[ZriverControlV1]):
|
|
||||||
interface = ZriverControlV1
|
|
||||||
|
|
||||||
@ZriverControlV1.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy the river_control object
|
|
||||||
|
|
||||||
This request indicates that the client will not use the river_control
|
|
||||||
object any more. Objects that have been created through this instance
|
|
||||||
are not affected.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@ZriverControlV1.request(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def add_argument(self, argument: str) -> None:
|
|
||||||
"""Add an argument to the current command
|
|
||||||
|
|
||||||
Arguments are stored by the server in the order they were sent until
|
|
||||||
the run_command request is made.
|
|
||||||
|
|
||||||
:param argument:
|
|
||||||
the argument to add
|
|
||||||
:type argument:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._marshal(1, argument)
|
|
||||||
|
|
||||||
@ZriverControlV1.request(
|
|
||||||
Argument(ArgumentType.Object, interface=WlSeat),
|
|
||||||
Argument(ArgumentType.NewId, interface=ZriverCommandCallbackV1),
|
|
||||||
)
|
|
||||||
def run_command(self, seat: WlSeat) -> Proxy[ZriverCommandCallbackV1]:
|
|
||||||
"""Run the current command
|
|
||||||
|
|
||||||
Execute the command built up using the add_argument request for the
|
|
||||||
given seat.
|
|
||||||
|
|
||||||
:param seat:
|
|
||||||
:type seat:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSeat`
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.river_control_unstable_v1.ZriverCommandCallbackV1`
|
|
||||||
-- callback object
|
|
||||||
"""
|
|
||||||
callback = self._marshal_constructor(2, ZriverCommandCallbackV1, seat)
|
|
||||||
return callback
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverControlV1Resource(Resource):
|
|
||||||
interface = ZriverControlV1
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverControlV1Global(Global):
|
|
||||||
interface = ZriverControlV1
|
|
||||||
|
|
||||||
|
|
||||||
ZriverControlV1._gen_c()
|
|
||||||
ZriverControlV1.proxy_class = ZriverControlV1Proxy
|
|
||||||
ZriverControlV1.resource_class = ZriverControlV1Resource
|
|
||||||
ZriverControlV1.global_class = ZriverControlV1Global
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright 2020 The River Developers
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
|
||||||
# copyright notice and this permission notice appear in all copies.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
from .zriver_output_status_v1 import ZriverOutputStatusV1 # noqa: F401
|
|
||||||
from .zriver_seat_status_v1 import ZriverSeatStatusV1 # noqa: F401
|
|
||||||
from .zriver_status_manager_v1 import ZriverStatusManagerV1 # noqa: F401
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright 2020 The River Developers
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
|
||||||
# copyright notice and this permission notice appear in all copies.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverOutputStatusV1(Interface):
|
|
||||||
"""Track output tags and focus
|
|
||||||
|
|
||||||
This interface allows clients to receive information about the current
|
|
||||||
windowing state of an output.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "zriver_output_status_v1"
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverOutputStatusV1Proxy(Proxy[ZriverOutputStatusV1]):
|
|
||||||
interface = ZriverOutputStatusV1
|
|
||||||
|
|
||||||
@ZriverOutputStatusV1.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy the river_output_status object
|
|
||||||
|
|
||||||
This request indicates that the client will not use the
|
|
||||||
river_output_status object any more.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverOutputStatusV1Resource(Resource):
|
|
||||||
interface = ZriverOutputStatusV1
|
|
||||||
|
|
||||||
@ZriverOutputStatusV1.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
)
|
|
||||||
def focused_tags(self, tags: int) -> None:
|
|
||||||
"""Focused tags of the output
|
|
||||||
|
|
||||||
Sent once binding the interface and again whenever the tag focus of the
|
|
||||||
output changes.
|
|
||||||
|
|
||||||
:param tags:
|
|
||||||
32-bit bitfield
|
|
||||||
:type tags:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(0, tags)
|
|
||||||
|
|
||||||
@ZriverOutputStatusV1.event(
|
|
||||||
Argument(ArgumentType.Array),
|
|
||||||
)
|
|
||||||
def view_tags(self, tags: list) -> None:
|
|
||||||
"""Tag state of an output's views
|
|
||||||
|
|
||||||
Sent once on binding the interface and again whenever the tag state of
|
|
||||||
the output changes.
|
|
||||||
|
|
||||||
:param tags:
|
|
||||||
array of 32-bit bitfields
|
|
||||||
:type tags:
|
|
||||||
`ArgumentType.Array`
|
|
||||||
"""
|
|
||||||
self._post_event(1, tags)
|
|
||||||
|
|
||||||
@ZriverOutputStatusV1.event(
|
|
||||||
Argument(ArgumentType.Uint),
|
|
||||||
version=2,
|
|
||||||
)
|
|
||||||
def urgent_tags(self, tags: int) -> None:
|
|
||||||
"""Tags of the output with an urgent view
|
|
||||||
|
|
||||||
Sent once on binding the interface and again whenever the set of tags
|
|
||||||
with at least one urgent view changes.
|
|
||||||
|
|
||||||
:param tags:
|
|
||||||
32-bit bitfield
|
|
||||||
:type tags:
|
|
||||||
`ArgumentType.Uint`
|
|
||||||
"""
|
|
||||||
self._post_event(2, tags)
|
|
||||||
|
|
||||||
@ZriverOutputStatusV1.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
version=4,
|
|
||||||
)
|
|
||||||
def layout_name(self, name: str) -> None:
|
|
||||||
"""Name of the layout
|
|
||||||
|
|
||||||
Sent once on binding the interface should a layout name exist and again
|
|
||||||
whenever the name changes.
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
layout name
|
|
||||||
:type name:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(3, name)
|
|
||||||
|
|
||||||
@ZriverOutputStatusV1.event(version=4)
|
|
||||||
def layout_name_clear(self) -> None:
|
|
||||||
"""Name of the layout
|
|
||||||
|
|
||||||
Sent when the current layout name has been removed without a new one
|
|
||||||
being set, for example when the active layout generator disconnects.
|
|
||||||
"""
|
|
||||||
self._post_event(4)
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverOutputStatusV1Global(Global):
|
|
||||||
interface = ZriverOutputStatusV1
|
|
||||||
|
|
||||||
|
|
||||||
ZriverOutputStatusV1._gen_c()
|
|
||||||
ZriverOutputStatusV1.proxy_class = ZriverOutputStatusV1Proxy
|
|
||||||
ZriverOutputStatusV1.resource_class = ZriverOutputStatusV1Resource
|
|
||||||
ZriverOutputStatusV1.global_class = ZriverOutputStatusV1Global
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright 2020 The River Developers
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
|
||||||
# copyright notice and this permission notice appear in all copies.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pywayland.protocol_core import (
|
|
||||||
Argument,
|
|
||||||
ArgumentType,
|
|
||||||
Global,
|
|
||||||
Interface,
|
|
||||||
Proxy,
|
|
||||||
Resource,
|
|
||||||
)
|
|
||||||
|
|
||||||
from pywayland.protocol.wayland import WlOutput
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverSeatStatusV1(Interface):
|
|
||||||
"""Track seat focus
|
|
||||||
|
|
||||||
This interface allows clients to receive information about the current
|
|
||||||
focus of a seat. Note that (un)focused_output events will only be sent if
|
|
||||||
the client has bound the relevant
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput` globals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "zriver_seat_status_v1"
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverSeatStatusV1Proxy(Proxy[ZriverSeatStatusV1]):
|
|
||||||
interface = ZriverSeatStatusV1
|
|
||||||
|
|
||||||
@ZriverSeatStatusV1.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy the river_seat_status object
|
|
||||||
|
|
||||||
This request indicates that the client will not use the
|
|
||||||
river_seat_status object any more.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverSeatStatusV1Resource(Resource):
|
|
||||||
interface = ZriverSeatStatusV1
|
|
||||||
|
|
||||||
@ZriverSeatStatusV1.event(
|
|
||||||
Argument(ArgumentType.Object, interface=WlOutput),
|
|
||||||
)
|
|
||||||
def focused_output(self, output: WlOutput) -> None:
|
|
||||||
"""The seat focused an output
|
|
||||||
|
|
||||||
Sent on binding the interface and again whenever an output gains focus.
|
|
||||||
|
|
||||||
:param output:
|
|
||||||
:type output:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput`
|
|
||||||
"""
|
|
||||||
self._post_event(0, output)
|
|
||||||
|
|
||||||
@ZriverSeatStatusV1.event(
|
|
||||||
Argument(ArgumentType.Object, interface=WlOutput),
|
|
||||||
)
|
|
||||||
def unfocused_output(self, output: WlOutput) -> None:
|
|
||||||
"""The seat unfocused an output
|
|
||||||
|
|
||||||
Sent whenever an output loses focus.
|
|
||||||
|
|
||||||
:param output:
|
|
||||||
:type output:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput`
|
|
||||||
"""
|
|
||||||
self._post_event(1, output)
|
|
||||||
|
|
||||||
@ZriverSeatStatusV1.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
)
|
|
||||||
def focused_view(self, title: str) -> None:
|
|
||||||
"""Information on the focused view
|
|
||||||
|
|
||||||
Sent once on binding the interface and again whenever the focused view
|
|
||||||
or a property thereof changes. The title may be an empty string if no
|
|
||||||
view is focused or the focused view did not set a title.
|
|
||||||
|
|
||||||
:param title:
|
|
||||||
title of the focused view
|
|
||||||
:type title:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(2, title)
|
|
||||||
|
|
||||||
@ZriverSeatStatusV1.event(
|
|
||||||
Argument(ArgumentType.String),
|
|
||||||
version=3,
|
|
||||||
)
|
|
||||||
def mode(self, name: str) -> None:
|
|
||||||
"""The active mode changed
|
|
||||||
|
|
||||||
Sent once on binding the interface and again whenever a new mode is
|
|
||||||
entered (e.g. with riverctl enter-mode foobar).
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
name of the mode
|
|
||||||
:type name:
|
|
||||||
`ArgumentType.String`
|
|
||||||
"""
|
|
||||||
self._post_event(3, name)
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverSeatStatusV1Global(Global):
|
|
||||||
interface = ZriverSeatStatusV1
|
|
||||||
|
|
||||||
|
|
||||||
ZriverSeatStatusV1._gen_c()
|
|
||||||
ZriverSeatStatusV1.proxy_class = ZriverSeatStatusV1Proxy
|
|
||||||
ZriverSeatStatusV1.resource_class = ZriverSeatStatusV1Resource
|
|
||||||
ZriverSeatStatusV1.global_class = ZriverSeatStatusV1Global
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
# This file has been autogenerated by the pywayland scanner
|
|
||||||
|
|
||||||
# Copyright 2020 The River Developers
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
|
||||||
# copyright notice and this permission notice appear in all copies.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverStatusManagerV1(Interface):
|
|
||||||
"""Manage river status objects
|
|
||||||
|
|
||||||
A global factory for objects that receive status information specific to
|
|
||||||
river. It could be used to implement, for example, a status bar.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "zriver_status_manager_v1"
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverStatusManagerV1Proxy(Proxy[ZriverStatusManagerV1]):
|
|
||||||
interface = ZriverStatusManagerV1
|
|
||||||
|
|
||||||
@ZriverStatusManagerV1.request()
|
|
||||||
def destroy(self) -> None:
|
|
||||||
"""Destroy the river_status_manager object
|
|
||||||
|
|
||||||
This request indicates that the client will not use the
|
|
||||||
river_status_manager object any more. Objects that have been created
|
|
||||||
through this instance are not affected.
|
|
||||||
"""
|
|
||||||
self._marshal(0)
|
|
||||||
self._destroy()
|
|
||||||
|
|
||||||
@ZriverStatusManagerV1.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=ZriverOutputStatusV1),
|
|
||||||
Argument(ArgumentType.Object, interface=WlOutput),
|
|
||||||
)
|
|
||||||
def get_river_output_status(self, output: WlOutput) -> Proxy[ZriverOutputStatusV1]:
|
|
||||||
"""Create an output status object
|
|
||||||
|
|
||||||
This creates a new river_output_status object for the given
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput`.
|
|
||||||
|
|
||||||
:param output:
|
|
||||||
:type output:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlOutput`
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.river_status_unstable_v1.ZriverOutputStatusV1`
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(1, ZriverOutputStatusV1, output)
|
|
||||||
return id
|
|
||||||
|
|
||||||
@ZriverStatusManagerV1.request(
|
|
||||||
Argument(ArgumentType.NewId, interface=ZriverSeatStatusV1),
|
|
||||||
Argument(ArgumentType.Object, interface=WlSeat),
|
|
||||||
)
|
|
||||||
def get_river_seat_status(self, seat: WlSeat) -> Proxy[ZriverSeatStatusV1]:
|
|
||||||
"""Create a seat status object
|
|
||||||
|
|
||||||
This creates a new river_seat_status object for the given
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSeat`.
|
|
||||||
|
|
||||||
:param seat:
|
|
||||||
:type seat:
|
|
||||||
:class:`~pywayland.protocol.wayland.WlSeat`
|
|
||||||
:returns:
|
|
||||||
:class:`~pywayland.protocol.river_status_unstable_v1.ZriverSeatStatusV1`
|
|
||||||
"""
|
|
||||||
id = self._marshal_constructor(2, ZriverSeatStatusV1, seat)
|
|
||||||
return id
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverStatusManagerV1Resource(Resource):
|
|
||||||
interface = ZriverStatusManagerV1
|
|
||||||
|
|
||||||
|
|
||||||
class ZriverStatusManagerV1Global(Global):
|
|
||||||
interface = ZriverStatusManagerV1
|
|
||||||
|
|
||||||
|
|
||||||
ZriverStatusManagerV1._gen_c()
|
|
||||||
ZriverStatusManagerV1.proxy_class = ZriverStatusManagerV1Proxy
|
|
||||||
ZriverStatusManagerV1.resource_class = ZriverStatusManagerV1Resource
|
|
||||||
ZriverStatusManagerV1.global_class = ZriverStatusManagerV1Global
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<protocol name="river_control_unstable_v1">
|
|
||||||
<copyright>
|
|
||||||
Copyright 2020 The River Developers
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
</copyright>
|
|
||||||
|
|
||||||
<interface name="zriver_control_v1" version="1">
|
|
||||||
<description summary="run compositor commands">
|
|
||||||
This interface allows clients to run compositor commands and receive a
|
|
||||||
success/failure response with output or a failure message respectively.
|
|
||||||
|
|
||||||
Each command is built up in a series of add_argument requests and
|
|
||||||
executed with a run_command request. The first argument is the command
|
|
||||||
to be run.
|
|
||||||
|
|
||||||
A complete list of commands should be made available in the man page of
|
|
||||||
the compositor.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<request name="destroy" type="destructor">
|
|
||||||
<description summary="destroy the river_control object">
|
|
||||||
This request indicates that the client will not use the
|
|
||||||
river_control object any more. Objects that have been created
|
|
||||||
through this instance are not affected.
|
|
||||||
</description>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="add_argument">
|
|
||||||
<description summary="add an argument to the current command">
|
|
||||||
Arguments are stored by the server in the order they were sent until
|
|
||||||
the run_command request is made.
|
|
||||||
</description>
|
|
||||||
<arg name="argument" type="string" summary="the argument to add"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="run_command">
|
|
||||||
<description summary="run the current command">
|
|
||||||
Execute the command built up using the add_argument request for the
|
|
||||||
given seat.
|
|
||||||
</description>
|
|
||||||
<arg name="seat" type="object" interface="wl_seat"/>
|
|
||||||
<arg name="callback" type="new_id" interface="zriver_command_callback_v1"
|
|
||||||
summary="callback object"/>
|
|
||||||
</request>
|
|
||||||
</interface>
|
|
||||||
|
|
||||||
<interface name="zriver_command_callback_v1" version="1">
|
|
||||||
<description summary="callback object">
|
|
||||||
This object is created by the run_command request. Exactly one of the
|
|
||||||
success or failure events will be sent. This object will be destroyed
|
|
||||||
by the compositor after one of the events is sent.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<event name="success" type="destructor">
|
|
||||||
<description summary="command successful">
|
|
||||||
Sent when the command has been successfully received and executed by
|
|
||||||
the compositor. Some commands may produce output, in which case the
|
|
||||||
output argument will be a non-empty string.
|
|
||||||
</description>
|
|
||||||
<arg name="output" type="string" summary="the output of the command"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="failure" type="destructor">
|
|
||||||
<description summary="command failed">
|
|
||||||
Sent when the command could not be carried out. This could be due to
|
|
||||||
sending a non-existent command, no command, not enough arguments, too
|
|
||||||
many arguments, invalid arguments, etc.
|
|
||||||
</description>
|
|
||||||
<arg name="failure_message" type="string"
|
|
||||||
summary="a message explaining why failure occurred"/>
|
|
||||||
</event>
|
|
||||||
</interface>
|
|
||||||
</protocol>
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<protocol name="river_status_unstable_v1">
|
|
||||||
<copyright>
|
|
||||||
Copyright 2020 The River Developers
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
</copyright>
|
|
||||||
|
|
||||||
<interface name="zriver_status_manager_v1" version="4">
|
|
||||||
<description summary="manage river status objects">
|
|
||||||
A global factory for objects that receive status information specific
|
|
||||||
to river. It could be used to implement, for example, a status bar.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<request name="destroy" type="destructor">
|
|
||||||
<description summary="destroy the river_status_manager object">
|
|
||||||
This request indicates that the client will not use the
|
|
||||||
river_status_manager object any more. Objects that have been created
|
|
||||||
through this instance are not affected.
|
|
||||||
</description>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="get_river_output_status">
|
|
||||||
<description summary="create an output status object">
|
|
||||||
This creates a new river_output_status object for the given wl_output.
|
|
||||||
</description>
|
|
||||||
<arg name="id" type="new_id" interface="zriver_output_status_v1"/>
|
|
||||||
<arg name="output" type="object" interface="wl_output"/>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<request name="get_river_seat_status">
|
|
||||||
<description summary="create a seat status object">
|
|
||||||
This creates a new river_seat_status object for the given wl_seat.
|
|
||||||
</description>
|
|
||||||
<arg name="id" type="new_id" interface="zriver_seat_status_v1"/>
|
|
||||||
<arg name="seat" type="object" interface="wl_seat"/>
|
|
||||||
</request>
|
|
||||||
</interface>
|
|
||||||
|
|
||||||
<interface name="zriver_output_status_v1" version="4">
|
|
||||||
<description summary="track output tags and focus">
|
|
||||||
This interface allows clients to receive information about the current
|
|
||||||
windowing state of an output.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<request name="destroy" type="destructor">
|
|
||||||
<description summary="destroy the river_output_status object">
|
|
||||||
This request indicates that the client will not use the
|
|
||||||
river_output_status object any more.
|
|
||||||
</description>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<event name="focused_tags">
|
|
||||||
<description summary="focused tags of the output">
|
|
||||||
Sent once binding the interface and again whenever the tag focus of
|
|
||||||
the output changes.
|
|
||||||
</description>
|
|
||||||
<arg name="tags" type="uint" summary="32-bit bitfield"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="view_tags">
|
|
||||||
<description summary="tag state of an output's views">
|
|
||||||
Sent once on binding the interface and again whenever the tag state
|
|
||||||
of the output changes.
|
|
||||||
</description>
|
|
||||||
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="urgent_tags" since="2">
|
|
||||||
<description summary="tags of the output with an urgent view">
|
|
||||||
Sent once on binding the interface and again whenever the set of
|
|
||||||
tags with at least one urgent view changes.
|
|
||||||
</description>
|
|
||||||
<arg name="tags" type="uint" summary="32-bit bitfield"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="layout_name" since="4">
|
|
||||||
<description summary="name of the layout">
|
|
||||||
Sent once on binding the interface should a layout name exist and again
|
|
||||||
whenever the name changes.
|
|
||||||
</description>
|
|
||||||
<arg name="name" type="string" summary="layout name"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="layout_name_clear" since="4">
|
|
||||||
<description summary="name of the layout">
|
|
||||||
Sent when the current layout name has been removed without a new one
|
|
||||||
being set, for example when the active layout generator disconnects.
|
|
||||||
</description>
|
|
||||||
</event>
|
|
||||||
</interface>
|
|
||||||
|
|
||||||
<interface name="zriver_seat_status_v1" version="3">
|
|
||||||
<description summary="track seat focus">
|
|
||||||
This interface allows clients to receive information about the current
|
|
||||||
focus of a seat. Note that (un)focused_output events will only be sent
|
|
||||||
if the client has bound the relevant wl_output globals.
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<request name="destroy" type="destructor">
|
|
||||||
<description summary="destroy the river_seat_status object">
|
|
||||||
This request indicates that the client will not use the
|
|
||||||
river_seat_status object any more.
|
|
||||||
</description>
|
|
||||||
</request>
|
|
||||||
|
|
||||||
<event name="focused_output">
|
|
||||||
<description summary="the seat focused an output">
|
|
||||||
Sent on binding the interface and again whenever an output gains focus.
|
|
||||||
</description>
|
|
||||||
<arg name="output" type="object" interface="wl_output"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="unfocused_output">
|
|
||||||
<description summary="the seat unfocused an output">
|
|
||||||
Sent whenever an output loses focus.
|
|
||||||
</description>
|
|
||||||
<arg name="output" type="object" interface="wl_output"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="focused_view">
|
|
||||||
<description summary="information on the focused view">
|
|
||||||
Sent once on binding the interface and again whenever the focused
|
|
||||||
view or a property thereof changes. The title may be an empty string
|
|
||||||
if no view is focused or the focused view did not set a title.
|
|
||||||
</description>
|
|
||||||
<arg name="title" type="string" summary="title of the focused view"/>
|
|
||||||
</event>
|
|
||||||
|
|
||||||
<event name="mode" since="3">
|
|
||||||
<description summary="the active mode changed">
|
|
||||||
Sent once on binding the interface and again whenever a new mode
|
|
||||||
is entered (e.g. with riverctl enter-mode foobar).
|
|
||||||
</description>
|
|
||||||
<arg name="name" type="string" summary="name of the mode"/>
|
|
||||||
</event>
|
|
||||||
</interface>
|
|
||||||
</protocol>
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
import os
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
from loguru import logger
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import Dict, List, Optional, Any, Set
|
|
||||||
|
|
||||||
from fabric.core.service import Service, Signal, Property
|
|
||||||
from fabric.utils.helpers import idle_add
|
|
||||||
|
|
||||||
# Import pywayland components - ensure these imports are correct
|
|
||||||
from pywayland.client import Display
|
|
||||||
from pywayland.protocol.wayland import WlOutput, WlSeat
|
|
||||||
from .generated.river_status_unstable_v1 import ZriverStatusManagerV1
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class OutputInfo:
|
|
||||||
"""Information about a River output"""
|
|
||||||
|
|
||||||
name: int
|
|
||||||
output: WlOutput
|
|
||||||
status: Any = None # ZriverOutputStatusV1
|
|
||||||
tags_view: List[int] = field(default_factory=list)
|
|
||||||
tags_focused: List[int] = field(default_factory=list)
|
|
||||||
tags_urgent: List[int] = field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class RiverEvent:
|
|
||||||
"""Event data from River compositor"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
data: List[Any]
|
|
||||||
output_id: Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
class River(Service):
|
|
||||||
"""Connection to River Wayland compositor via river-status protocol"""
|
|
||||||
|
|
||||||
@Property(bool, "readable", "is-ready", default_value=False)
|
|
||||||
def ready(self) -> bool:
|
|
||||||
return self._ready
|
|
||||||
|
|
||||||
@Property(str, "readable", "active-window", default_value="")
|
|
||||||
def active_window(self) -> str:
|
|
||||||
"""Get the title of the currently active window"""
|
|
||||||
return self._active_window_title
|
|
||||||
|
|
||||||
@Signal
|
|
||||||
def ready(self):
|
|
||||||
return self.notify("ready")
|
|
||||||
|
|
||||||
@Signal("event", flags="detailed")
|
|
||||||
def event(self, event: object): ...
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
"""Initialize the River service"""
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self._ready = False
|
|
||||||
self._active_window_title = ""
|
|
||||||
self.outputs: Dict[int, OutputInfo] = {}
|
|
||||||
self.river_status_mgr = 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.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')}"
|
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
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.connect()
|
|
||||||
logger.debug("[RiverService] Display connection created")
|
|
||||||
|
|
||||||
# Get the registry
|
|
||||||
registry = display.get_registry()
|
|
||||||
logger.debug("[RiverService] Registry obtained")
|
|
||||||
|
|
||||||
# Create state object to hold our data
|
|
||||||
state = {
|
|
||||||
"display": display,
|
|
||||||
"registry": registry,
|
|
||||||
"outputs": {},
|
|
||||||
"river_status_mgr": 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})"
|
|
||||||
)
|
|
||||||
if iface == "zriver_status_manager_v1":
|
|
||||||
state["river_status_mgr"] = registry.bind(
|
|
||||||
name, ZriverStatusManagerV1, version
|
|
||||||
)
|
|
||||||
logger.info("[RiverService] Found river status manager")
|
|
||||||
elif iface == "wl_output":
|
|
||||||
output = registry.bind(name, WlOutput, version)
|
|
||||||
state["outputs"][name] = OutputInfo(name=name, output=output)
|
|
||||||
logger.info(f"[RiverService] Found output {name}")
|
|
||||||
elif iface == "wl_seat":
|
|
||||||
state["seat"] = registry.bind(name, WlSeat, version)
|
|
||||||
logger.info("[RiverService] Found seat")
|
|
||||||
|
|
||||||
def handle_global_remove(registry, name):
|
|
||||||
if name in state["outputs"]:
|
|
||||||
logger.info(f"[RiverService] Output {name} removed")
|
|
||||||
del state["outputs"][name]
|
|
||||||
idle_add(
|
|
||||||
lambda: self.emit(
|
|
||||||
"event::output_removed",
|
|
||||||
RiverEvent("output_removed", [name]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set up the dispatchers
|
|
||||||
registry.dispatcher["global"] = handle_global
|
|
||||||
registry.dispatcher["global_remove"] = handle_global_remove
|
|
||||||
|
|
||||||
# Discover globals
|
|
||||||
logger.debug("[RiverService] Performing initial roundtrip")
|
|
||||||
display.roundtrip()
|
|
||||||
|
|
||||||
# Check if we found the river status manager
|
|
||||||
if not state["river_status_mgr"]:
|
|
||||||
logger.error("[RiverService] River status manager not found")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Handle the window title updates through seat status
|
|
||||||
|
|
||||||
def focused_view_handler(_, title):
|
|
||||||
logger.debug(f"[RiverService] Focused view title: {title}")
|
|
||||||
self._active_window_title = title
|
|
||||||
idle_add(lambda: self._emit_active_window(title))
|
|
||||||
|
|
||||||
# Get the seat status to track active window
|
|
||||||
|
|
||||||
if state["seat"]:
|
|
||||||
seat_status = state["river_status_mgr"].get_river_seat_status(
|
|
||||||
state["seat"]
|
|
||||||
)
|
|
||||||
seat_status.dispatcher["focused_view"] = focused_view_handler
|
|
||||||
state["seat_status"] = seat_status
|
|
||||||
logger.info("[RiverService] Set up seat status for window tracking")
|
|
||||||
|
|
||||||
# Create view tags and focused tags handlers
|
|
||||||
def make_view_tags_handler(output_id):
|
|
||||||
def handler(_, tags):
|
|
||||||
decoded = self._decode_bitfields(tags)
|
|
||||||
state["outputs"][output_id].tags_view = decoded
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverService] Output {output_id} view tags: {decoded}"
|
|
||||||
)
|
|
||||||
idle_add(lambda: self._emit_view_tags(output_id, decoded))
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def make_focused_tags_handler(output_id):
|
|
||||||
def handler(_, tags):
|
|
||||||
decoded = self._decode_bitfields(tags)
|
|
||||||
state["outputs"][output_id].tags_focused = decoded
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverService] Output {output_id} focused tags: {decoded}"
|
|
||||||
)
|
|
||||||
idle_add(lambda: self._emit_focused_tags(output_id, decoded))
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def make_urgent_tags_handler(output_id):
|
|
||||||
def handler(_, tags):
|
|
||||||
decoded = self._decode_bitfields(tags)
|
|
||||||
state["outputs"][output_id].tags_urgent = decoded
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverService] Output {output_id} urgent tags: {decoded}"
|
|
||||||
)
|
|
||||||
idle_add(lambda: self._emit_urgent_tags(output_id, decoded))
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
# Bind output status listeners
|
|
||||||
for name, info in list(state["outputs"].items()):
|
|
||||||
status = state["river_status_mgr"].get_river_output_status(info.output)
|
|
||||||
status.dispatcher["view_tags"] = make_view_tags_handler(name)
|
|
||||||
status.dispatcher["focused_tags"] = make_focused_tags_handler(name)
|
|
||||||
status.dispatcher["urgent_tags"] = make_urgent_tags_handler(name)
|
|
||||||
info.status = status
|
|
||||||
logger.info(f"[RiverService] Set up status for output {name}")
|
|
||||||
|
|
||||||
# Initial data fetch
|
|
||||||
logger.debug("[RiverService] Performing second roundtrip")
|
|
||||||
display.roundtrip()
|
|
||||||
|
|
||||||
# Update our outputs dictionary
|
|
||||||
self.outputs.update(state["outputs"])
|
|
||||||
self.river_status_mgr = state["river_status_mgr"]
|
|
||||||
self.seat = state["seat"]
|
|
||||||
self.seat_status = state.get("seat_status")
|
|
||||||
|
|
||||||
# Mark service as ready
|
|
||||||
idle_add(self._set_ready)
|
|
||||||
|
|
||||||
# Main event loop
|
|
||||||
logger.info("[RiverService] Entering main event loop")
|
|
||||||
while True:
|
|
||||||
display.dispatch(block=True)
|
|
||||||
time.sleep(0.01) # Small sleep to prevent CPU spinning
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[RiverService] Error in River connection: {e}")
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
logger.error(traceback.format_exc())
|
|
||||||
|
|
||||||
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()
|
|
||||||
return False # Don't repeat
|
|
||||||
|
|
||||||
def _emit_view_tags(self, output_id, tags):
|
|
||||||
"""Emit view_tags events (called on main thread)"""
|
|
||||||
event = RiverEvent("view_tags", tags, output_id)
|
|
||||||
self.emit("event::view_tags", event)
|
|
||||||
self.emit(f"event::view_tags::{output_id}", tags)
|
|
||||||
return False # Don't repeat
|
|
||||||
|
|
||||||
def _emit_focused_tags(self, output_id, tags):
|
|
||||||
"""Emit focused_tags events (called on main thread)"""
|
|
||||||
event = RiverEvent("focused_tags", tags, output_id)
|
|
||||||
self.emit("event::focused_tags", event)
|
|
||||||
self.emit(f"event::focused_tags::{output_id}", tags)
|
|
||||||
return False # Don't repeat
|
|
||||||
|
|
||||||
def _emit_active_window(self, title):
|
|
||||||
"""Emit active window title events (called on main thread)"""
|
|
||||||
event = RiverEvent("active_window", [title])
|
|
||||||
self.emit("event::active_window", event)
|
|
||||||
self.notify("active-window")
|
|
||||||
return False # Don't repeat
|
|
||||||
|
|
||||||
def _emit_urgent_tags(self, output_id, tags):
|
|
||||||
"""Emit urgent_tags events (called on main thread)"""
|
|
||||||
event = RiverEvent("urgent_tags", tags, output_id)
|
|
||||||
self.emit("event::urgent_tags", event)
|
|
||||||
self.emit(f"event::urgent_tags::{output_id}", tags)
|
|
||||||
return False # Don't repeat
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _decode_bitfields(bitfields) -> List[int]:
|
|
||||||
"""Decode River's tag bitfields into a list of tag indices"""
|
|
||||||
tags: Set[int] = set()
|
|
||||||
|
|
||||||
# Ensure we have an iterable
|
|
||||||
if not hasattr(bitfields, "__iter__"):
|
|
||||||
bitfields = [bitfields]
|
|
||||||
|
|
||||||
for bits in bitfields:
|
|
||||||
for i in range(32):
|
|
||||||
if bits & (1 << i):
|
|
||||||
tags.add(i)
|
|
||||||
|
|
||||||
return sorted(tags)
|
|
||||||
|
|
||||||
def run_command(self, command, *args):
|
|
||||||
"""Run a riverctl command"""
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
cmd = ["riverctl", command] + [str(arg) for arg in args]
|
|
||||||
try:
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
||||||
logger.info(f"[RiverService] Ran command: {' '.join(cmd)}")
|
|
||||||
return result.stdout.strip()
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
logger.error(
|
|
||||||
f"[RiverService] Command failed: {' '.join(cmd)}, error: {e.stderr}"
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def toggle_focused_tag(self, tag):
|
|
||||||
"""Toggle a tag in the focused tags"""
|
|
||||||
tag_mask = 1 << int(tag)
|
|
||||||
self.run_command("set-focused-tags", str(tag_mask))
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
from loguru import logger
|
|
||||||
from fabric.core.service import Property
|
|
||||||
from fabric.widgets.button import Button
|
|
||||||
from fabric.widgets.box import Box
|
|
||||||
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
|
|
||||||
|
|
||||||
_connection: River | None = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_river_connection() -> River:
|
|
||||||
global _connection
|
|
||||||
if not _connection:
|
|
||||||
_connection = River()
|
|
||||||
return _connection
|
|
||||||
|
|
||||||
|
|
||||||
class RiverWorkspaceButton(Button):
|
|
||||||
@Property(int, "readable")
|
|
||||||
def id(self) -> int:
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@Property(bool, "read-write", default_value=False)
|
|
||||||
def active(self) -> bool:
|
|
||||||
return self._active
|
|
||||||
|
|
||||||
@active.setter
|
|
||||||
def active(self, value: bool):
|
|
||||||
self._active = value
|
|
||||||
(self.remove_style_class if not value else self.add_style_class)("active")
|
|
||||||
|
|
||||||
@Property(bool, "read-write", default_value=False)
|
|
||||||
def empty(self) -> bool:
|
|
||||||
return self._empty
|
|
||||||
|
|
||||||
@empty.setter
|
|
||||||
def empty(self, value: bool):
|
|
||||||
self._empty = value
|
|
||||||
(self.remove_style_class if not value else self.add_style_class)("empty")
|
|
||||||
|
|
||||||
@Property(bool, "read-write", default_value=False)
|
|
||||||
def urgent(self) -> bool:
|
|
||||||
return self._urgent
|
|
||||||
|
|
||||||
@urgent.setter
|
|
||||||
def urgent(self, value: bool):
|
|
||||||
self._urgent = value
|
|
||||||
self._update_style()
|
|
||||||
|
|
||||||
def __init__(self, id: int, label: str = None, **kwargs):
|
|
||||||
super().__init__(label or str(id), **kwargs)
|
|
||||||
self._id = id
|
|
||||||
self._active = False
|
|
||||||
self._empty = True
|
|
||||||
self._urgent = False
|
|
||||||
|
|
||||||
def _update_style(self):
|
|
||||||
"""Update button styles based on states"""
|
|
||||||
# Remove all state-related styles first
|
|
||||||
self.remove_style_class("active")
|
|
||||||
self.remove_style_class("empty")
|
|
||||||
self.remove_style_class("urgent")
|
|
||||||
|
|
||||||
# Then apply current states
|
|
||||||
if self._active:
|
|
||||||
self.add_style_class("active")
|
|
||||||
if self._empty:
|
|
||||||
self.add_style_class("empty")
|
|
||||||
if self._urgent:
|
|
||||||
self.add_style_class("urgent")
|
|
||||||
|
|
||||||
|
|
||||||
class RiverWorkspaces(EventBox):
|
|
||||||
def __init__(self, output_id=None, max_tags=9, **kwargs):
|
|
||||||
super().__init__(events="scroll")
|
|
||||||
self.service = get_river_connection()
|
|
||||||
self._box = Box(**kwargs)
|
|
||||||
self.children = self._box
|
|
||||||
|
|
||||||
# Store output_id as received
|
|
||||||
self.output_id = output_id
|
|
||||||
|
|
||||||
self.max_tags = max_tags
|
|
||||||
# Create buttons for tags 0 to max_tags-1 (to match River's 0-based tag indexing)
|
|
||||||
self._buttons = {i: RiverWorkspaceButton(i) for i in range(max_tags)}
|
|
||||||
|
|
||||||
for btn in self._buttons.values():
|
|
||||||
btn.connect("clicked", self.on_workspace_click)
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Initial setup when service is ready
|
|
||||||
if self.service.ready:
|
|
||||||
self.on_ready(None)
|
|
||||||
else:
|
|
||||||
self.service.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())}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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()))
|
|
||||||
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]
|
|
||||||
|
|
||||||
# Initialize buttons with current state
|
|
||||||
# Access fields directly on the OutputInfo dataclass
|
|
||||||
focused_tags = output_info.tags_focused
|
|
||||||
view_tags = output_info.tags_view
|
|
||||||
urgent_tags = output_info.tags_urgent
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverWorkspaces] Initial state - focused: {focused_tags}, view: {view_tags}, urgent: {urgent_tags}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, btn in self._buttons.items():
|
|
||||||
btn.active = i in focused_tags
|
|
||||||
btn.empty = i not in view_tags
|
|
||||||
btn.urgent = i in urgent_tags
|
|
||||||
|
|
||||||
def on_focus_change(self, _, tags):
|
|
||||||
"""Handle focused tags change for our specific output"""
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverWorkspaces] Focus change on output {self.output_id}: {tags}"
|
|
||||||
)
|
|
||||||
for i, btn in self._buttons.items():
|
|
||||||
btn.active = i in tags
|
|
||||||
|
|
||||||
def on_view_change(self, _, tags):
|
|
||||||
"""Handle view tags change for our specific output"""
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverWorkspaces] View change on output {self.output_id}: {tags}"
|
|
||||||
)
|
|
||||||
for i, btn in self._buttons.items():
|
|
||||||
btn.empty = i not in tags
|
|
||||||
|
|
||||||
def on_focus_change_general(self, _, event):
|
|
||||||
"""Handle general focused tags event"""
|
|
||||||
# Only handle event if it's for our output
|
|
||||||
if event.output_id == self.output_id:
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverWorkspaces] General focus change for output {self.output_id}"
|
|
||||||
)
|
|
||||||
self.on_focus_change(_, event.data)
|
|
||||||
|
|
||||||
def on_view_change_general(self, _, event):
|
|
||||||
"""Handle general view tags event"""
|
|
||||||
# Only handle event if it's for our output
|
|
||||||
if event.output_id == self.output_id:
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverWorkspaces] General view change for output {self.output_id}"
|
|
||||||
)
|
|
||||||
self.on_view_change(_, event.data)
|
|
||||||
|
|
||||||
def on_urgent_change(self, _, tags):
|
|
||||||
"""Handle urgent tags change for our specific output"""
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverWorkspaces] Urgent change on output {self.output_id}: {tags}"
|
|
||||||
)
|
|
||||||
for i, btn in self._buttons.items():
|
|
||||||
btn.urgent = i in tags
|
|
||||||
|
|
||||||
def on_urgent_change_general(self, _, event):
|
|
||||||
"""Handle general urgent tags event"""
|
|
||||||
# Only handle event if it's for our output
|
|
||||||
if event.output_id == self.output_id:
|
|
||||||
logger.debug(
|
|
||||||
f"[RiverWorkspaces] General urgent change for output {self.output_id}"
|
|
||||||
)
|
|
||||||
self.on_urgent_change(_, event.data)
|
|
||||||
|
|
||||||
def on_output_removed(self, _, event):
|
|
||||||
"""Handle output removal"""
|
|
||||||
removed_id = event.data[0]
|
|
||||||
|
|
||||||
if removed_id == self.output_id:
|
|
||||||
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()))
|
|
||||||
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]
|
|
||||||
# Access fields directly on the OutputInfo dataclass
|
|
||||||
focused_tags = output_info.tags_focused
|
|
||||||
view_tags = output_info.tags_view
|
|
||||||
|
|
||||||
for i, btn in self._buttons.items():
|
|
||||||
btn.active = i in focused_tags
|
|
||||||
btn.empty = i not in view_tags
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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")
|
|
||||||
elif direction == Gdk.ScrollDirection.UP:
|
|
||||||
logger.info("[RiverWorkspaces] Scroll up - focusing previous view")
|
|
||||||
self.service.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):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.service = get_river_connection()
|
|
||||||
self.max_length = max_length
|
|
||||||
self.ellipsize = ellipsize
|
|
||||||
|
|
||||||
# Set initial state
|
|
||||||
if self.service.ready:
|
|
||||||
self.on_ready(None)
|
|
||||||
else:
|
|
||||||
self.service.connect("event::ready", self.on_ready)
|
|
||||||
|
|
||||||
# Connect to active window changes
|
|
||||||
self.service.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)
|
|
||||||
|
|
||||||
def on_active_window_changed(self, _, event):
|
|
||||||
"""Update widget when active window changes"""
|
|
||||||
title = event.data[0] if event.data else ""
|
|
||||||
logger.debug(f"[RiverActiveWindow] Window changed to: {title}")
|
|
||||||
self.update_title(title)
|
|
||||||
|
|
||||||
def update_title(self, title):
|
|
||||||
"""Update the label with the window title"""
|
|
||||||
if not title:
|
|
||||||
self.label = ""
|
|
||||||
self.set_label(self.label)
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.max_length and len(title) > self.max_length:
|
|
||||||
if self.ellipsize == "end":
|
|
||||||
title = title[: self.max_length] + "..."
|
|
||||||
elif self.ellipsize == "middle":
|
|
||||||
half = (self.max_length - 3) // 2
|
|
||||||
title = title[:half] + "..." + title[-half:]
|
|
||||||
elif self.ellipsize == "start":
|
|
||||||
title = "..." + title[-self.max_length :]
|
|
||||||
|
|
||||||
self.label = title
|
|
||||||
self.set_label(self.label)
|
|
||||||
51
bar/styles/bar.css
Normal file
51
bar/styles/bar.css
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#bar-inner {
|
||||||
|
padding: 4px;
|
||||||
|
border-bottom: solid 2px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
background-color: var(--window-bg);
|
||||||
|
min-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#center-container {
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-window {
|
||||||
|
color: var(--foreground);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu-progress-bar,
|
||||||
|
#ram-progress-bar,
|
||||||
|
#volume-progress-bar {
|
||||||
|
color: transparent;
|
||||||
|
background-color: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu-progress-bar {
|
||||||
|
border: solid 0px alpha(var(--violet), 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ram-progress-bar,
|
||||||
|
#volume-progress-bar {
|
||||||
|
border: solid 0px var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#widgets-container {
|
||||||
|
background-color: var(--module-bg);
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nixos-label {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip {
|
||||||
|
border: solid 2px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
background-color: var(--window-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip>* {
|
||||||
|
padding: 2px 4px
|
||||||
|
}
|
||||||
28
bar/styles/colors.css
Normal file
28
bar/styles/colors.css
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
:vars {
|
||||||
|
--background: #17181C;
|
||||||
|
--mid-bg: #1E1F24;
|
||||||
|
--light-bg: #26272B;
|
||||||
|
--dark-grey: #333438;
|
||||||
|
--light-grey: #8F9093;
|
||||||
|
--dark-fg: #B0B1B4;
|
||||||
|
--mid-fg: #CBCCCE;
|
||||||
|
--foreground: #E4E5E7;
|
||||||
|
|
||||||
|
--pink: #FA3867;
|
||||||
|
--orange: #F3872F;
|
||||||
|
--gold: #FEBD16;
|
||||||
|
--lime: #3FD43B;
|
||||||
|
--turquoise: #47E7CE;
|
||||||
|
--blue: #53ADE1;
|
||||||
|
--violet: #AD60FF;
|
||||||
|
--red: #FC3F2C;
|
||||||
|
|
||||||
|
--window-bg: alpha(var(--background), 0.9);
|
||||||
|
--module-bg: alpha(var(--mid-bg), 0.8);
|
||||||
|
--border-color: var(--light-bg);
|
||||||
|
--ws-active: var(--pink);
|
||||||
|
--ws-inactive: var(--blue);
|
||||||
|
--ws-empty: var(--dark-grey);
|
||||||
|
--ws-hover: var(--turquoise);
|
||||||
|
--ws-urgent: var(--red);
|
||||||
|
}
|
||||||
29
bar/styles/finder.css
Normal file
29
bar/styles/finder.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#picker-box {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: rgba(40, 40, 40, 0.95); /* darker for contrast */
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#viewport {
|
||||||
|
padding: 8px;
|
||||||
|
background-color: rgba(30, 30, 30, 0.9); /* dark background for contrast */
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
color: white; /* ensure contrast */
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewport > * {
|
||||||
|
padding: 6px 10px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewport:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15); /* hover feedback */
|
||||||
|
}
|
||||||
20
bar/styles/main.css
Normal file
20
bar/styles/main.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@import url("./colors.css");
|
||||||
|
@import url("./workspaces.css");
|
||||||
|
@import url("./menu.css");
|
||||||
|
@import url("./vinyl.css");
|
||||||
|
@import url("./bar.css");
|
||||||
|
@import url("./finder.css");
|
||||||
|
|
||||||
|
|
||||||
|
/* unset so we can style everything from the ground up. */
|
||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
color: var(--foreground);
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Jost*", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-size: 400% 400%;
|
||||||
|
}
|
||||||
|
|
||||||
38
bar/styles/menu.css
Normal file
38
bar/styles/menu.css
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#date-time,
|
||||||
|
menu>menuitem>label,
|
||||||
|
#date-time>label,
|
||||||
|
/* system tray */
|
||||||
|
#system-tray {
|
||||||
|
padding: 2px 4px;
|
||||||
|
background-color: var(--module-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* menu and menu items (written for the system tray) */
|
||||||
|
menu {
|
||||||
|
border: solid 2px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
background-color: var(--window-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu>menuitem {
|
||||||
|
border-radius: 0px;
|
||||||
|
background-color: var(--module-bg);
|
||||||
|
padding: 6px;
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu>menuitem:first-child {
|
||||||
|
margin-top: 1px;
|
||||||
|
border-radius: 8px 8px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu>menuitem:last-child {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
border-radius: 0px 0px 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu>menuitem:hover {
|
||||||
|
background-color: var(--pink);
|
||||||
|
}
|
||||||
41
bar/styles/vinyl.css
Normal file
41
bar/styles/vinyl.css
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/* Vinyl button styling */
|
||||||
|
#vinyl-button {
|
||||||
|
padding: 0px 8px;
|
||||||
|
transition: padding 0.05s steps(8);
|
||||||
|
background-color: rgba(180, 180, 180, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active state styling */
|
||||||
|
.active #vinyl-button {
|
||||||
|
background-color: rgba(108, 158, 175, 0.7);
|
||||||
|
padding: 0px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon styling */
|
||||||
|
#vinyl-icon {
|
||||||
|
color: #555555;
|
||||||
|
min-width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Label styling */
|
||||||
|
#vinyl-label {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active state changes for icon and label */
|
||||||
|
.active #vinyl-icon,
|
||||||
|
.active #vinyl-label {
|
||||||
|
color: var(--pink);
|
||||||
|
padding: 0px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover effect */
|
||||||
|
#vinyl-button:hover {
|
||||||
|
background-color: rgba(180, 180, 180, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active #vinyl-button:hover {
|
||||||
|
background-color: rgba(108, 158, 175, 0.9);
|
||||||
|
}
|
||||||
42
bar/styles/workspaces.css
Normal file
42
bar/styles/workspaces.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#workspaces {
|
||||||
|
padding: 6px;
|
||||||
|
min-width: 0px;
|
||||||
|
background-color: var(--module-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces>button {
|
||||||
|
padding: 0px 8px;
|
||||||
|
transition: padding 0.05s steps(8);
|
||||||
|
background-color: var(--foreground);
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces>button>label {
|
||||||
|
font-size: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.hover {
|
||||||
|
background-color: var(--ws-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.urgent {
|
||||||
|
background-color: var(--ws-urgent);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-weight: bold;
|
||||||
|
animation: urgent-blink 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes urgent-blink {
|
||||||
|
0% { opacity: 1.0; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
100% { opacity: 1.0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces>button.empty {
|
||||||
|
background-color: var(--ws-empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces>button.active {
|
||||||
|
padding: 0px 32px;
|
||||||
|
background-color: var(--ws-active);
|
||||||
|
}
|
||||||
2
example.yaml
Normal file
2
example.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
vinyl:
|
||||||
|
enabled: true
|
||||||
36
flake.lock
generated
36
flake.lock
generated
@@ -6,16 +6,16 @@
|
|||||||
"utils": "utils"
|
"utils": "utils"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1725442219,
|
"lastModified": 1747045720,
|
||||||
"narHash": "sha256-xgTjqwlAgfY0Kv6G6CogOV2pN6U0wllRYteVAAZs7BU=",
|
"narHash": "sha256-2Z0F4hnluJZunwRfx80EQXpjGLhunV2wrseT42nzh7M=",
|
||||||
"owner": "wholikeel",
|
"owner": "Makesesama",
|
||||||
"repo": "fabric-nix",
|
"repo": "fabric",
|
||||||
"rev": "3bc86cfb8c988ff5488526a47e1914f03a34a87c",
|
"rev": "dae50c763e8bf2b4e5807b49b9e62425e0725cfa",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "wholikeel",
|
"owner": "Makesesama",
|
||||||
"repo": "fabric-nix",
|
"repo": "fabric",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -41,32 +41,32 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717179513,
|
"lastModified": 1733261153,
|
||||||
"narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=",
|
"narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0",
|
"rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "24.05",
|
"ref": "nixos-24.11",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717179513,
|
"lastModified": 1731603435,
|
||||||
"narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=",
|
"narHash": "sha256-CqCX4JG7UiHvkrBTpYC3wcEurvbtTADLbo3Ns2CEoL8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0",
|
"rev": "8b27c1239e5c421a2bbc2c65d52e4a6fbf2ff296",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "24.05",
|
"ref": "24.11",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -131,11 +131,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
62
flake.nix
62
flake.nix
@@ -2,10 +2,10 @@
|
|||||||
description = "Fabric Bar Example";
|
description = "Fabric Bar Example";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/24.05";
|
nixpkgs.url = "github:NixOS/nixpkgs/24.11";
|
||||||
unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
utils.url = "github:numtide/flake-utils";
|
utils.url = "github:numtide/flake-utils";
|
||||||
fabric.url = "github:wholikeel/fabric-nix";
|
fabric.url = "github:Makesesama/fabric";
|
||||||
home-manager.url = "github:nix-community/home-manager";
|
home-manager.url = "github:nix-community/home-manager";
|
||||||
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
@@ -31,8 +31,13 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
formatter = pkgs.nixfmt-rfc-style;
|
formatter = pkgs.nixfmt-rfc-style;
|
||||||
devShells.default = pkgs.callPackage ./shell.nix { inherit pkgs; };
|
devShells.default = pkgs.callPackage ./nix/shell.nix { inherit pkgs; };
|
||||||
packages.default = pkgs.callPackage ./derivation.nix { inherit (pkgs) lib python3Packages; };
|
packages = {
|
||||||
|
default = pkgs.callPackage ./nix/derivation.nix { inherit (pkgs) lib python3Packages; };
|
||||||
|
makku = pkgs.writeShellScriptBin "makku" ''
|
||||||
|
dbus-send --session --print-reply --dest=org.Fabric.fabric.bar /org/Fabric/fabric org.Fabric.fabric.Evaluate string:"finder.show()" > /dev/null 2>&1
|
||||||
|
'';
|
||||||
|
};
|
||||||
apps.default = {
|
apps.default = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = "${self.packages.${system}.default}/bin/bar";
|
program = "${self.packages.${system}.default}/bin/bar";
|
||||||
@@ -47,6 +52,11 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.services.makku-bar;
|
||||||
|
|
||||||
|
settingsFormat = pkgs.formats.yaml { };
|
||||||
|
in
|
||||||
{
|
{
|
||||||
options.services.makku-bar = {
|
options.services.makku-bar = {
|
||||||
enable = lib.mkEnableOption "makku-bar status bar";
|
enable = lib.mkEnableOption "makku-bar status bar";
|
||||||
@@ -56,24 +66,44 @@
|
|||||||
default = self.packages.${pkgs.system}.default;
|
default = self.packages.${pkgs.system}.default;
|
||||||
description = "The makku-bar package to use.";
|
description = "The makku-bar package to use.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
vinyl = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = {
|
||||||
|
vinyl.enable = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf config.services.makku-bar.enable {
|
config = lib.mkIf config.services.makku-bar.enable {
|
||||||
systemd.user.services.makku-bar = {
|
systemd.user.services.makku-bar =
|
||||||
Unit = {
|
let
|
||||||
Description = "Makku Status Bar";
|
configFile = settingsFormat.generate "config.yaml" cfg.settings;
|
||||||
After = [ "graphical-session.target" ];
|
in
|
||||||
};
|
{
|
||||||
|
Unit = {
|
||||||
|
Description = "Makku Status Bar";
|
||||||
|
After = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
Service = {
|
Service = {
|
||||||
ExecStart = "${config.services.makku-bar.package}/bin/bar";
|
ExecStart = "${config.services.makku-bar.package}/bin/bar --config ${configFile}";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
};
|
};
|
||||||
|
|
||||||
Install = {
|
Install = {
|
||||||
WantedBy = [ "default.target" ];
|
WantedBy = [ "default.target" ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
gobject-introspection,
|
gobject-introspection,
|
||||||
libdbusmenu-gtk3,
|
libdbusmenu-gtk3,
|
||||||
gdk-pixbuf,
|
gdk-pixbuf,
|
||||||
gnome,
|
gnome-bluetooth,
|
||||||
cinnamon,
|
cinnamon-desktop,
|
||||||
wrapGAppsHook3,
|
wrapGAppsHook3,
|
||||||
playerctl,
|
playerctl,
|
||||||
webp-pixbuf-loader,
|
webp-pixbuf-loader,
|
||||||
@@ -20,7 +20,7 @@ python3Packages.buildPythonApplication {
|
|||||||
version = "0.0.1";
|
version = "0.0.1";
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
|
|
||||||
src = ./.;
|
src = ../.;
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
wrapGAppsHook3
|
wrapGAppsHook3
|
||||||
@@ -33,8 +33,8 @@ python3Packages.buildPythonApplication {
|
|||||||
buildInputs = [
|
buildInputs = [
|
||||||
libdbusmenu-gtk3
|
libdbusmenu-gtk3
|
||||||
gtk-layer-shell
|
gtk-layer-shell
|
||||||
gnome.gnome-bluetooth
|
gnome-bluetooth
|
||||||
cinnamon.cinnamon-desktop
|
cinnamon-desktop
|
||||||
gdk-pixbuf
|
gdk-pixbuf
|
||||||
playerctl
|
playerctl
|
||||||
webp-pixbuf-loader
|
webp-pixbuf-loader
|
||||||
@@ -43,10 +43,26 @@ python3Packages.buildPythonApplication {
|
|||||||
dependencies = with python3Packages; [
|
dependencies = with python3Packages; [
|
||||||
python-fabric
|
python-fabric
|
||||||
pywayland
|
pywayland
|
||||||
|
pyyaml
|
||||||
|
platformdirs
|
||||||
];
|
];
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
dontWrapGApps = true;
|
dontWrapGApps = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/${python3Packages.python.sitePackages}
|
||||||
|
cp -r bar $out/${python3Packages.python.sitePackages}/
|
||||||
|
|
||||||
|
# If you have any scripts to install
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp scripts/launcher.py $out/bin/bar
|
||||||
|
chmod +x $out/bin/bar
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
preFixup = ''
|
preFixup = ''
|
||||||
makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
|
makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
|
||||||
'';
|
'';
|
||||||
@@ -22,11 +22,13 @@ pkgs.mkShell {
|
|||||||
gobject-introspection
|
gobject-introspection
|
||||||
libdbusmenu-gtk3
|
libdbusmenu-gtk3
|
||||||
gdk-pixbuf
|
gdk-pixbuf
|
||||||
gnome.gnome-bluetooth
|
gnome-bluetooth
|
||||||
cinnamon.cinnamon-desktop
|
cinnamon-desktop
|
||||||
wayland-scanner
|
wayland-scanner
|
||||||
wayland
|
wayland
|
||||||
wayland-protocols
|
wayland-protocols
|
||||||
|
playerctl
|
||||||
|
|
||||||
(python3.withPackages (
|
(python3.withPackages (
|
||||||
ps: with ps; [
|
ps: with ps; [
|
||||||
setuptools
|
setuptools
|
||||||
@@ -39,6 +41,8 @@ pkgs.mkShell {
|
|||||||
pylsp-mypy
|
pylsp-mypy
|
||||||
pyls-isort
|
pyls-isort
|
||||||
python-lsp-ruff
|
python-lsp-ruff
|
||||||
|
pyyaml
|
||||||
|
platformdirs
|
||||||
]
|
]
|
||||||
))
|
))
|
||||||
];
|
];
|
||||||
@@ -14,11 +14,11 @@ description = "Fabric using Nix example."
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
|
|
||||||
[project.scripts]
|
[tool.setuptools]
|
||||||
bar = "bar.bar:main"
|
include-package-data = true
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages]
|
||||||
where = ["."]
|
find = { namespaces = true }
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
bar = ["bar.css"]
|
"*" = ["*.css", "styles"]
|
||||||
|
|||||||
21
scripts/launcher.py
Normal file
21
scripts/launcher.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
site_packages_dir = os.path.join(
|
||||||
|
script_dir,
|
||||||
|
os.pardir,
|
||||||
|
"lib",
|
||||||
|
f"python{sys.version_info.major}.{sys.version_info.minor}",
|
||||||
|
"site-packages",
|
||||||
|
)
|
||||||
|
|
||||||
|
if site_packages_dir not in sys.path:
|
||||||
|
sys.path.insert(0, site_packages_dir)
|
||||||
|
|
||||||
|
|
||||||
|
from bar.main import *
|
||||||
|
|
||||||
|
sys.argv[0] = os.path.join(script_dir, os.path.basename(__file__))
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user