diff --git a/sims/cli.py b/sims/cli.py index 0cec401..ab75c14 100644 --- a/sims/cli.py +++ b/sims/cli.py @@ -56,6 +56,7 @@ def _action(name: str) -> Callable[[argparse.Namespace], None]: COMMANDS: list[Command] = [ Command("finder", "open window finder", _action("open-finder")), Command("apps", "open application launcher", _action("open-app-launcher")), + Command("clipboard", "open clipboard history picker", _action("open-clipboard-menu")), Command("power", "open power menu", _action("open-power-menu")), Command("screenshot", "open screenshot menu", _action("open-screenshot-menu")), Command("notmuch-refresh", "refresh unread mail count", _action("refresh-notmuch")), diff --git a/sims/main.py b/sims/main.py index 264038a..3b1c5d6 100644 --- a/sims/main.py +++ b/sims/main.py @@ -21,6 +21,7 @@ from fabric.utils import ( from .modules.bar import StatusBar from .modules.window_fuzzy import FuzzyWindowFinder from .modules.launcher.apps import AppLauncher +from .modules.launcher.clipboard import ClipboardMenu from .modules.launcher.power import PowerMenu from .modules.launcher.screenrec import ScreenrecMenu from .modules.launcher.screenshot import ScreenshotMenu @@ -36,6 +37,7 @@ get_i3_connection() dummy = Window(visible=False) finder = FuzzyWindowFinder() app_launcher = AppLauncher() +clipboard_menu = ClipboardMenu() power_menu = PowerMenu(lock_command=POWER.get("lock_command", ["waylock"])) screenshot_menu = ScreenshotMenu() @@ -50,7 +52,7 @@ if SCREENREC.get("enable", False): bar_windows = [] notmuch_widget = None -_app_windows = [dummy, finder, app_launcher, power_menu, screenshot_menu] +_app_windows = [dummy, finder, app_launcher, clipboard_menu, power_menu, screenshot_menu] if screenrec_menu is not None: _app_windows.append(screenrec_menu) app = Application("sims", *_app_windows) @@ -66,6 +68,11 @@ def open_app_launcher(): app_launcher.show() +@Application.action() +def open_clipboard_menu(): + clipboard_menu.show() + + @Application.action() def open_power_menu(): power_menu.show() diff --git a/sims/modules/launcher/clipboard.py b/sims/modules/launcher/clipboard.py new file mode 100644 index 0000000..d78a57c --- /dev/null +++ b/sims/modules/launcher/clipboard.py @@ -0,0 +1,74 @@ +import subprocess +from dataclasses import dataclass + +from fabric.widgets.box import Box +from fabric.widgets.label import Label +from gi.repository import Gtk + +from .base import FuzzyMenu + + +@dataclass +class ClipEntry: + raw: str # full "\t" line as emitted by cliphist + preview: str + + +class ClipboardProvider: + def items(self) -> list[ClipEntry]: + proc = subprocess.run( + ["cliphist", "list"], + capture_output=True, + text=True, + ) + if proc.returncode != 0: + return [] + entries: list[ClipEntry] = [] + for line in proc.stdout.splitlines(): + if not line: + continue + _id, sep, preview = line.partition("\t") + if not sep: + continue + entries.append(ClipEntry(raw=line, preview=preview)) + return entries + + def filter(self, items: list[ClipEntry], query: str) -> list[ClipEntry]: + if not query: + return items + q = query.lower() + return [e for e in items if q in e.preview.lower()] + + def render(self, item: ClipEntry) -> Gtk.Widget: + return Box( + name="slot-box", + orientation="h", + children=[ + Label( + label=item.preview, + h_align="start", + ellipsization="end", + name="clip-preview", + ), + ], + ) + + def activate(self, item: ClipEntry) -> None: + decoded = subprocess.run( + ["cliphist", "decode"], + input=item.raw.encode("utf-8"), + capture_output=True, + ) + if decoded.returncode != 0: + return + subprocess.run(["wl-copy"], input=decoded.stdout) + + +def ClipboardMenu(monitor: int = 0) -> FuzzyMenu: + return FuzzyMenu( + provider=ClipboardProvider(), + monitor=monitor, + placeholder="Clipboard...", + window_name="clipboard-menu", + max_results=12, + )