quickshell: screen recorder card in quick settings
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
d725e68d1e
commit
11a856800a
1 changed files with 186 additions and 1 deletions
|
|
@ -106,6 +106,29 @@ in
|
|||
pw=$(${pkgs.zenity}/bin/zenity --password --title="WiFi Password" 2>/dev/null)
|
||||
[ -n "$pw" ] && ${pkgs.networkmanager}/bin/nmcli device wifi connect "$ssid" password "$pw"
|
||||
'';
|
||||
# Screen recording, driven by the quick-settings card. The shell execs
|
||||
# into wf-recorder so the card's SIGINT lands on the recorder itself —
|
||||
# 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 card can offer the result. Audio means the default sink's monitor
|
||||
# (desktop sound), not the microphone.
|
||||
screenRecScript = pkgs.writeShellScript "screen-rec" ''
|
||||
out="$HOME/Videos/rec-$(${pkgs.coreutils}/bin/date +%Y%m%d-%H%M%S).mp4"
|
||||
${pkgs.coreutils}/bin/mkdir -p "$HOME/Videos"
|
||||
audio=""
|
||||
if [ "$2" = audio ]; then
|
||||
sink=$(${pkgs.wireplumber}/bin/wpctl inspect @DEFAULT_AUDIO_SINK@ \
|
||||
| ${pkgs.gnused}/bin/sed -n 's/.*node\.name = "\(.*\)".*/\1/p')
|
||||
[ -n "$sink" ] && audio="--audio=$sink.monitor"
|
||||
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"
|
||||
exec ${pkgs.wf-recorder}/bin/wf-recorder $audio -f "$out"
|
||||
'';
|
||||
nmcli = "${pkgs.networkmanager}/bin/nmcli";
|
||||
# Follow stylix's sans choice so a font swap propagates to the bar
|
||||
sansFont = osConfig.stylix.fonts.sansSerif.name;
|
||||
|
|
@ -1393,6 +1416,7 @@ in
|
|||
readonly property string askFetch: "${askFetchScript}"
|
||||
readonly property string wxFetch: "${wxFetchScript}"
|
||||
readonly property string xdgOpen: "${pkgs.xdg-utils}/bin/xdg-open"
|
||||
readonly property string screenRec: "${screenRecScript}"
|
||||
readonly property string themeApply: "${themeApply}"
|
||||
}
|
||||
'';
|
||||
|
|
@ -4690,7 +4714,11 @@ in
|
|||
// centerIn, not verticalCenter: the cell used to be
|
||||
// qsIcon.width so horizontal centring was implicit.
|
||||
anchors.centerIn: parent
|
||||
text: "moon"
|
||||
// Doubles as the recording indicator: with the
|
||||
// dropdown shut, this dot is the only sign that
|
||||
// wf-recorder is still running.
|
||||
text: recCard.recording ? "circle-dot" : "moon"
|
||||
color: recCard.recording ? Theme.err : Theme.base05
|
||||
font.pixelSize: 18
|
||||
}
|
||||
|
||||
|
|
@ -6223,6 +6251,163 @@ in
|
|||
anchors.centerIn: parent
|
||||
width: 240
|
||||
spacing: 8
|
||||
|
||||
// Screen recorder. wf-recorder is held as a live
|
||||
// Process (not execDetached) so Stop can SIGINT it;
|
||||
// slurp's region pick happens inside the script,
|
||||
// before the shell execs into the recorder.
|
||||
Card {
|
||||
id: recCard
|
||||
width: parent.width
|
||||
readonly property bool recording: recProc.running
|
||||
property bool audio: false
|
||||
property int elapsed: 0
|
||||
property string lastFile: ""
|
||||
|
||||
function start(mode) {
|
||||
lastFile = "";
|
||||
elapsed = 0;
|
||||
recProc.command = [Commands.screenRec, mode, recCard.audio ? "audio" : "silent"];
|
||||
recProc.running = true;
|
||||
// The panel would otherwise be in the shot —
|
||||
// and slurp needs the screen to itself.
|
||||
qsDropdown.animateClose();
|
||||
}
|
||||
|
||||
Process {
|
||||
id: recProc
|
||||
stdout: StdioCollector {
|
||||
// First line is the path the script chose;
|
||||
// wf-recorder's own chatter goes to stderr.
|
||||
onStreamFinished: recCard.lastFile = text.trim().split("\n")[0]
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: recCard.recording
|
||||
onTriggered: recCard.elapsed++
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 20
|
||||
|
||||
Row {
|
||||
spacing: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
SIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "video"
|
||||
color: recCard.recording ? Theme.err : Theme.base05
|
||||
font.pixelSize: 15
|
||||
}
|
||||
SText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: recCard.recording
|
||||
? Math.floor(recCard.elapsed / 60) + ":" + ("0" + (recCard.elapsed % 60)).slice(-2)
|
||||
: "Screen Recording"
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 6
|
||||
visible: !recCard.recording
|
||||
SText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Audio"
|
||||
color: Theme.base04
|
||||
font.pixelSize: 10
|
||||
}
|
||||
ToggleSwitch {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
on: recCard.audio
|
||||
onToggled: (v) => recCard.audio = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
visible: !recCard.recording
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ key: "region", label: "Region" },
|
||||
{ key: "screen", label: "Full screen" }
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
width: (parent.width - 6) / 2
|
||||
height: 26
|
||||
radius: Theme.radiusSmall
|
||||
color: recBtn.containsMouse ? Theme.base02 : Theme.base02t
|
||||
Behavior on color { ColorAnimation { duration: Theme.animFade } }
|
||||
border.color: Theme.base03
|
||||
border.width: 1
|
||||
|
||||
SText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.label
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: recBtn
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: recCard.start(modelData.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: recCard.recording
|
||||
width: parent.width
|
||||
height: 26
|
||||
radius: Theme.radiusSmall
|
||||
color: Theme.err
|
||||
|
||||
SText {
|
||||
anchors.centerIn: parent
|
||||
text: "Stop"
|
||||
font.pixelSize: 11
|
||||
color: Theme.base00
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
// SIGINT, not kill — wf-recorder writes the
|
||||
// container trailer on it.
|
||||
onClicked: recProc.signal(2)
|
||||
}
|
||||
}
|
||||
|
||||
SText {
|
||||
visible: !recCard.recording && recCard.lastFile !== ""
|
||||
width: parent.width
|
||||
elide: Text.ElideMiddle
|
||||
text: "Saved " + recCard.lastFile.split("/").pop()
|
||||
color: Theme.base04
|
||||
font.pixelSize: 10
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Quickshell.execDetached([Commands.xdgOpen, recCard.lastFile])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NightlightCard { width: parent.width }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue