qs: add replay functionality (yet disabled)
This commit is contained in:
@@ -29,6 +29,7 @@ binds {
|
|||||||
Mod+Shift+K { spawn-sh "quickshell-kill || quickshell"; }
|
Mod+Shift+K { spawn-sh "quickshell-kill || quickshell"; }
|
||||||
Mod+I { spawn "qs" "ipc" "call" "idleInhibitor" "toggle"; }
|
Mod+I { spawn "qs" "ipc" "call" "idleInhibitor" "toggle"; }
|
||||||
Mod+Alt+R { spawn "qs" "ipc" "call" "recording" "startOrStop"; }
|
Mod+Alt+R { spawn "qs" "ipc" "call" "recording" "startOrStop"; }
|
||||||
|
Mod+Alt+G { spawn "qs" "ipc" "call" "recording" "saveReplay"; }
|
||||||
Mod+Shift+E { spawn "qs" "ipc" "call" "sunset" "toggle"; }
|
Mod+Shift+E { spawn "qs" "ipc" "call" "sunset" "toggle"; }
|
||||||
Mod+X { spawn "qs" "ipc" "call" "notes" "openRecent"; }
|
Mod+X { spawn "qs" "ipc" "call" "notes" "openRecent"; }
|
||||||
Mod+Shift+X { spawn "qs" "ipc" "call" "notes" "create"; }
|
Mod+Shift+X { spawn "qs" "ipc" "call" "notes" "create"; }
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ environment {
|
|||||||
XCURSOR_THEME "Bibata-Modern-Ice"
|
XCURSOR_THEME "Bibata-Modern-Ice"
|
||||||
XCURSOR_SIZE "24"
|
XCURSOR_SIZE "24"
|
||||||
ELECTRON_OZONE_PLATFORM_HINT "wayland"
|
ELECTRON_OZONE_PLATFORM_HINT "wayland"
|
||||||
QSG_RHI_BACKEND "vulkan"
|
// QSG_RHI_BACKEND "vulkan"
|
||||||
// GSK_RENDERER "vulkan"
|
// GSK_RENDERER "vulkan"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
screenshot-path "~/Pictures/Screenshots/niri_screenshot_%Y-%m-%d_%H-%M-%S.png"
|
screenshot-path "~/Pictures/Screenshots/niri_screenshot_%Y-%m-%d_%H-%M-%S.png"
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
render-drm-device "/dev/dri/renderD128"
|
render-drm-device "/dev/dri/renderD129"
|
||||||
}
|
}
|
||||||
|
|
||||||
// gestures {
|
// gestures {
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
|
environment {
|
||||||
|
__NV_PRIME_RENDER_OFFLOAD "1"
|
||||||
|
__VK_LAYER_NV_optimus "NVIDIA_only"
|
||||||
|
__GLX_VENDOR_LIBRARY_NAME "nvidia"
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ Item {
|
|||||||
RecordService.startOrStop();
|
RecordService.startOrStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveReplay() {
|
||||||
|
RecordService.stopReplay();
|
||||||
|
}
|
||||||
|
|
||||||
target: "recording"
|
target: "recording"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,33 @@ import qs.Utils
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
|
// Connections {
|
||||||
|
// function onSinkChanged() {
|
||||||
|
// if (!isReplayInitStarted && AudioService.sink) {
|
||||||
|
// Logger.i("RecordService", "Audio sink available, starting replay buffer.");
|
||||||
|
// startReplay();
|
||||||
|
// isReplayInitStarted = true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// target: AudioService
|
||||||
|
// }
|
||||||
|
|
||||||
readonly property string recordingDir: Paths.recordingDir
|
readonly property string recordingDir: Paths.recordingDir
|
||||||
property bool isRecording: false
|
property bool isRecording: false
|
||||||
|
property bool isReplayInitStarted: false
|
||||||
|
property bool isReplayStarted: false
|
||||||
property bool isStopping: false
|
property bool isStopping: false
|
||||||
|
property bool isReplayStopping: false
|
||||||
readonly property string codec: "libx264"
|
readonly property string codec: "libx264"
|
||||||
readonly property string container: "mkv"
|
readonly property string container: "mkv"
|
||||||
readonly property string pixelFormat: "yuv420p"
|
readonly property string pixelFormat: "yuv420p"
|
||||||
property string recordingDisplay: ""
|
property string recordingDisplay: ""
|
||||||
|
readonly property int replayDuration: 15
|
||||||
readonly property int framerate: 60
|
readonly property int framerate: 60
|
||||||
readonly property var codecParams: Object.freeze(["preset=ultrafast", "crf=15", "tune=zerolatency", "color_range=tv"])
|
readonly property var codecParams: Object.freeze(["preset=ultrafast", "crf=15", "tune=zerolatency", "color_range=tv"])
|
||||||
readonly property var filterArgs: ""
|
readonly property var filterArgs: ""
|
||||||
|
|
||||||
function getFilename() {
|
function getFilename(prefix = "recording") {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
var year = d.getFullYear();
|
var year = d.getFullYear();
|
||||||
var month = ("0" + (d.getMonth() + 1)).slice(-2);
|
var month = ("0" + (d.getMonth() + 1)).slice(-2);
|
||||||
@@ -26,7 +41,7 @@ Singleton {
|
|||||||
var hours = ("0" + d.getHours()).slice(-2);
|
var hours = ("0" + d.getHours()).slice(-2);
|
||||||
var minutes = ("0" + d.getMinutes()).slice(-2);
|
var minutes = ("0" + d.getMinutes()).slice(-2);
|
||||||
var seconds = ("0" + d.getSeconds()).slice(-2);
|
var seconds = ("0" + d.getSeconds()).slice(-2);
|
||||||
return "recording_" + year + "-" + month + "-" + day + "_" + hours + "." + minutes + "." + seconds + "." + container;
|
return prefix + "_" + year + "-" + month + "-" + day + "_" + hours + "." + minutes + "." + seconds + "." + container;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAudioSink() {
|
function getAudioSink() {
|
||||||
@@ -37,6 +52,15 @@ Singleton {
|
|||||||
return Niri.focusedOutput || null;
|
return Niri.focusedOutput || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPrimaryVideoSource() {
|
||||||
|
for (const screen of Quickshell.screens) {
|
||||||
|
if (screen.name === "DP-1")
|
||||||
|
return screen.name;
|
||||||
|
|
||||||
|
}
|
||||||
|
return Quickshell.screens.length ? Quickshell.screens[0].name : null;
|
||||||
|
}
|
||||||
|
|
||||||
function startOrStop() {
|
function startOrStop() {
|
||||||
if (isRecording)
|
if (isRecording)
|
||||||
stop();
|
stop();
|
||||||
@@ -76,7 +100,7 @@ Singleton {
|
|||||||
Logger.e("RecordService", "No audio sink available.");
|
Logger.e("RecordService", "No audio sink available.");
|
||||||
return ;
|
return ;
|
||||||
}
|
}
|
||||||
recordProcess.filePath = recordingDir + getFilename();
|
recordProcess.filePath = recordingDir + getFilename("recording");
|
||||||
recordProcess.command = ["wf-recorder", "--audio=" + audioSink, "-o", source, "--codec", codec, "--pixel-format", pixelFormat, "--framerate", framerate.toString(), "-f", recordProcess.filePath];
|
recordProcess.command = ["wf-recorder", "--audio=" + audioSink, "-o", source, "--codec", codec, "--pixel-format", pixelFormat, "--framerate", framerate.toString(), "-f", recordProcess.filePath];
|
||||||
for (const param of codecParams) {
|
for (const param of codecParams) {
|
||||||
recordProcess.command.push("-p");
|
recordProcess.command.push("-p");
|
||||||
@@ -87,39 +111,78 @@ Singleton {
|
|||||||
recordProcess.command.push(filterArgs);
|
recordProcess.command.push(filterArgs);
|
||||||
}
|
}
|
||||||
Logger.i("RecordService", "Starting recording with command: " + recordProcess.command.join(" "));
|
Logger.i("RecordService", "Starting recording with command: " + recordProcess.command.join(" "));
|
||||||
recordProcess.onErrorExit = function() {
|
|
||||||
Logger.e("RecordService", "Recording process exited with an error.");
|
|
||||||
SendNotification.show("Recording failed", "An error occurred while trying to record the screen.");
|
|
||||||
};
|
|
||||||
recordProcess.onNormalExit = function() {
|
|
||||||
Logger.i("RecordService", "Recording stopped, file saved to: " + recordProcess.filePath);
|
|
||||||
SendNotification.show("Recording stopped", recordProcess.filePath);
|
|
||||||
};
|
|
||||||
recordProcess.running = true;
|
recordProcess.running = true;
|
||||||
SendNotification.show("Recording started", "Recording to " + recordProcess.filePath);
|
SendNotification.show("Recording started", "Recording to " + recordProcess.filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startReplay() {
|
||||||
|
if (isReplayStarted || isReplayStopping) {
|
||||||
|
Logger.w("RecordService", "Replay buffer already active, cannot start.");
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
isReplayStarted = true;
|
||||||
|
const source = getPrimaryVideoSource();
|
||||||
|
if (!source) {
|
||||||
|
SendNotification.show("Replay buffer failed", "Could not determine which display to record from.");
|
||||||
|
Logger.e("RecordService", "No recording source available for replay buffer.");
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
const audioSink = getAudioSink();
|
||||||
|
if (!audioSink) {
|
||||||
|
SendNotification.show("Replay buffer failed", "No audio sink available to record from.");
|
||||||
|
Logger.e("RecordService", "No audio sink available for replay buffer.");
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
replayProcess.filePath = recordingDir + getFilename("replay");
|
||||||
|
replayProcess.command = ["wf-recorder", "--history", replayDuration, "--audio=" + audioSink, "-o", source, "--codec", codec, "--pixel-format", pixelFormat, "--framerate", framerate.toString(), "-f", replayProcess.filePath];
|
||||||
|
for (const param of codecParams) {
|
||||||
|
replayProcess.command.push("-p");
|
||||||
|
replayProcess.command.push(param);
|
||||||
|
}
|
||||||
|
if (filterArgs !== "") {
|
||||||
|
replayProcess.command.push("-F");
|
||||||
|
replayProcess.command.push(filterArgs);
|
||||||
|
}
|
||||||
|
Logger.i("RecordService", "Starting replay buffer with command: " + replayProcess.command.join(" "));
|
||||||
|
replayProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopReplay() {
|
||||||
|
if (!isReplayStarted) {
|
||||||
|
Logger.w("RecordService", "Replay buffer not active, cannot stop.");
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (isReplayStopping) {
|
||||||
|
Logger.w("RecordService", "Already stopping replay buffer, please wait.");
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
isReplayStopping = true;
|
||||||
|
replayStopTimeout.restart();
|
||||||
|
replayProcess.signal(10); // SIGUSR1
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: function() {
|
||||||
|
if (recordProcess.running)
|
||||||
|
recordProcess.running = false;
|
||||||
|
|
||||||
|
if (replayProcess.running)
|
||||||
|
replayProcess.signal(2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: recordProcess
|
id: recordProcess
|
||||||
|
|
||||||
property string filePath: ""
|
property string filePath: ""
|
||||||
property var onNormalExit: null
|
|
||||||
property var onErrorExit: null
|
|
||||||
|
|
||||||
running: false
|
running: false
|
||||||
onExited: function(exitCode, exitStatus) {
|
onExited: function(exitCode, exitStatus) {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
Logger.i("RecordService", "Recording stopped successfully.");
|
Logger.i("RecordService", "Recording stopped successfully.");
|
||||||
if (onNormalExit) {
|
SendNotification.show("Recording stopped", "File saved to: " + filePath);
|
||||||
onNormalExit();
|
|
||||||
onNormalExit = null;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Logger.e("RecordService", "Recording process exited with error code: " + exitCode);
|
Logger.e("RecordService", "Recording process exited with error code: " + exitCode);
|
||||||
if (onErrorExit) {
|
SendNotification.show("Recording failed", "An error occurred while trying to record the screen.");
|
||||||
onErrorExit();
|
|
||||||
onErrorExit = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
isRecording = false;
|
isRecording = false;
|
||||||
isStopping = false;
|
isStopping = false;
|
||||||
@@ -127,4 +190,50 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: replayProcess
|
||||||
|
|
||||||
|
property string filePath: ""
|
||||||
|
|
||||||
|
running: false
|
||||||
|
onExited: function(exitCode, exitStatus) {
|
||||||
|
replayStopTimeout.stop();
|
||||||
|
if (exitCode === 0) {
|
||||||
|
Logger.i("RecordService", "Replay buffer saved successfully.");
|
||||||
|
SendNotification.show("Replay saved", "File saved to: " + filePath);
|
||||||
|
} else {
|
||||||
|
Logger.e("RecordService", "Replay process exited with error code: " + exitCode);
|
||||||
|
SendNotification.show("Replay failed", "An error occurred while trying to record the replay buffer.");
|
||||||
|
}
|
||||||
|
isReplayStarted = false;
|
||||||
|
isReplayStopping = false;
|
||||||
|
replayRestartTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: replayRestartTimer
|
||||||
|
|
||||||
|
interval: 1000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: function() {
|
||||||
|
if (!isReplayStarted && !isReplayStopping)
|
||||||
|
startReplay();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: replayStopTimeout
|
||||||
|
|
||||||
|
interval: 5000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: function() {
|
||||||
|
if (isReplayStopping) {
|
||||||
|
Logger.w("RecordService", "Replay buffer did not stop in time, killing process.");
|
||||||
|
replayProcess.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,16 @@
|
|||||||
pid=$(pgrep -x quickshell)
|
pid=$(pgrep -x quickshell)
|
||||||
[ -z "$pid" ] && exit 1
|
[ -z "$pid" ] && exit 1
|
||||||
|
|
||||||
for child in $(pgrep -P "$pid" 2>/dev/null); do
|
# for child in $(pgrep -P "$pid" 2>/dev/null); do
|
||||||
kill "$child"
|
# kill "$child"
|
||||||
done
|
# done
|
||||||
|
|
||||||
sleep 0.3
|
children=$(pgrep -P "$pid" 2>/dev/null)
|
||||||
|
|
||||||
kill "$pid"
|
kill "$pid"
|
||||||
|
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
for child in $children; do
|
||||||
|
kill "$child" || true
|
||||||
|
done
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
niri_config_file="$HOME/.config/niri/config/misc.kdl"
|
niri_config_file="$HOME/.config/niri/config/misc.kdl"
|
||||||
prefer_order=(intel nvidia)
|
prefer_order=(nvidia intel)
|
||||||
|
|
||||||
# Get vendor and path of each GPU
|
# Get vendor and path of each GPU
|
||||||
default_card_path="$(find /dev/dri/card* 2>/dev/null | head -n 1)"
|
default_card_path="$(find /dev/dri/card* 2>/dev/null | head -n 1)"
|
||||||
|
|||||||
Reference in New Issue
Block a user