Files

151 lines
5.0 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
Item {
id: service
property var daemon: null
readonly property var pluginData: (daemon && daemon.pluginData) ? daemon.pluginData : ({})
readonly property string saveDirectory: String(pluginData.captureDirectory || "~/Videos/GameBar").trim()
readonly property int fps: Math.max(10, pluginData.captureFps || 60)
readonly property int replaySeconds: Math.max(5, pluginData.captureReplaySeconds || 30)
readonly property string audioSource: String(pluginData.captureAudio || "default_output").trim()
readonly property string quality: String(pluginData.captureQuality || "very_high").trim()
// "" | "recording" | "replay"
property string mode: ""
property string targetMonitor: ""
property string lastFile: ""
property string lastError: ""
property double startedAt: 0
property int elapsedSeconds: 0
readonly property bool recording: mode === "recording"
readonly property bool replayArmed: mode === "replay"
readonly property bool busy: mode !== ""
signal notice(string message)
function expandedDirectory() {
const home = Quickshell.env("HOME") || "";
if (service.saveDirectory.startsWith("~/"))
return home + service.saveDirectory.substring(1);
return service.saveDirectory;
}
function timestampedFile() {
const now = new Date();
const stamp = Qt.formatDateTime(now, "yyyy-MM-dd_HH-mm-ss");
return service.expandedDirectory() + "/GameBar_" + stamp + ".mp4";
}
function baseArgs(monitor) {
return ["-w", monitor, "-f", String(service.fps), "-a", service.audioSource, "-q", service.quality, "-cursor", "no"];
}
function startRecording(monitor) {
if (service.busy)
return;
service.targetMonitor = monitor || "focused";
service.lastFile = service.timestampedFile();
service.lastError = "";
recorder.command = ["sh", "-c", 'mkdir -p "$1" && exec gpu-screen-recorder "${@:2}"', "gameBar", service.expandedDirectory()].concat(service.baseArgs(service.targetMonitor)).concat(["-o", service.lastFile]);
recorder.running = true;
service.mode = "recording";
service.startedAt = Date.now();
service.elapsedSeconds = 0;
service.notice("Recording started");
}
function startReplay(monitor) {
if (service.busy)
return;
service.targetMonitor = monitor || "focused";
service.lastError = "";
recorder.command = ["sh", "-c", 'mkdir -p "$1" && exec gpu-screen-recorder "${@:2}"', "gameBar", service.expandedDirectory()].concat(service.baseArgs(service.targetMonitor)).concat(["-c", "mp4", "-r", String(service.replaySeconds), "-o", service.expandedDirectory()]);
recorder.running = true;
service.mode = "replay";
service.startedAt = Date.now();
service.elapsedSeconds = 0;
service.notice("Replay buffer armed (" + service.replaySeconds + "s)");
}
// SIGINT stops and saves a recording; in replay mode it just stops.
function stop() {
if (!service.busy)
return;
recorder.signal(2);
service.notice(service.recording ? "Recording saved" : "Replay buffer stopped");
}
// SIGUSR1 writes the last <replaySeconds> to disk, replay mode only.
function saveReplay() {
if (!service.replayArmed)
return;
recorder.signal(10);
service.notice("Replay saved");
}
function toggleRecording(monitor) {
if (service.recording)
service.stop();
else if (!service.busy)
service.startRecording(monitor);
}
function toggleReplay(monitor) {
if (service.replayArmed)
service.stop();
else if (!service.busy)
service.startReplay(monitor);
}
function screenshot(mode) {
// Delegate to the quickCapture plugin when it is installed.
Quickshell.execDetached(["dms", "ipc", "call", "quickCapture", "screenshot", mode || "region", "edit"]);
}
Process {
id: recorder
running: false
stderr: StdioCollector {
onStreamFinished: {
const raw = String(text || "").trim();
if (raw !== "")
service.lastError = raw.split("\n").pop();
}
}
onExited: exitCode => {
service.mode = "";
service.elapsedSeconds = 0;
// A clean exit still prints encoder chatter on stderr; only keep it
// around when the recorder actually failed.
if (exitCode === 0 || exitCode === 130) {
service.lastError = "";
return;
}
if (service.lastError !== "")
service.notice("Recorder: " + service.lastError);
}
}
Timer {
running: service.busy
interval: 1000
repeat: true
onTriggered: service.elapsedSeconds = Math.floor((Date.now() - service.startedAt) / 1000)
}
}