diff --git a/nix/module.nix b/nix/module.nix index 4c54c46..478889a 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 fc70eb6..7d53acc 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 f203689..3854db9 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_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') + monitor_name="$3" + outputDir="$4" + elif [ "$target" == "region" ]; then + outputDir="$3" + else + echo "Usage: $0 start {screen | region} " + 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 + 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 | region] | 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 1f054dd..b48cc38 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 = 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 ( monitorBinding.drop()} hexpand> - {bind(monitorList).as((monitors) => { - return monitors.map((monitor) => ( - { - 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 ( + { + 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); + }} + /> + ); + }), + )} { - 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); }} /> @@ -58,7 +55,7 @@ export const RecordingButton = (): JSX.Element => { return (