Repository: Jas-SinghFSU/HyprPanel
Pull request #828: Refactored RecordingButton to fetch the latest recording path dynamically.
Author: login: siddharthjain25
Created: 2025-03-12T07:41:37Z | Merged: 2025-03-25T03:52:17Z

Implemented a file picker using Zenity to allow users to choose the save location for their screen recordings after stopping.

Replaced the hardcoded save path with dynamic user input.

Improved the notification system to inform users when recordings are saved or discarded.

Closes #826

Linked issue #826: Screen recording location is hardcoded in the script and changes every update.
**Describe the bug**
A clear and concise description of what the bug is.
in the screen_record.sh script the outputDir is hardcoded to $HOME/Videos/Screencasts". i keep my videos in ~/vids. This is a bit annoying and every update i have to edit this script to change the location.

**To Reproduce**
Steps to reproduce the behavior:
record screen

file saves to "$HOME/Videos/Screencasts"

**Expected behavior**
A clear and concise description of what you expected to happen.
configurable save location.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - Distribution: [e.g. Arch Linux]
 - Window Manager/Desktop Environment: [e.g. Hyprland]

**Additional context**
Add any other context about the problem here.

Linked issue comments: user: login: dotaxis
created_at: 2025-03-15T18:22:04Z
body: https://github.com/Jas-SinghFSU/HyprPanel/pull/828
author_association: CONTRIBUTOR

Diff:
diff --git a/nix/module.nix b/nix/module.nix
index 4c54c460e..478889af1 100644
--- a/nix/module.nix
+++ b/nix/module.nix
@@ -394,6 +394,7 @@ in
       menus.dashboard.powermenu.reboot = mkStrOption "systemctl reboot";
       menus.dashboard.powermenu.shutdown = mkStrOption "systemctl poweroff";
       menus.dashboard.powermenu.sleep = mkStrOption "systemctl suspend";
+      menus.dashboard.recording.path = mkStrOption "$HOME/Videos/Screencasts"
       menus.dashboard.shortcuts.enabled = mkBoolOption true;
       menus.dashboard.shortcuts.left.shortcut1.command = mkStrOption "microsoft-edge-stable";
       menus.dashboard.shortcuts.left.shortcut1.icon = mkStrOption "󰇩";
diff --git a/package-lock.json b/package-lock.json
index fc70eb66d..7d53accfd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,8 +24,13 @@
                 "typescript": "^5.6.2"
             }
         },
+        "../../../../../usr/share/astal/gjs": {
+            "name": "astal",
+            "license": "LGPL-2.1"
+        },
         "../../../../usr/share/astal/gjs": {
             "name": "astal",
+            "extraneous": true,
             "license": "LGPL-2.1"
         },
         "node_modules/@eslint-community/eslint-utils": {
@@ -601,7 +606,7 @@
             }
         },
         "node_modules/astal": {
-            "resolved": "../../../../usr/share/astal/gjs",
+            "resolved": "../../../../../usr/share/astal/gjs",
             "link": true
         },
         "node_modules/available-typed-arrays": {
diff --git a/scripts/screen_record.sh b/scripts/screen_record.sh
index f203689fd..3854db9e6 100755
--- a/scripts/screen_record.sh
+++ b/scripts/screen_record.sh
@@ -1,18 +1,18 @@
 #!/usr/bin/env bash
 # Requires wf-recorder: https://github.com/ammen99/wf-recorder
 
-outputDir="$HOME/Videos/Screencasts"
+# Get the default audio sink
 defaultSink=$(pactl get-default-sink)
 WF_RECORDER_OPTS="--audio=$defaultSink.monitor -c libx264rgb"
+outputFile=""
+outputDir=""
 
+# Function to check if recording is active
 checkRecording() {
-    if pgrep -f "wf-recorder" >/dev/null; then
-        return 0
-    else
-        return 1
-    fi
+    pgrep -f "wf-recorder" >/dev/null
 }
 
+# Function to start screen recording
 startRecording() {
     if checkRecording; then
         echo "A recording is already in progress."
@@ -21,66 +21,113 @@ startRecording() {
 
     target="$2"
 
-    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S)"
-    outputPath="$outputDir/${outputFile}.mp4"
-    mkdir -p "$outputDir"
+    if [ "$target" == "screen" ]; then
+        monitor_name="$3"
+        outputDir="$4"
+    elif [ "$target" == "region" ]; then
+        outputDir="$3"
+    else
+        echo "Usage: $0 start {screen <monitor_name> | region} <output_directory>"
+        exit 1
+    fi
+
+    # Set a default output directory if not provided
+    outputDir="${outputDir:-$HOME/Videos}"
+
+    # Expand ~ to $HOME if present in outputDir
+    outputDir="${outputDir/#\~/$HOME}"
+
+    # Ensure output directory exists
+    if [ ! -d "$outputDir" ]; then
+        echo "Error: Output directory '$outputDir' does not exist."
+        exit 1
+    fi
+
+    # Generate output filename and path
+    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S).mp4"
+    outputPath="$outputDir/$outputFile"
+
+    echo "Target: $target"
+    echo "Monitor: ${monitor_name:-N/A}"
+    echo "Output dir: $outputDir"
+    echo "Output file: $outputPath"
 
+    # Start screen recording
     if [ "$target" == "screen" ]; then
-        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$3\")")
-        w=$(echo $monitor_info | jq -r '.width')
-        h=$(echo $monitor_info | jq -r '.height')
-        scale=$(echo $monitor_info | jq -r '.scale')
-        scaled_width=$(awk "BEGIN {print $w / $scale}")
-        scaled_height=$(awk "BEGIN {print $h / $scale}")
-        x=$(echo $monitor_info | jq -r '.x')
-        y=$(echo $monitor_info | jq -r '.y')
+        if [ -z "$monitor_name" ]; then
+            echo "Error: Monitor name is required for screen recording."
+            exit 1
+        fi
+
+        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$monitor_name\")")
+        if [ -z "$monitor_info" ]; then
+            echo "Error: Monitor '$monitor_name' not found."
+            exit 1
+        fi
+
+        w=$(echo "$monitor_info" | jq -r '.width')
+        h=$(echo "$monitor_info" | jq -r '.height')
+        scale=$(echo "$monitor_info" | jq -r '.scale')
+        scaled_width=$(awk "BEGIN { print $w / $scale }")
+        scaled_height=$(awk "BEGIN { print $h / $scale }")
+        x=$(echo "$monitor_info" | jq -r '.x')
+        y=$(echo "$monitor_info" | jq -r '.y')
+
         wf-recorder $WF_RECORDER_OPTS --geometry "${x},${y} ${scaled_width}x${scaled_height}" --file "$outputPath" &
     elif [ "$target" == "region" ]; then
         wf-recorder $WF_RECORDER_OPTS --geometry "$(slurp)" --file "$outputPath" &
-    else
-        echo "Usage: $0 start {region|screen [screen_name]}"
-        exit 1
     fi
-    disown "$(jobs -p | tail -n 1)"
 
-    echo "Recording started. Output will be saved to $outputPath"
+    disown "$(jobs -p | tail -n 1)"
+    echo "Recording started. Saving to $outputPath"
+    echo "$outputPath" > /tmp/last_recording_path
 }
 
+# Function to stop screen recording
 stopRecording() {
     if ! checkRecording; then
-        echo "No recording is in progress."
+        echo "No recording in progress."
         exit 1
     fi
 
     pkill -SIGINT -f wf-recorder
+    sleep 1  # Allow wf-recorder time to terminate before proceeding
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    outputPath=$(cat /tmp/last_recording_path 2>/dev/null)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
+    if [ -z "$outputPath" ] || [ ! -f "$outputPath" ]; then
+        notify-send "Recording stopped" "No recent recording found." \
+            -i video-x-generic \
+            -a "Screen Recorder" \
+            -t 10000
+        exit 1
+    fi
+
+    notify-send "Recording stopped" "Saved to: $outputPath" \
         -i video-x-generic \
         -a "Screen Recorder" \
         -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+        --action="scriptAction:-xdg-open $(dirname "$outputPath")=Open Directory" \
+        --action="scriptAction:-xdg-open $outputPath=Play"
 }
 
+# Handle script arguments
 case "$1" in
-start)
-    startRecording "$@"
-    ;;
-stop)
-    stopRecording
-    ;;
-status)
-    if checkRecording; then
-        echo "recording"
-    else
-        echo "not recording"
-    fi
-    ;;
-*)
-    echo "Usage: $0 {start [screen screen_name|region]|stop|status}"
-    exit 1
-    ;;
-esac
+    start)
+        startRecording "$@"
+        ;;
+    stop)
+        stopRecording
+        ;;
+    status)
+        if checkRecording; then
+            echo "recording"
+        else
+            echo "not recording"
+        fi
+        ;;
+    *)
+        echo "Usage: $0 {start [screen <monitor_name> | region] <output_directory> | stop | status}"
+        exit 1
+        ;;
+esac
\ No newline at end of file
diff --git a/src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx b/src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
index 1f054ddfd..b48cc38b9 100644
--- a/src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
+++ b/src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
@@ -1,8 +1,8 @@
-import { bind, execAsync, Variable } from 'astal';
+import { bind, Variable } from 'astal';
 import { App, Gdk, Gtk } from 'astal/gtk3';
 import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
-import { isRecording } from '../helpers';
+import { isRecording, getRecordingPath, executeCommand } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
 
 const hyprlandService = AstalHyprland.get_default();
@@ -10,44 +10,41 @@ const hyprlandService = AstalHyprland.get_default();
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
-        monitorList.set(hyprlandService.get_monitors()),
-    );
+    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () => {
+        monitorList.set(hyprlandService.get_monitors());
+    });
 
     return (
         <Menu className={'dropdown recording'} halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
-            {bind(monitorList).as((monitors) => {
-                return monitors.map((monitor) => (
-                    <MenuItem
-                        label={`Display ${monitor.name}`}
-                        onButtonPressEvent={(_, event) => {
-                            const buttonClicked = event.get_button()[1];
+            {bind(monitorList).as((monitors) =>
+                monitors.map((monitor) => {
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                            if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                                return;
-                            }
+                    return (
+                        <MenuItem
+                            label={`Display ${monitor.name}`}
+                            onButtonPressEvent={(_, event) => {
+                                if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
-                            App.get_window('dashboardmenu')?.set_visible(false);
+                                App.get_window('dashboardmenu')?.set_visible(false);
 
-                            execAsync(`${SRC_DIR}/scripts/screen_record.sh start screen ${monitor.name}`).catch((err) =>
-                                console.error(err),
-                            );
-                        }}
-                    />
-                ));
-            })}
+                                const command = `${SRC_DIR}/scripts/screen_record.sh start screen "${monitor.name}" "${sanitizedPath}"`;
+                                executeCommand(command);
+                            }}
+                        />
+                    );
+                }),
+            )}
             <MenuItem
                 label="Region"
                 onButtonPressEvent={(_, event) => {
-                    const buttonClicked = event.get_button()[1];
-
-                    if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                        return;
-                    }
+                    if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
                     App.get_window('dashboardmenu')?.set_visible(false);
 
-                    execAsync(`${SRC_DIR}/scripts/screen_record.sh start region`).catch((err) => console.error(err));
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
+                    const command = `${SRC_DIR}/scripts/screen_record.sh start region "${sanitizedPath}"`;
+                    executeCommand(command);
                 }}
             />
         </Menu>
@@ -58,7 +55,7 @@ export const RecordingButton = (): JSX.Element => {
     return (
         <button
             className={`dashboard-button record ${isRecording.get() ? 'active' : ''}`}
-            tooltipText={'Record Screen'}
+            tooltipText="Record Screen"
             vexpand
             onButtonPressEvent={(_, event) => {
                 const buttonClicked = event.get_button()[1];
@@ -67,9 +64,12 @@ export const RecordingButton = (): JSX.Element => {
                     return;
                 }
 
+                const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
+
                 if (isRecording.get() === true) {
                     App.get_window('dashboardmenu')?.set_visible(false);
-                    return execAsync(`${SRC_DIR}/scripts/screen_record.sh stop`).catch((err) => console.error(err));
+                    const command = `${SRC_DIR}/scripts/screen_record.sh stop "${sanitizedPath}"`;
+                    executeCommand(command);
                 } else {
                     const monitorDropdownList = MonitorListDropdown() as Gtk.Menu;
                     monitorDropdownList.popup_at_pointer(event);
diff --git a/src/components/menus/dashboard/shortcuts/helpers.ts b/src/components/menus/dashboard/shortcuts/helpers.ts
index 01e8e657f..d26f71342 100644
--- a/src/components/menus/dashboard/shortcuts/helpers.ts
+++ b/src/components/menus/dashboard/shortcuts/helpers.ts
@@ -6,6 +6,27 @@ import options from 'src/options';
 
 const { left } = options.menus.dashboard.shortcuts;
 
+/**
+ * Retrieves the latest recording path from options.
+ *
+ * @returns The configured recording path.
+ */
+export const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+/**
+ * Executes a shell command asynchronously with proper error handling.
+ *
+ * @param command The command to execute.
+ */
+export const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
 /**
  * Handles the recorder status based on the command output.
  *
@@ -16,10 +37,7 @@ const { left } = options.menus.dashboard.shortcuts;
  * @returns True if the command output is 'recording', false otherwise.
  */
 export const handleRecorder = (commandOutput: string): boolean => {
-    if (commandOutput === 'recording') {
-        return true;
-    }
-    return false;
+    return commandOutput === 'recording';
 };
 
 /**
@@ -35,9 +53,7 @@ export const handleClick = (action: string, tOut: number = 0): void => {
 
     timeout(tOut, () => {
         execAsync(`bash -c "${action}"`)
-            .then((res) => {
-                return res;
-            })
+            .then((res) => res)
             .catch((err) => console.error(err));
     });
 };
@@ -45,10 +61,7 @@ export const handleClick = (action: string, tOut: number = 0): void => {
 /**
  * Checks if a shortcut has a command.
  *
- * This function determines if the provided shortcut has a command defined.
- *
  * @param shortCut The shortcut to check.
- *
  * @returns True if the shortcut has a command, false otherwise.
  */
 export const hasCommand = (shortCut: ShortcutVariable): boolean => {
@@ -58,7 +71,7 @@ export const hasCommand = (shortCut: ShortcutVariable): boolean => {
 /**
  * A variable indicating whether the left card is hidden.
  *
- * This variable is set to true if none of the left shortcuts have commands defined.
+ * This is set to true if none of the left shortcuts have commands.
  */
 export const leftCardHidden = Variable(
     !(
@@ -82,7 +95,8 @@ export const isRecording = Variable(false);
 /**
  * A poller for checking the recording status.
  *
- * This poller periodically checks the recording status by executing a bash command and updates the `isRecording` variable.
+ * This poller periodically checks the recording status by executing a bash command
+ * and updates the `isRecording` variable accordingly.
  */
 export const recordingPoller = new BashPoller<boolean, []>(
     isRecording,
diff --git a/src/components/settings/pages/config/menus/dashboard.tsx b/src/components/settings/pages/config/menus/dashboard.tsx
index 65797ec2c..39ef36462 100644
--- a/src/components/settings/pages/config/menus/dashboard.tsx
+++ b/src/components/settings/pages/config/menus/dashboard.tsx
@@ -48,6 +48,9 @@ export const DashboardMenuSettings = (): JSX.Element => {
                 <Option opt={options.menus.dashboard.powermenu.logout} title="Logout Command" type="string" />
                 <Option opt={options.menus.dashboard.powermenu.sleep} title="Sleep Command" type="string" />
 
+                <Header title="Recording" />
+                <Option opt={options.menus.dashboard.recording.path} title="Recording Path" type="string" />
+
                 <Header title="Controls" />
                 <Option opt={options.menus.dashboard.controls.enabled} title="Enabled" type="boolean" />
 
diff --git a/src/options.ts b/src/options.ts
index 4dbe25856..0112ec164 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -1280,6 +1280,9 @@ const options = mkOptions({
                 interval: opt(2000),
                 enable_gpu: opt(false),
             },
+            recording: {
+                path: opt('$HOME/Videos/Screencasts'),
+            },
             controls: {
                 enabled: opt(true),
             },


Reviews:
user: login: davfsa
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T09:37:54Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:06:46Z
user: login: davfsa
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T12:25:28Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:08:43Z
user: login: davfsa
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T21:03:58Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T20:11:04Z
user: login: davfsa
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-15T15:28:53Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T15:38:12Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T15:39:33Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T15:41:29Z
user: login: dotaxis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:08:07Z
user: login: dotaxis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:09:25Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:47:32Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T18:47:52Z
user: login: dotaxis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T19:33:53Z
user: login: dotaxis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T19:33:55Z
user: login: dotaxis
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T20:31:15Z
user: login: davfsa
body: +1 from me. Thanks for making the PR!

I haven't really reviewed the TS part, ill leave that up to @Jas-SinghFSU  :)
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-15T21:14:47Z
user: login: davfsa
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-15T21:16:24Z
user: login: davfsa
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-16T09:39:05Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T07:56:08Z
user: login: siddharthjain25
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T07:58:10Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T08:29:22Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T08:30:50Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T08:37:25Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T08:38:46Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T08:40:20Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T08:41:24Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T08:42:18Z
user: login: Jas-SinghFSU
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-23T08:42:52Z

Review comments:
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
-        -i video-x-generic \
-        -a "Screen Recorder" \
-        -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+    # Let the user pick a directory using Zenity
+    saveDir=$(zenity --file-selection --directory --title="Select Directory to Save Recording")
path: scripts/screen_record.sh
user: login: davfsa
body: Instead of forcing zenity onto the user, we can just use whatever files application they want by pulling it from `xdg-desktop-portal-termfilechooser`

Tbh, I do not know quite well how this works just yet, so will be worth some investigation.
created_at: 2025-03-12T09:31:38Z
author_association: COLLABORATOR
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
-        -i video-x-generic \
-        -a "Screen Recorder" \
-        -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+    # Let the user pick a directory using Zenity
+    saveDir=$(zenity --file-selection --directory --title="Select Directory to Save Recording")
+    
+    if [ -n "$saveDir" ]; then
+        mv "$recentFile" "$saveDir/"
+        notify-send "Recording saved" "File saved to $saveDir."
path: scripts/screen_record.sh
user: login: davfsa
body: Looking at the code this replaces, its missing the action button to open the file location where it's saved, which I think is a nice addition to keep
created_at: 2025-03-12T09:32:17Z
author_association: COLLABORATOR
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
-        -i video-x-generic \
-        -a "Screen Recorder" \
-        -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+    # Let the user pick a directory using Zenity
+    saveDir=$(zenity --file-selection --directory --title="Select Directory to Save Recording")
+    
+    if [ -n "$saveDir" ]; then
+        mv "$recentFile" "$saveDir/"
+        notify-send "Recording saved" "File saved to $saveDir."
+    else
+        notify-send "Recording discarded" "File was not saved."
path: scripts/screen_record.sh
user: login: davfsa
body: Most tools do not notify when the file was not saved, so I think we should probably do the same 
created_at: 2025-03-12T09:32:58Z
author_association: COLLABORATOR
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
path: scripts/screen_record.sh
user: login: davfsa
body: Let's make the search a bit unique, as we don't want to just pickup any .mp4 file there is.

We could just always use the same filename and prevent two recordings from happening at the same time
created_at: 2025-03-12T09:37:45Z
author_association: COLLABORATOR
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
-        -i video-x-generic \
-        -a "Screen Recorder" \
-        -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+    # Let the user pick a directory using Zenity
+    saveDir=$(zenity --file-selection --directory --title="Select Directory to Save Recording")
path: scripts/screen_record.sh
user: login: siddharthjain25
body: I initially tried using xdg-desktop-portal since it’s supposed to work natively with Hyprland and other Wayland environments, but I run into a few issues(plus I tried it on few others as well):

Inconsistent behavior — Even with the right packages (xdg-desktop-portal, xdg-desktop-portal-hyprland), the file picker doesn’t always show up. It defaults to GTK in some cases but still doesn’t trigger the directory selection.

Activation errors — Sometimes we get this error:

"The name is not activatable"
which seems tied to how the portal services communicate, but even after checking the configs, it’s hit-or-miss.

User setup dependency — It relies a lot on the user’s portal setup, so if something’s off in their environment, the picker just won’t work — which feels like too much hassle for something that should be simple.

Because of all this, I think using Zenity might be a better option. It’s lightweight, works without extra setup, and gives a clean GUI for picking directories. Basically, it "just works" without users needing to debug their system.
created_at: 2025-03-12T12:06:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
-        -i video-x-generic \
-        -a "Screen Recorder" \
-        -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+    # Let the user pick a directory using Zenity
+    saveDir=$(zenity --file-selection --directory --title="Select Directory to Save Recording")
path: scripts/screen_record.sh
user: login: davfsa
body: As nice as Zenity might be, its still better to try to get `xdg-desktop-portal` to work as, even if it requires extra setup on peoples part if they didnt set it up correctly (which if people dont have setup by default in their distro, its something they are 99% willing to do, or is already setup by default in all major distributions), we will 100% get people asking for the feature.

I will try to work with you to get this done, dont want to put too much burden on you for it.

If you already toyed around with the idea a bit, I assume that you probably better  at this than me, hahaha. If you can push (or explain) a basic setup for it, I can give it a shot on my system and provide a working patch :)

---

> Inconsistent behavior — Even with the right packages (xdg-desktop-portal, xdg-desktop-portal-hyprland), the file picker doesn’t always show up. It defaults to GTK in some cases but still doesn’t trigger the directory selection.

Also note, that for a files chooser, you also need `xdg-desktop-portal-termfilechooser` in my experience. It provides the best reliability
created_at: 2025-03-12T12:25:28Z
author_association: COLLABORATOR
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
-        -i video-x-generic \
-        -a "Screen Recorder" \
-        -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+    # Let the user pick a directory using Zenity
+    saveDir=$(zenity --file-selection --directory --title="Select Directory to Save Recording")
path: scripts/screen_record.sh
user: login: siddharthjain25
body: Thanks for the suggestion! You're absolutely right -- using xdg-desktop-portal makes more sense for broader compatibility across distros. I’ll start looking into integrating it for the file picker and notifications. If I run into any issues, I'll definitely reach out for your input. Appreciate your support!
created_at: 2025-03-12T13:08:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
-        -i video-x-generic \
-        -a "Screen Recorder" \
-        -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+    # Let the user pick a directory using Zenity
+    saveDir=$(zenity --file-selection --directory --title="Select Directory to Save Recording")
path: scripts/screen_record.sh
user: login: davfsa
body: Additionally, as pointed out by @dotaxis, providing a configuration value for default setting the default save location might be a better solution :)
created_at: 2025-03-12T21:03:58Z
author_association: COLLABORATOR
diff_hunk: @@ -54,15 +49,17 @@ stopRecording() {
 
     pkill -SIGINT -f wf-recorder
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    recentFile=$(ls -t "$tempDir"/*.mp4 | head -n 1)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
-        -i video-x-generic \
-        -a "Screen Recorder" \
-        -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+    # Let the user pick a directory using Zenity
+    saveDir=$(zenity --file-selection --directory --title="Select Directory to Save Recording")
path: scripts/screen_record.sh
user: login: siddharthjain25
body: > Additionally, as pointed out by @dotaxis, providing a configuration value for default setting the default save location might be a better solution :)

Done as requested. 
created_at: 2025-03-14T20:11:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,49 +17,84 @@ startRecording() {
     fi
 
     target="$2"
+    outputDir=$(eval echo "$3")
path: scripts/screen_record.sh
user: login: davfsa
body: Why eval here rather than just


```suggestion
    outputDir="$3"
```

?
created_at: 2025-03-15T10:42:17Z
author_association: COLLABORATOR
diff_hunk: @@ -20,49 +17,84 @@ startRecording() {
     fi
 
     target="$2"
+    outputDir=$(eval echo "$3")
+    monitor_name="$4"
+
+    if [ -z "$outputDir" ]; then
+        echo "Error: Output directory is empty."
+        exit 1
+    fi
+
+    if [ ! -d "$outputDir" ]; then
+        echo "Error: Output directory '$outputDir' does not exist."
+        exit 1
+    fi
+
+    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S).mp4"
+    outputPath="$outputDir/$outputFile"
 
-    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S)"
-    outputPath="$outputDir/${outputFile}.mp4"
-    mkdir -p "$outputDir"
+    echo "Target: $target"
+    echo "Output dir: $outputDir"
+    echo "Output file: $outputPath"
 
     if [ "$target" == "screen" ]; then
-        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$3\")")
-        w=$(echo $monitor_info | jq -r '.width')
-        h=$(echo $monitor_info | jq -r '.height')
-        scale=$(echo $monitor_info | jq -r '.scale')
-        scaled_width=$(echo "${w} / ${scale}" | bc)
-        scaled_height=$(echo "${h} / ${scale}" | bc)
-        x=$(echo $monitor_info | jq -r '.x')
-        y=$(echo $monitor_info | jq -r '.y')
+        if [ -z "$monitor_name" ]; then
+            echo "Error: Monitor name is required for screen recording."
+            exit 1
+        fi
+
+        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$monitor_name\")")
+        if [ -z "$monitor_info" ]; then
+            echo "Error: Monitor '$monitor_name' not found."
+            exit 1
+        fi
+
+        w=$(echo "$monitor_info" | jq -r '.width')
+        h=$(echo "$monitor_info" | jq -r '.height')
+        scale=$(echo "$monitor_info" | jq -r '.scale')
+        scaled_width=$(awk "BEGIN { print $w / $scale }")
+        scaled_height=$(awk "BEGIN { print $h / $scale }")
+        x=$(echo "$monitor_info" | jq -r '.x')
+        y=$(echo "$monitor_info" | jq -r '.y')
+
         wf-recorder $WF_RECORDER_OPTS --geometry "${x},${y} ${scaled_width}x${scaled_height}" --file "$outputPath" &
     elif [ "$target" == "region" ]; then
         wf-recorder $WF_RECORDER_OPTS --geometry "$(slurp)" --file "$outputPath" &
     else
-        echo "Usage: $0 start {region|screen [screen_name]}"
+        echo "Usage: $0 start {screen [output_directory] [monitor_name]|region [output_directory]}"
         exit 1
     fi
-    disown "$(jobs -p | tail -n 1)"
 
-    echo "Recording started. Output will be saved to $outputPath"
+    disown "$(jobs -p | tail -n 1)"
+    echo "Recording started. Saving to $outputPath"
+    echo "$outputPath" > /tmp/last_recording_path
 }
 
 stopRecording() {
     if ! checkRecording; then
-        echo "No recording is in progress."
+        echo "No recording in progress."
         exit 1
     fi
 
     pkill -SIGINT -f wf-recorder
+    sleep 1
path: scripts/screen_record.sh
user: login: davfsa
body: If we add a sleep, probably nice to document why above
created_at: 2025-03-15T10:44:08Z
author_association: COLLABORATOR
diff_hunk: @@ -1280,6 +1280,9 @@ const options = mkOptions(CONFIG, {
                 interval: opt(2000),
                 enable_gpu: opt(false),
             },
+            recording: {
+                path: opt('$HOME/Videos/Screencasts')
+            },
path: src/options.ts
user: login: davfsa
body: New options need to be added to the nix flake too :)

Also, I don't think we expand `$HOME`, but I do know we expand `~`
created_at: 2025-03-15T10:45:46Z
author_association: COLLABORATOR
diff_hunk: @@ -20,49 +17,84 @@ startRecording() {
     fi
 
     target="$2"
+    outputDir=$(eval echo "$3")
path: scripts/screen_record.sh
user: login: siddharthjain25
body: Using outputDir=$(eval echo "$3") is a better option because it ensures proper expansion of ~ and any other shell variables passed as arguments.

- Simply using outputDir="$3" does not expand ~ when it's inside quotes, which causes issues when specifying paths like ~/home/siddharth/test/.

- eval echo "$3" forces the shell to process the argument as if it were typed directly in the terminal, correctly expanding ~ and any embedded variables.

- This approach is commonly used for handling user-provided paths in shell scripts where variable expansion is required.
created_at: 2025-03-15T15:38:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,49 +17,84 @@ startRecording() {
     fi
 
     target="$2"
+    outputDir=$(eval echo "$3")
+    monitor_name="$4"
+
+    if [ -z "$outputDir" ]; then
+        echo "Error: Output directory is empty."
+        exit 1
+    fi
+
+    if [ ! -d "$outputDir" ]; then
+        echo "Error: Output directory '$outputDir' does not exist."
+        exit 1
+    fi
+
+    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S).mp4"
+    outputPath="$outputDir/$outputFile"
 
-    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S)"
-    outputPath="$outputDir/${outputFile}.mp4"
-    mkdir -p "$outputDir"
+    echo "Target: $target"
+    echo "Output dir: $outputDir"
+    echo "Output file: $outputPath"
 
     if [ "$target" == "screen" ]; then
-        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$3\")")
-        w=$(echo $monitor_info | jq -r '.width')
-        h=$(echo $monitor_info | jq -r '.height')
-        scale=$(echo $monitor_info | jq -r '.scale')
-        scaled_width=$(echo "${w} / ${scale}" | bc)
-        scaled_height=$(echo "${h} / ${scale}" | bc)
-        x=$(echo $monitor_info | jq -r '.x')
-        y=$(echo $monitor_info | jq -r '.y')
+        if [ -z "$monitor_name" ]; then
+            echo "Error: Monitor name is required for screen recording."
+            exit 1
+        fi
+
+        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$monitor_name\")")
+        if [ -z "$monitor_info" ]; then
+            echo "Error: Monitor '$monitor_name' not found."
+            exit 1
+        fi
+
+        w=$(echo "$monitor_info" | jq -r '.width')
+        h=$(echo "$monitor_info" | jq -r '.height')
+        scale=$(echo "$monitor_info" | jq -r '.scale')
+        scaled_width=$(awk "BEGIN { print $w / $scale }")
+        scaled_height=$(awk "BEGIN { print $h / $scale }")
+        x=$(echo "$monitor_info" | jq -r '.x')
+        y=$(echo "$monitor_info" | jq -r '.y')
+
         wf-recorder $WF_RECORDER_OPTS --geometry "${x},${y} ${scaled_width}x${scaled_height}" --file "$outputPath" &
     elif [ "$target" == "region" ]; then
         wf-recorder $WF_RECORDER_OPTS --geometry "$(slurp)" --file "$outputPath" &
     else
-        echo "Usage: $0 start {region|screen [screen_name]}"
+        echo "Usage: $0 start {screen [output_directory] [monitor_name]|region [output_directory]}"
         exit 1
     fi
-    disown "$(jobs -p | tail -n 1)"
 
-    echo "Recording started. Output will be saved to $outputPath"
+    disown "$(jobs -p | tail -n 1)"
+    echo "Recording started. Saving to $outputPath"
+    echo "$outputPath" > /tmp/last_recording_path
 }
 
 stopRecording() {
     if ! checkRecording; then
-        echo "No recording is in progress."
+        echo "No recording in progress."
         exit 1
     fi
 
     pkill -SIGINT -f wf-recorder
+    sleep 1
path: scripts/screen_record.sh
user: login: siddharthjain25
body: Good point! The sleep 1 is there to ensure wf-recorder has enough time to gracefully terminate before we proceed. Without it, there might be a race condition where the file is still being written when we try to access it immediately after stopping.

I'll add a comment above it to clarify this. Thanks for the suggestion!
created_at: 2025-03-15T15:39:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1280,6 +1280,9 @@ const options = mkOptions(CONFIG, {
                 interval: opt(2000),
                 enable_gpu: opt(false),
             },
+            recording: {
+                path: opt('$HOME/Videos/Screencasts')
+            },
path: src/options.ts
user: login: siddharthjain25
body: > New options need to be added to the nix flake too :)
> 
> Also, I don't think we expand `$HOME`, but I do know we expand `~`

I don't use Nix, so I'm not familiar with how to update the flake. Could you guide me on what needs to be added?

nvm, I updated nix module as well.
created_at: 2025-03-15T15:41:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,49 +17,84 @@ startRecording() {
     fi
 
     target="$2"
+    outputDir=$(eval echo "$3")
+    monitor_name="$4"
+
+    if [ -z "$outputDir" ]; then
+        echo "Error: Output directory is empty."
+        exit 1
+    fi
+
+    if [ ! -d "$outputDir" ]; then
+        echo "Error: Output directory '$outputDir' does not exist."
+        exit 1
+    fi
+
+    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S).mp4"
+    outputPath="$outputDir/$outputFile"
 
-    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S)"
-    outputPath="$outputDir/${outputFile}.mp4"
-    mkdir -p "$outputDir"
+    echo "Target: $target"
+    echo "Output dir: $outputDir"
+    echo "Output file: $outputPath"
 
     if [ "$target" == "screen" ]; then
-        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$3\")")
-        w=$(echo $monitor_info | jq -r '.width')
-        h=$(echo $monitor_info | jq -r '.height')
-        scale=$(echo $monitor_info | jq -r '.scale')
-        scaled_width=$(echo "${w} / ${scale}" | bc)
-        scaled_height=$(echo "${h} / ${scale}" | bc)
-        x=$(echo $monitor_info | jq -r '.x')
-        y=$(echo $monitor_info | jq -r '.y')
+        if [ -z "$monitor_name" ]; then
+            echo "Error: Monitor name is required for screen recording."
+            exit 1
+        fi
+
+        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$monitor_name\")")
+        if [ -z "$monitor_info" ]; then
+            echo "Error: Monitor '$monitor_name' not found."
+            exit 1
+        fi
+
+        w=$(echo "$monitor_info" | jq -r '.width')
+        h=$(echo "$monitor_info" | jq -r '.height')
+        scale=$(echo "$monitor_info" | jq -r '.scale')
+        scaled_width=$(awk "BEGIN { print $w / $scale }")
+        scaled_height=$(awk "BEGIN { print $h / $scale }")
+        x=$(echo "$monitor_info" | jq -r '.x')
+        y=$(echo "$monitor_info" | jq -r '.y')
+
         wf-recorder $WF_RECORDER_OPTS --geometry "${x},${y} ${scaled_width}x${scaled_height}" --file "$outputPath" &
     elif [ "$target" == "region" ]; then
         wf-recorder $WF_RECORDER_OPTS --geometry "$(slurp)" --file "$outputPath" &
     else
-        echo "Usage: $0 start {region|screen [screen_name]}"
+        echo "Usage: $0 start {screen [output_directory] [monitor_name]|region [output_directory]}"
path: scripts/screen_record.sh
user: login: dotaxis
body: This is a little weird. May want to rearrange our args.

I think it should look something like `$0 start {screen [monitor_name]|region} [output_directory]`
created_at: 2025-03-15T18:08:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,49 +17,84 @@ startRecording() {
     fi
 
     target="$2"
+    outputDir=$(eval echo "$3")
path: scripts/screen_record.sh
user: login: dotaxis
body: May want to have a default path here in case $3 is not passed or output_directory is not set.
created_at: 2025-03-15T18:09:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,49 +17,84 @@ startRecording() {
     fi
 
     target="$2"
+    outputDir=$(eval echo "$3")
+    monitor_name="$4"
+
+    if [ -z "$outputDir" ]; then
+        echo "Error: Output directory is empty."
+        exit 1
+    fi
+
+    if [ ! -d "$outputDir" ]; then
+        echo "Error: Output directory '$outputDir' does not exist."
+        exit 1
+    fi
+
+    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S).mp4"
+    outputPath="$outputDir/$outputFile"
 
-    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S)"
-    outputPath="$outputDir/${outputFile}.mp4"
-    mkdir -p "$outputDir"
+    echo "Target: $target"
+    echo "Output dir: $outputDir"
+    echo "Output file: $outputPath"
 
     if [ "$target" == "screen" ]; then
-        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$3\")")
-        w=$(echo $monitor_info | jq -r '.width')
-        h=$(echo $monitor_info | jq -r '.height')
-        scale=$(echo $monitor_info | jq -r '.scale')
-        scaled_width=$(echo "${w} / ${scale}" | bc)
-        scaled_height=$(echo "${h} / ${scale}" | bc)
-        x=$(echo $monitor_info | jq -r '.x')
-        y=$(echo $monitor_info | jq -r '.y')
+        if [ -z "$monitor_name" ]; then
+            echo "Error: Monitor name is required for screen recording."
+            exit 1
+        fi
+
+        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$monitor_name\")")
+        if [ -z "$monitor_info" ]; then
+            echo "Error: Monitor '$monitor_name' not found."
+            exit 1
+        fi
+
+        w=$(echo "$monitor_info" | jq -r '.width')
+        h=$(echo "$monitor_info" | jq -r '.height')
+        scale=$(echo "$monitor_info" | jq -r '.scale')
+        scaled_width=$(awk "BEGIN { print $w / $scale }")
+        scaled_height=$(awk "BEGIN { print $h / $scale }")
+        x=$(echo "$monitor_info" | jq -r '.x')
+        y=$(echo "$monitor_info" | jq -r '.y')
+
         wf-recorder $WF_RECORDER_OPTS --geometry "${x},${y} ${scaled_width}x${scaled_height}" --file "$outputPath" &
     elif [ "$target" == "region" ]; then
         wf-recorder $WF_RECORDER_OPTS --geometry "$(slurp)" --file "$outputPath" &
     else
-        echo "Usage: $0 start {region|screen [screen_name]}"
+        echo "Usage: $0 start {screen [output_directory] [monitor_name]|region [output_directory]}"
path: scripts/screen_record.sh
user: login: siddharthjain25
body: Done as requested.
created_at: 2025-03-15T18:47:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,49 +17,84 @@ startRecording() {
     fi
 
     target="$2"
+    outputDir=$(eval echo "$3")
path: scripts/screen_record.sh
user: login: siddharthjain25
body: I've set a default save location ($HOME/Videos) if no directory is specified and added validation to prevent issues with non-existing directories.
created_at: 2025-03-15T18:47:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,66 +21,113 @@ startRecording() {
 
     target="$2"
 
-    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S)"
-    outputPath="$outputDir/${outputFile}.mp4"
-    mkdir -p "$outputDir"
+    if [ "$target" == "screen" ]; then
+        monitor_name="$3"
+        outputDir="$4"
+    elif [ "$target" == "region" ]; then
+        outputDir="$3"
+    else
+        echo "Usage: $0 start {screen [monitor_name] [output_directory] | region [output_directory]}"
path: scripts/screen_record.sh
user: login: dotaxis
body: ```suggestion
        echo "Usage: $0 start {screen <monitor_name> | region} <output_directory>"
```
created_at: 2025-03-15T19:33:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,66 +21,113 @@ startRecording() {
 
     target="$2"
 
-    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S)"
-    outputPath="$outputDir/${outputFile}.mp4"
-    mkdir -p "$outputDir"
+    if [ "$target" == "screen" ]; then
+        monitor_name="$3"
+        outputDir="$4"
+    elif [ "$target" == "region" ]; then
+        outputDir="$3"
+    else
+        echo "Usage: $0 start {screen [monitor_name] [output_directory] | region [output_directory]}"
+        exit 1
+    fi
+
+    # Set a default output directory if not provided
+    outputDir="${outputDir:-$HOME/Videos}"
+
+    # Expand ~ to $HOME if present in outputDir
+    outputDir="${outputDir/#\~/$HOME}"
+
+    # Ensure output directory exists
+    if [ ! -d "$outputDir" ]; then
+        echo "Error: Output directory '$outputDir' does not exist."
+        exit 1
+    fi
+
+    # Generate output filename and path
+    outputFile="recording_$(date +%Y-%m-%d_%H-%M-%S).mp4"
+    outputPath="$outputDir/$outputFile"
+
+    echo "Target: $target"
+    echo "Monitor: ${monitor_name:-N/A}"
+    echo "Output dir: $outputDir"
+    echo "Output file: $outputPath"
 
+    # Start screen recording
     if [ "$target" == "screen" ]; then
-        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$3\")")
-        w=$(echo $monitor_info | jq -r '.width')
-        h=$(echo $monitor_info | jq -r '.height')
-        scale=$(echo $monitor_info | jq -r '.scale')
-        scaled_width=$(echo "${w} / ${scale}" | bc)
-        scaled_height=$(echo "${h} / ${scale}" | bc)
-        x=$(echo $monitor_info | jq -r '.x')
-        y=$(echo $monitor_info | jq -r '.y')
+        if [ -z "$monitor_name" ]; then
+            echo "Error: Monitor name is required for screen recording."
+            exit 1
+        fi
+
+        monitor_info=$(hyprctl -j monitors | jq -r ".[] | select(.name == \"$monitor_name\")")
+        if [ -z "$monitor_info" ]; then
+            echo "Error: Monitor '$monitor_name' not found."
+            exit 1
+        fi
+
+        w=$(echo "$monitor_info" | jq -r '.width')
+        h=$(echo "$monitor_info" | jq -r '.height')
+        scale=$(echo "$monitor_info" | jq -r '.scale')
+        scaled_width=$(awk "BEGIN { print $w / $scale }")
+        scaled_height=$(awk "BEGIN { print $h / $scale }")
+        x=$(echo "$monitor_info" | jq -r '.x')
+        y=$(echo "$monitor_info" | jq -r '.y')
+
         wf-recorder $WF_RECORDER_OPTS --geometry "${x},${y} ${scaled_width}x${scaled_height}" --file "$outputPath" &
     elif [ "$target" == "region" ]; then
         wf-recorder $WF_RECORDER_OPTS --geometry "$(slurp)" --file "$outputPath" &
-    else
-        echo "Usage: $0 start {region|screen [screen_name]}"
-        exit 1
     fi
-    disown "$(jobs -p | tail -n 1)"
 
-    echo "Recording started. Output will be saved to $outputPath"
+    disown "$(jobs -p | tail -n 1)"
+    echo "Recording started. Saving to $outputPath"
+    echo "$outputPath" > /tmp/last_recording_path
 }
 
+# Function to stop screen recording
 stopRecording() {
     if ! checkRecording; then
-        echo "No recording is in progress."
+        echo "No recording in progress."
         exit 1
     fi
 
     pkill -SIGINT -f wf-recorder
+    sleep 1  # Allow wf-recorder time to terminate before proceeding
 
-    recentFile=$(ls -t "$outputDir"/recording_*.mp4 | head -n 1)
+    outputPath=$(cat /tmp/last_recording_path 2>/dev/null)
 
-    notify-send "Recording stopped" "Your recording has been saved." \
+    if [ -z "$outputPath" ] || [ ! -f "$outputPath" ]; then
+        notify-send "Recording stopped" "No recent recording found." \
+            -i video-x-generic \
+            -a "Screen Recorder" \
+            -t 10000
+        exit 1
+    fi
+
+    notify-send "Recording stopped" "Saved to: $outputPath" \
         -i video-x-generic \
         -a "Screen Recorder" \
         -t 10000 \
-        -u normal \
-        --action="scriptAction:-xdg-open $outputDir=Directory" \
-        --action="scriptAction:-xdg-open $recentFile=Play"
+        --action="scriptAction:-xdg-open $(dirname "$outputPath")=Open Directory" \
+        --action="scriptAction:-xdg-open $outputPath=Play"
 }
 
+# Handle script arguments
 case "$1" in
-start)
-    startRecording "$@"
-    ;;
-stop)
-    stopRecording
-    ;;
-status)
-    if checkRecording; then
-        echo "recording"
-    else
-        echo "not recording"
-    fi
-    ;;
-*)
-    echo "Usage: $0 {start [screen screen_name|region]|stop|status}"
-    exit 1
-    ;;
+    start)
+        startRecording "$@"
+        ;;
+    stop)
+        stopRecording
+        ;;
+    status)
+        if checkRecording; then
+            echo "recording"
+        else
+            echo "not recording"
+        fi
+        ;;
+    *)
+        echo "Usage: $0 {start [screen monitor_name output_directory | region output_directory] | stop | status}"
path: scripts/screen_record.sh
user: login: dotaxis
body: ```suggestion
        echo "Usage: $0 {start [screen <monitor_name> | region] <output_directory> | stop | status}"
```
created_at: 2025-03-15T19:33:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,79 +4,95 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = () => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string) => {
+    try {
+        console.log('Executing command:', command);
+        const output = await execAsync(`/bin/bash -c '${command}'`);
+        console.log('Command output:', output);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: davfsa
body: Also, we probably want to remove the console logs as to not spam users
created_at: 2025-03-15T21:16:23Z
author_association: COLLABORATOR
diff_hunk: @@ -4,79 +4,93 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
-        monitorList.set(hyprlandService.get_monitors()),
-    );
+    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () => {
+        monitorList.set(hyprlandService.get_monitors());
+    });
 
     return (
-        <Menu className={'dropdown recording'} halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
+        <Menu className="dropdown recording" halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
             {bind(monitorList).as((monitors) => {
-                return monitors.map((monitor) => (
-                    <MenuItem
-                        label={`Display ${monitor.name}`}
-                        onButtonPressEvent={(_, event) => {
-                            const buttonClicked = event.get_button()[1];
+                return monitors.map((monitor) => {
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                            if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                                return;
-                            }
+                    return (
+                        <MenuItem
+                            key={monitor.name}
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: ```suggestion
```
Don't think `key` is a valid property AFAIK. You may be thinking of React component `key` property for mapping?
created_at: 2025-03-23T07:56:07Z
author_association: OWNER
diff_hunk: @@ -4,79 +4,93 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
-        monitorList.set(hyprlandService.get_monitors()),
-    );
+    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () => {
+        monitorList.set(hyprlandService.get_monitors());
+    });
 
     return (
-        <Menu className={'dropdown recording'} halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
+        <Menu className="dropdown recording" halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
             {bind(monitorList).as((monitors) => {
-                return monitors.map((monitor) => (
-                    <MenuItem
-                        label={`Display ${monitor.name}`}
-                        onButtonPressEvent={(_, event) => {
-                            const buttonClicked = event.get_button()[1];
+                return monitors.map((monitor) => {
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                            if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                                return;
-                            }
+                    return (
+                        <MenuItem
+                            key={monitor.name}
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: siddharthjain25
body: Yeah, I was confused.
created_at: 2025-03-23T07:58:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,79 +4,92 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: Let's move these helper functions to `src/components/menus/dashboard/shortcuts/helpers.ts`
created_at: 2025-03-23T08:29:22Z
author_association: OWNER
diff_hunk: @@ -4,79 +4,92 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: If we're going to comment the functions, let's use the [JSDoc spec](https://jsdoc.app/about-getting-started).
created_at: 2025-03-23T08:30:50Z
author_association: OWNER
diff_hunk: @@ -4,79 +4,92 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
-        monitorList.set(hyprlandService.get_monitors()),
-    );
+    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () => {
+        monitorList.set(hyprlandService.get_monitors());
+    });
 
     return (
-        <Menu className={'dropdown recording'} halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
+        <Menu className="dropdown recording" halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
             {bind(monitorList).as((monitors) => {
-                return monitors.map((monitor) => (
-                    <MenuItem
-                        label={`Display ${monitor.name}`}
-                        onButtonPressEvent={(_, event) => {
-                            const buttonClicked = event.get_button()[1];
+                return monitors.map((monitor) => {
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                            if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                                return;
-                            }
+                    return (
+                        <MenuItem
+                            label={`Display ${monitor.name}`}
+                            onButtonPressEvent={(_, event) => {
+                                if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
-                            App.get_window('dashboardmenu')?.set_visible(false);
+                                App.get_window('dashboardmenu')?.set_visible(false);
 
-                            execAsync(`${SRC_DIR}/scripts/screen_record.sh start screen ${monitor.name}`).catch((err) =>
-                                console.error(err),
-                            );
-                        }}
-                    />
-                ));
+                                // Update command to match new script argument order
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: ```suggestion
```
Noisy comment. Refer to [section 4](https://mitcommlab.mit.edu/broad/commkit/coding-and-comment-style/)
created_at: 2025-03-23T08:37:25Z
author_association: OWNER
diff_hunk: @@ -4,79 +4,92 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
-        monitorList.set(hyprlandService.get_monitors()),
-    );
+    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () => {
+        monitorList.set(hyprlandService.get_monitors());
+    });
 
     return (
-        <Menu className={'dropdown recording'} halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
+        <Menu className="dropdown recording" halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
             {bind(monitorList).as((monitors) => {
-                return monitors.map((monitor) => (
-                    <MenuItem
-                        label={`Display ${monitor.name}`}
-                        onButtonPressEvent={(_, event) => {
-                            const buttonClicked = event.get_button()[1];
+                return monitors.map((monitor) => {
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                            if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                                return;
-                            }
+                    return (
+                        <MenuItem
+                            label={`Display ${monitor.name}`}
+                            onButtonPressEvent={(_, event) => {
+                                if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
-                            App.get_window('dashboardmenu')?.set_visible(false);
+                                App.get_window('dashboardmenu')?.set_visible(false);
 
-                            execAsync(`${SRC_DIR}/scripts/screen_record.sh start screen ${monitor.name}`).catch((err) =>
-                                console.error(err),
-                            );
-                        }}
-                    />
-                ));
+                                // Update command to match new script argument order
+                                const command = `${SRC_DIR}/scripts/screen_record.sh start screen "${monitor.name}" "${sanitizedPath}"`;
+                                executeCommand(command);
+                            }}
+                        />
+                    );
+                });
             })}
             <MenuItem
                 label="Region"
                 onButtonPressEvent={(_, event) => {
-                    const buttonClicked = event.get_button()[1];
-
-                    if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                        return;
-                    }
+                    if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
                     App.get_window('dashboardmenu')?.set_visible(false);
 
-                    execAsync(`${SRC_DIR}/scripts/screen_record.sh start region`).catch((err) => console.error(err));
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
+                    const command = `${SRC_DIR}/scripts/screen_record.sh start region "${sanitizedPath}"`;
+                    executeCommand(command);
                 }}
             />
         </Menu>
     );
 };
 
+// Recording button to start/stop screen recording
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: ```suggestion
```
Noisy comment. Adds nothing more than what the function name already explains.
created_at: 2025-03-23T08:38:45Z
author_association: OWNER
diff_hunk: @@ -4,79 +4,92 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
-        monitorList.set(hyprlandService.get_monitors()),
-    );
+    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () => {
+        monitorList.set(hyprlandService.get_monitors());
+    });
 
     return (
-        <Menu className={'dropdown recording'} halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
+        <Menu className="dropdown recording" halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
             {bind(monitorList).as((monitors) => {
-                return monitors.map((monitor) => (
-                    <MenuItem
-                        label={`Display ${monitor.name}`}
-                        onButtonPressEvent={(_, event) => {
-                            const buttonClicked = event.get_button()[1];
+                return monitors.map((monitor) => {
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                            if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                                return;
-                            }
+                    return (
+                        <MenuItem
+                            label={`Display ${monitor.name}`}
+                            onButtonPressEvent={(_, event) => {
+                                if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
-                            App.get_window('dashboardmenu')?.set_visible(false);
+                                App.get_window('dashboardmenu')?.set_visible(false);
 
-                            execAsync(`${SRC_DIR}/scripts/screen_record.sh start screen ${monitor.name}`).catch((err) =>
-                                console.error(err),
-                            );
-                        }}
-                    />
-                ));
+                                // Update command to match new script argument order
+                                const command = `${SRC_DIR}/scripts/screen_record.sh start screen "${monitor.name}" "${sanitizedPath}"`;
+                                executeCommand(command);
+                            }}
+                        />
+                    );
+                });
             })}
             <MenuItem
                 label="Region"
                 onButtonPressEvent={(_, event) => {
-                    const buttonClicked = event.get_button()[1];
-
-                    if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                        return;
-                    }
+                    if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
                     App.get_window('dashboardmenu')?.set_visible(false);
 
-                    execAsync(`${SRC_DIR}/scripts/screen_record.sh start region`).catch((err) => console.error(err));
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
+                    const command = `${SRC_DIR}/scripts/screen_record.sh start region "${sanitizedPath}"`;
+                    executeCommand(command);
                 }}
             />
         </Menu>
     );
 };
 
+// Recording button to start/stop screen recording
 export const RecordingButton = (): JSX.Element => {
     return (
         <button
             className={`dashboard-button record ${isRecording.get() ? 'active' : ''}`}
-            tooltipText={'Record Screen'}
+            tooltipText="Record Screen"
             vexpand
             onButtonPressEvent={(_, event) => {
-                const buttonClicked = event.get_button()[1];
+                if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
-                if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                    return;
-                }
+                const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                if (isRecording.get() === true) {
+                if (isRecording.get()) {
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: Why are we making so many changes here just to refactor coding style? These aren't functional changes.
created_at: 2025-03-23T08:40:20Z
author_association: OWNER
diff_hunk: @@ -4,79 +4,92 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
-        monitorList.set(hyprlandService.get_monitors()),
-    );
+    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () => {
+        monitorList.set(hyprlandService.get_monitors());
+    });
 
     return (
-        <Menu className={'dropdown recording'} halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
+        <Menu className="dropdown recording" halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
             {bind(monitorList).as((monitors) => {
-                return monitors.map((monitor) => (
-                    <MenuItem
-                        label={`Display ${monitor.name}`}
-                        onButtonPressEvent={(_, event) => {
-                            const buttonClicked = event.get_button()[1];
+                return monitors.map((monitor) => {
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                            if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                                return;
-                            }
+                    return (
+                        <MenuItem
+                            label={`Display ${monitor.name}`}
+                            onButtonPressEvent={(_, event) => {
+                                if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
-                            App.get_window('dashboardmenu')?.set_visible(false);
+                                App.get_window('dashboardmenu')?.set_visible(false);
 
-                            execAsync(`${SRC_DIR}/scripts/screen_record.sh start screen ${monitor.name}`).catch((err) =>
-                                console.error(err),
-                            );
-                        }}
-                    />
-                ));
+                                // Update command to match new script argument order
+                                const command = `${SRC_DIR}/scripts/screen_record.sh start screen "${monitor.name}" "${sanitizedPath}"`;
+                                executeCommand(command);
+                            }}
+                        />
+                    );
+                });
             })}
             <MenuItem
                 label="Region"
                 onButtonPressEvent={(_, event) => {
-                    const buttonClicked = event.get_button()[1];
-
-                    if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                        return;
-                    }
+                    if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
                     App.get_window('dashboardmenu')?.set_visible(false);
 
-                    execAsync(`${SRC_DIR}/scripts/screen_record.sh start region`).catch((err) => console.error(err));
+                    const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
+                    const command = `${SRC_DIR}/scripts/screen_record.sh start region "${sanitizedPath}"`;
+                    executeCommand(command);
                 }}
             />
         </Menu>
     );
 };
 
+// Recording button to start/stop screen recording
 export const RecordingButton = (): JSX.Element => {
     return (
         <button
             className={`dashboard-button record ${isRecording.get() ? 'active' : ''}`}
-            tooltipText={'Record Screen'}
+            tooltipText="Record Screen"
             vexpand
             onButtonPressEvent={(_, event) => {
-                const buttonClicked = event.get_button()[1];
+                if (event.get_button()[1] !== Gdk.BUTTON_PRIMARY) return;
 
-                if (buttonClicked !== Gdk.BUTTON_PRIMARY) {
-                    return;
-                }
+                const sanitizedPath = getRecordingPath().replace(/"/g, '\\"');
 
-                if (isRecording.get() === true) {
+                if (isRecording.get()) {
                     App.get_window('dashboardmenu')?.set_visible(false);
-                    return execAsync(`${SRC_DIR}/scripts/screen_record.sh stop`).catch((err) => console.error(err));
+                    const command = `${SRC_DIR}/scripts/screen_record.sh stop "${sanitizedPath}"`;
+                    executeCommand(command);
                 } else {
                     const monitorDropdownList = MonitorListDropdown() as Gtk.Menu;
                     monitorDropdownList.popup_at_pointer(event);
                 }
             }}
         >
-            <label className={'button-label txt-icon'} label={'󰑊'} />
+            <label className="button-label txt-icon" label="󰑊" />
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: Why are we polluting the PR with just formatting changes?
created_at: 2025-03-23T08:41:24Z
author_association: OWNER
diff_hunk: @@ -4,79 +4,92 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: Lots of reformatting pollution here. Let's only make functional changes unless we're violating linter rules.
created_at: 2025-03-23T08:42:18Z
author_association: OWNER
diff_hunk: @@ -4,79 +4,92 @@ import Menu from 'src/components/shared/Menu';
 import MenuItem from 'src/components/shared/MenuItem';
 import { isRecording } from '../helpers';
 import AstalHyprland from 'gi://AstalHyprland?version=0.1';
+import options from 'src/options';
 
 const hyprlandService = AstalHyprland.get_default();
 
+// Function to get the latest recording path
+const getRecordingPath = (): string => options.menus.dashboard.recording.path.get();
+
+// Execute shell commands safely with path expansion
+const executeCommand = async (command: string): Promise<void> => {
+    try {
+        await execAsync(`/bin/bash -c '${command}'`);
+    } catch (err) {
+        console.error('Command failed:', command);
+        console.error('Error:', err);
+    }
+};
+
+// Monitor dropdown for screen selection
 const MonitorListDropdown = (): JSX.Element => {
     const monitorList: Variable<AstalHyprland.Monitor[]> = Variable([]);
 
-    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () =>
-        monitorList.set(hyprlandService.get_monitors()),
-    );
+    const monitorBinding = Variable.derive([bind(hyprlandService, 'monitors')], () => {
+        monitorList.set(hyprlandService.get_monitors());
+    });
 
     return (
-        <Menu className={'dropdown recording'} halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
+        <Menu className="dropdown recording" halign={Gtk.Align.FILL} onDestroy={() => monitorBinding.drop()} hexpand>
path: src/components/menus/dashboard/shortcuts/buttons/RecordingButton.tsx
user: login: Jas-SinghFSU
body: Reformatting pollution
created_at: 2025-03-23T08:42:52Z
author_association: OWNER

PR comments:
user: login: dotaxis
created_at: 2025-03-12T20:41:51Z
body: I don't like this implementation.

IMO, a config option of where to save them would be much better than relying on a Zenity window for every single recording.
author_association: CONTRIBUTOR
user: login: siddharthjain25
created_at: 2025-03-13T07:19:00Z
body: > I don't like this implementation.
> 
> IMO, a config option of where to save them would be much better than relying on a Zenity window for every single recording.

Appreciate the feedback! That makes sense — I’ll add a config option for the save path when I get some free time.
author_association: CONTRIBUTOR
user: login: Jas-SinghFSU
created_at: 2025-03-23T08:26:14Z
body: :heart: :tada: 
author_association: OWNER
user: login: Jas-SinghFSU
created_at: 2025-03-25T01:35:06Z
body: Seems like we have a few linter errors.
author_association: OWNER
user: login: Jas-SinghFSU
created_at: 2025-03-25T03:51:27Z
body: Noice, let's merge this! Good work
author_association: OWNER