From 41a129bb9026ea7dd3d23b2e78438b202626ac1a Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 22 May 2026 11:04:54 +0200 Subject: [PATCH 01/23] init commit --- cli/src/zshell/subcommands/record.py | 259 +++++++++++++++++++++++++-- 1 file changed, 244 insertions(+), 15 deletions(-) diff --git a/cli/src/zshell/subcommands/record.py b/cli/src/zshell/subcommands/record.py index 328e2bf..f0ef76b 100644 --- a/cli/src/zshell/subcommands/record.py +++ b/cli/src/zshell/subcommands/record.py @@ -1,16 +1,245 @@ -# import typer -# import subprocess -# -# from typing import Optional -# -# app = typer.Typer() -# -# RECORDER = "gpu-screen-recorder" -# HOME = str(os.getenv("HOME")) -# CONFIG = Path(HOME + "/.config/zshell/config.json") -# -# -# @app.command() -# def start(): +#!/usr/bin/env python3 +import os +import json +import subprocess +import time +import signal +from pathlib import Path +from typing import Optional -# TODO: Currently unused +import typer + +app = typer.Typer() + +RECORDER = "gpu-screen-recorder" +HOME = str(os.getenv("HOME", str(Path.home()))) +CONFIG = Path(HOME) / ".config/zshell/config.json" + +# Paths for temp recording and notifications +STATE_DIR = Path(HOME) / ".local/state/zshell/record" +TEMP_RECORDING = STATE_DIR / "recording.mp4" +NOTIF_ID_FILE = STATE_DIR / "notifid.txt" + +# Where final recordings are saved +RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR", + str(Path(HOME) / "Videos/Recordings")) + + +# ── helpers ────────────────────────────────────────────── +def _read_extra_args() -> list[str]: + """Return extra gpu-screen-recorder arguments from the user config.""" + try: + if CONFIG.is_file(): + data = json.loads(CONFIG.read_text()) + return data.get("record", {}).get("extraArgs", []) + except Exception: + pass + return [] + + +def _is_recording() -> bool: + """Check if gpu-screen-recorder process exists.""" + return subprocess.run(["pidof", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0 + + +def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 3000) -> Optional[int]: + """Send a desktop notification. Returns the notification ID or None.""" + args = ["notify-send", summary, body, "-t", str(timeout), "-p"] + if actions: + for action in actions: + args.extend(["-A", action]) + try: + proc = subprocess.run(args, capture_output=True, text=True) + return int(proc.stdout.strip()) if proc.stdout.strip().isdigit() else None + except Exception: + return None + + +def _close_notification(notif_id: int): + """Close a notification by its ID.""" + subprocess.run(["notify-send", "--close", str(notif_id)], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def _get_monitors() -> list[dict]: + """Get monitor info from Hyprland.""" + try: + res = subprocess.run(["hyprctl", "monitors", "-j"], + capture_output=True, text=True) + return json.loads(res.stdout) + except Exception: + return [] + + +def _focused_monitor_name() -> Optional[str]: + """Return name of the currently focused monitor.""" + monitors = _get_monitors() + for m in monitors: + if m.get("focused"): + return m["name"] + return None + + +def _monitors_intersecting_region(x: int, y: int, w: int, h: int) -> list[dict]: + """Return monitors whose area intersects the given region.""" + region = (x, y, x + w, y + h) + intersecting = [] + for m in _get_monitors(): + mx, my, mw, mh = m["x"], m["y"], m["width"], m["height"] + if not (region[2] <= mx or region[0] >= mx + mw or region[3] <= my or region[1] >= my + mh): + intersecting.append(m) + return intersecting + + +def _highest_refresh(monitors: list[dict]) -> float: + """Return the maximum refresh rate among the given monitors.""" + return max((m["refreshRate"] for m in monitors), default=60.0) + + +def _slurp_region() -> Optional[str]: + """Call slurp and return geometry like 'WxH+X+Y'.""" + try: + return subprocess.check_output(["slurp", "-f", "%wx%h+%x+%y"], text=True).strip() + except subprocess.CalledProcessError: + return None + + +def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]: + """Parse 'WxH+X+Y' into (x,y,w,h).""" + import re + match = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", geometry) + if match: + return int(match.group(3)), int(match.group(4)), int(match.group(1)), int(match.group(2)) + return None + + +# ── core actions ───────────────────────────────────────── +def start_recording(region: Optional[str], sound: bool): + """Launch gpu-screen-recorder.""" + STATE_DIR.mkdir(parents=True, exist_ok=True) + + cmd = [RECORDER, "-w"] # -w for window/display + extra_args = _read_extra_args() + + if region: + if region.lower() == "slurp" or not region: + geometry = _slurp_region() + if not geometry: + typer.echo("Region selection cancelled.") + raise typer.Abort() + else: + geometry = region + parsed = _parse_geometry(geometry) + if parsed: + x, y, w, h = parsed + monitors = _monitors_intersecting_region(x, y, w, h) + framerate = _highest_refresh(monitors) + cmd.extend(["-region", geometry, "-f", str(int(framerate))]) + else: + typer.echo("Invalid geometry format.") + raise typer.Abort() + else: + # Fullscreen: use focused monitor + monitor = _focused_monitor_name() + if monitor: + cmd.extend(["-m", monitor]) + # Refresh rate comes from that monitor + monitors = _get_monitors() + mon = next((m for m in monitors if m["name"] == monitor), None) + if mon: + cmd.extend(["-f", str(int(mon["refreshRate"]))]) + + if sound: + cmd.append("-a") + cmd.append("default_output") + + cmd.extend(extra_args) + cmd.append(str(TEMP_RECORDING)) + + # Launch detached + subprocess.Popen(cmd, start_new_session=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + # Notification + notif_id = _notify("Recording started", f"Saving to {TEMP_RECORDING}") + if notif_id is not None: + NOTIF_ID_FILE.write_text(str(notif_id)) + + # Early failure check + time.sleep(1) + if not _is_recording(): + _notify("Recording failed", + "Check gpu-screen-recorder output.", timeout=5000) + raise typer.Exit(code=1) + + +def stop_recording(clipboard: bool): + """Stop the recording and finalise the file.""" + # Kill the process + subprocess.run(["pkill", "-f", RECORDER], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + # Wait until it really stops + for _ in range(50): # 5 seconds max + if not _is_recording(): + break + time.sleep(0.1) + + # Move the recording + dest_dir = Path(RECORDINGS_DIR) + dest_dir.mkdir(parents=True, exist_ok=True) + timestamp = time.strftime("%Y-%m-%d_%H-%M-%S") + final_path = dest_dir / f"recording_{timestamp}.mp4" + if TEMP_RECORDING.exists(): + TEMP_RECORDING.rename(final_path) + + # Close the start notification + if NOTIF_ID_FILE.is_file(): + try: + _close_notification(int(NOTIF_ID_FILE.read_text().strip())) + except Exception: + pass + NOTIF_ID_FILE.unlink() + + # Clipboard + if clipboard: + subprocess.run(["wl-copy", "--type", "text/uri-list", f"file://{final_path}"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + # Final notification (simplified: no actions) + _notify("Recording stopped", f"Saved to {final_path}", timeout=5000) + + +def toggle_pause(): + """Send SIGUSR2 to gpu-screen-recorder.""" + subprocess.run(["pkill", "-USR2", "-f", RECORDER], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + typer.echo("Toggled pause.") + + +# ── Typer command ──────────────────────────────────────── +@app.command() +def record( + region: Optional[str] = typer.Option( + None, "--region", "-r", + help="Record a region. Use 'slurp' (or omit value) to select interactively, or give 'WxH+X+Y'.", + ), + sound: bool = typer.Option( + False, "--sound", "-s", help="Record audio from default output."), + pause: bool = typer.Option( + False, "--pause", "-p", help="Toggle pause/resume."), + clipboard: bool = typer.Option( + False, "--clipboard", "-c", help="Copy the final recording path to clipboard."), +): + """ + Start or stop a screen recording with gpu-screen-recorder. + Running again stops the current recording. + """ + if pause: + toggle_pause() + raise typer.Exit() + + if _is_recording(): + stop_recording(clipboard) + else: + start_recording(region, sound) From ec5e6d3995f82d6382e82758bcc3020887cd91cb Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 22 May 2026 11:06:17 +0200 Subject: [PATCH 02/23] add typer command --- cli/src/zshell/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/zshell/__init__.py b/cli/src/zshell/__init__.py index 1b062a4..365e8d8 100644 --- a/cli/src/zshell/__init__.py +++ b/cli/src/zshell/__init__.py @@ -1,6 +1,6 @@ from __future__ import annotations import typer -from zshell.subcommands import shell, scheme, screenshot, wallpaper +from zshell.subcommands import shell, scheme, screenshot, wallpaper, record app = typer.Typer() @@ -8,6 +8,7 @@ app.add_typer(shell.app, name="shell") app.add_typer(scheme.app, name="scheme") app.add_typer(screenshot.app, name="screenshot") app.add_typer(wallpaper.app, name="wallpaper") +app.add_typer(record.app, name="record") # app.add_typer(preset.app, name="preset") From 0ec426e0f07ed52c28affa9ec866874e0d100385 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 22 May 2026 12:51:06 +0200 Subject: [PATCH 03/23] Record module added to sidebar, file list and buttons. Region recording is broken --- Helpers/Recorder.qml | 123 +++++--- .../Sidebar/Utils/Cards/Record.qml | 290 ++++++++++++++++++ .../Sidebar/Utils/Cards/RecordingList.qml | 226 ++++++++++++++ .../Notifications/Sidebar/Utils/Content.qml | 13 +- cli/pyproject.toml | 1 + .../__pycache__/scheme.cpython-313.pyc | Bin 7101 -> 24035 bytes .../__pycache__/screenshot.cpython-313.pyc | Bin 978 -> 965 bytes .../__pycache__/shell.cpython-313.pyc | Bin 2184 -> 2181 bytes .../__pycache__/wallpaper.cpython-313.pyc | Bin 1920 -> 1983 bytes cli/src/zshell/subcommands/record.py | 81 ++--- .../schemepalettes.cpython-313.pyc | Bin 1388 -> 1388 bytes 11 files changed, 634 insertions(+), 100 deletions(-) create mode 100644 Modules/Notifications/Sidebar/Utils/Cards/Record.qml create mode 100644 Modules/Notifications/Sidebar/Utils/Cards/RecordingList.qml diff --git a/Helpers/Recorder.qml b/Helpers/Recorder.qml index 4c7b330..128c65b 100644 --- a/Helpers/Recorder.qml +++ b/Helpers/Recorder.qml @@ -1,41 +1,82 @@ -// pragma Singleton -// -// import Quickshell -// import QtQuick -// -// Singleton { -// id: root -// -// function start(extraArgs = []): void { -// needsStart = true; -// startArgs = extraArgs; -// checkProc.running = true; -// } -// -// PersistentProperties { -// id: props -// -// property real elapsed: 0 -// property bool paused: false -// property bool running: false -// -// reloadableId: "recorder" -// } -// -// Process { -// id: checkProc -// -// command: ["pidof", "gpu-screen-recorder"] -// running: true -// -// onExited: code => { -// props.running = code === 0; -// -// if (code === 0) { -// if (root.needsStop) { -// Quickshell.execDetached(["zshell-cli"]); -// } -// } -// } -// } -// } +pragma Singleton + +import Quickshell +import Quickshell.Io +import QtQuick + +Singleton { + id: root + + readonly property alias elapsed: props.elapsed + property bool needsPause + property bool needsStart + property bool needsStop + readonly property alias paused: props.paused + readonly property alias running: props.running + property list startArgs + + function start(extraArgs = []): void { + needsStart = true; + startArgs = extraArgs; + checkProc.running = true; + } + + function stop(): void { + needsStop = true; + checkProc.running = true; + } + + function togglePause(): void { + needsPause = true; + checkProc.running = true; + } + + PersistentProperties { + id: props + + property real elapsed: 0 + property bool paused: false + property bool running: false + + reloadableId: "recorder" + } + + Process { + id: checkProc + + command: ["pidof", "gpu-screen-recorder"] + running: true + + onExited: code => { + props.running = code === 0; + + if (code === 0) { + if (root.needsStop) { + Quickshell.execDetached(["zshell-cli", "record", "record"]); + props.running = false; + props.paused = false; + } else if (root.needsPause) { + Quickshell.execDetached(["zshell-cli", "record", "record", "-p"]); + props.paused = !props.paused; + } + } else if (root.needsStart) { + Quickshell.execDetached(["zshell-cli", "record", "record", ...root.startArgs]); + props.running = true; + props.paused = false; + props.elapsed = 0; + } + + root.needsStart = false; + root.needsStop = false; + root.needsPause = false; + } + } + + Connections { + function onSecondsChanged(): void { + props.elapsed++; + } + + target: Time // qmllint disable incompatible-type + } +} diff --git a/Modules/Notifications/Sidebar/Utils/Cards/Record.qml b/Modules/Notifications/Sidebar/Utils/Cards/Record.qml new file mode 100644 index 0000000..7d7c832 --- /dev/null +++ b/Modules/Notifications/Sidebar/Utils/Cards/Record.qml @@ -0,0 +1,290 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Helpers + +CustomRect { + id: root + + required property var props + required property PersistentProperties visibilities + + Layout.fillWidth: true + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: layout.implicitHeight + layout.anchors.margins * 2 + radius: Appearance.rounding.smallest + + ColumnLayout { + id: layout + + anchors.fill: parent + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal + + RowLayout { + spacing: Appearance.spacing.normal + z: 1 + + CustomRect { + color: Recorder.running ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3secondaryContainer + implicitHeight: { + const h = icon.implicitHeight + Appearance.padding.smaller * 2; + return h - (h % 2); + } + implicitWidth: implicitHeight + radius: Appearance.rounding.full + + MaterialIcon { + id: icon + + anchors.centerIn: parent + anchors.horizontalCenterOffset: -0.5 + anchors.verticalCenterOffset: 1.5 + color: Recorder.running ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSecondaryContainer + font.pointSize: Appearance.font.size.large + text: "screen_record" + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.normal + text: qsTr("Screen Recorder") + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3onSurfaceVariant + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: Recorder.paused ? qsTr("Recording paused") : Recorder.running ? qsTr("Recording running") : qsTr("Recording off") + } + } + + CustomSplitButton { + active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0] + disabled: Recorder.running + + menuItems: [ + MenuItem { + activeText: qsTr("Fullscreen") + icon: "fullscreen" + text: qsTr("Record fullscreen") + + onClicked: Recorder.start() + }, + MenuItem { + activeText: qsTr("Region") + icon: "screenshot_region" + text: qsTr("Record region") + + onClicked: Recorder.start(["-r"]) + }, + MenuItem { + activeText: qsTr("Fullscreen") + icon: "select_to_speak" + text: qsTr("Record fullscreen with sound") + + onClicked: Recorder.start(["-s"]) + }, + MenuItem { + activeText: qsTr("Region") + icon: "volume_up" + text: qsTr("Record region with sound") + + onClicked: Recorder.start(["-s", "-r"]) + } + ] + + menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text + } + } + + Loader { + id: listOrControls + + property bool running: Recorder.running + + Layout.fillWidth: true + Layout.preferredHeight: implicitHeight + asynchronous: true + sourceComponent: running ? recordingControls : recordingList + + Behavior on Layout.preferredHeight { + id: locHeightAnim + + enabled: false + + Anim { + } + } + Behavior on running { + SequentialAnimation { + ParallelAnimation { + Anim { + duration: Appearance.anim.durations.small + easing: Appearance.anim.curves.standardAccel + property: "scale" + target: listOrControls + to: 0.7 + } + + Anim { + duration: Appearance.anim.durations.small + easing: Appearance.anim.curves.standardAccel + property: "opacity" + target: listOrControls + to: 0 + } + } + + PropertyAction { + property: "enabled" + target: locHeightAnim + value: true + } + + PropertyAction { + } + + PropertyAction { + property: "enabled" + target: locHeightAnim + value: false + } + + ParallelAnimation { + Anim { + duration: Appearance.anim.durations.small + easing: Appearance.anim.curves.standardDecel + property: "scale" + target: listOrControls + to: 1 + } + + Anim { + duration: Appearance.anim.durations.small + easing: Appearance.anim.curves.standardDecel + property: "opacity" + target: listOrControls + to: 1 + } + } + } + } + } + } + + Component { + id: recordingList + + RecordingList { + props: root.props + visibilities: root.visibilities + } + } + + Component { + id: recordingControls + + RowLayout { + spacing: Appearance.spacing.normal + + CustomRect { + color: Recorder.paused ? DynamicColors.palette.m3tertiary : DynamicColors.palette.m3error + implicitHeight: recText.implicitHeight + Appearance.padding.smaller * 2 + implicitWidth: recText.implicitWidth + Appearance.padding.normal * 2 + radius: Appearance.rounding.full + + Behavior on implicitWidth { + Anim { + } + } + SequentialAnimation on opacity { + alwaysRunToEnd: true + loops: Animation.Infinite + running: !Recorder.paused + + Anim { + duration: Appearance.anim.durations.large + easing: Appearance.anim.curves.emphasizedAccel + from: 1 + to: 0 + } + + Anim { + duration: Appearance.anim.durations.extraLarge + easing: Appearance.anim.curves.emphasizedDecel + from: 0 + to: 1 + } + } + + CustomText { + id: recText + + anchors.centerIn: parent + animate: true + color: Recorder.paused ? DynamicColors.palette.m3onTertiary : DynamicColors.palette.m3onError + font.family: Appearance.font.family.mono + text: Recorder.paused ? "PAUSED" : "REC" + } + } + + CustomText { + font.pointSize: Appearance.font.size.normal + text: { + const elapsed = Recorder.elapsed; + + const hours = Math.floor(elapsed / 3600); + const mins = Math.floor((elapsed % 3600) / 60); + const secs = Math.floor(elapsed % 60).toString().padStart(2, "0"); + + let time; + if (hours > 0) + time = `${hours}:${mins.toString().padStart(2, "0")}:${secs}`; + else + time = `${mins}:${secs}`; + + return qsTr("Recording for %1").arg(time); + } + } + + Item { + Layout.fillWidth: true + } + + IconButton { + checked: Recorder.paused + font.pointSize: Appearance.font.size.large + icon: Recorder.paused ? "play_arrow" : "pause" + label.animate: true + toggle: true + type: IconButton.Tonal + + onClicked: { + Recorder.togglePause(); + internalChecked = Recorder.paused; + } + } + + IconButton { + font.pointSize: Appearance.font.size.large + icon: "stop" + inactiveColour: DynamicColors.palette.m3error + inactiveOnColour: DynamicColors.palette.m3onError + + onClicked: Recorder.stop() + } + } + } +} diff --git a/Modules/Notifications/Sidebar/Utils/Cards/RecordingList.qml b/Modules/Notifications/Sidebar/Utils/Cards/RecordingList.qml new file mode 100644 index 0000000..bb94fc9 --- /dev/null +++ b/Modules/Notifications/Sidebar/Utils/Cards/RecordingList.qml @@ -0,0 +1,226 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import ZShell.Models +import qs.Components +import qs.Helpers +import qs.Paths +import qs.Config + +ColumnLayout { + id: root + + required property var props + required property PersistentProperties visibilities + + spacing: 0 + + WrapperMouseArea { + Layout.fillWidth: true + cursorShape: Qt.PointingHandCursor + + onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded + + RowLayout { + spacing: Appearance.spacing.smaller + + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + font.pointSize: Appearance.font.size.large + text: "list" + } + + CustomText { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + font.pointSize: Appearance.font.size.normal + text: qsTr("Recordings") + } + + IconButton { + icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more" + label.animate: true + type: IconButton.Text + + onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded + } + } + } + + CustomListView { + id: list + + Layout.fillWidth: true + Layout.rightMargin: -Appearance.spacing.small + clip: true + implicitHeight: (Appearance.font.size.larger + Appearance.padding.small) * (root.props.recordingListExpanded ? 10 : 3) + + CustomScrollBar.vertical: CustomScrollBar { + flickable: list + } + add: Transition { + Anim { + from: 0 + property: "opacity" + to: 1 + } + + Anim { + from: 0.5 + property: "scale" + to: 1 + } + } + delegate: RowLayout { + id: recording + + property string baseName + required property FileSystemEntry modelData + + anchors.left: list.contentItem.left + anchors.right: list.contentItem.right + anchors.rightMargin: Appearance.spacing.small + spacing: Appearance.spacing.small / 2 + + Component.onCompleted: baseName = modelData.baseName + + CustomText { + Layout.fillWidth: true + Layout.rightMargin: Appearance.spacing.small / 2 + color: DynamicColors.palette.m3onSurfaceVariant + elide: Text.ElideRight + text: { + const time = recording.baseName; + const matches = time.match(/^recording_(\d{4})(\d{2})(\d{2})_(\d{2})-(\d{2})-(\d{2})/); + if (!matches) + return time; + const date = new Date(...matches.slice(1)); + date.setMonth(date.getMonth() - 1); + return qsTr("Recording at %1").arg(Qt.formatDateTime(date, Qt.locale())); + } + } + + IconButton { + icon: "play_arrow" + type: IconButton.Text + + onClicked: { + root.visibilities.sidebar = false; + Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]); + } + } + + IconButton { + icon: "folder" + type: IconButton.Text + + onClicked: { + root.visibilities.sidebar = false; + Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]); + } + } + } + displaced: Transition { + Anim { + properties: "opacity,scale" + to: 1 + } + + Anim { + property: "y" + } + } + Behavior on implicitHeight { + Anim { + } + } + model: FileSystemModel { + nameFilters: ["recording_*.mp4"] + path: Paths.recsdir + sortReverse: true + } + remove: Transition { + Anim { + property: "opacity" + to: 0 + } + + Anim { + property: "scale" + to: 0.5 + } + } + + Loader { + active: opacity > 0 + anchors.centerIn: parent + asynchronous: true + opacity: list.count === 0 ? 1 : 0 + + Behavior on opacity { + Anim { + } + } + sourceComponent: ColumnLayout { + spacing: Appearance.spacing.small + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: root.props.recordingListExpanded ? implicitHeight : 0 + color: DynamicColors.palette.m3outline + font.pointSize: Appearance.font.size.extraLarge + opacity: root.props.recordingListExpanded ? 1 : 0 + scale: root.props.recordingListExpanded ? 1 : 0 + text: "scan_delete" + + Behavior on Layout.preferredHeight { + Anim { + } + } + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } + } + + RowLayout { + spacing: Appearance.spacing.smaller + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: !root.props.recordingListExpanded ? implicitWidth : 0 + color: DynamicColors.palette.m3outline + opacity: !root.props.recordingListExpanded ? 1 : 0 + scale: !root.props.recordingListExpanded ? 1 : 0 + text: "scan_delete" + + Behavior on Layout.preferredWidth { + Anim { + } + } + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } + } + + CustomText { + color: DynamicColors.palette.m3outline + text: qsTr("No recordings found") + } + } + } + } + } +} diff --git a/Modules/Notifications/Sidebar/Utils/Content.qml b/Modules/Notifications/Sidebar/Utils/Content.qml index d9404ea..348340a 100644 --- a/Modules/Notifications/Sidebar/Utils/Content.qml +++ b/Modules/Notifications/Sidebar/Utils/Content.qml @@ -1,13 +1,14 @@ -import qs.Modules.Notifications.Sidebar.Utils.Cards -import qs.Config +import Quickshell import QtQuick import QtQuick.Layouts +import qs.Modules.Notifications.Sidebar.Utils.Cards +import qs.Config Item { id: root required property Item popouts - required property var props + required property PersistentProperties props required property var visibilities implicitHeight: layout.implicitHeight @@ -22,6 +23,12 @@ Item { IdleInhibit { } + Record { + props: root.props + visibilities: root.visibilities + z: 1 + } + Toggles { popouts: root.popouts visibilities: root.visibilities diff --git a/cli/pyproject.toml b/cli/pyproject.toml index a234745..d7e1888 100644 --- a/cli/pyproject.toml +++ b/cli/pyproject.toml @@ -9,6 +9,7 @@ version = "0.1.0" dependencies = [ "typer", "pillow", + "jinja2", "materialyoucolor" ] diff --git a/cli/src/zshell/subcommands/__pycache__/scheme.cpython-313.pyc b/cli/src/zshell/subcommands/__pycache__/scheme.cpython-313.pyc index 71fa4f894106bca816f890854e4b585d2df759bb..7e9924e359f43ab124a1f7be44fc065360606053 100644 GIT binary patch literal 24035 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 delta 3527 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 diff --git a/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-313.pyc b/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-313.pyc index 043fc1df55b2e6a5d932ad16cb9504f6c6f86496..307d7075fdd6fe85f74a972e7066a81f0e6578cf 100644 GIT binary patch delta 151 zcmcb_ew3Z}GcPX}0}!|`;mvZ`$a{~GQG4EB6mYkeE|oErFp}^ zjM`96KlJ4qja=5Mp%*)Hg00cUBr)9D;Z{$0`#%MYDJe!i#4H2mc79W`zMPzQsX@Y328-}Kz znHgElCo{A6Fus^Pf&Hj96QlVA;SUTzYDO}c+Q70Qcq8W*kjQ6d1}6TGOh8rtD8AEV0TOlDP1reH>^2nL2k;mPZnCCwRwnM|2LCNeN6Fr+ht zvIH}-0@=aLRzeI6DVz)-5f-orD@Y_!A1J~c#5q}*MOhLe&ZZ00Z^9G}HUmTiaZUcn ztY{0=iw@wXvxD@&O$PHYOps9mTEvTP1Or1fA51JrU}AyD<)vg(f6kW*5J~!QIc%$uU8CM(Py~l^Yy<57@ao92?6gh&0#D5SfuUQ+$ERY`M+5 zSe+T`85xrpCzyO@0MVbB8JPG#FoBpKI2f2%+oc<&n`J+UFi1#!6qaCAydfxcgM;S= z2X8-57f(0e2WCc2iI2?8LTn$HfwCXC7=)y8$a90_g&2fHzktjE+w+kL$SPtH2D%;q DRH<)U delta 463 zcmZ{ey-Nad7{`CFdD@*Xb$W=_bzq2uwHQ(%MTIRQl{861P=pX=N)0U)4ZS#Y@{2NB z8f$2X_8g=C;P8)#ldGYrem@~<>AO6i=lMQwP2Yh}aTLV^xLz}trK?Bf5y45;M3K0q z>Sm^mp=wyM#BEy8kTsmXD+(I3re+-sX%?~%G>tAGdxkN#j0X);fRt}5pz$66R^0$B zkpk{ih^VZLuo@1tH!K-6iT=2qEQN~Vm5vrArV)dgSmoV*h}jp`pu~2Fu6O_R{KlgE z!;9W}S!FLog8^2@!JuoDpAi}W>mVWJD` zU7S{S*VqP~=<4Nm^`1RYU6tgMg;SRqt9cu-#`bmmcJ^lY>66aDeFU@6OgTVA2Lt4@ zkwDu65Iv3@MvnSy6-4{ny(%=UV812%Eje(WJImGbHik;Hjd93hWA5^ikp7R~E&Lu3 Q3crgzojYyBsY1k)-vC@`SpWb4 diff --git a/cli/src/zshell/subcommands/record.py b/cli/src/zshell/subcommands/record.py index f0ef76b..32b94fd 100644 --- a/cli/src/zshell/subcommands/record.py +++ b/cli/src/zshell/subcommands/record.py @@ -3,7 +3,6 @@ import os import json import subprocess import time -import signal from pathlib import Path from typing import Optional @@ -15,19 +14,15 @@ RECORDER = "gpu-screen-recorder" HOME = str(os.getenv("HOME", str(Path.home()))) CONFIG = Path(HOME) / ".config/zshell/config.json" -# Paths for temp recording and notifications STATE_DIR = Path(HOME) / ".local/state/zshell/record" TEMP_RECORDING = STATE_DIR / "recording.mp4" NOTIF_ID_FILE = STATE_DIR / "notifid.txt" -# Where final recordings are saved RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR", str(Path(HOME) / "Videos/Recordings")) -# ── helpers ────────────────────────────────────────────── def _read_extra_args() -> list[str]: - """Return extra gpu-screen-recorder arguments from the user config.""" try: if CONFIG.is_file(): data = json.loads(CONFIG.read_text()) @@ -38,12 +33,10 @@ def _read_extra_args() -> list[str]: def _is_recording() -> bool: - """Check if gpu-screen-recorder process exists.""" return subprocess.run(["pidof", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0 -def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 3000) -> Optional[int]: - """Send a desktop notification. Returns the notification ID or None.""" +def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 5000) -> Optional[int]: args = ["notify-send", summary, body, "-t", str(timeout), "-p"] if actions: for action in actions: @@ -56,13 +49,11 @@ def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 3 def _close_notification(notif_id: int): - """Close a notification by its ID.""" subprocess.run(["notify-send", "--close", str(notif_id)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def _get_monitors() -> list[dict]: - """Get monitor info from Hyprland.""" try: res = subprocess.run(["hyprctl", "monitors", "-j"], capture_output=True, text=True) @@ -72,16 +63,13 @@ def _get_monitors() -> list[dict]: def _focused_monitor_name() -> Optional[str]: - """Return name of the currently focused monitor.""" - monitors = _get_monitors() - for m in monitors: + for m in _get_monitors(): if m.get("focused"): return m["name"] return None def _monitors_intersecting_region(x: int, y: int, w: int, h: int) -> list[dict]: - """Return monitors whose area intersects the given region.""" region = (x, y, x + w, y + h) intersecting = [] for m in _get_monitors(): @@ -92,12 +80,10 @@ def _monitors_intersecting_region(x: int, y: int, w: int, h: int) -> list[dict]: def _highest_refresh(monitors: list[dict]) -> float: - """Return the maximum refresh rate among the given monitors.""" return max((m["refreshRate"] for m in monitors), default=60.0) def _slurp_region() -> Optional[str]: - """Call slurp and return geometry like 'WxH+X+Y'.""" try: return subprocess.check_output(["slurp", "-f", "%wx%h+%x+%y"], text=True).strip() except subprocess.CalledProcessError: @@ -105,7 +91,6 @@ def _slurp_region() -> Optional[str]: def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]: - """Parse 'WxH+X+Y' into (x,y,w,h).""" import re match = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", geometry) if match: @@ -113,12 +98,9 @@ def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]: return None -# ── core actions ───────────────────────────────────────── def start_recording(region: Optional[str], sound: bool): - """Launch gpu-screen-recorder.""" STATE_DIR.mkdir(parents=True, exist_ok=True) - - cmd = [RECORDER, "-w"] # -w for window/display + cmd = [RECORDER] extra_args = _read_extra_args() if region: @@ -129,43 +111,41 @@ def start_recording(region: Optional[str], sound: bool): raise typer.Abort() else: geometry = region + parsed = _parse_geometry(geometry) - if parsed: - x, y, w, h = parsed - monitors = _monitors_intersecting_region(x, y, w, h) - framerate = _highest_refresh(monitors) - cmd.extend(["-region", geometry, "-f", str(int(framerate))]) - else: + if not parsed: typer.echo("Invalid geometry format.") raise typer.Abort() + x, y, w, h = parsed + + monitors = _monitors_intersecting_region(x, y, w, h) + framerate = _highest_refresh(monitors) + cmd.extend(["-w", "-region", geometry, "-f", str(int(framerate))]) + else: - # Fullscreen: use focused monitor - monitor = _focused_monitor_name() - if monitor: - cmd.extend(["-m", monitor]) - # Refresh rate comes from that monitor - monitors = _get_monitors() - mon = next((m for m in monitors if m["name"] == monitor), None) - if mon: - cmd.extend(["-f", str(int(mon["refreshRate"]))]) + monitor_name = _focused_monitor_name() + if not monitor_name: + typer.echo("No focused monitor found.") + raise typer.Abort() + + monitors = _get_monitors() + mon = next((m for m in monitors if m["name"] == monitor_name), None) + rate = int(mon["refreshRate"]) if mon else 60 + cmd.extend(["-w", monitor_name, "-f", str(rate)]) if sound: - cmd.append("-a") - cmd.append("default_output") + cmd.extend(["-a", "default_output"]) cmd.extend(extra_args) - cmd.append(str(TEMP_RECORDING)) + cmd.extend(["-o", str(TEMP_RECORDING)]) - # Launch detached subprocess.Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - # Notification notif_id = _notify("Recording started", f"Saving to {TEMP_RECORDING}") if notif_id is not None: NOTIF_ID_FILE.write_text(str(notif_id)) - # Early failure check time.sleep(1) if not _is_recording(): _notify("Recording failed", @@ -174,26 +154,22 @@ def start_recording(region: Optional[str], sound: bool): def stop_recording(clipboard: bool): - """Stop the recording and finalise the file.""" - # Kill the process subprocess.run(["pkill", "-f", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - # Wait until it really stops - for _ in range(50): # 5 seconds max + for _ in range(50): if not _is_recording(): break time.sleep(0.1) - # Move the recording dest_dir = Path(RECORDINGS_DIR) dest_dir.mkdir(parents=True, exist_ok=True) timestamp = time.strftime("%Y-%m-%d_%H-%M-%S") final_path = dest_dir / f"recording_{timestamp}.mp4" + if TEMP_RECORDING.exists(): TEMP_RECORDING.rename(final_path) - # Close the start notification if NOTIF_ID_FILE.is_file(): try: _close_notification(int(NOTIF_ID_FILE.read_text().strip())) @@ -201,23 +177,19 @@ def stop_recording(clipboard: bool): pass NOTIF_ID_FILE.unlink() - # Clipboard if clipboard: subprocess.run(["wl-copy", "--type", "text/uri-list", f"file://{final_path}"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - # Final notification (simplified: no actions) _notify("Recording stopped", f"Saved to {final_path}", timeout=5000) def toggle_pause(): - """Send SIGUSR2 to gpu-screen-recorder.""" subprocess.run(["pkill", "-USR2", "-f", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) typer.echo("Toggled pause.") -# ── Typer command ──────────────────────────────────────── @app.command() def record( region: Optional[str] = typer.Option( @@ -231,10 +203,7 @@ def record( clipboard: bool = typer.Option( False, "--clipboard", "-c", help="Copy the final recording path to clipboard."), ): - """ - Start or stop a screen recording with gpu-screen-recorder. - Running again stops the current recording. - """ + """Start or stop a screen recording with gpu-screen-recorder.""" if pause: toggle_pause() raise typer.Exit() diff --git a/cli/src/zshell/utils/__pycache__/schemepalettes.cpython-313.pyc b/cli/src/zshell/utils/__pycache__/schemepalettes.cpython-313.pyc index 4f3c04b2c71eaf549300244b60f0c8436376b824..61bbb4752ce20bfa3ccccc775f31196b894f6cfa 100644 GIT binary patch delta 21 bcmaFE^@fY*GcPX}0}%8q Date: Fri, 22 May 2026 20:42:51 +0200 Subject: [PATCH 04/23] prep for replay --- cli/src/zshell/subcommands/record.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/zshell/subcommands/record.py b/cli/src/zshell/subcommands/record.py index 32b94fd..f568137 100644 --- a/cli/src/zshell/subcommands/record.py +++ b/cli/src/zshell/subcommands/record.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import os import json import subprocess @@ -16,6 +15,7 @@ CONFIG = Path(HOME) / ".config/zshell/config.json" STATE_DIR = Path(HOME) / ".local/state/zshell/record" TEMP_RECORDING = STATE_DIR / "recording.mp4" +REPLAY_RECORDING = STATE_DIR / "replay.mp4" NOTIF_ID_FILE = STATE_DIR / "notifid.txt" RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR", From d0cda51639389dd139403629695a2776d3bc80f5 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Sat, 23 May 2026 20:31:48 +0200 Subject: [PATCH 05/23] wait for instance to fully terminate before restart --- .../__pycache__/scheme.cpython-314.pyc | Bin 30628 -> 30628 bytes .../__pycache__/shell.cpython-314.pyc | Bin 3233 -> 3592 bytes cli/src/zshell/subcommands/shell.py | 8 +++++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc index 010ddf9aca33ee16104f3c05a33b7ea086c845dd..e5535703c5f0e815418b2501a20b9cf701d2044c 100644 GIT binary patch delta 21 bcmZ4To^i>0MlNkWUM>b8SoD4)*Q{~?O>+j| delta 21 bcmZ4To^i>0MlNkWUM>b8$U3!=YgRb`OKJv# diff --git a/cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc index a47f91f92d18495fe005df7c52d64cec7377d725..7f6e76463bf6cf7365accb2eab566883d2689ae9 100644 GIT binary patch delta 1001 zcmZ8fO-vI(6rQ&~-R*9FSOcY$mLjFqEl_@f(SVVtk@$-M^xy%6#G5*^ZI$?v`DW&OZ{Bc7721IxHLL%T1mD25isFr!ny~mHI#D)YKFGQB$5cr4u;*LF3HOw58#YGQ1T{}}ie!=2ZhHtExe;mHxx7Od;zW*4pTm}%y8-YLEfL$(W>`NEi$ zXJt#&*B3h>QPSx}jIxz<<)_FhS9Lc2UTE9&?wjp+aCHVYwc1B@^L0zYGuPtOvVTQb z(@xE}KC0Tf8eUbyi~O1z`{dC!WzR$9o-*6{K@M)ak*|7JL86~zg$9ywE&3DGJ7y4N zVIpKj4ijBk@E>G6gGR;&C|^mwNXrl>cjYFfiD?<=lG!_gI{fNecZ=_4+XD zKK*J7<5LV;DHMO2D9)T??m&usRt9B%v64~Jqb3{KAYxVlCqE|{C#EeQ$D@UDbHc(& za$k)EyQmGDuKYI&TRZ@4AZ-Jw(w(szW^KWqg?c}?vYiy276gzHJl7#2xAA_65 Ak^lez delta 691 zcmYk3&ubGw6vyZ7&i?)pgV82w<7yJuY%8@ws~3q>LGZGMZH`7GrHE}6oLxbXLJ`DX zDy%O;K@h}?Ud5A#B7%Q|P_OpjEkVJnGrOrehtItCo%eX(nc13pU2$hz$0U4h>>gTu zFQ;y~{s9QtBsWL|S4ooZkiaa7>}!xnJ?N---zd5BqKmJD_GrgSUrnalwxZ_oSw+i( zS~@Bz`naH|O_0A`#(}8+Cz@jsH^5dbk>E3!l%Z1=Bk>cQaGVGlByfL-(IKY#no8I1 ztgmG_e=k}e-jBp7-bwJ`Oa*9z6>~o5`4tIv52dRgMDinWbCVrWjYI|dBwqslt?-+ z<(Wq!mAlp$<@pt+aIy)3GDkOLnuseLF@JmFGlz3eV=HlTizTeav%}(W0WWA(iMsew z`&>BA6(*4N)zCAia7!PUb)gF!L*Lj15BH53^P~_38dx<)5TI@SYI{O#a&$9Cxs5<( zr-a@T2smPy!}oJjS$T#Qw(XwmG9BO8(@pV+na|8oHa;7-r!TVG5ALqqOIQ;}cI}Ax qe{sinpJONkz&BDv->F-3KV>2nh7!OWE;&I_e85-|;n2iw=g}W)m4*ZW diff --git a/cli/src/zshell/subcommands/shell.py b/cli/src/zshell/subcommands/shell.py index c6a8587..ae73d0b 100644 --- a/cli/src/zshell/subcommands/shell.py +++ b/cli/src/zshell/subcommands/shell.py @@ -1,4 +1,5 @@ import subprocess +import time import typer args = ["qs", "-c", "zshell"] @@ -8,7 +9,7 @@ app = typer.Typer() @app.command() def kill(): - subprocess.run(args + ["kill"], check=True) + subprocess.run(args + ["kill"], check=False) @app.command() @@ -19,6 +20,11 @@ def start(no_daemon: bool = False): @app.command() def restart(no_daemon: bool = False): subprocess.run(args + ["kill"], check=False) + for _ in range(50): + result = subprocess.run(args + ["kill"], capture_output=True) + if result.returncode == 255: + break + time.sleep(0.05) subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), check=True) From b49165e7eac336d6edfde36bd1ef92fbd1797186 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Sat, 23 May 2026 20:48:51 +0200 Subject: [PATCH 06/23] minor typer adjustments to use typer in error/exception throws --- .../__pycache__/shell.cpython-314.pyc | Bin 3592 -> 6639 bytes cli/src/zshell/subcommands/shell.py | 44 ++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc index 7f6e76463bf6cf7365accb2eab566883d2689ae9..a33767152f267077925eaebafc8768711f2566be 100644 GIT binary patch literal 6639 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 literal 3592 zcmcH*TWl0n^xm1-cONW8icl!ErLaPG5Gx=c2+^uBEbCYcCZ+2#JKe53yR+PxjfD?W z!35%m64SS&*x1l~S*mk{buj<$V9_a<&FnAm*LC`H!qg#`LJ5i+RHe-pY4BOy= zi3s0f4A2-FwD(b5aHh_r0}a~IfD{zPI~)oE85Chpue6%gE1VEK@$)b1F%CPFu37 zXJnJ8@>z34({&l{QH@+KnNOKA--#B^5GEc+ES}A^fX2`-QqzUlx!8E`8HOIg)H`gDT+%Yx?T-)^?zAD1Napi~m9(@(f;cY_fRBS0Fb|DUxbA+;zBES$ne{jf?XvTC73zy4 zRB(mbJ?EjQNVeMw!oafCc<9f)C+yH}M@QEW^6Jk-iL<<@0jMt?TfTPmqJmHWZMv6A z;awV7%LbosDUTv9sV061z5vhbJ|21fkYY`0vS1a7mN1G|p=hN)`jElQA<_!+lXZY% z5g%c_hy)m7I#Lfw=F=Jt5)F>#RU@TQ$;#w3>M?aqD?~&d%M>T5muO~Dw@l{v?#-La zf$f7$9@?F-%L8l_ChpL#l$beb=dH%omv+7T>P37jyy1gQ?{E54n)XhOe%5?Nx*mS^ zqW4B9Tne>)8)};puZKE+Xb9i(H@p{oCwQs%d;hwH0BUOeEr>kLmC98oV?Y20oB}Zf zTlW{KHU3zp7*H`&P^n~&7;m`i0SUo>^CaRnBi(GtP&<6)MnGxsV&mN4cD=ClB+UJdDR%=K1PlwLD&yF2DR`fc5F&O2c3Z z+TcX&6zn`m&kxi*x&~@`cE7M6+nqfP6173nMbVOH+A@jEV}mfI-9Z7hum`qClp$^w zreb$@xyN&Mju_qC2k76$$dzoV@Ax(Ny>fi6<;1nuQYA^_Arld)aN0a8^r+NZe&R)N zU!=KQ!CCE~9_vg&Bb7ql&*oH277El?c?XtXf9%ykIvL`XhvzQ~Vd_6j(#4#Xx5~1I z7z=B6<&6D z1kZ&HZ*4fgaY4ZNu|*LH>$F?{_G7VI9K`DTbHO*ps6|W7GLH&N+7DKIhmz*C|Gk o9g18-iQD&K9C!cMjRS|jiG3Bj+WXys_+1{k{E9npStW@70&SWktpET3 diff --git a/cli/src/zshell/subcommands/shell.py b/cli/src/zshell/subcommands/shell.py index ae73d0b..d66ea57 100644 --- a/cli/src/zshell/subcommands/shell.py +++ b/cli/src/zshell/subcommands/shell.py @@ -1,5 +1,8 @@ import subprocess +import sys import time + +import click import typer args = ["qs", "-c", "zshell"] @@ -9,40 +12,65 @@ app = typer.Typer() @app.command() def kill(): - subprocess.run(args + ["kill"], check=False) + result = subprocess.run(args + ["kill"], capture_output=True) + if result.returncode != 0: + raise click.ClickException("No running instance to kill.") + sys.stderr.write(result.stderr.decode()) @app.command() def start(no_daemon: bool = False): - subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), check=True) + check = subprocess.run(args + ["ipc"] + ["show"], capture_output=True) + if check.returncode == 0: + raise click.ClickException("An instance of this configuration is already running.") + result = subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), capture_output=True) + if result.returncode != 0: + raise click.ClickException(result.stderr.decode().strip()) + sys.stderr.write(result.stderr.decode()) @app.command() def restart(no_daemon: bool = False): - subprocess.run(args + ["kill"], check=False) + subprocess.run(args + ["kill"]) for _ in range(50): result = subprocess.run(args + ["kill"], capture_output=True) if result.returncode == 255: break time.sleep(0.05) - subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), check=True) + result = subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), capture_output=True) + if result.returncode != 0: + raise click.ClickException(result.stderr.decode().strip()) + sys.stderr.write(result.stderr.decode()) @app.command() def show(): - subprocess.run(args + ["ipc"] + ["show"], check=True) + result = subprocess.run(args + ["ipc"] + ["show"], capture_output=True) + if result.returncode != 0: + raise click.ClickException(result.stderr.decode().strip()) + sys.stderr.write(result.stderr.decode()) @app.command() def log(): - subprocess.run(args + ["log"], check=True) + result = subprocess.run(args + ["log"], capture_output=True) + if result.returncode != 0: + raise click.ClickException(result.stderr.decode().strip()) + sys.stdout.write(result.stdout.decode()) + sys.stderr.write(result.stderr.decode()) @app.command() def lock(): - subprocess.run(args + ["ipc"] + ["call"] + ["lock"] + ["lock"], check=True) + result = subprocess.run(args + ["ipc"] + ["call"] + ["lock"] + ["lock"], capture_output=True) + if result.returncode != 0: + raise click.ClickException(result.stderr.decode().strip()) + sys.stderr.write(result.stderr.decode()) @app.command() def call(target: str, method: str, method_args: list[str] = typer.Argument(None)): - subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + (method_args or []), check=True) + result = subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + (method_args or []), capture_output=True) + if result.returncode != 0: + raise click.ClickException(result.stderr.decode().strip()) + sys.stderr.write(result.stderr.decode()) From 5e9b3734059e7bafc9d3125f6178e072f8184c81 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Sat, 23 May 2026 20:54:35 +0200 Subject: [PATCH 07/23] tests did not match changed code logic --- cli/tests/test_shell.py | 82 ++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/cli/tests/test_shell.py b/cli/tests/test_shell.py index feb292c..9875d99 100644 --- a/cli/tests/test_shell.py +++ b/cli/tests/test_shell.py @@ -1,13 +1,15 @@ from __future__ import annotations +from subprocess import CompletedProcess from unittest.mock import patch, call + +from typer.testing import CliRunner from zshell.subcommands.shell import app +runner = CliRunner() -def invoke(*args: str) -> None: - from typer.testing import CliRunner - runner = CliRunner() +def invoke(*args: str): result = runner.invoke(app, args) if result.exit_code != 0: raise RuntimeError(result.output) @@ -16,72 +18,118 @@ def invoke(*args: str) -> None: class TestKill: @patch("zshell.subcommands.shell.subprocess.run") - def test_kill_runs_qs_kill(self, mock_run): + def test_kill_runs_qs_kill_success(self, mock_run): + mock_run.return_value = CompletedProcess([], 0, b"", b"Killed abc\n") invoke("kill") - mock_run.assert_called_once_with(["qs", "-c", "zshell", "kill"], check=True) + mock_run.assert_called_once_with(["qs", "-c", "zshell", "kill"], capture_output=True) + + @patch("zshell.subcommands.shell.subprocess.run") + def test_kill_no_instance_errors(self, mock_run): + mock_run.return_value = CompletedProcess([], 255, b"", b"No running instances\n") + result = runner.invoke(app, ["kill"]) + assert result.exit_code != 0 + assert "No running instance to kill" in result.output class TestStart: @patch("zshell.subcommands.shell.subprocess.run") def test_start_default_daemon(self, mock_run): + mock_run.side_effect = [ + CompletedProcess([], 1, b"", b""), # ipc show → no instance + CompletedProcess([], 0, b"", b"Launching config\n"), # launch ok + ] invoke("start") - mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n", "-d"], check=True) + assert mock_run.call_args_list == [ + call(["qs", "-c", "zshell", "ipc", "show"], capture_output=True), + call(["qs", "-c", "zshell", "-n", "-d"], capture_output=True), + ] @patch("zshell.subcommands.shell.subprocess.run") def test_start_no_daemon(self, mock_run): + mock_run.side_effect = [ + CompletedProcess([], 1, b"", b""), + CompletedProcess([], 0, b"", b"Launching config\n"), + ] invoke("start", "--no-daemon") - mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n"], check=True) + assert mock_run.call_args_list == [ + call(["qs", "-c", "zshell", "ipc", "show"], capture_output=True), + call(["qs", "-c", "zshell", "-n"], capture_output=True), + ] + + @patch("zshell.subcommands.shell.subprocess.run") + def test_start_already_running_errors(self, mock_run): + mock_run.return_value = CompletedProcess([], 0, b"", b"target visibilities\n") + result = runner.invoke(app, ["start"]) + assert result.exit_code != 0 + assert "already running" in result.output class TestShow: @patch("zshell.subcommands.shell.subprocess.run") def test_show_runs_ipc_show(self, mock_run): + mock_run.return_value = CompletedProcess([], 0, b"", b"target visibilities\n") invoke("show") - mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "show"], check=True) + mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "show"], capture_output=True) class TestLog: @patch("zshell.subcommands.shell.subprocess.run") def test_log_runs_qs_log(self, mock_run): + mock_run.return_value = CompletedProcess([], 0, b"log output\n", b"") invoke("log") - mock_run.assert_called_once_with(["qs", "-c", "zshell", "log"], check=True) + mock_run.assert_called_once_with(["qs", "-c", "zshell", "log"], capture_output=True) class TestLock: @patch("zshell.subcommands.shell.subprocess.run") def test_lock_runs_ipc_call_lock(self, mock_run): + mock_run.return_value = CompletedProcess([], 0, b"", b"") invoke("lock") - mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "lock", "lock"], check=True) + mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "lock", "lock"], capture_output=True) class TestCall: @patch("zshell.subcommands.shell.subprocess.run") def test_call_no_args(self, mock_run): + mock_run.return_value = CompletedProcess([], 0, b"", b"") invoke("call", "target", "method") - mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "target", "method"], check=True) + mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "call", "target", "method"], capture_output=True) @patch("zshell.subcommands.shell.subprocess.run") def test_call_with_args(self, mock_run): + mock_run.return_value = CompletedProcess([], 0, b"", b"") invoke("call", "target", "method", "arg1", "arg2") mock_run.assert_called_once_with( ["qs", "-c", "zshell", "ipc", "call", "target", "method", "arg1", "arg2"], - check=True, + capture_output=True, ) class TestRestart: @patch("zshell.subcommands.shell.subprocess.run") - def test_restart_kills_then_starts_daemon(self, mock_run): + def test_restart_kills_then_starts(self, mock_run): + mock_run.side_effect = [ + CompletedProcess([], 0, b"", b"Killed abc\n"), # first kill (no capture) + CompletedProcess([], 255, b"", b""), # poll → no instance + CompletedProcess([], 0, b"", b"Launching config\n"), # launch ok + ] invoke("restart") assert mock_run.call_args_list == [ - call(["qs", "-c", "zshell", "kill"], check=False), - call(["qs", "-c", "zshell", "-n", "-d"], check=True), + call(["qs", "-c", "zshell", "kill"]), # no capture_output + call(["qs", "-c", "zshell", "kill"], capture_output=True), + call(["qs", "-c", "zshell", "-n", "-d"], capture_output=True), ] @patch("zshell.subcommands.shell.subprocess.run") def test_restart_no_daemon(self, mock_run): + mock_run.side_effect = [ + CompletedProcess([], 0, b"", b"Killed abc\n"), + CompletedProcess([], 255, b"", b""), + CompletedProcess([], 0, b"", b"Launching config\n"), + ] invoke("restart", "--no-daemon") assert mock_run.call_args_list == [ - call(["qs", "-c", "zshell", "kill"], check=False), - call(["qs", "-c", "zshell", "-n"], check=True), + call(["qs", "-c", "zshell", "kill"]), + call(["qs", "-c", "zshell", "kill"], capture_output=True), + call(["qs", "-c", "zshell", "-n"], capture_output=True), ] From 78fcf33b3a77003e102401acedf5e40f4ba69f62 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Sun, 24 May 2026 03:09:19 +0200 Subject: [PATCH 08/23] refactor(cli): clean shell start/restart, drop redundant ipc check --- .../__pycache__/scheme.cpython-314.pyc | Bin 30628 -> 30626 bytes .../__pycache__/screenshot.cpython-314.pyc | Bin 1042 -> 1040 bytes .../__pycache__/shell.cpython-314.pyc | Bin 6639 -> 6472 bytes .../__pycache__/wallpaper.cpython-314.pyc | Bin 2438 -> 2436 bytes cli/src/zshell/subcommands/shell.py | 29 ++++++----- cli/tests/test_shell.py | 47 ++++++++---------- 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc index e5535703c5f0e815418b2501a20b9cf701d2044c..06584ac0875cc72ca33cd0db9527c59ae4813dbb 100644 GIT binary patch delta 39 tcmZ4To^jE8Ms96BUM>b8P<9vE$gRi1ZL6P=pPQ0Ms96BUM>b8SoB_SBexz4uf2Xoer~FMc7A1kZsukembhX71CR{K diff --git a/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-314.pyc index ecd3450d69ddad48296ea32af8832e861aa929c1..f1921ea0037d724a68c84ccba54f287a3d7a389b 100644 GIT binary patch delta 37 rcmbQlF@b|yn~#@^0SJ`cg*I}}W8`+!&&bbB)lV!+%-y_=F@^~Mjt~hU delta 39 tcmbQhF^Pj)n~#@^0SNN832fw^$H?ofpOK%Ns-K--nV*}vc?)9<69BdD3eo@o diff --git a/cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc index a33767152f267077925eaebafc8768711f2566be..d6ca5095d3c790b30885eb76e34adb694c63a97e 100644 GIT binary patch delta 1671 zcmZ8h&u<$=6rNeH?akV|o3)+TiR~n=W5-TNoY17W6chrYqExt%Rar$OR8ed-M$X#I zZm8%Xb<3ep1gV*#h98F@kZ2`@it;BQpXu!EhXMy_T;?UAS2p7`4lXuwk9$sVaLaBq^trw8e|kycZSP z3%GJKgq08QgrubHU=>a;q1M^nE-iryX82&a%TWl*+9hewLr^qo*Vf z-N2IH38$sj>HAS?8i!H5ef=K(9y^{&EEE@Wi8(U2NUvJH7J0~Hfq5rJ|L{aYX-k5G zVE$JQY>hUpcr-hgTP$h~8_5|IzP>XxMpLo`><_S8yc@{YMO)v^+?Ww!-(>PH@8-%(d2dw7G*@Ih44wqy)ygG zY+VXfrBF=@*QIz>ir1u}^8TOg(xxM@X8Y3lne%h`nq#Et^449^tFGwU8#UL+hI3@o zF4gV6s@=CbbF+Wp`cPbyeQAwx=c!ZU}?7 ztw`u0z%C{Hl$~_KeTbJj+Qv>|+;bZNoIh+nSPYuqE)XnRYBCiSfK3_A-1lYBP8E7l z8bK9sDvr%>zJyJ-bQT(IQ3DJEH6#j_KA1XVwv2w^1=qQ~H~1j&&=Nnzkucn;Kft1N z#%N2AF{Z%Sc~ginQ-~S}af<#RigGWSu|c3YrUlkF{X314wtxojj}m?~(7h9w!AYoV z_7G^0Cah0GUIn_dD!G;al1IP+y%Rt$`DSeNrY%6PSe+fc=->iEGs;f(3`$#{pg%hN zue2=uGsiyuh>c5=Bm$;XTzFJ}ypdnbHQJyTi$+n)&zo8UAr^AEl1XCRVv^VpvY_Nb zUdz!}oq^|uAsfr6zYf3%l+UD2r_PVB?pcj}IC**eI-hJR?v>@U%QdBUEqq;xlwW9y z^2*5Bk($`OdgPiIpA$#hy(ks1+OXtH z^9^2KC?0Qh7G1Ls4U%4XH<1`H28aq^*1FXOWRw{hjIBGB3tany@E+z-Xv`Bd{|EZJ zR8>6df2zAUoS^L5cZVaA5rEiL9WgG|t$eBI)HFf!%qzf;^bwePMBf6=W@#97byPx;>p{>?$R-LW7} a*2b3T5qsVzI(tKtU1|B+j|{r|BBDpdh3v9j(ACxh+{{ z%*6R&x-8q8!y+0@@Z0ufUmCw@T;dmF#DGJm4Iz9mCVo+t>6Z9K&wU-lpeMP%dwR~f zzkBXE@ARvl-+R?|O;rfWoj*OVZxq!A`c1Lz2z3?562;x1t|n|cNr*a`bI;Kzk3%Ua z=%Q+|+-H+@0!=hR9dL}ct4^Kl$7sdffcTmi55THW*G8kXa<-Qy6lAPA!01lU5Q%6d zhp&Sqep^|K13GBri8kDDaZIsjitMYT$IOQ9GLk(5!4+Q)t jxo;yv^)AZWHF#=HumJ0D`=20vowI7 None: + result = subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), capture_output=True) + stdout = result.stdout.decode().strip() + if stdout: + if "already running" in stdout.lower(): + raise click.ClickException(stdout) + if result.returncode != 0: + stderr = result.stderr.decode().strip() + raise click.ClickException(stderr) + + @app.command() def start(no_daemon: bool = False): - check = subprocess.run(args + ["ipc"] + ["show"], capture_output=True) - if check.returncode == 0: - raise click.ClickException("An instance of this configuration is already running.") - result = subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), capture_output=True) - if result.returncode != 0: - raise click.ClickException(result.stderr.decode().strip()) - sys.stderr.write(result.stderr.decode()) + start_instance(no_daemon) @app.command() def restart(no_daemon: bool = False): - subprocess.run(args + ["kill"]) - for _ in range(50): + subprocess.run(args + ["kill"], capture_output=True) + deadline = time.monotonic() + 2.5 + while time.monotonic() < deadline: result = subprocess.run(args + ["kill"], capture_output=True) if result.returncode == 255: break time.sleep(0.05) - result = subprocess.run(args + ["-n"] + ([] if no_daemon else ["-d"]), capture_output=True) - if result.returncode != 0: - raise click.ClickException(result.stderr.decode().strip()) - sys.stderr.write(result.stderr.decode()) + start_instance(no_daemon=no_daemon) @app.command() diff --git a/cli/tests/test_shell.py b/cli/tests/test_shell.py index 9875d99..1763247 100644 --- a/cli/tests/test_shell.py +++ b/cli/tests/test_shell.py @@ -34,35 +34,30 @@ class TestKill: class TestStart: @patch("zshell.subcommands.shell.subprocess.run") def test_start_default_daemon(self, mock_run): - mock_run.side_effect = [ - CompletedProcess([], 1, b"", b""), # ipc show → no instance - CompletedProcess([], 0, b"", b"Launching config\n"), # launch ok - ] + mock_run.return_value = CompletedProcess([], 0, b"", b"Launching config\n") invoke("start") - assert mock_run.call_args_list == [ - call(["qs", "-c", "zshell", "ipc", "show"], capture_output=True), - call(["qs", "-c", "zshell", "-n", "-d"], capture_output=True), - ] + mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n", "-d"], capture_output=True) @patch("zshell.subcommands.shell.subprocess.run") def test_start_no_daemon(self, mock_run): - mock_run.side_effect = [ - CompletedProcess([], 1, b"", b""), - CompletedProcess([], 0, b"", b"Launching config\n"), - ] + mock_run.return_value = CompletedProcess([], 0, b"", b"Launching config\n") invoke("start", "--no-daemon") - assert mock_run.call_args_list == [ - call(["qs", "-c", "zshell", "ipc", "show"], capture_output=True), - call(["qs", "-c", "zshell", "-n"], capture_output=True), - ] + mock_run.assert_called_once_with(["qs", "-c", "zshell", "-n"], capture_output=True) @patch("zshell.subcommands.shell.subprocess.run") def test_start_already_running_errors(self, mock_run): - mock_run.return_value = CompletedProcess([], 0, b"", b"target visibilities\n") + mock_run.return_value = CompletedProcess([], 0, b"An instance of this configuration is already running.\n", b"") result = runner.invoke(app, ["start"]) assert result.exit_code != 0 assert "already running" in result.output + @patch("zshell.subcommands.shell.subprocess.run") + def test_start_other_failure_errors(self, mock_run): + mock_run.return_value = CompletedProcess([], 1, b"", b"Config error\n") + result = runner.invoke(app, ["start"]) + assert result.exit_code != 0 + assert "Config error" in result.output + class TestShow: @patch("zshell.subcommands.shell.subprocess.run") @@ -106,30 +101,30 @@ class TestCall: class TestRestart: + @patch("zshell.subcommands.shell.start_instance") @patch("zshell.subcommands.shell.subprocess.run") - def test_restart_kills_then_starts(self, mock_run): + def test_restart_kills_then_starts(self, mock_run, mock_start): mock_run.side_effect = [ - CompletedProcess([], 0, b"", b"Killed abc\n"), # first kill (no capture) + CompletedProcess([], 0, b"", b"Killed abc\n"), # first kill (captured) CompletedProcess([], 255, b"", b""), # poll → no instance - CompletedProcess([], 0, b"", b"Launching config\n"), # launch ok ] invoke("restart") assert mock_run.call_args_list == [ - call(["qs", "-c", "zshell", "kill"]), # no capture_output call(["qs", "-c", "zshell", "kill"], capture_output=True), - call(["qs", "-c", "zshell", "-n", "-d"], capture_output=True), + call(["qs", "-c", "zshell", "kill"], capture_output=True), ] + mock_start.assert_called_once_with(no_daemon=False) + @patch("zshell.subcommands.shell.start_instance") @patch("zshell.subcommands.shell.subprocess.run") - def test_restart_no_daemon(self, mock_run): + def test_restart_no_daemon(self, mock_run, mock_start): mock_run.side_effect = [ CompletedProcess([], 0, b"", b"Killed abc\n"), CompletedProcess([], 255, b"", b""), - CompletedProcess([], 0, b"", b"Launching config\n"), ] invoke("restart", "--no-daemon") assert mock_run.call_args_list == [ - call(["qs", "-c", "zshell", "kill"]), call(["qs", "-c", "zshell", "kill"], capture_output=True), - call(["qs", "-c", "zshell", "-n"], capture_output=True), + call(["qs", "-c", "zshell", "kill"], capture_output=True), ] + mock_start.assert_called_once_with(no_daemon=True) From c30128cf95befc11a5deafa77b6c209f0bf56689 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Sun, 24 May 2026 18:03:32 +0200 Subject: [PATCH 09/23] check every 50ms -> 250ms for restart --- cli/src/zshell/subcommands/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/zshell/subcommands/shell.py b/cli/src/zshell/subcommands/shell.py index de8ca3b..4b30da7 100644 --- a/cli/src/zshell/subcommands/shell.py +++ b/cli/src/zshell/subcommands/shell.py @@ -42,7 +42,7 @@ def restart(no_daemon: bool = False): result = subprocess.run(args + ["kill"], capture_output=True) if result.returncode == 255: break - time.sleep(0.05) + time.sleep(0.25) start_instance(no_daemon=no_daemon) From 16e84ca9983cd4dfc81a1625451c2bfc0857aa21 Mon Sep 17 00:00:00 2001 From: zach Date: Sun, 24 May 2026 18:21:37 +0200 Subject: [PATCH 10/23] fixed region selection for recording, plus cache file cleanup --- .../__pycache__/scheme.cpython-313.pyc | Bin 24035 -> 0 bytes .../__pycache__/scheme.cpython-314.pyc | Bin 10447 -> 0 bytes .../__pycache__/screenshot.cpython-313.pyc | Bin 965 -> 0 bytes .../__pycache__/screenshot.cpython-314.pyc | Bin 1060 -> 0 bytes .../__pycache__/shell.cpython-313.pyc | Bin 2181 -> 0 bytes .../__pycache__/shell.cpython-314.pyc | Bin 2622 -> 0 bytes .../__pycache__/wallpaper.cpython-313.pyc | Bin 1983 -> 0 bytes .../__pycache__/wallpaper.cpython-314.pyc | Bin 2376 -> 0 bytes cli/src/zshell/subcommands/record.py | 2 +- .../__pycache__/schemepalettes.cpython-313.pyc | Bin 1388 -> 0 bytes 10 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 cli/src/zshell/subcommands/__pycache__/scheme.cpython-313.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/screenshot.cpython-313.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/screenshot.cpython-314.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/shell.cpython-313.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/wallpaper.cpython-313.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/wallpaper.cpython-314.pyc delete mode 100644 cli/src/zshell/utils/__pycache__/schemepalettes.cpython-313.pyc diff --git a/cli/src/zshell/subcommands/__pycache__/scheme.cpython-313.pyc b/cli/src/zshell/subcommands/__pycache__/scheme.cpython-313.pyc deleted file mode 100644 index 7e9924e359f43ab124a1f7be44fc065360606053..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24035 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 diff --git a/cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc deleted file mode 100644 index 71fe8172881a230ded2b282cf4cf25f1b83c5f2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10447 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 diff --git a/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-314.pyc deleted file mode 100644 index 91589d5d4114af75e7adc499f1421a33f2c827aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1060 zcmd5*F>ezw6t>TImyot86_FT1K@me0(c~CHkgBL7lqHQ$5mO7gx^6GzlDj@%hGsya z9kMb|DPz|BAf(7$5U?OI1R}(WeaR`11x76Sz3=%wKREAvs20uNo&9p-;+) zhc*Y(fdoDD0@?T(3TGOJxQJ|hABWl@u9@+ch0dYU+z9nUy|%LO#3=UL+U-TG`IlJo zTWrF{_fg*Xq7qhVoK|T=P9o>@wG=&cAmwb-;yc9)Qs>Zt9-B5Q;_cXw+Qv(^wq%nf zTepqJ^E2)t+m!Vs1Zf0R=+yB%VFnG_WFT}uVD0({3xm3hHmf5dQoM5%fKM6cf$u*eJug-1z^p4yvc19s;P>DN>5@Cz6ORt3y(*R6)IbNGqYP z0ym`)s}VadPohA`$R*|bXOarTv?9qGmv)TOA3d&mtn5|w-`5{Kl1!Y$$g#~iVILDLIrxo|*o<^BNPjCbwD6bH48wC~Nl_vX#} z-n=*4NhU`Sj4R(IH=Za6Jravx#QjOGK&h z?=L9Hk)=dm6U+j=sZR85wPS3PCdoxJiW!+e8)O3qK4|QieOl;@_%XK8knPAZwy}_H zVz@2j>6HL`pU*&6=OMphWUv{}CCw4|61lNlG9qM}IXZxKyol3de~1l6TII@3wx!Hj zT-#zL+i@%*4~B4{!q-^@au2Nra zeas_P$+jx{Dj#8+>vif{He+0+n>9}Iw8-{RM>ygi>Ls^o>sxuNq+cm}S?X@smdEt1 zx%E7qyXoncQ`Q-^^ls=nIJ4YpHD4<*U4l7VzePoDNg~RTodU9rekWr)#%<%9OygW* zcK7|g(=DRYF>z$P=H4je?W$X|{G@^upL%K|-J}iKQSZ_}CPXFo5~S zncc&48bx1rc@A-b@nEZe@(r%_hmEn?%}z^*=Fj}5p>B_ z36N`0e?{BlTGjSSZh=OFS6C==DtwB+r9>1$9xc_z%m9BA=IV@5mg`SK*)# z__RtVK|)^>L;@D!O;2|_Nm@%X15)-5kpGs8pI4iy>@U}IE%Ld{NE%;GpP=Gb$RpmZ zx=rb*oG*(xSM&8ckNQuOe2X|)cU?zzaJLm?fr(4uv5U00S+#2(SD8oQHp{>Ss(}{b zv4yHz*mUegdIq%Od0>A6X{#9H2k3@r{AP43TaS_v6*M|VGhGp{q0_v%7-T(jq diff --git a/cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc deleted file mode 100644 index f3b6e65c82657ff6bd8e73ad4591495d5ba254ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2622 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 diff --git a/cli/src/zshell/subcommands/record.py b/cli/src/zshell/subcommands/record.py index f568137..986687c 100644 --- a/cli/src/zshell/subcommands/record.py +++ b/cli/src/zshell/subcommands/record.py @@ -120,7 +120,7 @@ def start_recording(region: Optional[str], sound: bool): monitors = _monitors_intersecting_region(x, y, w, h) framerate = _highest_refresh(monitors) - cmd.extend(["-w", "-region", geometry, "-f", str(int(framerate))]) + cmd.extend(["-w", "region", "-region", geometry, "-f", str(int(framerate))]) else: monitor_name = _focused_monitor_name() diff --git a/cli/src/zshell/utils/__pycache__/schemepalettes.cpython-313.pyc b/cli/src/zshell/utils/__pycache__/schemepalettes.cpython-313.pyc deleted file mode 100644 index 61bbb4752ce20bfa3ccccc775f31196b894f6cfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1388 zcmY*Z%}*Og6rcTGfASGiwviLX}(xmq4K?Lrz&~HCZ-z>3Y|h9aq2! zi9^$>Qhg|r53Q8m+JB}ZvQ)vSsVeov&B&2@?abO(;z|2^zu$ZFvGeBbb~BkIVD`hW znf+fx0RH9T+7f-z{ezKHa2G%Tg$=MSU}1`LaYI~}uoQD?Lta;~G6gIl51Ewj z1t;K$m6#WB(o!tVQj4O(7PV4v^ke4tZWVHue3ODe6T65Y$461bgtX=m=Cn;zuY1AX z4&&+z93HuW!-rp+)F)kwkyBs+EI@!oC}8O>uw+YtBHt!v(tybWrVNP1<%X+->-rr)~RX1Nr^TkZr&4a727BaD&jc zvCMa8Q4|CrLBtD#h((8Zbj-FR9&&6%2=<;gh|6=sEZ1cZpcS(k)?D)lIaTuuk8I;` z-*rf29?d>Sc=jbR9p5t}?3hPU)%AU|K|DV)Bd6-t+uj+YcqyV!7<>!< z$qb)-c|J6HvUQ#vIoUjq<(ScyjdNr2c=gp{Gxy!YKjle0em$AWRrkQADt1W&2Ts4? zSQY@jF2CRI8iP~tjA757^qcrvbDsy#cwfZ^g{L437DXry+P%N-9Z-YoAEiRT3#;ASC*-I|53TPTBK@a>GMacOfM7{?k|?8y12Yp zDpm>#=6?X3;*jQ$;gDr;lyeZN*Bg%G1rKY;ajG67A=S2@tishDxK&8g)ZpuJBh1T2 zjT&OT=pp|wY&fAG;yL#A{OO%pC5QaW!n|zpJyIzmI6fWCl}kI@+fU2o$6NMhsa#%r zT-x~x&+w6B9Qg5jcNkYkU+FdOXZTZ=;AM+`V$hKULHHX?zXkW+f{7~?h^gkCH^TUT zqAGmY0Svkl5RFbOM+ITN2|Iwf7JSFul@5%LF4FHc58COei>%Qy+S&X?X1t}hGt+~3 y?sn^-ox69HQVsD8ym_nn_1Vbun?(K{5Ct)Q>;C~S6H77x From 9c36f0de5b27882e73d5c0562246d04acbbef785 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Sun, 24 May 2026 18:28:32 +0200 Subject: [PATCH 11/23] pycache in cache removal --- .../__pycache__/scheme.cpython-313.pyc | Bin 7101 -> 0 bytes .../__pycache__/scheme.cpython-314.pyc | Bin 30626 -> 0 bytes .../__pycache__/screenshot.cpython-313.pyc | Bin 978 -> 0 bytes .../__pycache__/screenshot.cpython-314.pyc | Bin 1040 -> 0 bytes .../__pycache__/shell.cpython-313.pyc | Bin 2184 -> 0 bytes .../__pycache__/shell.cpython-314.pyc | Bin 6472 -> 0 bytes .../__pycache__/wallpaper.cpython-313.pyc | Bin 1920 -> 0 bytes .../__pycache__/wallpaper.cpython-314.pyc | Bin 2436 -> 0 bytes .../__pycache__/schemepalettes.cpython-313.pyc | Bin 1388 -> 0 bytes 9 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 cli/src/zshell/subcommands/__pycache__/scheme.cpython-313.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/screenshot.cpython-313.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/screenshot.cpython-314.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/shell.cpython-313.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/shell.cpython-314.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/wallpaper.cpython-313.pyc delete mode 100644 cli/src/zshell/subcommands/__pycache__/wallpaper.cpython-314.pyc delete mode 100644 cli/src/zshell/utils/__pycache__/schemepalettes.cpython-313.pyc diff --git a/cli/src/zshell/subcommands/__pycache__/scheme.cpython-313.pyc b/cli/src/zshell/subcommands/__pycache__/scheme.cpython-313.pyc deleted file mode 100644 index 71fa4f894106bca816f890854e4b585d2df759bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7101 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 F{U1Y~pv?dP diff --git a/cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/scheme.cpython-314.pyc deleted file mode 100644 index 06584ac0875cc72ca33cd0db9527c59ae4813dbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30626 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 diff --git a/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-313.pyc b/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-313.pyc deleted file mode 100644 index 043fc1df55b2e6a5d932ad16cb9504f6c6f86496..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 978 zcmc&zF>ljA6u$G>PDqoM(u#pl1YN2~1T&!Z3aU3eAcZ`urO$o(e{Lh4DTKJ;8Di8 zAkgay-GK(5piu28_N=DYVOHOvP3u`JcnRJcMgwm3i$@L=PXmj(EsF!S<|-}OjC!4p z(>>-Er@&a3H@)DIe^?0Q2QFG(1nZ~F4EdS3ldy4=3pdcQl~AMbrydpKRc z`)=o&;Prdc2V3K`@(R<(rI28w46AbCf4EIhj|x%>(h@{Q-Q-iLu+QqC9xY@Q+*F{B z2)87NY^3`2NmK{7<XHFDvoZw7jFlSE9~^G~`roBRKOJfNCg=y7C2ad|#yE zFA&{zwCA6vN>@7GanEJDke7?)e}?!A2{VE*zCx>4DElR~G0;DoOT+eg`=T=-GZSgX eF#k4xv2m?wxEvCst<01+%lww9L>W~xKkQEidBfQN diff --git a/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/screenshot.cpython-314.pyc deleted file mode 100644 index f1921ea0037d724a68c84ccba54f287a3d7a389b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1040 zcmd5*KX21O6u-02PDtC75+sJGLRAM8wTYLasuHR|swgm+V4~0=1-Vz3gxInB&Ka5k zg?7jn04ZZfvN7f(FtS9It_uPdwm`lBcTTK8mad%i{`~H}dw1{8t)&-INUYV}8*lDo zguY7Px#A)OKC22Q^xEfD=D(8l@iQ8m&6CZuV53V$Fj9eCja9 zRcJOivEeoAp^B45-m!RmXJ=5rz7mXEzR+ z!6-ORgH`>o5S(z0g*Jq+B*I-2#MT6BqaP&QF;C6+8{NCzJ0G^s%jb^>WDM3=~m>?$|(vrzk;JRRZJ}Szq zqsS3%iY@8DDHvOLq#|W;7^%bLKeoNG?YFS);V6Ya6bipR diff --git a/cli/src/zshell/subcommands/__pycache__/shell.cpython-313.pyc b/cli/src/zshell/subcommands/__pycache__/shell.cpython-313.pyc deleted file mode 100644 index af12c926f11d084ecb69ceb0cd57ba02dc91c7c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2184 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 Uj|WPJW1k~`urdFE%Cg1y7ZxR~pa1{> diff --git a/cli/src/zshell/subcommands/__pycache__/wallpaper.cpython-313.pyc b/cli/src/zshell/subcommands/__pycache__/wallpaper.cpython-313.pyc deleted file mode 100644 index e70c525ab8f2494bed48f13cdeb03decded87504..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1920 zcmb7E%TF6e7@yhMhu=VWC^SF}qKZs0+e$`xv`L$WR{%NS#l6^Sv@Bb*V6T_ib*msn za*_j(9H0_uiF)A(r?yh1{tdmgMOCeds-iv)+(6DB(3!Q@ZVzqJIn4L_zWHABecwzq z91Z}wzPtXJ@i_wEcgA?jUkBA;2*6XI00qtfT%!;}%5yW^G{Pv#WN3z;7O*faV$tRK z8EM*weNi9-MVN=!pM|Lo&vlayfYCaGgV|uJyLsXv?_3eHz67U8uYncbli~1x+w=h? zr1-KZ>!ZxL0i_fRho9dE-hNPsOh#%5H6X7S8>jc(S}oXn`Is)lk#%0pv&v3wk%H? z&KxeT=$d28>+wYu$JZQL%Nw$dHMw?mnYz@9g@S4>*>a-_Bi20}rtaL00sCFLmBAl; zWFz@7xtrJ<-MjSZjr|Yyr+#Q3u5?`7xqgOVM}K87wttzSH!35S4>Am0t-Lo@MNpW4 zRq6tepkZ1i=dMv}PqE}M&Z2E0!Nq(D-&G66lIduy?A~g$Vb^^eFKa^@jMkfIqBKf1 zn&PeboDM<5%Hqyr$$d3E!sTferZj)&4vk-WRO!|O|8 zb~OXE0C2*JZ&>vDCroz2>QB&Iax&(}F6N7tPDj@^)^#Fc-KA1;{AOxgSs>y^?pwvI z6(lsNmTcQl&G+eKPz&{AJtkoGv*Av*b-AvMLJs#*WHXy6TbOnbkU+kut=byabrTO# zK|9kqq+1!hI1hrY+e43sb}~nS{_?F#FtUAR>q>d7;tOrgZ%I3YPa=DRduz`w?T>vi zQi-%}uWYT<1@q5_5BP)mL+Pux4nyB}kA747_WsxRk0Rq+==jwXH_>q*ygjrvw43?4 zGyY>|{9yH{^V(71?eeW-UfP&?FtwT4d;KRqido7rc*1TRYs0j-fC$b#`b`m$62r`4 z_D*PZZ_m25t(8q;&na&#Vv!zV);}UFxK>EDEF#t4yZg9_c*k;#qDem=({amm2;8u> z8v5QT6qibQ{RV!Mx?!EQzogqKf)G9jZO=jIPtfx#82AO8|4r&G^T%RvWA)+cqe2;- zihvU~ER=_XYC!ZztY)P3AI*2ZQHYtXP+c# ltZ>g6LS2DVL{F(n*TsK153=)fpsV`@^W(-j<0x`3^cPiyeX#%l diff --git a/cli/src/zshell/subcommands/__pycache__/wallpaper.cpython-314.pyc b/cli/src/zshell/subcommands/__pycache__/wallpaper.cpython-314.pyc deleted file mode 100644 index 37f69d41183976d4731e942ad4c79a58f6a2222c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2436 zcmbsqOKcNIbY^GmU3(o9oTLc^N&+<0X@XS|)Tl)WN+FKa8skP*RYKB@vrU%xv&^oG zh(u*RRS1=u96<7+NI8ZB5|t{ohaRe)I7SUA=|I(}p98nh(DvFlW3OFXDn#m&_RV|q zX5QQPomYYlegv>*AawQ)LFiY`xFDB_jU^xoXaptjAaY78b};bLpcEwz*~RzBpd9r$ zo~YOH3SJ&mqN=0rLi81yz~ za$I*NQy4DrkAnms1PpEL1yn#uG$Qq1MQ9xMcA!&u5-0F5NJJ&AK4>Yx)1%kEYJ?Z-VX18V};@lqX6YWZaSeL3NIt!#4_q#xp_Fk)IM zCZ`~dlvgh!w&4sxAU&x`BQ+HZ6Xu8DSTvV1Y?~39&oWu36LuJj^CHk)Gyqp=$VEI% z_5nZz^qU-+>s3C+>yswXZY{Ee(J0 z095n%)>MZA8zO$+2#wFKj^Rp7t8t9;+NgtSs~nJewpWD-shXevt&0q+uI31VXv@?G zJk!jqm3KzXQgh_;4(BXr3Z{*v^K?|tTt^PaREGna*=fgDo} zM5RhEC4k3W)MyhI3<2Ub+J-xyaP(;F_D5>qk=ig9xE}a4xK6OI?N35N-gN@fr+yS_ z{|~vwVFSrKntG% zaEgba4w5hz`l|IT?LkR-1l44d^g~fjY!N!-5nLmaxorGMSwGKA9n@#GcMgMho3SoG(|V0`^VY#OubMg-=1nLd-sn=*wG$;JH-qjf zGP$W7L0$7GG7^ss#7qRaF)e75u#fUWU~gwv~5WT|u>0D~cPEo_i z(w9NRhnM{wpaOc@hP1}H?(5z2r;4qhW`{)V~k+3p*sAGGv--_pA{wcPUhvj2_3u{Bwl ziC&6cJ6(L~2f0HuC=71toINe|rkJ<%$c6FaX7De(h4O{A17cp7D`s{=Od80@;M*bz zrN}CLB&E$UF@Y$5@t9{wj3J_17E{U(W@+s65Z*?@aw)#*p-gT(pEeFt{t)nfx8DZ@ zQwQURXzN21_#N%|8SVZFwf&;(EXZqKZD#87)Rjzutb38Lrz&~HCZ-z)Ag=1JFb8e z5{IU})Q3{}&`LSB|4c<>se(~cRqBbJj2tP~&a90kp0vOB`@J_GJ8#}@H!U+W-P6Y=CtE3sann8{)czrIAm~VMzrUCMUoP8S9oh11z1*NG%J!VX;bIQ<-Zjdrb{pDL8yj z!3j8GB^Csnv=mFT)Pkt6MXeMZ{gnB=8~NM?-y|>4#4aMp@lg~pA+0%tIc*cw>t3+8 z!?^k!hlg(9@Znb`^-0%a0)Jrs~Fs<;@3_;--ksczc=s=2mJleS$8cN;$EY1@9;Kz=_nWZTa@91-6O+#s}V zEc4x26a_&@5b?qwV$mTU9kcC-ha4Lbg1u)A;_}=u%XQfUXvM6CHP<{uPSyO%BilH9 z;W{KT59gjCJol2Aj_;WfcFe=5>iWLfAf6wYkyCYRZoPNmD!*E4=DxrGr#y+ruO?Hu>h9ZA#V%>!!09&} z%L2gH<@eiNV{ihVGVIyoeiL75?q|SL-dC|f;R(osB@v2)cJHry3*@CODlhC;Di^#W zd8t=QowZiY_5wmB26+L0$dVDCG=&X@G|AtRgm%>-c!~x1Qj8J2h8Rz6KrgRjSpFfSW5 zYKZlshx~)E;e>vO=h@ryr*~$R9Plp-^RmVFNTrD2_;fT^D(-A=KPiQ|v9T=aSr$1=!x6@PSS)*mNvoq(J@s{4sOb_C@ xo2~tJ?#^XOHN;bp>x=^BqtnTSjwH^CN7}3L=8fhzrz6vE5;O0BD2VY}{}06FORWF^ From ef71ae8afd4a5a30925b26ebe933a0c99ceb8050 Mon Sep 17 00:00:00 2001 From: zach Date: Sun, 24 May 2026 19:28:02 +0200 Subject: [PATCH 12/23] button to install colorscheme and wallpaper to greeter in settings --- Modules/Settings/Categories/Lockscreen.qml | 12 +++ .../Settings/Categories/Lockscreen/Idle.qml | 22 ++++- .../Settings/Controls/SettingsIconButton.qml | 83 +++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 Modules/Settings/Controls/SettingsIconButton.qml diff --git a/Modules/Settings/Categories/Lockscreen.qml b/Modules/Settings/Categories/Lockscreen.qml index 50c93db..d08b8a1 100644 --- a/Modules/Settings/Categories/Lockscreen.qml +++ b/Modules/Settings/Categories/Lockscreen.qml @@ -103,6 +103,18 @@ SettingsPage { } } + SettingsSection { + sectionId: "Greeter" + + SettingsHeader { + name: "Greeter" + } + + SettingsIconButton { + name: "Install wallpaper and color scheme to greeter" + } + } + SettingsSection { sectionId: "Idle" diff --git a/Modules/Settings/Categories/Lockscreen/Idle.qml b/Modules/Settings/Categories/Lockscreen/Idle.qml index c431fa0..7e43721 100644 --- a/Modules/Settings/Categories/Lockscreen/Idle.qml +++ b/Modules/Settings/Categories/Lockscreen/Idle.qml @@ -9,6 +9,8 @@ import qs.Modules.Settings.Controls ColumnLayout { id: root + property bool shouldBeActive: true + function addTimeoutEntry() { let list = [...Config.general.idle.timeouts]; @@ -40,8 +42,26 @@ ColumnLayout { Config.save(); } - Layout.fillWidth: true + anchors.left: parent.left + anchors.right: parent.right + height: shouldBeActive ? implicitHeight : 0 + opacity: shouldBeActive ? 1 : 0 + scale: shouldBeActive ? 1 : 0.8 spacing: Appearance.spacing.smaller + visible: opacity > 0 + + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } + Behavior on y { + Anim { + } + } Settings { name: "Idle Monitors" diff --git a/Modules/Settings/Controls/SettingsIconButton.qml b/Modules/Settings/Controls/SettingsIconButton.qml new file mode 100644 index 0000000..5f6673d --- /dev/null +++ b/Modules/Settings/Controls/SettingsIconButton.qml @@ -0,0 +1,83 @@ +import Quickshell +import QtQuick +import QtQuick.Layouts +import qs.Paths +import qs.Components +import qs.Config +import qs.Helpers + +Item { + id: root + + property alias button: iButton + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name + required property string name + property bool shouldBeActive: true + + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 + opacity: shouldBeActive ? 1 : 0 + scale: shouldBeActive ? 1 : 0.8 + visible: opacity > 0 + + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } + Behavior on y { + Anim { + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + + RowLayout { + id: row + + anchors.left: parent.left + anchors.margins: Appearance.padding.small + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + CustomText { + id: text + + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + font.pointSize: Appearance.font.size.larger + text: root.name + } + + IconButton { + id: iButton + + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + icon: "download" + + onClicked: { + const lockBg = `${Paths.state}/lockscreen_bg.png`; + const scheme = `${Paths.state}/scheme.json`; + const face = `${Paths.home}/.face`; + const destination = "/etc/zshell-greeter/images"; + Quickshell.execDetached(["pkexec", "sh", "-c", `mkdir -p ${destination}; cp ${lockBg} ${destination}; cp ${scheme} /etc/zshell-greeter; cp ${face} ${destination}`]); + } + } + } +} From 5097e30a779cfa0ef20df05ce6b70123e40cb9de Mon Sep 17 00:00:00 2001 From: zach Date: Sun, 24 May 2026 19:33:06 +0200 Subject: [PATCH 13/23] fix anchors used in Layouts --- Modules/Settings/Controls/SettingList.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/Settings/Controls/SettingList.qml b/Modules/Settings/Controls/SettingList.qml index 6351c91..fad061f 100644 --- a/Modules/Settings/Controls/SettingList.qml +++ b/Modules/Settings/Controls/SettingList.qml @@ -194,6 +194,9 @@ Item { } Separator { + Layout.fillWidth: true + anchors.left: undefined + anchors.right: undefined } RowLayout { @@ -225,6 +228,9 @@ Item { } Separator { + Layout.fillWidth: true + anchors.left: undefined + anchors.right: undefined } Item { From f2f9fa13022d2b144a1cd01ef9c59384524bffdf Mon Sep 17 00:00:00 2001 From: zach Date: Mon, 25 May 2026 11:00:34 +0200 Subject: [PATCH 14/23] fix opening links on non-uwsm and disable file watcher when shell is installed --- CMakeLists.txt | 9 ++++++++- Components/CustomText.qml | 1 + Modules/Notifications/Sidebar/Notif.qml | 5 ++++- shell.qml | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e20e14..ce22903 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,13 @@ if("shell" IN_LIST ENABLE_MODULES) foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths) install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}") endforeach() - install(FILES shell.qml DESTINATION "${INSTALL_QSCONFDIR}") + + # Disable watching for changes + file(READ shell.qml SHELL_QML) + string(REPLACE "settings.watchFiles: true" "settings.watchFiles: false" SHELL_QML "${SHELL_QML}") + file(WRITE "${CMAKE_BINARY_DIR}/qml/shell.qml" "${SHELL_QML}") + install(FILES "${CMAKE_BINARY_DIR}/qml/shell.qml" DESTINATION "${INSTALL_QSCONFDIR}") + + # Greeter install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}") endif() diff --git a/Components/CustomText.qml b/Components/CustomText.qml index cff8b2d..f4c1600 100644 --- a/Components/CustomText.qml +++ b/Components/CustomText.qml @@ -15,6 +15,7 @@ Text { color: DynamicColors.palette.m3onSurface font.family: Appearance.font.family.sans font.pointSize: Appearance.font.size.normal + linkColor: DynamicColors.palette.m3onPrimaryFixedVariant renderType: Text.NativeRendering textFormat: Text.PlainText diff --git a/Modules/Notifications/Sidebar/Notif.qml b/Modules/Notifications/Sidebar/Notif.qml index 1d99414..53445e9 100644 --- a/Modules/Notifications/Sidebar/Notif.qml +++ b/Modules/Notifications/Sidebar/Notif.qml @@ -136,7 +136,10 @@ CustomRect { wrapMode: Text.WordWrap onLinkActivated: link => { - Quickshell.execDetached(["app2unit", "-O", "--", link]); + if (Config.launcher.uwsm) + Quickshell.execDetached(["app2unit", "-O", "--", link]); + else + Quickshell.execDetached(["xdg-open", link]); root.visibilities.sidebar = false; } } diff --git a/shell.qml b/shell.qml index 892321c..bc06a34 100644 --- a/shell.qml +++ b/shell.qml @@ -14,6 +14,8 @@ import qs.Helpers import qs.Modules.Polkit ShellRoot { + settings.watchFiles: true + Windows { } From 06ebc4ffbf80b363e1a4f79e87f6f55ddf70d336 Mon Sep 17 00:00:00 2001 From: zach Date: Mon, 25 May 2026 11:51:16 +0200 Subject: [PATCH 15/23] remove irrelevant settings options plus bugfixes --- Helpers/Searcher.qml | 3 +- Modules/Osd/Content.qml | 14 +- Modules/Settings/Categories/Dashboard.qml | 222 ++++++++--------- .../Settings/Categories/Lockscreen/Idle.qml | 2 + Modules/Settings/Categories/Utilities.qml | 194 +++++++-------- .../Settings/Controls/SettingActionList.qml | 11 + .../Settings/Controls/SettingAliasList.qml | 228 +++++++++--------- shell.qml | 2 +- 8 files changed, 352 insertions(+), 324 deletions(-) diff --git a/Helpers/Searcher.qml b/Helpers/Searcher.qml index 5bfdef5..c6f5016 100644 --- a/Helpers/Searcher.qml +++ b/Helpers/Searcher.qml @@ -1,11 +1,12 @@ -import Quickshell import "../scripts/fzf.js" as Fzf import "../scripts/fuzzysort.js" as Fuzzy import QtQuick +import Quickshell Singleton { property var extraOpts: ({}) readonly property list fuzzyPrepped: useFuzzy ? list.map(e => { + console.log(useFuzzy); const obj = { _item: e }; diff --git a/Modules/Osd/Content.qml b/Modules/Osd/Content.qml index 04d5b1b..ef5ce62 100644 --- a/Modules/Osd/Content.qml +++ b/Modules/Osd/Content.qml @@ -100,12 +100,14 @@ Item { icon: `brightness_${(Math.round(value * 6) + 1)}` value: root.brightness - onMoved: { - if (Config.osd.allMonBrightness) { - root.monitor?.setBrightness(value); - } else { - for (const mon of Brightness.monitors) { - mon.setBrightness(value); + onPressedChanged: { + if (!pressed) { + if (Config.osd.allMonBrightness) { + for (const mon of Brightness.monitors) { + mon.setBrightness(value); + } + } else { + root.monitor?.setBrightness(value); } } } diff --git a/Modules/Settings/Categories/Dashboard.qml b/Modules/Settings/Categories/Dashboard.qml index dabb37e..1e9ad84 100644 --- a/Modules/Settings/Categories/Dashboard.qml +++ b/Modules/Settings/Categories/Dashboard.qml @@ -19,8 +19,8 @@ SettingsPage { } SettingSpinBox { - name: "Media update interval" min: 0 + name: "Media update interval" object: Config.dashboard setting: "mediaUpdateInterval" step: 50 @@ -30,8 +30,8 @@ SettingsPage { } SettingSpinBox { - name: "Resource update interval" min: 0 + name: "Resource update interval" object: Config.dashboard setting: "resourceUpdateInterval" step: 50 @@ -41,8 +41,8 @@ SettingsPage { } SettingSpinBox { - name: "Drag threshold" min: 0 + name: "Drag threshold" object: Config.dashboard setting: "dragThreshold" } @@ -107,112 +107,112 @@ SettingsPage { } } - SettingsSection { - sectionId: "Layout Sizes" - - SettingsHeader { - name: "Layout Sizes" - } - - SettingReadOnly { - name: "Tab indicator height" - value: String(Config.dashboard.sizes.tabIndicatorHeight) - } - - Separator { - } - - SettingReadOnly { - name: "Tab indicator spacing" - value: String(Config.dashboard.sizes.tabIndicatorSpacing) - } - - Separator { - } - - SettingReadOnly { - name: "Info width" - value: String(Config.dashboard.sizes.infoWidth) - } - - Separator { - } - - SettingReadOnly { - name: "Info icon size" - value: String(Config.dashboard.sizes.infoIconSize) - } - - Separator { - } - - SettingReadOnly { - name: "Date time width" - value: String(Config.dashboard.sizes.dateTimeWidth) - } - - Separator { - } - - SettingReadOnly { - name: "Media width" - value: String(Config.dashboard.sizes.mediaWidth) - } - - Separator { - } - - SettingReadOnly { - name: "Media progress sweep" - value: String(Config.dashboard.sizes.mediaProgressSweep) - } - - Separator { - } - - SettingReadOnly { - name: "Media progress thickness" - value: String(Config.dashboard.sizes.mediaProgressThickness) - } - - Separator { - } - - SettingReadOnly { - name: "Resource progress thickness" - value: String(Config.dashboard.sizes.resourceProgessThickness) - } - - Separator { - } - - SettingReadOnly { - name: "Weather width" - value: String(Config.dashboard.sizes.weatherWidth) - } - - Separator { - } - - SettingReadOnly { - name: "Media cover art size" - value: String(Config.dashboard.sizes.mediaCoverArtSize) - } - - Separator { - } - - SettingReadOnly { - name: "Media visualiser size" - value: String(Config.dashboard.sizes.mediaVisualiserSize) - } - - Separator { - } - - SettingReadOnly { - name: "Resource size" - value: String(Config.dashboard.sizes.resourceSize) - } - } + // SettingsSection { + // sectionId: "Layout Sizes" + // + // SettingsHeader { + // name: "Layout Sizes" + // } + // + // SettingReadOnly { + // name: "Tab indicator height" + // value: String(Config.dashboard.sizes.tabIndicatorHeight) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Tab indicator spacing" + // value: String(Config.dashboard.sizes.tabIndicatorSpacing) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Info width" + // value: String(Config.dashboard.sizes.infoWidth) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Info icon size" + // value: String(Config.dashboard.sizes.infoIconSize) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Date time width" + // value: String(Config.dashboard.sizes.dateTimeWidth) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Media width" + // value: String(Config.dashboard.sizes.mediaWidth) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Media progress sweep" + // value: String(Config.dashboard.sizes.mediaProgressSweep) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Media progress thickness" + // value: String(Config.dashboard.sizes.mediaProgressThickness) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Resource progress thickness" + // value: String(Config.dashboard.sizes.resourceProgessThickness) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Weather width" + // value: String(Config.dashboard.sizes.weatherWidth) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Media cover art size" + // value: String(Config.dashboard.sizes.mediaCoverArtSize) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Media visualiser size" + // value: String(Config.dashboard.sizes.mediaVisualiserSize) + // } + // + // Separator { + // } + // + // SettingReadOnly { + // name: "Resource size" + // value: String(Config.dashboard.sizes.resourceSize) + // } + // } } diff --git a/Modules/Settings/Categories/Lockscreen/Idle.qml b/Modules/Settings/Categories/Lockscreen/Idle.qml index 7e43721..43ea412 100644 --- a/Modules/Settings/Categories/Lockscreen/Idle.qml +++ b/Modules/Settings/Categories/Lockscreen/Idle.qml @@ -72,6 +72,8 @@ ColumnLayout { SettingList { Layout.fillWidth: true + anchors.left: undefined + anchors.right: undefined onAddActiveActionRequested: { root.updateTimeoutEntry(index, "activeAction", ""); diff --git a/Modules/Settings/Categories/Utilities.qml b/Modules/Settings/Categories/Utilities.qml index 0fb6bcd..a40af60 100644 --- a/Modules/Settings/Categories/Utilities.qml +++ b/Modules/Settings/Categories/Utilities.qml @@ -19,8 +19,8 @@ SettingsPage { } SettingSpinBox { - name: "Max toasts" min: 1 + name: "Max toasts" object: Config.utilities setting: "maxToasts" } @@ -29,8 +29,8 @@ SettingsPage { } SettingSpinBox { - name: "Panel width" min: 1 + name: "Panel width" object: Config.utilities.sizes setting: "width" } @@ -39,8 +39,8 @@ SettingsPage { } SettingSpinBox { - name: "Toast width" min: 1 + name: "Toast width" object: Config.utilities.sizes setting: "toastWidth" } @@ -77,100 +77,100 @@ SettingsPage { setting: "gameModeChanged" } - Separator { - } - - SettingSwitch { - name: "Do not disturb changed" - object: Config.utilities.toasts - setting: "dndChanged" - } - - Separator { - } - - SettingSwitch { - name: "Audio output changed" - object: Config.utilities.toasts - setting: "audioOutputChanged" - } - - Separator { - } - - SettingSwitch { - name: "Audio input changed" - object: Config.utilities.toasts - setting: "audioInputChanged" - } - - Separator { - } - - SettingSwitch { - name: "Caps lock changed" - object: Config.utilities.toasts - setting: "capsLockChanged" - } - - Separator { - } - - SettingSwitch { - name: "Num lock changed" - object: Config.utilities.toasts - setting: "numLockChanged" - } - - Separator { - } - - SettingSwitch { - name: "Keyboard layout changed" - object: Config.utilities.toasts - setting: "kbLayoutChanged" - } - - Separator { - } - - SettingSwitch { - name: "VPN changed" - object: Config.utilities.toasts - setting: "vpnChanged" - } - - Separator { - } - - SettingSwitch { - name: "Now playing" - object: Config.utilities.toasts - setting: "nowPlaying" - } + // Separator { + // } + // + // SettingSwitch { + // name: "Do not disturb changed" + // object: Config.utilities.toasts + // setting: "dndChanged" + // } + // + // Separator { + // } + // + // SettingSwitch { + // name: "Audio output changed" + // object: Config.utilities.toasts + // setting: "audioOutputChanged" + // } + // + // Separator { + // } + // + // SettingSwitch { + // name: "Audio input changed" + // object: Config.utilities.toasts + // setting: "audioInputChanged" + // } + // + // Separator { + // } + // + // SettingSwitch { + // name: "Caps lock changed" + // object: Config.utilities.toasts + // setting: "capsLockChanged" + // } + // + // Separator { + // } + // + // SettingSwitch { + // name: "Num lock changed" + // object: Config.utilities.toasts + // setting: "numLockChanged" + // } + // + // Separator { + // } + // + // SettingSwitch { + // name: "Keyboard layout changed" + // object: Config.utilities.toasts + // setting: "kbLayoutChanged" + // } + // + // Separator { + // } + // + // SettingSwitch { + // name: "VPN changed" + // object: Config.utilities.toasts + // setting: "vpnChanged" + // } + // + // Separator { + // } + // + // SettingSwitch { + // name: "Now playing" + // object: Config.utilities.toasts + // setting: "nowPlaying" + // } } - SettingsSection { - sectionId: "VPN" - - SettingsHeader { - name: "VPN" - } - - SettingSwitch { - name: "Enable VPN integration" - object: Config.utilities.vpn - setting: "enabled" - } - - Separator { - } - - SettingStringList { - name: "Provider" - addLabel: qsTr("Add VPN provider") - object: Config.utilities.vpn - setting: "provider" - } - } + // SettingsSection { + // sectionId: "VPN" + // + // SettingsHeader { + // name: "VPN" + // } + // + // SettingSwitch { + // name: "Enable VPN integration" + // object: Config.utilities.vpn + // setting: "enabled" + // } + // + // Separator { + // } + // + // SettingStringList { + // name: "Provider" + // addLabel: qsTr("Add VPN provider") + // object: Config.utilities.vpn + // setting: "provider" + // } + // } } diff --git a/Modules/Settings/Controls/SettingActionList.qml b/Modules/Settings/Controls/SettingActionList.qml index af32bb1..373e021 100644 --- a/Modules/Settings/Controls/SettingActionList.qml +++ b/Modules/Settings/Controls/SettingActionList.qml @@ -127,6 +127,9 @@ ColumnLayout { } Separator { + Layout.fillWidth: true + anchors.left: undefined + anchors.right: undefined } RowLayout { @@ -207,6 +210,8 @@ ColumnLayout { StringListEditor { Layout.fillWidth: true addLabel: qsTr("Add command argument") + anchors.left: undefined + anchors.right: undefined values: [...(modelData.command ?? [])] onListEdited: function (values) { @@ -215,6 +220,9 @@ ColumnLayout { } Separator { + Layout.fillWidth: true + anchors.left: undefined + anchors.right: undefined } RowLayout { @@ -233,6 +241,9 @@ ColumnLayout { } Separator { + Layout.fillWidth: true + anchors.left: undefined + anchors.right: undefined } RowLayout { diff --git a/Modules/Settings/Controls/SettingAliasList.qml b/Modules/Settings/Controls/SettingAliasList.qml index bfdb215..908dc29 100644 --- a/Modules/Settings/Controls/SettingAliasList.qml +++ b/Modules/Settings/Controls/SettingAliasList.qml @@ -6,7 +6,7 @@ import qs.Components import qs.Config import qs.Helpers -ColumnLayout { +CustomRect { id: root readonly property bool highlighted: SettingsHighlight.highlightedSetting === name @@ -43,10 +43,9 @@ ColumnLayout { anchors.left: parent.left anchors.right: parent.right - height: shouldBeActive ? implicitHeight : 0 + height: shouldBeActive ? layout.implicitHeight : 0 opacity: shouldBeActive ? 1 : 0 scale: shouldBeActive ? 1 : 0.8 - spacing: Appearance.spacing.smaller visible: opacity > 0 Behavior on opacity { @@ -77,115 +76,128 @@ ColumnLayout { } } - CustomText { - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } + ColumnLayout { + id: layout - Repeater { - model: [...root.object[root.setting]] - - Item { - required property int index - required property var modelData - - Layout.fillWidth: true - Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2 - - CustomRect { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: -(Appearance.spacing.smaller / 2) - color: DynamicColors.tPalette.m3outlineVariant - implicitHeight: 1 - visible: index !== 0 - } - - ColumnLayout { - id: layout - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("From") - } - - CustomRect { - Layout.fillWidth: true - Layout.preferredHeight: 33 - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - anchors.rightMargin: Appearance.padding.normal - text: modelData.from ?? "" - - onEditingFinished: root.updateAlias(index, "from", text) - } - } - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "delete" - type: IconButton.Tonal - - onClicked: root.removeAlias(index) - } - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("To") - } - - CustomRect { - Layout.fillWidth: true - Layout.preferredHeight: 33 - color: DynamicColors.tPalette.m3surface - radius: Appearance.rounding.small - - CustomTextField { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - anchors.rightMargin: Appearance.padding.normal - text: modelData.to ?? "" - - onEditingFinished: root.updateAlias(index, "to", text) - } - } - } - } - } - } - - RowLayout { - Layout.fillWidth: true - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "add" - - onClicked: root.addAlias() - } + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: Appearance.spacing.smaller CustomText { Layout.fillWidth: true - text: qsTr("Add alias") + font.pointSize: Appearance.font.size.larger + text: root.name + } + + Repeater { + model: [...root.object[root.setting]] + + Item { + required property int index + required property var modelData + + Layout.fillWidth: true + Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2 + + CustomRect { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: -(Appearance.spacing.smaller / 2) + color: DynamicColors.tPalette.m3outlineVariant + implicitHeight: 1 + visible: index !== 0 + } + + ColumnLayout { + id: layout + + anchors.left: parent.left + anchors.margins: Appearance.padding.small + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: Appearance.spacing.small + + RowLayout { + Layout.fillWidth: true + + CustomText { + Layout.fillWidth: true + text: qsTr("From") + } + + CustomRect { + Layout.preferredHeight: 33 + Layout.preferredWidth: Math.max(Math.min(fromTextField.contentWidth + Appearance.padding.large * 2, 550), 50) + color: DynamicColors.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.full + + CustomTextField { + id: fromTextField + + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550) + text: modelData.from ?? "" + + onEditingFinished: root.updateAlias(index, "from", text) + } + } + + IconButton { + font.pointSize: Appearance.font.size.large + icon: "delete" + type: IconButton.Tonal + + onClicked: root.removeAlias(index) + } + } + + RowLayout { + Layout.fillWidth: true + + CustomText { + Layout.fillWidth: true + text: qsTr("To") + } + + CustomRect { + Layout.preferredHeight: 33 + Layout.preferredWidth: Math.max(Math.min(toTextField.contentWidth + Appearance.padding.large * 2, 550), 50) + color: DynamicColors.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.full + + CustomTextField { + id: toTextField + + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550) + text: modelData.to ?? "" + + onEditingFinished: root.updateAlias(index, "to", text) + } + } + } + } + } + } + + RowLayout { + Layout.fillWidth: true + + IconButton { + font.pointSize: Appearance.font.size.large + icon: "add" + + onClicked: root.addAlias() + } + + CustomText { + Layout.fillWidth: true + text: qsTr("Add alias") + } } } } diff --git a/shell.qml b/shell.qml index bc06a34..90f6efa 100644 --- a/shell.qml +++ b/shell.qml @@ -1,6 +1,6 @@ //@ pragma UseQApplication //@ pragma Env QSG_RENDER_LOOP=threaded -// @ pragma Env QSG_RHI_BACKEND=vulkan +//@ pragma Env QSG_RHI_BACKEND=vulkan //@ pragma Env QSG_NO_VSYNC=1 //@ pragma Env QS_NO_RELOAD_POPUP=1 //@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round From 1c11549811b6d52c3f601c1df85b4539e292b7f5 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Mon, 25 May 2026 17:45:39 +0200 Subject: [PATCH 16/23] initial commit From 17fcf1a02c7f066ce91b97e0f125d02a57294fc2 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Mon, 25 May 2026 18:42:34 +0200 Subject: [PATCH 17/23] pyright error fixes. added autocomplete to some commands --- cli/src/zshell/__init__.py | 3 +- cli/src/zshell/subcommands/scheme.py | 118 +++++++++++++++++++++++---- 2 files changed, 103 insertions(+), 18 deletions(-) diff --git a/cli/src/zshell/__init__.py b/cli/src/zshell/__init__.py index 365e8d8..882f858 100644 --- a/cli/src/zshell/__init__.py +++ b/cli/src/zshell/__init__.py @@ -2,14 +2,13 @@ from __future__ import annotations import typer from zshell.subcommands import shell, scheme, screenshot, wallpaper, record -app = typer.Typer() +app = typer.Typer(name="zshell-cli") app.add_typer(shell.app, name="shell") app.add_typer(scheme.app, name="scheme") app.add_typer(screenshot.app, name="screenshot") app.add_typer(wallpaper.app, name="wallpaper") app.add_typer(record.app, name="record") -# app.add_typer(preset.app, name="preset") def main() -> None: diff --git a/cli/src/zshell/subcommands/scheme.py b/cli/src/zshell/subcommands/scheme.py index ef2a303..4b18eef 100644 --- a/cli/src/zshell/subcommands/scheme.py +++ b/cli/src/zshell/subcommands/scheme.py @@ -15,11 +15,61 @@ from materialyoucolor.score.score import Score from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors from materialyoucolor.hct.hct import Hct from materialyoucolor.utils.color_utils import argb_from_rgb -from materialyoucolor.utils.math_utils import difference_degrees, rotation_direction, sanitize_degrees_double +from materialyoucolor.utils.math_utils import ( + difference_degrees, + rotation_direction, + sanitize_degrees_double, +) app = typer.Typer() +def _complete_scheme_name(incomplete): + schemes = [ + "fruit-salad", + "expressive", + "monochrome", + "rainbow", + "tonal-spot", + "neutral", + "fidelity", + "content", + "vibrant", + ] + return [s for s in schemes if incomplete in s] + + +def _complete_preset(incomplete): + results = [] + for sid, meta in list_schemes().items(): + for v in meta.variants: + preset = f"{sid}:{v.id}" + if incomplete in preset: + results.append((preset, f"{meta.name} - {v.name}")) + return results + + +def _complete_mode(incomplete): + return [m for m in ("dark", "light") if incomplete in m] + + +def _complete_accent(ctx, incomplete): + preset_val = ctx.params.get("preset") + if preset_val: + try: + p_scheme, p_variant = resolve_preset(preset_val) + for v in list_schemes()[p_scheme].variants: + if v.id == p_variant: + return [a for a in v.accents if incomplete in a] + except (ValueError, KeyError): + pass + all_accents = set() + for meta in list_schemes().values(): + for v in meta.variants: + all_accents.update(v.accents) + return [a for a in sorted(all_accents) if incomplete in a] + + @app.command() def list_presets( json_format: bool = typer.Option(False, "--json", help="Output in JSON format"), @@ -30,7 +80,7 @@ def list_presets( for sid, meta in sorted(schemes.items()): variants = {} for v in meta.variants: - entry = {"modes": sorted(v.modes)} + entry: dict[str, Any] = {"modes": sorted(v.modes)} if v.accents: entry["accents"] = sorted(v.accents) entry["default_accent"] = sorted(v.accents)[0] @@ -55,13 +105,29 @@ def list_presets( @app.command() def generate( - image_path: Optional[Path] = typer.Option(None, help="Path to source image. Required for image mode."), - scheme: Optional[str] = typer.Option( - None, help="Color scheme algorithm to use for image mode. Ignored in preset mode." + image_path: Optional[Path] = typer.Option( + None, help="Path to source image. Required for image mode." + ), + scheme: Optional[str] = typer.Option( + None, + help="Color scheme algorithm to use for image mode. Ignored in preset mode.", + autocompletion=_complete_scheme_name, + ), + preset: Optional[str] = typer.Option( + None, + help="Name of a premade scheme in this format: :", + autocompletion=_complete_preset, + ), + mode: Optional[str] = typer.Option( + None, + help="Mode of the preset scheme (dark or light).", + autocompletion=_complete_mode, + ), + accent: Optional[str] = typer.Option( + None, + help="Accent for schemes that support it (e.g. mauve).", + autocompletion=_complete_accent, ), - preset: Optional[str] = typer.Option(None, help="Name of a premade scheme in this format: :"), - mode: Optional[str] = typer.Option(None, help="Mode of the preset scheme (dark or light)."), - accent: Optional[str] = typer.Option(None, help="Accent for schemes that support it (e.g. mauve)."), ): HOME = str(os.getenv("HOME")) @@ -200,11 +266,15 @@ def generate( def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct: diff = difference_degrees(from_hct.hue, to_hct.hue) rotation = min(diff * 0.8, 100) - output_hue = sanitize_degrees_double(from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue)) + output_hue = sanitize_degrees_double( + from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue) + ) tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost))) return Hct.from_hct(output_hue, from_hct.chroma, tone) - def terminal_palette(colors: dict[str, str], mode: str, variant: str) -> dict[str, str]: + def terminal_palette( + colors: dict[str, str], mode: str, variant: str + ) -> dict[str, str]: light = mode.lower() == "light" key_hex = ( @@ -236,7 +306,7 @@ def generate( image = Image.open(image_path) image = image.convert("RGB") - image.thumbnail(size, Image.NEAREST) + image.thumbnail(size, Image.Resampling.NEAREST) thumbnail_file.parent.mkdir(parents=True, exist_ok=True) image.save(thumbnail_path, "JPEG") @@ -268,8 +338,15 @@ def generate( is_dark = "" with Image.open(image_path) as img: - img.thumbnail((1, 1), Image.LANCZOS) - hct = Hct.from_int(argb_from_rgb(*img.getpixel((0, 0)))) + img.thumbnail((1, 1), Image.Resampling.LANCZOS) + px = img.getpixel((0, 0)) + if isinstance(px, (int, float)): + r = g = b = int(px) + elif px is not None: + r, g, b = int(px[0]), int(px[1]), int(px[2]) + else: + r = g = b = 0 + hct = Hct.from_int(argb_from_rgb(r, g, b)) is_dark = "light" if hct.tone > 50 else "dark" return is_dark @@ -431,6 +508,8 @@ def generate( raw = tpl_path.read_text(encoding="utf-8") out_path, body = split_directive_and_body(raw) + if out_path is None: + continue out_path.parent.mkdir(parents=True, exist_ok=True) @@ -484,23 +563,30 @@ def generate( with CONFIG.open() as f: config = json.load(f) - scheme = scheme or config["colors"]["schemeType"] + scheme_type = config["colors"].get("schemeType", "fruit-salad") + scheme = scheme or scheme_type + assert isinstance(scheme, str) config_mode = config["general"]["color"]["mode"] smart = bool(config["general"]["color"].get("smart", False)) scheme_class = get_scheme_class(scheme) + p_variant = "default" if preset: p_scheme, p_variant = resolve_preset(preset) schemes = list_schemes() if accent and p_scheme in schemes: meta = schemes[p_scheme] - var_accents = next((v.accents for v in meta.variants if v.id == p_variant), ()) + var_accents = next( + (v.accents for v in meta.variants if v.id == p_variant), () + ) if accent not in var_accents: available = ", ".join(var_accents) if var_accents else "none" raise typer.BadParameter( f"Accent '{accent}' not available for '{p_scheme}:{p_variant}'. Available accents: {available}" ) - palette_obj = get_palette(p_scheme, p_variant, mode or config_mode, accent=accent) + palette_obj = get_palette( + p_scheme, p_variant, mode or config_mode, accent=accent + ) colors = palette_obj.colors effective_mode = palette_obj.mode name = palette_obj.scheme From 32acfa6b9fcdf5e903e6b488c5936983fdf6e696 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Mon, 25 May 2026 19:03:00 +0200 Subject: [PATCH 18/23] pyright/ruff error fixes. Autoinstall check of autocomplete --- cli/src/zshell/__init__.py | 34 +++++++++++++++++++ cli/src/zshell/subcommands/record.py | 44 +++++++++++-------------- cli/src/zshell/subcommands/scheme.py | 20 +++-------- cli/src/zshell/subcommands/wallpaper.py | 4 +-- 4 files changed, 61 insertions(+), 41 deletions(-) diff --git a/cli/src/zshell/__init__.py b/cli/src/zshell/__init__.py index 882f858..9622faf 100644 --- a/cli/src/zshell/__init__.py +++ b/cli/src/zshell/__init__.py @@ -1,5 +1,10 @@ from __future__ import annotations +import sys +from pathlib import Path + +import click import typer +from typer._completion_shared import install, _get_shell_name from zshell.subcommands import shell, scheme, screenshot, wallpaper, record app = typer.Typer(name="zshell-cli") @@ -11,5 +16,34 @@ app.add_typer(wallpaper.app, name="wallpaper") app.add_typer(record.app, name="record") +def _completion_installed() -> bool: + shell = _get_shell_name() + match shell: + case "zsh": + return (Path.home() / ".zfunc" / "_zshell-cli").exists() + case "bash": + return (Path.home() / ".bash_completions" / "zshell-cli.sh").exists() + case "fish": + return (Path.home() / ".config" / "fish" / "completions" / "zshell-cli.fish").exists() + return False + + +def _auto_install_completion() -> None: + if not sys.stdout.isatty(): + return + if _completion_installed(): + return + shell = _get_shell_name() + if shell is None: + return + try: + _, path = install(prog_name="zshell-cli") + click.secho(f"zshell-cli: Shell completion installed ({shell}: {path})", fg="green") + click.echo("zshell-cli: Restart your shell or source the file to enable tab-completion.") + except Exception: + pass + + def main() -> None: + _auto_install_completion() app() diff --git a/cli/src/zshell/subcommands/record.py b/cli/src/zshell/subcommands/record.py index 986687c..00a9c07 100644 --- a/cli/src/zshell/subcommands/record.py +++ b/cli/src/zshell/subcommands/record.py @@ -18,8 +18,7 @@ TEMP_RECORDING = STATE_DIR / "recording.mp4" REPLAY_RECORDING = STATE_DIR / "replay.mp4" NOTIF_ID_FILE = STATE_DIR / "notifid.txt" -RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR", - str(Path(HOME) / "Videos/Recordings")) +RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR", str(Path(HOME) / "Videos/Recordings")) def _read_extra_args() -> list[str]: @@ -36,7 +35,7 @@ def _is_recording() -> bool: return subprocess.run(["pidof", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0 -def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 5000) -> Optional[int]: +def _notify(summary: str, body: str = "", actions: list | None = None, timeout: int = 5000) -> Optional[int]: args = ["notify-send", summary, body, "-t", str(timeout), "-p"] if actions: for action in actions: @@ -49,14 +48,12 @@ def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 5 def _close_notification(notif_id: int): - subprocess.run(["notify-send", "--close", str(notif_id)], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run(["notify-send", "--close", str(notif_id)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def _get_monitors() -> list[dict]: try: - res = subprocess.run(["hyprctl", "monitors", "-j"], - capture_output=True, text=True) + res = subprocess.run(["hyprctl", "monitors", "-j"], capture_output=True, text=True) return json.loads(res.stdout) except Exception: return [] @@ -92,6 +89,7 @@ def _slurp_region() -> Optional[str]: def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]: import re + match = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", geometry) if match: return int(match.group(3)), int(match.group(4)), int(match.group(1)), int(match.group(2)) @@ -139,8 +137,7 @@ def start_recording(region: Optional[str], sound: bool): cmd.extend(extra_args) cmd.extend(["-o", str(TEMP_RECORDING)]) - subprocess.Popen(cmd, start_new_session=True, - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) notif_id = _notify("Recording started", f"Saving to {TEMP_RECORDING}") if notif_id is not None: @@ -148,14 +145,12 @@ def start_recording(region: Optional[str], sound: bool): time.sleep(1) if not _is_recording(): - _notify("Recording failed", - "Check gpu-screen-recorder output.", timeout=5000) + _notify("Recording failed", "Check gpu-screen-recorder output.", timeout=5000) raise typer.Exit(code=1) def stop_recording(clipboard: bool): - subprocess.run(["pkill", "-f", RECORDER], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run(["pkill", "-f", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) for _ in range(50): if not _is_recording(): @@ -178,30 +173,31 @@ def stop_recording(clipboard: bool): NOTIF_ID_FILE.unlink() if clipboard: - subprocess.run(["wl-copy", "--type", "text/uri-list", f"file://{final_path}"], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run( + ["wl-copy", "--type", "text/uri-list", f"file://{final_path}"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) _notify("Recording stopped", f"Saved to {final_path}", timeout=5000) def toggle_pause(): - subprocess.run(["pkill", "-USR2", "-f", RECORDER], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run(["pkill", "-USR2", "-f", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) typer.echo("Toggled pause.") @app.command() def record( region: Optional[str] = typer.Option( - None, "--region", "-r", + None, + "--region", + "-r", help="Record a region. Use 'slurp' (or omit value) to select interactively, or give 'WxH+X+Y'.", ), - sound: bool = typer.Option( - False, "--sound", "-s", help="Record audio from default output."), - pause: bool = typer.Option( - False, "--pause", "-p", help="Toggle pause/resume."), - clipboard: bool = typer.Option( - False, "--clipboard", "-c", help="Copy the final recording path to clipboard."), + sound: bool = typer.Option(False, "--sound", "-s", help="Record audio from default output."), + pause: bool = typer.Option(False, "--pause", "-p", help="Toggle pause/resume."), + clipboard: bool = typer.Option(False, "--clipboard", "-c", help="Copy the final recording path to clipboard."), ): """Start or stop a screen recording with gpu-screen-recorder.""" if pause: diff --git a/cli/src/zshell/subcommands/scheme.py b/cli/src/zshell/subcommands/scheme.py index 4b18eef..067489d 100644 --- a/cli/src/zshell/subcommands/scheme.py +++ b/cli/src/zshell/subcommands/scheme.py @@ -105,9 +105,7 @@ def list_presets( @app.command() def generate( - image_path: Optional[Path] = typer.Option( - None, help="Path to source image. Required for image mode." - ), + image_path: Optional[Path] = typer.Option(None, help="Path to source image. Required for image mode."), scheme: Optional[str] = typer.Option( None, help="Color scheme algorithm to use for image mode. Ignored in preset mode.", @@ -266,15 +264,11 @@ def generate( def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct: diff = difference_degrees(from_hct.hue, to_hct.hue) rotation = min(diff * 0.8, 100) - output_hue = sanitize_degrees_double( - from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue) - ) + output_hue = sanitize_degrees_double(from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue)) tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost))) return Hct.from_hct(output_hue, from_hct.chroma, tone) - def terminal_palette( - colors: dict[str, str], mode: str, variant: str - ) -> dict[str, str]: + def terminal_palette(colors: dict[str, str], mode: str, variant: str) -> dict[str, str]: light = mode.lower() == "light" key_hex = ( @@ -576,17 +570,13 @@ def generate( schemes = list_schemes() if accent and p_scheme in schemes: meta = schemes[p_scheme] - var_accents = next( - (v.accents for v in meta.variants if v.id == p_variant), () - ) + var_accents = next((v.accents for v in meta.variants if v.id == p_variant), ()) if accent not in var_accents: available = ", ".join(var_accents) if var_accents else "none" raise typer.BadParameter( f"Accent '{accent}' not available for '{p_scheme}:{p_variant}'. Available accents: {available}" ) - palette_obj = get_palette( - p_scheme, p_variant, mode or config_mode, accent=accent - ) + palette_obj = get_palette(p_scheme, p_variant, mode or config_mode, accent=accent) colors = palette_obj.colors effective_mode = palette_obj.mode name = palette_obj.scheme diff --git a/cli/src/zshell/subcommands/wallpaper.py b/cli/src/zshell/subcommands/wallpaper.py index b803b54..58f6c85 100644 --- a/cli/src/zshell/subcommands/wallpaper.py +++ b/cli/src/zshell/subcommands/wallpaper.py @@ -34,9 +34,9 @@ def lockscreen( return if size[0] < 3840 or size[1] < 2160: - img = img.resize((size[0] // 2, size[1] // 2), Image.NEAREST) + img = img.resize((size[0] // 2, size[1] // 2), Image.Resampling.NEAREST) else: - img = img.resize((size[0] // 4, size[1] // 4), Image.NEAREST) + img = img.resize((size[0] // 4, size[1] // 4), Image.Resampling.NEAREST) img = img.filter(ImageFilter.GaussianBlur(blur_amount)) From d0b2a5fc1d0281651053623c860a7215748a3c68 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Mon, 25 May 2026 19:19:55 +0200 Subject: [PATCH 19/23] adding hint if is ran without -- flag --- cli/src/zshell/subcommands/scheme.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/cli/src/zshell/subcommands/scheme.py b/cli/src/zshell/subcommands/scheme.py index 067489d..8cb1feb 100644 --- a/cli/src/zshell/subcommands/scheme.py +++ b/cli/src/zshell/subcommands/scheme.py @@ -2,6 +2,7 @@ import typer import json import shutil import os +import sys import re import subprocess @@ -105,7 +106,9 @@ def list_presets( @app.command() def generate( - image_path: Optional[Path] = typer.Option(None, help="Path to source image. Required for image mode."), + image_path: Optional[Path] = typer.Option( + None, help="Path to source image. Required for image mode." + ), scheme: Optional[str] = typer.Option( None, help="Color scheme algorithm to use for image mode. Ignored in preset mode.", @@ -127,6 +130,11 @@ def generate( autocompletion=_complete_accent, ), ): + if not any([image_path, scheme, preset, mode, accent]): + print( + "Hint: use --preset : or --image-path ", + file=sys.stderr, + ) HOME = str(os.getenv("HOME")) OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json") @@ -264,11 +272,15 @@ def generate( def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct: diff = difference_degrees(from_hct.hue, to_hct.hue) rotation = min(diff * 0.8, 100) - output_hue = sanitize_degrees_double(from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue)) + output_hue = sanitize_degrees_double( + from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue) + ) tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost))) return Hct.from_hct(output_hue, from_hct.chroma, tone) - def terminal_palette(colors: dict[str, str], mode: str, variant: str) -> dict[str, str]: + def terminal_palette( + colors: dict[str, str], mode: str, variant: str + ) -> dict[str, str]: light = mode.lower() == "light" key_hex = ( @@ -570,13 +582,17 @@ def generate( schemes = list_schemes() if accent and p_scheme in schemes: meta = schemes[p_scheme] - var_accents = next((v.accents for v in meta.variants if v.id == p_variant), ()) + var_accents = next( + (v.accents for v in meta.variants if v.id == p_variant), () + ) if accent not in var_accents: available = ", ".join(var_accents) if var_accents else "none" raise typer.BadParameter( f"Accent '{accent}' not available for '{p_scheme}:{p_variant}'. Available accents: {available}" ) - palette_obj = get_palette(p_scheme, p_variant, mode or config_mode, accent=accent) + palette_obj = get_palette( + p_scheme, p_variant, mode or config_mode, accent=accent + ) colors = palette_obj.colors effective_mode = palette_obj.mode name = palette_obj.scheme From d19eead1f53dcddf09c4cb8e23d857f496f10380 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 26 May 2026 09:25:06 +0200 Subject: [PATCH 20/23] autocomplete now optional. Includes hint on first command input --- cli/src/zshell/__init__.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/cli/src/zshell/__init__.py b/cli/src/zshell/__init__.py index 9622faf..8e101e2 100644 --- a/cli/src/zshell/__init__.py +++ b/cli/src/zshell/__init__.py @@ -28,14 +28,14 @@ def _completion_installed() -> bool: return False -def _auto_install_completion() -> None: - if not sys.stdout.isatty(): - return +def _install_completion() -> None: if _completion_installed(): - return + click.echo("zshell-cli: Shell completion already installed.") + raise typer.Exit() shell = _get_shell_name() if shell is None: - return + click.echo("zshell-cli: Unable to detect shell type.", err=True) + raise typer.Exit(code=1) try: _, path = install(prog_name="zshell-cli") click.secho(f"zshell-cli: Shell completion installed ({shell}: {path})", fg="green") @@ -44,6 +44,16 @@ def _auto_install_completion() -> None: pass +@app.callback() +def main_options( + install_autocomplete: bool = typer.Option(False, "--install-autocomplete", help="Install shell completion for tab-completion."), +): + if install_autocomplete: + _install_completion() + raise typer.Exit() + + def main() -> None: - _auto_install_completion() + if sys.stdout.isatty() and not _completion_installed(): + click.echo("zshell-cli: Tip: run with --install-autocomplete for tab completion.", err=True) app() From 233ea3efb94f8e5b291fa41e943d27a6057ef96b Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 26 May 2026 09:30:58 +0200 Subject: [PATCH 21/23] accidental duplicate logic removed --- cli/src/zshell/__init__.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/cli/src/zshell/__init__.py b/cli/src/zshell/__init__.py index 8e101e2..57ba1c6 100644 --- a/cli/src/zshell/__init__.py +++ b/cli/src/zshell/__init__.py @@ -7,7 +7,7 @@ import typer from typer._completion_shared import install, _get_shell_name from zshell.subcommands import shell, scheme, screenshot, wallpaper, record -app = typer.Typer(name="zshell-cli") +app = typer.Typer(name="zshell-cli", add_completion=False) app.add_typer(shell.app, name="shell") app.add_typer(scheme.app, name="scheme") @@ -44,16 +44,10 @@ def _install_completion() -> None: pass -@app.callback() -def main_options( - install_autocomplete: bool = typer.Option(False, "--install-autocomplete", help="Install shell completion for tab-completion."), -): - if install_autocomplete: - _install_completion() - raise typer.Exit() - - def main() -> None: + if "--install-autocomplete" in sys.argv: + _install_completion() + return if sys.stdout.isatty() and not _completion_installed(): click.echo("zshell-cli: Tip: run with --install-autocomplete for tab completion.", err=True) app() From ca19a60e5c67073489d0e2ee7e1b92da27018432 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 26 May 2026 13:03:46 +0200 Subject: [PATCH 22/23] temporary fix for focus being stolen even after release. Change to async loaders --- Drawers/Windows.qml | 2 +- Modules/Launcher/Wrapper.qml | 67 ++++++--------------------------- Modules/Wallpaper/Wallpaper.qml | 2 +- 3 files changed, 14 insertions(+), 57 deletions(-) diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index 37bd73c..ed91fda 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -35,7 +35,7 @@ Variants { property var root: Quickshell.shellDir WlrLayershell.exclusionMode: ExclusionMode.Ignore - WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + // WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None color: "transparent" contentItem.focus: true mask: visibilities.isDrawing ? null : region diff --git a/Modules/Launcher/Wrapper.qml b/Modules/Launcher/Wrapper.qml index a7ababd..1e8b6d6 100644 --- a/Modules/Launcher/Wrapper.qml +++ b/Modules/Launcher/Wrapper.qml @@ -4,6 +4,7 @@ import Quickshell import QtQuick import qs.Components import qs.Config +import qs.Modules.Launcher.Services Item { id: root @@ -19,26 +20,17 @@ Item { max -= panels.popouts.nonAnimHeight; return max; } + property real offsetScale: shouldBeActive ? 0 : 1 required property var panels required property ShellScreen screen - required property PersistentProperties visibilities readonly property bool shouldBeActive: visibilities.launcher - property real offsetScale: shouldBeActive ? 0 : 1 + required property PersistentProperties visibilities - onShouldBeActiveChanged: { - if (shouldBeActive) { - implicitHeight = Qt.binding(() => content.implicitHeight); - timer.stop(); - } else { - implicitHeight = implicitHeight; - } - } - - visible: offsetScale < 1 anchors.bottomMargin: (-implicitHeight - 5) * offsetScale implicitHeight: content.implicitHeight implicitWidth: content.implicitWidth || 400 opacity: 1 - offsetScale + visible: offsetScale < 1 Behavior on offsetScale { Anim { @@ -47,61 +39,26 @@ Item { } } - onMaxHeightChanged: timer.start() - - Connections { - function onEnabledChanged(): void { - timer.start(); - } - - function onMaxShownChanged(): void { - timer.start(); - } - - target: Config.launcher - } - - Connections { - function onValuesChanged(): void { - if (DesktopEntries.applications.values.length < Config.launcher.maxAppsShown) - timer.start(); - } - - target: DesktopEntries.applications - } - - Timer { - id: timer - - interval: Appearance.anim.durations.small - - onRunningChanged: { - if (running && !root.shouldBeActive) { - content.visible = false; - content.active = true; - } else { - root.contentHeight = Math.min(root.maxHeight, content.implicitHeight); - content.active = Qt.binding(() => root.shouldBeActive || root.visible); - content.visible = true; - } - } + Component.onCompleted: Qt.callLater(() => Apps) + onShouldBeActiveChanged: { + if (shouldBeActive) + implicitHeight = Qt.binding(() => content.implicitHeight); + else + implicitHeight = implicitHeight; } Loader { id: content - active: false + active: root.shouldBeActive || root.visible anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top + asynchronous: true sourceComponent: Content { maxHeight: root.maxHeight panels: root.panels visibilities: root.visibilities - - Component.onCompleted: root.contentHeight = implicitHeight } - - Component.onCompleted: timer.start() } } diff --git a/Modules/Wallpaper/Wallpaper.qml b/Modules/Wallpaper/Wallpaper.qml index 9fac581..476fd29 100644 --- a/Modules/Wallpaper/Wallpaper.qml +++ b/Modules/Wallpaper/Wallpaper.qml @@ -6,7 +6,7 @@ import qs.Modules.DesktopIcons Loader { active: Config.background.enabled - asynchronous: true + asynchronous: false sourceComponent: Variants { model: Quickshell.screens From a2505ee875e556c7271d9d777e45c814ac372393 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 26 May 2026 15:57:40 +0200 Subject: [PATCH 23/23] add low battery toast, unload if not laptop battery. --- Config/Config.qml | 4 ++++ Config/General.qml | 13 +++++++++++++ Daemons/Battery.qml | 46 ++++++++++++++++++++++++++++++++++++++++++++ Helpers/Searcher.qml | 1 - shell.qml | 13 +++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Daemons/Battery.qml diff --git a/Config/Config.qml b/Config/Config.qml index 2f6686e..7bb6b0a 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -214,6 +214,10 @@ Singleton { }, idle: { timeouts: general.idle.timeouts + }, + battery: { + popupThresholds: general.battery.popupThresholds, + critPerc: general.battery.critPerc } }; } diff --git a/Config/General.qml b/Config/General.qml index a0a2a2c..7948941 100644 --- a/Config/General.qml +++ b/Config/General.qml @@ -4,6 +4,8 @@ import Quickshell JsonObject { property Apps apps: Apps { } + property Battery battery: Battery { + } property Color color: Color { } property string dateFormat: "ddd d MMM - hh:mm:ss" @@ -19,6 +21,17 @@ JsonObject { property list playback: ["mpv"] property list terminal: ["kitty"] } + component Battery: JsonObject { + property int critPerc: 5 + property list popupThresholds: [ + { + perc: 20, + name: qsTr("Low battery"), + message: qsTr("Battery at %1%").arg(Battery.currentPerc * 100), + icon: "battery_android_frame_2" + }, + ] + } component Color: JsonObject { property int hyprsunsetTemp: 5000 property string mode: "dark" diff --git a/Daemons/Battery.qml b/Daemons/Battery.qml new file mode 100644 index 0000000..ba346bd --- /dev/null +++ b/Daemons/Battery.qml @@ -0,0 +1,46 @@ +import Quickshell +import Quickshell.Services.UPower +import QtQuick +import ZShell +import qs.Config +import qs.Components.Toast + +Scope { + id: root + + readonly property real currentPerc: UPower.displayDevice.percentage + readonly property list popupThresholds: [...Config.general.battery.popupThresholds].sort((a, b) => b.perc - a.perc) + + Connections { + function onOnBatteryChanged(): void { + if (UPower.onBattery) { + if (Config.utilities.toasts.chargingChanged) + Toaster.toast(qsTr("Charger unplugged"), qsTr("Battery is discharging"), "power_off"); + } else { + if (Config.utilities.toasts.chargingChanged) + Toaster.toast(qsTr("Charger plugged in"), qsTr("Battery is charging"), "power"); + for (const level of root.popupThresholds) + level.warned = false; + } + } + + target: UPower + } + + Connections { + function onPercentageChanged(): void { + if (!UPower.onBattery) + return; + + const p = UPower.displayDevice.percentage * 100; + for (const perc of root.popupThresholds) { + if (p <= perc.perc && !perc.warned) { + perc.warned = true; + Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery perc is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning); + } + } + } + + target: UPower.displayDevice + } +} diff --git a/Helpers/Searcher.qml b/Helpers/Searcher.qml index c6f5016..23f15e5 100644 --- a/Helpers/Searcher.qml +++ b/Helpers/Searcher.qml @@ -6,7 +6,6 @@ import Quickshell Singleton { property var extraOpts: ({}) readonly property list fuzzyPrepped: useFuzzy ? list.map(e => { - console.log(useFuzzy); const obj = { _item: e }; diff --git a/shell.qml b/shell.qml index 90f6efa..599b41b 100644 --- a/shell.qml +++ b/shell.qml @@ -6,14 +6,20 @@ //@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round //@ pragma DropExpensiveFonts import Quickshell +import Quickshell.Services.UPower import qs.Modules import qs.Modules.Wallpaper import qs.Modules.Lock import qs.Drawers import qs.Helpers import qs.Modules.Polkit +import qs.Daemons ShellRoot { + id: root + + readonly property bool laptop: UPower.displayDevice.isLaptopBattery + settings.watchFiles: true Windows { @@ -38,4 +44,11 @@ ShellRoot { Polkit { } + + LazyLoader { + activeAsync: root.laptop + + component: Battery { + } + } }