quickshell: run slurp from the recorder card, not inside the script
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
87217f4675
commit
0d0009ca8e
1 changed files with 79 additions and 26 deletions
|
|
@ -106,12 +106,21 @@ in
|
||||||
pw=$(${pkgs.zenity}/bin/zenity --password --title="WiFi Password" 2>/dev/null)
|
pw=$(${pkgs.zenity}/bin/zenity --password --title="WiFi Password" 2>/dev/null)
|
||||||
[ -n "$pw" ] && ${pkgs.networkmanager}/bin/nmcli device wifi connect "$ssid" password "$pw"
|
[ -n "$pw" ] && ${pkgs.networkmanager}/bin/nmcli device wifi connect "$ssid" password "$pw"
|
||||||
'';
|
'';
|
||||||
# Screen recording, driven by the quick-settings card. The shell execs
|
# Screen recording, driven by the quick-settings card. $1 is a slurp
|
||||||
# into wf-recorder so the card's SIGINT lands on the recorder itself —
|
# geometry ("" = whole output), $2 is "audio" or anything else.
|
||||||
# wf-recorder finalises the mp4 on SIGINT, killing a wrapper instead
|
#
|
||||||
# would leave a truncated file. The path is echoed before the exec so
|
# The region pick deliberately does NOT happen here. A wrapper parked
|
||||||
# the card can offer the result. Audio means the default sink's monitor
|
# in $(slurp) is a process the card cannot stop: non-interactive bash
|
||||||
# (desktop sound), not the microphone.
|
# defers a signal until its foreground child exits, so Stop did
|
||||||
|
# nothing, and the wrapper's lifetime made the card claim it was
|
||||||
|
# recording while slurp was still waiting for a drag. The card runs
|
||||||
|
# slurp itself and passes the result in.
|
||||||
|
#
|
||||||
|
# Everything before the exec is a few ms of setup, so the process the
|
||||||
|
# card signals is wf-recorder itself — which finalises the mp4 on
|
||||||
|
# SIGINT, where killing a wrapper would leave a truncated file. The
|
||||||
|
# path is echoed first so the card can offer the result. Audio means
|
||||||
|
# the default sink's monitor (desktop sound), not the microphone.
|
||||||
screenRecScript = pkgs.writeShellScript "screen-rec" ''
|
screenRecScript = pkgs.writeShellScript "screen-rec" ''
|
||||||
out="$HOME/Videos/rec-$(${pkgs.coreutils}/bin/date +%Y%m%d-%H%M%S).mp4"
|
out="$HOME/Videos/rec-$(${pkgs.coreutils}/bin/date +%Y%m%d-%H%M%S).mp4"
|
||||||
${pkgs.coreutils}/bin/mkdir -p "$HOME/Videos"
|
${pkgs.coreutils}/bin/mkdir -p "$HOME/Videos"
|
||||||
|
|
@ -121,12 +130,10 @@ in
|
||||||
| ${pkgs.gnused}/bin/sed -n 's/.*node\.name = "\(.*\)".*/\1/p')
|
| ${pkgs.gnused}/bin/sed -n 's/.*node\.name = "\(.*\)".*/\1/p')
|
||||||
[ -n "$sink" ] && audio="--audio=$sink.monitor"
|
[ -n "$sink" ] && audio="--audio=$sink.monitor"
|
||||||
fi
|
fi
|
||||||
if [ "$1" = region ]; then
|
|
||||||
geom=$(${pkgs.slurp}/bin/slurp) || exit 1
|
|
||||||
echo "$out"
|
|
||||||
exec ${pkgs.wf-recorder}/bin/wf-recorder $audio -g "$geom" -f "$out"
|
|
||||||
fi
|
|
||||||
echo "$out"
|
echo "$out"
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
exec ${pkgs.wf-recorder}/bin/wf-recorder $audio -g "$1" -f "$out"
|
||||||
|
fi
|
||||||
exec ${pkgs.wf-recorder}/bin/wf-recorder $audio -f "$out"
|
exec ${pkgs.wf-recorder}/bin/wf-recorder $audio -f "$out"
|
||||||
'';
|
'';
|
||||||
nmcli = "${pkgs.networkmanager}/bin/nmcli";
|
nmcli = "${pkgs.networkmanager}/bin/nmcli";
|
||||||
|
|
@ -1417,6 +1424,7 @@ in
|
||||||
readonly property string wxFetch: "${wxFetchScript}"
|
readonly property string wxFetch: "${wxFetchScript}"
|
||||||
readonly property string xdgOpen: "${pkgs.xdg-utils}/bin/xdg-open"
|
readonly property string xdgOpen: "${pkgs.xdg-utils}/bin/xdg-open"
|
||||||
readonly property string screenRec: "${screenRecScript}"
|
readonly property string screenRec: "${screenRecScript}"
|
||||||
|
readonly property string slurp: "${pkgs.slurp}/bin/slurp"
|
||||||
readonly property string themeApply: "${themeApply}"
|
readonly property string themeApply: "${themeApply}"
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
@ -6259,19 +6267,53 @@ in
|
||||||
Card {
|
Card {
|
||||||
id: recCard
|
id: recCard
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
// recProc is wf-recorder itself, so `recording` is
|
||||||
|
// true only while frames are actually being
|
||||||
|
// written — the region pick is its own state.
|
||||||
readonly property bool recording: recProc.running
|
readonly property bool recording: recProc.running
|
||||||
|
readonly property bool selecting: slurpProc.running
|
||||||
property bool audio: false
|
property bool audio: false
|
||||||
property int elapsed: 0
|
property int elapsed: 0
|
||||||
property string lastFile: ""
|
property string lastFile: ""
|
||||||
|
|
||||||
function start(mode) {
|
function start(mode) {
|
||||||
|
if (recording || selecting) return;
|
||||||
lastFile = "";
|
lastFile = "";
|
||||||
elapsed = 0;
|
// slurp wants the screen to itself, and the
|
||||||
recProc.command = [Commands.screenRec, mode, recCard.audio ? "audio" : "silent"];
|
// panel would otherwise be in frame.
|
||||||
recProc.running = true;
|
|
||||||
// The panel would otherwise be in the shot —
|
|
||||||
// and slurp needs the screen to itself.
|
|
||||||
qsDropdown.animateClose();
|
qsDropdown.animateClose();
|
||||||
|
if (mode === "region") slurpProc.running = true;
|
||||||
|
else launch("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function launch(geom) {
|
||||||
|
elapsed = 0;
|
||||||
|
recProc.command = [Commands.screenRec, geom, recCard.audio ? "audio" : "silent"];
|
||||||
|
recProc.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region pick lives here, not in the script: bash
|
||||||
|
// blocked in $(slurp) swallows the Stop signal and
|
||||||
|
// makes the card claim it is recording while slurp
|
||||||
|
// is still waiting for a drag.
|
||||||
|
Process {
|
||||||
|
id: slurpProc
|
||||||
|
command: [Commands.slurp]
|
||||||
|
property string geom: ""
|
||||||
|
// SplitParser, not StdioCollector: the line has
|
||||||
|
// to be in hand by the time exited fires.
|
||||||
|
stdout: SplitParser { onRead: data => slurpProc.geom = data.trim() }
|
||||||
|
// Cancelled with Escape or right-click: slurp
|
||||||
|
// prints nothing, so an empty geom is the
|
||||||
|
// cancel signal and nothing starts. (Watching
|
||||||
|
// running, not exited: exited's exitStatus
|
||||||
|
// parameter is a C++ enum qmllint can't
|
||||||
|
// resolve, and the exit code adds nothing.)
|
||||||
|
onRunningChanged: {
|
||||||
|
if (running) return;
|
||||||
|
if (geom !== "") recCard.launch(geom);
|
||||||
|
geom = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
|
|
@ -6300,13 +6342,15 @@ in
|
||||||
SIcon {
|
SIcon {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "video"
|
text: "video"
|
||||||
color: recCard.recording ? Theme.err : Theme.base05
|
color: recCard.recording ? Theme.err
|
||||||
|
: recCard.selecting ? Theme.base0D : Theme.base05
|
||||||
font.pixelSize: 15
|
font.pixelSize: 15
|
||||||
}
|
}
|
||||||
SText {
|
SText {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: recCard.recording
|
text: recCard.recording
|
||||||
? Math.floor(recCard.elapsed / 60) + ":" + ("0" + (recCard.elapsed % 60)).slice(-2)
|
? Math.floor(recCard.elapsed / 60) + ":" + ("0" + (recCard.elapsed % 60)).slice(-2)
|
||||||
|
: recCard.selecting ? "Drag a region"
|
||||||
: "Screen Recording"
|
: "Screen Recording"
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
@ -6316,7 +6360,7 @@ in
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 6
|
spacing: 6
|
||||||
visible: !recCard.recording
|
visible: !recCard.recording && !recCard.selecting
|
||||||
SText {
|
SText {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Audio"
|
text: "Audio"
|
||||||
|
|
@ -6334,7 +6378,7 @@ in
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 6
|
spacing: 6
|
||||||
visible: !recCard.recording
|
visible: !recCard.recording && !recCard.selecting
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: [
|
model: [
|
||||||
|
|
@ -6369,26 +6413,35 @@ in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Doubles as the way out of a region pick: if slurp
|
||||||
|
// ever comes up without input (its grab can be
|
||||||
|
// dropped the same way the bar's is), Escape never
|
||||||
|
// reaches it and this is the only escape hatch.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: recCard.recording
|
visible: recCard.recording || recCard.selecting
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 26
|
height: 26
|
||||||
radius: Theme.radiusSmall
|
radius: Theme.radiusSmall
|
||||||
color: Theme.err
|
color: recCard.recording ? Theme.err : Theme.base02t
|
||||||
|
border.color: Theme.base03
|
||||||
|
border.width: recCard.recording ? 0 : 1
|
||||||
|
|
||||||
SText {
|
SText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Stop"
|
text: recCard.recording ? "Stop" : "Cancel"
|
||||||
font.pixelSize: 11
|
font.pixelSize: 11
|
||||||
color: Theme.base00
|
color: recCard.recording ? Theme.base00 : Theme.base05
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
// SIGINT, not kill — wf-recorder writes the
|
onClicked: {
|
||||||
// container trailer on it.
|
// SIGINT, not kill — wf-recorder writes
|
||||||
onClicked: recProc.signal(2)
|
// the container trailer on it.
|
||||||
|
if (recCard.recording) recProc.signal(2);
|
||||||
|
else slurpProc.running = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue