feat: power and screenshot launchers
This commit is contained in:
10
flake.nix
10
flake.nix
@@ -148,6 +148,13 @@
|
||||
description = "Directory to save recordings into";
|
||||
};
|
||||
};
|
||||
power = {
|
||||
lock_command = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "waylock" ];
|
||||
description = "argv for the Lock action in the power menu";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {
|
||||
@@ -170,6 +177,9 @@
|
||||
enable = false;
|
||||
output_dir = "~/Videos/wl-screenrec";
|
||||
};
|
||||
power = {
|
||||
lock_command = [ "waylock" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ usage() {
|
||||
usage: sims-cli <command> [args]
|
||||
finder open window finder
|
||||
apps open application launcher
|
||||
power open power menu
|
||||
screenshot open screenshot menu
|
||||
notmuch-refresh refresh unread mail count
|
||||
screenrec menu open screenrec menu (auto-detects state)
|
||||
screenrec start-monitor start recording the focused monitor
|
||||
@@ -35,6 +37,8 @@ EOF
|
||||
case "${1:-}" in
|
||||
finder) invoke open-finder ;;
|
||||
apps) invoke open-app-launcher ;;
|
||||
power) invoke open-power-menu ;;
|
||||
screenshot) invoke open-screenshot-menu ;;
|
||||
notmuch-refresh) invoke refresh-notmuch ;;
|
||||
screenrec)
|
||||
case "${2:-}" in
|
||||
|
||||
@@ -59,6 +59,9 @@ SCREENREC = app_config.get("screenrec", {
|
||||
"enable": False,
|
||||
"output_dir": "~/Videos/wl-screenrec",
|
||||
})
|
||||
POWER = app_config.get("power", {
|
||||
"lock_command": ["waylock"],
|
||||
})
|
||||
BAR_HEIGHT = app_config.get("height", 40)
|
||||
LOG_LEVEL = app_config.get("logLevel", "WARNING")
|
||||
DEV = app_config.get("dev", False)
|
||||
|
||||
18
sims/main.py
18
sims/main.py
@@ -21,9 +21,11 @@ from fabric.utils import (
|
||||
from .modules.bar import StatusBar
|
||||
from .modules.window_fuzzy import FuzzyWindowFinder
|
||||
from .modules.launcher.apps import AppLauncher
|
||||
from .modules.launcher.power import PowerMenu
|
||||
from .modules.launcher.screenrec import ScreenrecMenu
|
||||
from .modules.launcher.screenshot import ScreenshotMenu
|
||||
from .modules.stylix import get_stylix_css_path
|
||||
from .config import SCREENREC, STYLIX
|
||||
from .config import POWER, SCREENREC, STYLIX
|
||||
from .services.fenster import get_i3_connection
|
||||
from .services.screenrec import ScreenrecService
|
||||
|
||||
@@ -34,6 +36,8 @@ i3 = get_i3_connection()
|
||||
dummy = Window(visible=False)
|
||||
finder = FuzzyWindowFinder()
|
||||
app_launcher = AppLauncher()
|
||||
power_menu = PowerMenu(lock_command=POWER.get("lock_command", ["waylock"]))
|
||||
screenshot_menu = ScreenshotMenu()
|
||||
|
||||
screenrec_service: ScreenrecService | None = None
|
||||
screenrec_menu = None
|
||||
@@ -46,7 +50,7 @@ if SCREENREC.get("enable", False):
|
||||
bar_windows = []
|
||||
notmuch_widget = None
|
||||
|
||||
_app_windows = [dummy, finder, app_launcher]
|
||||
_app_windows = [dummy, finder, app_launcher, power_menu, screenshot_menu]
|
||||
if screenrec_menu is not None:
|
||||
_app_windows.append(screenrec_menu)
|
||||
app = Application("sims", *_app_windows)
|
||||
@@ -62,6 +66,16 @@ def open_app_launcher():
|
||||
app_launcher.show()
|
||||
|
||||
|
||||
@Application.action()
|
||||
def open_power_menu():
|
||||
power_menu.show()
|
||||
|
||||
|
||||
@Application.action()
|
||||
def open_screenshot_menu():
|
||||
screenshot_menu.show()
|
||||
|
||||
|
||||
@Application.action()
|
||||
def refresh_notmuch():
|
||||
if notmuch_widget is not None:
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
from .base import FuzzyMenu, LauncherProvider
|
||||
from .base import FuzzyMenu, LauncherProvider, StaticAction, StaticActionProvider
|
||||
from .windows import WindowProvider
|
||||
|
||||
__all__ = ["FuzzyMenu", "LauncherProvider", "WindowProvider"]
|
||||
__all__ = [
|
||||
"FuzzyMenu",
|
||||
"LauncherProvider",
|
||||
"StaticAction",
|
||||
"StaticActionProvider",
|
||||
"WindowProvider",
|
||||
]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from typing import Any, Protocol
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Protocol
|
||||
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.entry import Entry
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
from gi.repository import Gdk, Gtk
|
||||
|
||||
@@ -13,6 +15,59 @@ class LauncherProvider(Protocol):
|
||||
def activate(self, item: Any) -> None: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class StaticAction:
|
||||
label: str
|
||||
handler: Callable[[], None]
|
||||
|
||||
|
||||
class StaticActionProvider:
|
||||
"""Provider for menus whose items are a fixed list of (label, handler) pairs.
|
||||
|
||||
Pass either StaticAction instances or (label, handler) tuples; tuples are
|
||||
coerced. items_factory lets the list re-evaluate on each open (e.g. for
|
||||
state-dependent menus) — otherwise the list is captured at construction.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
actions: list[StaticAction | tuple[str, Callable[[], None]]] | None = None,
|
||||
items_factory: Callable[[], list[StaticAction | tuple[str, Callable[[], None]]]] | None = None,
|
||||
):
|
||||
if (actions is None) == (items_factory is None):
|
||||
raise ValueError("pass exactly one of actions or items_factory")
|
||||
self._static = [_coerce(a) for a in actions] if actions is not None else None
|
||||
self._factory = items_factory
|
||||
|
||||
def items(self) -> list[StaticAction]:
|
||||
if self._factory is not None:
|
||||
return [_coerce(a) for a in self._factory()]
|
||||
return list(self._static or [])
|
||||
|
||||
def filter(self, items: list[StaticAction], query: str) -> list[StaticAction]:
|
||||
if not query:
|
||||
return items
|
||||
q = query.lower()
|
||||
return [i for i in items if q in i.label.lower()]
|
||||
|
||||
def render(self, item: StaticAction) -> Gtk.Widget:
|
||||
return Box(
|
||||
name="slot-box",
|
||||
orientation="h",
|
||||
children=[Label(label=item.label, h_align="start")],
|
||||
)
|
||||
|
||||
def activate(self, item: StaticAction) -> None:
|
||||
item.handler()
|
||||
|
||||
|
||||
def _coerce(a: StaticAction | tuple[str, Callable[[], None]]) -> StaticAction:
|
||||
if isinstance(a, StaticAction):
|
||||
return a
|
||||
label, handler = a
|
||||
return StaticAction(label=label, handler=handler)
|
||||
|
||||
|
||||
class FuzzyMenu(Window):
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
25
sims/modules/launcher/power.py
Normal file
25
sims/modules/launcher/power.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import subprocess
|
||||
|
||||
from .base import FuzzyMenu, StaticActionProvider
|
||||
|
||||
|
||||
def _spawn(argv: list[str]) -> None:
|
||||
subprocess.Popen(argv, start_new_session=True)
|
||||
|
||||
|
||||
def PowerMenu(monitor: int = 0, lock_command: list[str] | None = None) -> FuzzyMenu:
|
||||
lock = lock_command or ["waylock"]
|
||||
provider = StaticActionProvider(
|
||||
actions=[
|
||||
("⏻ Poweroff", lambda: _spawn(["systemctl", "poweroff"])),
|
||||
("🔁 Reboot", lambda: _spawn(["systemctl", "reboot"])),
|
||||
("⏾ Suspend", lambda: _spawn(["systemctl", "suspend"])),
|
||||
("Lock", lambda: _spawn(lock)),
|
||||
]
|
||||
)
|
||||
return FuzzyMenu(
|
||||
provider=provider,
|
||||
monitor=monitor,
|
||||
placeholder="Power Menu...",
|
||||
window_name="power-menu",
|
||||
)
|
||||
@@ -1,50 +1,24 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.label import Label
|
||||
from gi.repository import Gtk
|
||||
|
||||
from sims.services.screenrec import ScreenrecService
|
||||
|
||||
from .base import FuzzyMenu
|
||||
from .base import FuzzyMenu, StaticActionProvider
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Item:
|
||||
label: str
|
||||
handler: Callable[[], None]
|
||||
|
||||
|
||||
class ScreenrecProvider:
|
||||
def __init__(self, service: ScreenrecService):
|
||||
self._service = service
|
||||
|
||||
def items(self) -> list[_Item]:
|
||||
if self._service.recording:
|
||||
return [_Item("Stop Recording", self._service.stop)]
|
||||
def _idle_actions(service: ScreenrecService):
|
||||
return [
|
||||
_Item("Monitor → Videos", lambda: self._service.start_monitor("videos")),
|
||||
_Item("Region → Videos", lambda: self._service.start_region("videos")),
|
||||
_Item("Monitor → Clipboard", lambda: self._service.start_monitor("clipboard")),
|
||||
_Item("Region → Clipboard", lambda: self._service.start_region("clipboard")),
|
||||
("Monitor → Videos", lambda: service.start_monitor("videos")),
|
||||
("Region → Videos", lambda: service.start_region("videos")),
|
||||
("Monitor → Clipboard", lambda: service.start_monitor("clipboard")),
|
||||
("Region → Clipboard", lambda: service.start_region("clipboard")),
|
||||
]
|
||||
|
||||
def filter(self, items: list[_Item], query: str) -> list[_Item]:
|
||||
if not query:
|
||||
return items
|
||||
q = query.lower()
|
||||
return [i for i in items if q in i.label.lower()]
|
||||
|
||||
def render(self, item: _Item) -> Gtk.Widget:
|
||||
return Box(
|
||||
name="slot-box",
|
||||
orientation="h",
|
||||
children=[Label(label=item.label, h_align="start")],
|
||||
)
|
||||
def ScreenrecProvider(service: ScreenrecService) -> StaticActionProvider:
|
||||
def items():
|
||||
if service.recording:
|
||||
return [("Stop Recording", service.stop)]
|
||||
return _idle_actions(service)
|
||||
|
||||
def activate(self, item: _Item) -> None:
|
||||
item.handler()
|
||||
return StaticActionProvider(items_factory=items)
|
||||
|
||||
|
||||
def ScreenrecMenu(service: ScreenrecService, monitor: int = 0) -> FuzzyMenu:
|
||||
|
||||
23
sims/modules/launcher/screenshot.py
Normal file
23
sims/modules/launcher/screenshot.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import subprocess
|
||||
|
||||
from .base import FuzzyMenu, StaticActionProvider
|
||||
|
||||
|
||||
def _spawn(argv: list[str]) -> None:
|
||||
subprocess.Popen(argv, start_new_session=True)
|
||||
|
||||
|
||||
def ScreenshotMenu(monitor: int = 0) -> FuzzyMenu:
|
||||
provider = StaticActionProvider(
|
||||
actions=[
|
||||
("Normal", lambda: _spawn(["grimnorm"])),
|
||||
("To Clipboard", lambda: _spawn(["grim2clip"])),
|
||||
("To Imv", lambda: _spawn(["grim2imv"])),
|
||||
]
|
||||
)
|
||||
return FuzzyMenu(
|
||||
provider=provider,
|
||||
monitor=monitor,
|
||||
placeholder="Screenshot...",
|
||||
window_name="screenshot-menu",
|
||||
)
|
||||
Reference in New Issue
Block a user