164 lines
5.9 KiB
QML
164 lines
5.9 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Services
|
|
import qs.Utils
|
|
pragma Singleton
|
|
|
|
Singleton {
|
|
readonly property string recordingDir: CacheService.recordingDir
|
|
property bool isRecording: false
|
|
property bool isStopping: false
|
|
readonly property string codec: "av1_nvenc"
|
|
readonly property string container: "mkv"
|
|
readonly property string pixelFormat: "p010le"
|
|
property string recordingDisplay: ""
|
|
readonly property int framerate: 60
|
|
readonly property var codecParams: Object.freeze(["preset=p5", "rc=vbr", "cq=18", "b:v=80M", "maxrate=120M", "bufsize=160M", "color_range=tv"])
|
|
readonly property var filterArgs: Object.freeze([])
|
|
|
|
function getFilename() {
|
|
var d = new Date();
|
|
var year = d.getFullYear();
|
|
var month = ("0" + (d.getMonth() + 1)).slice(-2);
|
|
var day = ("0" + d.getDate()).slice(-2);
|
|
var hours = ("0" + d.getHours()).slice(-2);
|
|
var minutes = ("0" + d.getMinutes()).slice(-2);
|
|
var seconds = ("0" + d.getSeconds()).slice(-2);
|
|
return "recording_" + year + "-" + month + "-" + day + "_" + hours + "." + minutes + "." + seconds + "." + container;
|
|
}
|
|
|
|
function getAudioSink() {
|
|
return AudioService.sink ? AudioService.sink.name + '.monitor' : null; // this works on my machine :)
|
|
}
|
|
|
|
function getVideoSource(callback) {
|
|
if (niriFocusedOutputProcess.running) {
|
|
Logger.warn("RecordService", "Already fetching focused output, returning null.");
|
|
callback(null);
|
|
}
|
|
niriFocusedOutputProcess.onGetName = callback;
|
|
niriFocusedOutputProcess.running = true;
|
|
}
|
|
|
|
function startOrStop() {
|
|
if (isRecording)
|
|
stop();
|
|
else
|
|
start();
|
|
}
|
|
|
|
function stop() {
|
|
if (!isRecording) {
|
|
Logger.warn("RecordService", "Not currently recording, cannot stop.");
|
|
return ;
|
|
}
|
|
if (isStopping) {
|
|
Logger.warn("RecordService", "Already stopping, please wait.");
|
|
return ;
|
|
}
|
|
isStopping = true;
|
|
recordProcess.signal(15);
|
|
}
|
|
|
|
function start() {
|
|
if (isRecording || isStopping) {
|
|
Logger.warn("RecordService", "Already recording, cannot start.");
|
|
return ;
|
|
}
|
|
isRecording = true;
|
|
getVideoSource((source) => {
|
|
if (!source) {
|
|
SendNotification.show("Recording failed", "Could not determine which display to record from.");
|
|
return ;
|
|
}
|
|
recordingDisplay = source;
|
|
const audioSink = getAudioSink();
|
|
if (!audioSink) {
|
|
SendNotification.show("Recording failed", "No audio sink available to record from.");
|
|
return ;
|
|
}
|
|
recordProcess.filePath = recordingDir + getFilename();
|
|
recordProcess.command = ["wf-recorder", "--audio=" + audioSink, "-o", source, "--codec", codec, "--pixel-format", pixelFormat, "--framerate", framerate.toString(), "-f", recordProcess.filePath];
|
|
for (const param of codecParams) {
|
|
recordProcess.command.push("-p");
|
|
recordProcess.command.push(param);
|
|
}
|
|
for (const filter of filterArgs) {
|
|
recordProcess.command.push("-F");
|
|
recordProcess.command.push(filter);
|
|
}
|
|
Logger.log("RecordService", "Starting recording with command: " + recordProcess.command.join(" "));
|
|
recordProcess.onErrorExit = function() {
|
|
SendNotification.show("Recording failed", "An error occurred while trying to record the screen.");
|
|
};
|
|
recordProcess.onNormalExit = function() {
|
|
SendNotification.show("Recording stopped", recordProcess.filePath);
|
|
};
|
|
recordProcess.running = true;
|
|
SendNotification.show("Recording started", "Recording to " + recordProcess.filePath);
|
|
});
|
|
}
|
|
|
|
Process {
|
|
id: recordProcess
|
|
|
|
property string filePath: ""
|
|
property var onNormalExit: null
|
|
property var onErrorExit: null
|
|
|
|
running: false
|
|
onExited: function(exitCode, exitStatus) {
|
|
if (exitCode === 0) {
|
|
Logger.log("RecordService", "Recording stopped successfully.");
|
|
if (onNormalExit) {
|
|
onNormalExit();
|
|
onNormalExit = null;
|
|
}
|
|
} else {
|
|
Logger.error("RecordService", "Recording process exited with error code: " + exitCode);
|
|
if (onErrorExit) {
|
|
onErrorExit();
|
|
onErrorExit = null;
|
|
}
|
|
}
|
|
isRecording = false;
|
|
isStopping = false;
|
|
recordingDisplay = "";
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: niriFocusedOutputProcess
|
|
|
|
property var onGetName: null
|
|
|
|
running: false
|
|
command: ["niri", "msg", "focused-output"]
|
|
onExited: function(exitCode, exitStatus) {
|
|
if (exitCode !== 0) {
|
|
Logger.error("RecordService", "Failed to get focused output via niri.");
|
|
if (niriFocusedOutputProcess.onGetName) {
|
|
niriFocusedOutputProcess.onGetName(null);
|
|
niriFocusedOutputProcess.onGetName = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
stdout: SplitParser {
|
|
splitMarker: "\n"
|
|
onRead: (data) => {
|
|
if (niriFocusedOutputProcess.onGetName) {
|
|
const parts = data.split(' ');
|
|
const name = parts.length > 0 ? parts[parts.length - 1].slice(1)?.slice(0, -1) : null;
|
|
name ? Logger.log("RecordService", "Focused output is: " + name) : Logger.warn("RecordService", "No focused output found.");
|
|
niriFocusedOutputProcess.onGetName(name);
|
|
niriFocusedOutputProcess.onGetName = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|