forked from Shinonome/dots-hyprland
use quickshell region selector for recording
This commit is contained in:
@@ -73,9 +73,10 @@ bindd = Super+Shift, C, Color picker, exec, hyprpicker -a # Pick color (Hex) >>
|
|||||||
bindld = ,Print, Screenshot >> clipboard ,exec,grim - | wl-copy # Screenshot >> clipboard
|
bindld = ,Print, Screenshot >> clipboard ,exec,grim - | wl-copy # Screenshot >> clipboard
|
||||||
bindld = Ctrl,Print, Screenshot >> clipboard & save, exec, mkdir -p $(xdg-user-dir PICTURES)/Screenshots && grim $(xdg-user-dir PICTURES)/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file
|
bindld = Ctrl,Print, Screenshot >> clipboard & save, exec, mkdir -p $(xdg-user-dir PICTURES)/Screenshots && grim $(xdg-user-dir PICTURES)/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file
|
||||||
# Recording stuff
|
# Recording stuff
|
||||||
bindl = Super+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh # Record region (no sound)
|
bindl = Super+Alt, R, global, quickshell:regionRecord # Record region (no sound)
|
||||||
bindl = Ctrl+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen # [hidden] Record screen (no sound)
|
bindl = Super+Alt, R, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/videos/record.sh # [hidden] Record region (no sound) (fallback)
|
||||||
bindl = Super+Shift+Alt, R, exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen-sound # Record screen (with sound)
|
bindl = Ctrl+Alt, R, exec, ~/.config/quickshell/$qsConfig/scripts/videos/record.sh --fullscreen # [hidden] Record screen (no sound)
|
||||||
|
bindl = Super+Shift+Alt, R, exec, ~/.config/quickshell/$qsConfig/scripts/videos/record.sh --fullscreen --sound # Record screen (with sound)
|
||||||
# AI
|
# AI
|
||||||
bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh # AI summary for selected text
|
bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh # AI summary for selected text
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
getdate() {
|
|
||||||
date '+%Y-%m-%d_%H.%M.%S'
|
|
||||||
}
|
|
||||||
getaudiooutput() {
|
|
||||||
pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2
|
|
||||||
}
|
|
||||||
getactivemonitor() {
|
|
||||||
hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'
|
|
||||||
}
|
|
||||||
|
|
||||||
xdgvideo="$(xdg-user-dir VIDEOS)"
|
|
||||||
if [[ $xdgvideo = "$HOME" ]]; then
|
|
||||||
unset xdgvideo
|
|
||||||
fi
|
|
||||||
mkdir -p "${xdgvideo:-$HOME/Videos}"
|
|
||||||
cd "${xdgvideo:-$HOME/Videos}" || exit
|
|
||||||
|
|
||||||
if pgrep wf-recorder > /dev/null; then
|
|
||||||
notify-send "Recording Stopped" "Stopped" -a 'Recorder' &
|
|
||||||
pkill wf-recorder &
|
|
||||||
else
|
|
||||||
if [[ "$1" == "--fullscreen-sound" ]]; then
|
|
||||||
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown
|
|
||||||
wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)"
|
|
||||||
elif [[ "$1" == "--fullscreen" ]]; then
|
|
||||||
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown
|
|
||||||
wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t
|
|
||||||
else
|
|
||||||
if ! region="$(slurp 2>&1)"; then
|
|
||||||
notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' & disown
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown
|
|
||||||
if [[ "$1" == "--sound" ]]; then
|
|
||||||
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)"
|
|
||||||
else
|
|
||||||
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
@@ -41,7 +41,7 @@ Item {
|
|||||||
visible: Config.options.bar.utilButtons.showScreenRecord
|
visible: Config.options.bar.utilButtons.showScreenRecord
|
||||||
sourceComponent: CircleUtilButton {
|
sourceComponent: CircleUtilButton {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
onClicked: Quickshell.execDetached(["bash", "-c", "~/.config/hypr/hyprland/scripts/record.sh"])
|
onClicked: Quickshell.execDetached([Directories.recordScriptPath])
|
||||||
MaterialSymbol {
|
MaterialSymbol {
|
||||||
horizontalAlignment: Qt.AlignHCenter
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
fill: 1
|
fill: 1
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ Singleton {
|
|||||||
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
|
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
|
||||||
property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
|
property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
|
||||||
property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
|
property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
|
||||||
|
property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`)
|
||||||
// Cleanup on init
|
// Cleanup on init
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
|
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ Toolbar {
|
|||||||
return "image_search";
|
return "image_search";
|
||||||
case RegionSelection.SnipAction.CharRecognition:
|
case RegionSelection.SnipAction.CharRecognition:
|
||||||
return "document_scanner";
|
return "document_scanner";
|
||||||
|
case RegionSelection.SnipAction.Record:
|
||||||
|
case RegionSelection.SnipAction.RecordWithSound:
|
||||||
|
return "videocam";
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import Qt.labs.synchronizer
|
|||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
visible: false
|
visible: false
|
||||||
|
color: "transparent"
|
||||||
WlrLayershell.namespace: "quickshell:regionSelector"
|
WlrLayershell.namespace: "quickshell:regionSelector"
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
@@ -26,7 +27,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Ask: sidebar AI; Ocr: tesseract
|
// TODO: Ask: sidebar AI; Ocr: tesseract
|
||||||
enum SnipAction { Copy, Edit, Search, CharRecognition }
|
enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
|
||||||
enum SelectionMode { RectCorners, Circle }
|
enum SelectionMode { RectCorners, Circle }
|
||||||
property var action: RegionSelection.SnipAction.Copy
|
property var action: RegionSelection.SnipAction.Copy
|
||||||
property var selectionMode: RegionSelection.SelectionMode.RectCorners
|
property var selectionMode: RegionSelection.SelectionMode.RectCorners
|
||||||
@@ -175,14 +176,35 @@ PanelWindow {
|
|||||||
property real regionY: Math.min(dragStartY, draggingY)
|
property real regionY: Math.min(dragStartY, draggingY)
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: screenshotProcess
|
id: screenshotProc
|
||||||
running: true
|
running: true
|
||||||
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`]
|
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`]
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
root.visible = true;
|
|
||||||
if (root.enableContentRegions) imageDetectionProcess.running = true;
|
if (root.enableContentRegions) imageDetectionProcess.running = true;
|
||||||
|
root.preparationDone = !checkRecordingProc.running;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
property bool isRecording: root.action === RegionSelection.SnipAction.Record || root.action === RegionSelection.SnipAction.RecordWithSound
|
||||||
|
property bool recordingShouldStop: false
|
||||||
|
Process {
|
||||||
|
id: checkRecordingProc
|
||||||
|
running: isRecording
|
||||||
|
command: ["pidof", "wf-recorder"]
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
root.preparationDone = !screenshotProc.running
|
||||||
|
root.recordingShouldStop = (exitCode === 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property bool preparationDone: false
|
||||||
|
onPreparationDoneChanged: {
|
||||||
|
if (!preparationDone) return;
|
||||||
|
if (root.isRecording && root.recordingShouldStop) {
|
||||||
|
Quickshell.execDetached([Directories.recordScriptPath]);
|
||||||
|
root.dismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: imageDetectionProcess
|
id: imageDetectionProcess
|
||||||
@@ -221,11 +243,16 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set command for action
|
// Set command for action
|
||||||
|
const rx = Math.round(root.regionX * root.monitorScale);
|
||||||
|
const ry = Math.round(root.regionY * root.monitorScale);
|
||||||
|
const rw = Math.round(root.regionWidth * root.monitorScale);
|
||||||
|
const rh = Math.round(root.regionHeight * root.monitorScale);
|
||||||
const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} `
|
const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} `
|
||||||
+ `-crop ${root.regionWidth * root.monitorScale}x${root.regionHeight * root.monitorScale}+${root.regionX * root.monitorScale}+${root.regionY * root.monitorScale}`
|
+ `-crop ${rw}x${rh}+${rx}+${ry}`
|
||||||
const cropToStdout = `${cropBase} -`
|
const cropToStdout = `${cropBase} -`
|
||||||
const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
|
const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
|
||||||
const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
|
const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
|
||||||
|
const slurpRegion = `${rx},${ry} ${rw}x${rh}`
|
||||||
const uploadAndGetUrl = (filePath) => {
|
const uploadAndGetUrl = (filePath) => {
|
||||||
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
|
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
|
||||||
}
|
}
|
||||||
@@ -242,6 +269,12 @@ PanelWindow {
|
|||||||
case RegionSelection.SnipAction.CharRecognition:
|
case RegionSelection.SnipAction.CharRecognition:
|
||||||
snipProc.command = ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`]
|
snipProc.command = ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`]
|
||||||
break;
|
break;
|
||||||
|
case RegionSelection.SnipAction.Record:
|
||||||
|
snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`]
|
||||||
|
break;
|
||||||
|
case RegionSelection.SnipAction.RecordWithSound:
|
||||||
|
snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`]
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn("[Region Selector] Unknown snip action, skipping snip.");
|
console.warn("[Region Selector] Unknown snip action, skipping snip.");
|
||||||
root.dismiss();
|
root.dismiss();
|
||||||
|
|||||||
@@ -62,6 +62,18 @@ Scope {
|
|||||||
GlobalStates.regionSelectorOpen = true
|
GlobalStates.regionSelectorOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function record() {
|
||||||
|
root.action = RegionSelection.SnipAction.Record
|
||||||
|
root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
||||||
|
GlobalStates.regionSelectorOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordWithSound() {
|
||||||
|
root.action = RegionSelection.SnipAction.RecordWithSound
|
||||||
|
root.selectionMode = RegionSelection.SelectionMode.RectCorners
|
||||||
|
GlobalStates.regionSelectorOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "region"
|
target: "region"
|
||||||
|
|
||||||
@@ -71,10 +83,15 @@ Scope {
|
|||||||
function search() {
|
function search() {
|
||||||
root.search()
|
root.search()
|
||||||
}
|
}
|
||||||
|
|
||||||
function ocr() {
|
function ocr() {
|
||||||
root.ocr()
|
root.ocr()
|
||||||
}
|
}
|
||||||
|
function record() {
|
||||||
|
root.record()
|
||||||
|
}
|
||||||
|
function recordWithSound() {
|
||||||
|
root.recordWithSound()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalShortcut {
|
GlobalShortcut {
|
||||||
@@ -92,4 +109,14 @@ Scope {
|
|||||||
description: "Recognizes text in the selected region"
|
description: "Recognizes text in the selected region"
|
||||||
onPressed: root.ocr()
|
onPressed: root.ocr()
|
||||||
}
|
}
|
||||||
|
GlobalShortcut {
|
||||||
|
name: "regionRecord"
|
||||||
|
description: "Records the selected region"
|
||||||
|
onPressed: root.record()
|
||||||
|
}
|
||||||
|
GlobalShortcut {
|
||||||
|
name: "regionRecordWithSound"
|
||||||
|
description: "Records the selected region with sound"
|
||||||
|
onPressed: root.recordWithSound()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
getdate() {
|
||||||
|
date '+%Y-%m-%d_%H.%M.%S'
|
||||||
|
}
|
||||||
|
getaudiooutput() {
|
||||||
|
pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2
|
||||||
|
}
|
||||||
|
getactivemonitor() {
|
||||||
|
hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'
|
||||||
|
}
|
||||||
|
|
||||||
|
xdgvideo="$(xdg-user-dir VIDEOS)"
|
||||||
|
if [[ $xdgvideo = "$HOME" ]]; then
|
||||||
|
unset xdgvideo
|
||||||
|
fi
|
||||||
|
mkdir -p "${xdgvideo:-$HOME/Videos}"
|
||||||
|
cd "${xdgvideo:-$HOME/Videos}" || exit
|
||||||
|
|
||||||
|
# parse --region <value> without modifying $@ so other flags like --fullscreen still work
|
||||||
|
ARGS=("$@")
|
||||||
|
MANUAL_REGION=""
|
||||||
|
SOUND_FLAG=0
|
||||||
|
FULLSCREEN_FLAG=0
|
||||||
|
for ((i=0;i<${#ARGS[@]};i++)); do
|
||||||
|
if [[ "${ARGS[i]}" == "--region" ]]; then
|
||||||
|
if (( i+1 < ${#ARGS[@]} )); then
|
||||||
|
MANUAL_REGION="${ARGS[i+1]}"
|
||||||
|
else
|
||||||
|
notify-send "Recording cancelled" "No region specified for --region" -a 'Recorder' & disown
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [[ "${ARGS[i]}" == "--sound" ]]; then
|
||||||
|
SOUND_FLAG=1
|
||||||
|
elif [[ "${ARGS[i]}" == "--fullscreen" ]]; then
|
||||||
|
FULLSCREEN_FLAG=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if pgrep wf-recorder > /dev/null; then
|
||||||
|
notify-send "Recording Stopped" "Stopped" -a 'Recorder' &
|
||||||
|
pkill wf-recorder &
|
||||||
|
else
|
||||||
|
if [[ $FULLSCREEN_FLAG -eq 1 ]]; then
|
||||||
|
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown
|
||||||
|
if [[ $SOUND_FLAG -eq 1 ]]; then
|
||||||
|
wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)"
|
||||||
|
else
|
||||||
|
wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# If a manual region was provided via --region, use it; otherwise run slurp as before.
|
||||||
|
if [[ -n "$MANUAL_REGION" ]]; then
|
||||||
|
region="$MANUAL_REGION"
|
||||||
|
else
|
||||||
|
if ! region="$(slurp 2>&1)"; then
|
||||||
|
notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' & disown
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown
|
||||||
|
if [[ $SOUND_FLAG -eq 1 ]]; then
|
||||||
|
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)"
|
||||||
|
else
|
||||||
|
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user