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";
|
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 = {
|
default = {
|
||||||
@@ -170,6 +177,9 @@
|
|||||||
enable = false;
|
enable = false;
|
||||||
output_dir = "~/Videos/wl-screenrec";
|
output_dir = "~/Videos/wl-screenrec";
|
||||||
};
|
};
|
||||||
|
power = {
|
||||||
|
lock_command = [ "waylock" ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ usage() {
|
|||||||
usage: sims-cli <command> [args]
|
usage: sims-cli <command> [args]
|
||||||
finder open window finder
|
finder open window finder
|
||||||
apps open application launcher
|
apps open application launcher
|
||||||
|
power open power menu
|
||||||
|
screenshot open screenshot menu
|
||||||
notmuch-refresh refresh unread mail count
|
notmuch-refresh refresh unread mail count
|
||||||
screenrec menu open screenrec menu (auto-detects state)
|
screenrec menu open screenrec menu (auto-detects state)
|
||||||
screenrec start-monitor start recording the focused monitor
|
screenrec start-monitor start recording the focused monitor
|
||||||
@@ -35,6 +37,8 @@ EOF
|
|||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
finder) invoke open-finder ;;
|
finder) invoke open-finder ;;
|
||||||
apps) invoke open-app-launcher ;;
|
apps) invoke open-app-launcher ;;
|
||||||
|
power) invoke open-power-menu ;;
|
||||||
|
screenshot) invoke open-screenshot-menu ;;
|
||||||
notmuch-refresh) invoke refresh-notmuch ;;
|
notmuch-refresh) invoke refresh-notmuch ;;
|
||||||
screenrec)
|
screenrec)
|
||||||
case "${2:-}" in
|
case "${2:-}" in
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ SCREENREC = app_config.get("screenrec", {
|
|||||||
"enable": False,
|
"enable": False,
|
||||||
"output_dir": "~/Videos/wl-screenrec",
|
"output_dir": "~/Videos/wl-screenrec",
|
||||||
})
|
})
|
||||||
|
POWER = app_config.get("power", {
|
||||||
|
"lock_command": ["waylock"],
|
||||||
|
})
|
||||||
BAR_HEIGHT = app_config.get("height", 40)
|
BAR_HEIGHT = app_config.get("height", 40)
|
||||||
LOG_LEVEL = app_config.get("logLevel", "WARNING")
|
LOG_LEVEL = app_config.get("logLevel", "WARNING")
|
||||||
DEV = app_config.get("dev", False)
|
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.bar import StatusBar
|
||||||
from .modules.window_fuzzy import FuzzyWindowFinder
|
from .modules.window_fuzzy import FuzzyWindowFinder
|
||||||
from .modules.launcher.apps import AppLauncher
|
from .modules.launcher.apps import AppLauncher
|
||||||
|
from .modules.launcher.power import PowerMenu
|
||||||
from .modules.launcher.screenrec import ScreenrecMenu
|
from .modules.launcher.screenrec import ScreenrecMenu
|
||||||
|
from .modules.launcher.screenshot import ScreenshotMenu
|
||||||
from .modules.stylix import get_stylix_css_path
|
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.fenster import get_i3_connection
|
||||||
from .services.screenrec import ScreenrecService
|
from .services.screenrec import ScreenrecService
|
||||||
|
|
||||||
@@ -34,6 +36,8 @@ i3 = get_i3_connection()
|
|||||||
dummy = Window(visible=False)
|
dummy = Window(visible=False)
|
||||||
finder = FuzzyWindowFinder()
|
finder = FuzzyWindowFinder()
|
||||||
app_launcher = AppLauncher()
|
app_launcher = AppLauncher()
|
||||||
|
power_menu = PowerMenu(lock_command=POWER.get("lock_command", ["waylock"]))
|
||||||
|
screenshot_menu = ScreenshotMenu()
|
||||||
|
|
||||||
screenrec_service: ScreenrecService | None = None
|
screenrec_service: ScreenrecService | None = None
|
||||||
screenrec_menu = None
|
screenrec_menu = None
|
||||||
@@ -46,7 +50,7 @@ if SCREENREC.get("enable", False):
|
|||||||
bar_windows = []
|
bar_windows = []
|
||||||
notmuch_widget = None
|
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:
|
if screenrec_menu is not None:
|
||||||
_app_windows.append(screenrec_menu)
|
_app_windows.append(screenrec_menu)
|
||||||
app = Application("sims", *_app_windows)
|
app = Application("sims", *_app_windows)
|
||||||
@@ -62,6 +66,16 @@ def open_app_launcher():
|
|||||||
app_launcher.show()
|
app_launcher.show()
|
||||||
|
|
||||||
|
|
||||||
|
@Application.action()
|
||||||
|
def open_power_menu():
|
||||||
|
power_menu.show()
|
||||||
|
|
||||||
|
|
||||||
|
@Application.action()
|
||||||
|
def open_screenshot_menu():
|
||||||
|
screenshot_menu.show()
|
||||||
|
|
||||||
|
|
||||||
@Application.action()
|
@Application.action()
|
||||||
def refresh_notmuch():
|
def refresh_notmuch():
|
||||||
if notmuch_widget is not None:
|
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
|
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.box import Box
|
||||||
from fabric.widgets.entry import Entry
|
from fabric.widgets.entry import Entry
|
||||||
|
from fabric.widgets.label import Label
|
||||||
from fabric.widgets.wayland import WaylandWindow as Window
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
from gi.repository import Gdk, Gtk
|
from gi.repository import Gdk, Gtk
|
||||||
|
|
||||||
@@ -13,6 +15,59 @@ class LauncherProvider(Protocol):
|
|||||||
def activate(self, item: Any) -> None: ...
|
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):
|
class FuzzyMenu(Window):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
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 sims.services.screenrec import ScreenrecService
|
||||||
|
|
||||||
from .base import FuzzyMenu
|
from .base import FuzzyMenu, StaticActionProvider
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def _idle_actions(service: ScreenrecService):
|
||||||
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)]
|
|
||||||
return [
|
return [
|
||||||
_Item("Monitor → Videos", lambda: self._service.start_monitor("videos")),
|
("Monitor → Videos", lambda: service.start_monitor("videos")),
|
||||||
_Item("Region → Videos", lambda: self._service.start_region("videos")),
|
("Region → Videos", lambda: service.start_region("videos")),
|
||||||
_Item("Monitor → Clipboard", lambda: self._service.start_monitor("clipboard")),
|
("Monitor → Clipboard", lambda: service.start_monitor("clipboard")),
|
||||||
_Item("Region → Clipboard", lambda: self._service.start_region("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:
|
def ScreenrecProvider(service: ScreenrecService) -> StaticActionProvider:
|
||||||
return Box(
|
def items():
|
||||||
name="slot-box",
|
if service.recording:
|
||||||
orientation="h",
|
return [("Stop Recording", service.stop)]
|
||||||
children=[Label(label=item.label, h_align="start")],
|
return _idle_actions(service)
|
||||||
)
|
|
||||||
|
|
||||||
def activate(self, item: _Item) -> None:
|
return StaticActionProvider(items_factory=items)
|
||||||
item.handler()
|
|
||||||
|
|
||||||
|
|
||||||
def ScreenrecMenu(service: ScreenrecService, monitor: int = 0) -> FuzzyMenu:
|
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