From 0ec426e0f07ed52c28affa9ec866874e0d100385 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 22 May 2026 12:51:06 +0200 Subject: [PATCH] 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