This commit is contained in:
2026-03-13 17:23:49 -05:00
parent 1432d75bb6
commit 381b5700bd
32 changed files with 814 additions and 424 deletions

View File

@@ -4,6 +4,7 @@ import Quickshell.Io
import qs.Commons
import qs.Services.UI
import qs.Services.System
import qs.Services.Compositor
Item {
id: root
@@ -15,6 +16,8 @@ Item {
property bool hasActiveRecording: false
property string outputPath: ""
property bool isAvailable: false
property string detectedMonitor: ""
property bool usePrimeRun: false
// Single reusable Process object
Process {
@@ -33,22 +36,22 @@ Item {
IpcHandler {
target: "plugin:screen-recorder"
function toggle() {
if (root.isAvailable) {
root.toggleRecording()
root.toggleRecording();
}
}
function start() {
if (root.isAvailable && !root.isRecording && !root.isPending) {
root.startRecording()
root.startRecording();
}
}
function stop() {
if (root.isRecording || root.isPending) {
root.stopRecording()
root.stopRecording();
}
}
}
@@ -58,6 +61,7 @@ Item {
readonly property string directory: pluginApi?.pluginSettings?.directory || ""
readonly property string filenamePattern: pluginApi?.pluginSettings?.filenamePattern || "recording_yyyyMMdd_HHmmss"
readonly property string frameRate: pluginApi?.pluginSettings?.frameRate || "60"
readonly property string customFrameRate: pluginApi?.pluginSettings?.customFrameRate || "60"
readonly property string audioCodec: pluginApi?.pluginSettings?.audioCodec || "opus"
readonly property string videoCodec: pluginApi?.pluginSettings?.videoCodec || "h264"
readonly property string quality: pluginApi?.pluginSettings?.quality || "very_high"
@@ -70,86 +74,83 @@ Item {
function buildTooltip() {
if (!isAvailable) {
return pluginApi.tr("messages.not-installed")
return pluginApi.tr("messages.not-installed");
}
if (isPending) {
pluginApi.tr("messages.started")
pluginApi.tr("messages.started");
}
if (isRecording) {
return pluginApi.tr("messages.stop-recording")
return pluginApi.tr("messages.stop-recording");
}
return pluginApi.tr("messages.start-recording")
return pluginApi.tr("messages.start-recording");
}
// Start or Stop recording
function toggleRecording() {
(isRecording || isPending) ? stopRecording() : startRecording()
(isRecording || isPending) ? stopRecording() : startRecording();
}
// Open recording file
function openFile(path) {
if (!path) {
return
return;
}
Quickshell.execDetached(["xdg-open", path])
Quickshell.execDetached(["xdg-open", path]);
}
// Copy file to clipboard as file reference
function copyFileToClipboard(filePath) {
if (!filePath) {
return
return;
}
// Convert path to file:// URI format for copying as file reference
const fileUri = "file://" + filePath.replace(/ /g, "%20").replace(/'/g, "%27").replace(/"/g, "%22")
const escapedUri = fileUri.replace(/'/g, "'\\''")
const command = "printf '%s' '" + escapedUri + "' | wl-copy --type text/uri-list"
const fileUri = "file://" + filePath.replace(/ /g, "%20").replace(/'/g, "%27").replace(/"/g, "%22");
const escapedUri = fileUri.replace(/'/g, "'\\''");
const command = "printf '%s' '" + escapedUri + "' | wl-copy --type text/uri-list";
copyToClipboardProcess.exec({
"command": ["sh", "-c", command]
})
});
}
// Start screen recording
function startRecording() {
if (!isAvailable) {
return
return;
}
if (isRecording || isPending) {
return
return;
}
isPending = true
hasActiveRecording = false
isPending = true;
hasActiveRecording = false;
// Close any opened panel
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
PanelService.openedPanel.close()
PanelService.openedPanel.close();
}
// First, ensure xdg-desktop-portal and a compositor portal are running
portalCheckProcess.exec({
"command": ["sh", "-c",
"pidof xdg-desktop-portal >/dev/null 2>&1 && (pidof xdg-desktop-portal-wlr >/dev/null 2>&1 || pidof xdg-desktop-portal-hyprland >/dev/null 2>&1 || pidof xdg-desktop-portal-gnome >/dev/null 2>&1 || pidof xdg-desktop-portal-kde >/dev/null 2>&1)"]
})
"command": ["sh", "-c", "pidof xdg-desktop-portal >/dev/null 2>&1 && (pidof xdg-desktop-portal-wlr >/dev/null 2>&1 || pidof xdg-desktop-portal-hyprland >/dev/null 2>&1 || pidof xdg-desktop-portal-gnome >/dev/null 2>&1 || pidof xdg-desktop-portal-kde >/dev/null 2>&1)"]
});
}
function expandFilenamePattern(pattern) {
var now = new Date()
var now = new Date();
// Known Qt date/time format tokens sorted by length (longest first to match greedily)
var tokens = ['unix', 'MMMM', 'dddd', 'yyyy', 'MMM', 'ddd', 'zzz', 'HH', 'hh', 'mm', 'ss',
'yy', 'MM', 'dd', 'AP', 'ap', 'M', 'd', 'H', 'h', 'm', 's', 'z', 'A', 'a', 't'];
var tokens = ['unix', 'MMMM', 'dddd', 'yyyy', 'MMM', 'ddd', 'zzz', 'HH', 'hh', 'mm', 'ss', 'yy', 'MM', 'dd', 'AP', 'ap', 'M', 'd', 'H', 'h', 'm', 's', 'z', 'A', 'a', 't'];
// Escape literal text by wrapping non-token sequences in single quotes
var escaped = "";
var i = 0;
var literalBuffer = "";
while (i < pattern.length) {
var matched = false;
// Try to match each token at current position (longest first)
for (var j = 0; j < tokens.length; j++) {
var token = tokens[j];
@@ -164,7 +165,7 @@ Item {
continue; // Skip this token match, treat as literal
}
}
// Flush any accumulated literal text
if (literalBuffer) {
escaped += "'" + literalBuffer + "'";
@@ -185,103 +186,117 @@ Item {
break;
}
}
if (!matched) {
// Character is not part of a token, add to literal buffer
literalBuffer += pattern[i];
i++;
}
}
// Flush any remaining literal text
if (literalBuffer) {
escaped += "'" + literalBuffer + "'";
}
// Use Qt's I18n.locale.toString for proper date/time formatting
var expanded = I18n.locale.toString(now, escaped)
return expanded + ".mp4"
var expanded = I18n.locale.toString(now, escaped);
return expanded + ".mp4";
}
function launchRecorder() {
var pattern = filenamePattern || "recording_yyyyMMdd_HHmmss"
var filename = expandFilenamePattern(pattern)
var videoDir = Settings.preprocessPath(directory)
// If focused-monitor is selected, detect monitor first
if (videoSource === "focused-monitor" && CompositorService.isHyprland) {
var script = 'set -euo pipefail\n' + 'pos=$(hyprctl cursorpos)\n' + 'cx=${pos%,*}; cy=${pos#*,}\n' + 'mon=$(hyprctl monitors -j | jq -r --argjson cx "$cx" --argjson cy "$cy" ' + "'.[] | select(($cx>=.x) and ($cx<(.x+.width)) and ($cy>=.y) and ($cy<(.y+.height))) | .name' " + '| head -n1)\n' + '[ -n "${mon:-}" ] || { echo "MONITOR_NOT_FOUND"; exit 1; }\n' + 'use_prime=0\n' + 'for v in /sys/class/drm/card*/device/vendor; do\n' + ' [ -f "$v" ] || continue\n' + ' if grep -qi "0x10de" "$v"; then\n' + ' card="$(basename "$(dirname "$(dirname "$v")")")"\n' + ' [ -e "/sys/class/drm/${card}-${mon}" ] && use_prime=1 && break\n' + ' fi\n' + 'done\n' + 'echo "${mon}:${use_prime}"';
monitorDetectProcess.exec({
"command": ["sh", "-c", script]
});
return;
}
launchRecorderWithSource(videoSource, false);
}
function launchRecorderWithSource(source, primeRun) {
var pattern = filenamePattern || "recording_yyyyMMdd_HHmmss";
var filename = expandFilenamePattern(pattern);
var videoDir = Settings.preprocessPath(directory);
if (!videoDir) {
videoDir = Quickshell.env("HOME") + "/Videos"
videoDir = Quickshell.env("HOME") + "/Videos";
}
if (videoDir && !videoDir.endsWith("/")) {
videoDir += "/"
videoDir += "/";
}
outputPath = videoDir + filename
outputPath = videoDir + filename;
const audioFlags = (() => {
if (audioSource === "none") {
return ""
}
if (audioSource === "both") {
return `-ac ${audioCodec} -a "default_output|default_input"`
}
return `-ac ${audioCodec} -a ${audioSource}`
})()
if (audioSource === "none") {
return "";
}
if (audioSource === "both") {
return `-ac ${audioCodec} -a "default_output|default_input"`;
}
return `-ac ${audioCodec} -a ${audioSource}`;
})();
var resolutionFlag = (resolution !== "original") ? `-s ${resolution}` : ""
var flags = `-w ${videoSource} -f ${frameRate} -k ${videoCodec} ${audioFlags} -q ${quality} -cursor ${showCursor ? "yes" : "no"} -cr ${colorRange} ${resolutionFlag} -o "${outputPath}"`
var actualFrameRate = (frameRate === "custom") ? customFrameRate : frameRate;
var resolutionFlag = (resolution !== "original") ? `-s ${resolution}` : "";
var flags = `-w ${source} -f ${actualFrameRate} -k ${videoCodec} ${audioFlags} -q ${quality} -cursor ${showCursor ? "yes" : "no"} -cr ${colorRange} ${resolutionFlag} -o "${outputPath}"`;
var primePrefix = primeRun ? "prime-run " : "";
var command = `
_gpuscreenrecorder_flatpak_installed() {
flatpak list --app | grep -q "com.dec05eba.gpu_screen_recorder"
}
if command -v gpu-screen-recorder >/dev/null 2>&1; then
gpu-screen-recorder ${flags}
${primePrefix}gpu-screen-recorder ${flags}
elif command -v flatpak >/dev/null 2>&1 && _gpuscreenrecorder_flatpak_installed; then
flatpak run --command=gpu-screen-recorder --file-forwarding com.dec05eba.gpu_screen_recorder ${flags}
${primePrefix}flatpak run --command=gpu-screen-recorder --file-forwarding com.dec05eba.gpu_screen_recorder ${flags}
else
echo "GPU_SCREEN_RECORDER_NOT_INSTALLED"
fi`
fi`;
// Use Process to monitor it and read stderr
recorderProcess.exec({
"command": ["sh", "-c", command]
})
});
// Start monitoring - if process ends quickly, it was likely cancelled
pendingTimer.running = true
pendingTimer.running = true;
}
// Stop recording
function stopRecording() {
if (!isRecording && !isPending) {
return
return;
}
ToastService.showNotice(pluginApi.tr("messages.stopping"), outputPath, "video")
ToastService.showNotice(pluginApi.tr("messages.stopping"), outputPath, "video");
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder' || pkill -SIGINT -f 'com.dec05eba.gpu_screen_recorder'"])
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder' || pkill -SIGINT -f 'com.dec05eba.gpu_screen_recorder'"]);
isRecording = false
isPending = false
pendingTimer.running = false
monitorTimer.running = false
hasActiveRecording = false
isRecording = false;
isPending = false;
pendingTimer.running = false;
monitorTimer.running = false;
hasActiveRecording = false;
// Just in case, force kill after 3 seconds
killTimer.running = true
killTimer.running = true;
}
// Helper function to truncate text for toast display
function truncateForToast(text, maxLength = 128) {
if (text.length <= maxLength) return text
return text.substring(0, maxLength) + "…"
if (text.length <= maxLength)
return text;
return text.substring(0, maxLength) + "…";
}
// Helper function to check if output indicates user cancellation
function isCancelledByUser(stdoutText, stderrText) {
const stdout = String(stdoutText || "").toLowerCase()
const stderr = String(stderrText || "").toLowerCase()
const combined = stdout + " " + stderr
return combined.includes("canceled by") || combined.includes("cancelled by") ||
combined.includes("canceled by user") || combined.includes("cancelled by user") ||
combined.includes("canceled by the user") || combined.includes("cancelled by the user")
const stdout = String(stdoutText || "").toLowerCase();
const stderr = String(stderrText || "").toLowerCase();
const combined = stdout + " " + stderr;
return combined.includes("canceled by") || combined.includes("cancelled by") || combined.includes("canceled by user") || combined.includes("cancelled by user") || combined.includes("canceled by the user") || combined.includes("cancelled by the user");
}
// Process to run and monitor gpu-screen-recorder
@@ -290,59 +305,59 @@ Item {
stdout: StdioCollector {}
stderr: StdioCollector {}
onExited: function (exitCode, exitStatus) {
const stdout = String(recorderProcess.stdout.text || "").trim()
const stderr = String(recorderProcess.stderr.text || "").trim()
const wasCancelled = isCancelledByUser(stdout, stderr)
const stdout = String(recorderProcess.stdout.text || "").trim();
const stderr = String(recorderProcess.stderr.text || "").trim();
const wasCancelled = isCancelledByUser(stdout, stderr);
if (isPending) {
// Process ended while we were pending - likely cancelled or error
isPending = false
pendingTimer.running = false
isPending = false;
pendingTimer.running = false;
// Check if gpu-screen-recorder is not installed
if (stdout === "GPU_SCREEN_RECORDER_NOT_INSTALLED") {
ToastService.showError(pluginApi.tr("messages.not-installed"), pluginApi.tr("messages.not-installed-desc"))
return
ToastService.showError(pluginApi.tr("messages.not-installed"), pluginApi.tr("messages.not-installed-desc"));
return;
}
// If it failed to start, show a clear error toast with stderr
// But don't show error if user intentionally cancelled via portal
if (exitCode !== 0) {
if (stderr.length > 0 && !wasCancelled) {
ToastService.showError(pluginApi.tr("messages.failed-start"), truncateForToast(stderr))
Logger.e("ScreenRecorder", stderr)
ToastService.showError(pluginApi.tr("messages.failed-start"), truncateForToast(stderr));
Logger.e("ScreenRecorder", stderr);
}
}
} else if (isRecording || hasActiveRecording) {
// Process ended normally while recording
isRecording = false
monitorTimer.running = false
isRecording = false;
monitorTimer.running = false;
if (exitCode === 0) {
// ToastService.showNotice(pluginApi.tr("messages.saved"), outputPath, "video")
ToastService.showNotice(pluginApi.tr("messages.saved"), outputPath, "video", 3000, pluginApi.tr("messages.open-file"), () => openFile(outputPath))
ToastService.showNotice(pluginApi.tr("messages.saved"), outputPath, "video", 3000, pluginApi.tr("messages.open-file"), () => openFile(outputPath));
if (copyToClipboard) {
copyFileToClipboard(outputPath)
copyFileToClipboard(outputPath);
}
} else {
// Don't show error if user intentionally cancelled
if (!wasCancelled) {
if (stderr.length > 0) {
ToastService.showError(pluginApi.tr("messages.failed-start"), truncateForToast(stderr))
Logger.e("ScreenRecorder", stderr)
ToastService.showError(pluginApi.tr("messages.failed-start"), truncateForToast(stderr));
Logger.e("ScreenRecorder", stderr);
} else {
ToastService.showError(pluginApi.tr("messages.failed-start"), pluginApi.tr("messages.failed-general"))
ToastService.showError(pluginApi.tr("messages.failed-start"), pluginApi.tr("messages.failed-general"));
}
}
}
hasActiveRecording = false
hasActiveRecording = false;
} else if (!isPending && exitCode === 0 && outputPath) {
// Fallback: if process exited successfully with an outputPath, handle it
// ToastService.showNotice(pluginApi.tr("messages.saved"), outputPath, "video")
ToastService.showNotice(pluginApi.tr("messages.saved"), outputPath, "video", 3000, pluginApi.tr("messages.open-file"), () => openFile(outputPath))
ToastService.showNotice(pluginApi.tr("messages.saved"), outputPath, "video", 3000, pluginApi.tr("messages.open-file"), () => openFile(outputPath));
if (copyToClipboard) {
copyFileToClipboard(outputPath)
copyFileToClipboard(outputPath);
}
}
}
@@ -354,21 +369,49 @@ Item {
onExited: function (exitCode, exitStatus) {
if (exitCode === 0) {
// Portals available, proceed to launch
launchRecorder()
launchRecorder();
} else {
isPending = false
hasActiveRecording = false
ToastService.showError(pluginApi.tr("messages.no-portals"), pluginApi.tr("messages.no-portals-desc"))
isPending = false;
hasActiveRecording = false;
ToastService.showError(pluginApi.tr("messages.no-portals"), pluginApi.tr("messages.no-portals-desc"));
}
}
}
// Detect focused monitor on Hyprland
Process {
id: monitorDetectProcess
stdout: StdioCollector {}
stderr: StdioCollector {}
onExited: function (exitCode, exitStatus) {
const output = String(monitorDetectProcess.stdout.text || "").trim();
if (exitCode !== 0 || output === "MONITOR_NOT_FOUND" || !output) {
isPending = false;
hasActiveRecording = false;
ToastService.showError(pluginApi.tr("messages.failed-start"), pluginApi.tr("messages.monitor-not-found"));
return;
}
// Parse "MONITOR_NAME:USE_PRIME" format
const parts = output.split(":");
const monitorName = parts[0];
const primeRun = parts.length > 1 && parts[1] === "1";
detectedMonitor = monitorName;
usePrimeRun = primeRun;
Logger.i("ScreenRecorder", "Detected monitor: " + monitorName + (primeRun ? " (prime-run)" : ""));
launchRecorderWithSource(monitorName, primeRun);
}
}
// Process to copy file to clipboard
Process {
id: copyToClipboardProcess
onExited: function (exitCode, exitStatus) {
if (exitCode !== 0) {
Logger.e("ScreenRecorder", "Failed to copy file to clipboard, exit code:", exitCode)
Logger.e("ScreenRecorder", "Failed to copy file to clipboard, exit code:", exitCode);
}
}
}
@@ -381,13 +424,13 @@ Item {
onTriggered: {
if (isPending && recorderProcess.running) {
// Process is still running after 2 seconds - assume recording started successfully
isPending = false
isRecording = true
hasActiveRecording = true
monitorTimer.running = true
isPending = false;
isRecording = true;
hasActiveRecording = true;
monitorTimer.running = true;
} else if (isPending) {
// Process not running anymore - was cancelled or failed
isPending = false
isPending = false;
}
}
}
@@ -400,8 +443,8 @@ Item {
repeat: true
onTriggered: {
if (!recorderProcess.running && isRecording) {
isRecording = false
running = false
isRecording = false;
running = false;
}
}
}
@@ -412,7 +455,7 @@ Item {
running: false
repeat: false
onTriggered: {
Quickshell.execDetached(["sh", "-c", "pkill -9 -f 'gpu-screen-recorder' 2>/dev/null || pkill -9 -f 'com.dec05eba.gpu_screen_recorder' 2>/dev/null || true"])
Quickshell.execDetached(["sh", "-c", "pkill -9 -f 'gpu-screen-recorder' 2>/dev/null || pkill -9 -f 'com.dec05eba.gpu_screen_recorder' 2>/dev/null || true"]);
}
}
}

View File

@@ -1,9 +1,11 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Widgets
import qs.Services.UI
import qs.Services.Compositor
ColumnLayout {
id: root
@@ -11,80 +13,56 @@ ColumnLayout {
property var pluginApi: null
property bool editHideInactive:
pluginApi?.pluginSettings?.hideInactive ??
pluginApi?.manifest?.metadata?.defaultSettings?.hideInactive ??
false
property bool editHideInactive: pluginApi?.pluginSettings?.hideInactive ?? pluginApi?.manifest?.metadata?.defaultSettings?.hideInactive ?? false
property string editIconColor:
pluginApi?.pluginSettings?.iconColor ??
pluginApi?.manifest?.metadata?.defaultSettings?.iconColor ??
"none"
property string editIconColor: pluginApi?.pluginSettings?.iconColor ?? pluginApi?.manifest?.metadata?.defaultSettings?.iconColor ?? "none"
property string editDirectory:
pluginApi?.pluginSettings?.directory ||
pluginApi?.manifest?.metadata?.defaultSettings?.directory ||
""
property string editDirectory: pluginApi?.pluginSettings?.directory || pluginApi?.manifest?.metadata?.defaultSettings?.directory || ""
property string editFilenamePattern:
pluginApi?.pluginSettings?.filenamePattern ||
pluginApi?.manifest?.metadata?.defaultSettings?.filenamePattern ||
"recording_yyyyMMdd_HHmmss"
property string editFilenamePattern: pluginApi?.pluginSettings?.filenamePattern || pluginApi?.manifest?.metadata?.defaultSettings?.filenamePattern || "recording_yyyyMMdd_HHmmss"
// Migrate legacy frame rates to "custom"
readonly property var _validFrameRates: ["30", "60", "120", "custom"]
readonly property string _rawFrameRate:
pluginApi?.pluginSettings?.frameRate ||
pluginApi?.manifest?.metadata?.defaultSettings?.frameRate ||
"60"
property string editFrameRate:
pluginApi?.pluginSettings?.frameRate ||
pluginApi?.manifest?.metadata?.defaultSettings?.frameRate ||
"60"
_validFrameRates.includes(_rawFrameRate) ? _rawFrameRate : "custom"
property string editCustomFrameRate:
_validFrameRates.includes(_rawFrameRate)
? (pluginApi?.pluginSettings?.customFrameRate ||
pluginApi?.manifest?.metadata?.defaultSettings?.customFrameRate ||
"60")
: _rawFrameRate
property string editAudioCodec:
pluginApi?.pluginSettings?.audioCodec ||
pluginApi?.manifest?.metadata?.defaultSettings?.audioCodec ||
"opus"
property string editVideoCodec:
pluginApi?.pluginSettings?.videoCodec ||
pluginApi?.manifest?.metadata?.defaultSettings?.videoCodec ||
"h264"
property string editVideoCodec: pluginApi?.pluginSettings?.videoCodec || pluginApi?.manifest?.metadata?.defaultSettings?.videoCodec || "h264"
property string editQuality:
pluginApi?.pluginSettings?.quality ||
pluginApi?.manifest?.metadata?.defaultSettings?.quality ||
"very_high"
property string editQuality: pluginApi?.pluginSettings?.quality || pluginApi?.manifest?.metadata?.defaultSettings?.quality || "very_high"
property string editColorRange:
pluginApi?.pluginSettings?.colorRange ||
pluginApi?.manifest?.metadata?.defaultSettings?.colorRange ||
"limited"
property string editColorRange: pluginApi?.pluginSettings?.colorRange || pluginApi?.manifest?.metadata?.defaultSettings?.colorRange || "limited"
property bool editShowCursor:
pluginApi?.pluginSettings?.showCursor ??
pluginApi?.manifest?.metadata?.defaultSettings?.showCursor ??
true
property bool editShowCursor: pluginApi?.pluginSettings?.showCursor ?? pluginApi?.manifest?.metadata?.defaultSettings?.showCursor ?? true
property bool editCopyToClipboard:
pluginApi?.pluginSettings?.copyToClipboard ??
pluginApi?.manifest?.metadata?.defaultSettings?.copyToClipboard ??
false
property bool editCopyToClipboard: pluginApi?.pluginSettings?.copyToClipboard ?? pluginApi?.manifest?.metadata?.defaultSettings?.copyToClipboard ?? false
property string editAudioSource:
pluginApi?.pluginSettings?.audioSource ||
pluginApi?.manifest?.metadata?.defaultSettings?.audioSource ||
"default_output"
property string editAudioSource: pluginApi?.pluginSettings?.audioSource || pluginApi?.manifest?.metadata?.defaultSettings?.audioSource || "default_output"
property string editVideoSource:
pluginApi?.pluginSettings?.videoSource ||
pluginApi?.manifest?.metadata?.defaultSettings?.videoSource ||
"portal"
property string editVideoSource: pluginApi?.pluginSettings?.videoSource || pluginApi?.manifest?.metadata?.defaultSettings?.videoSource || "portal"
property string editResolution:
pluginApi?.pluginSettings?.resolution ||
pluginApi?.manifest?.metadata?.defaultSettings?.resolution ||
"original"
property string editResolution: pluginApi?.pluginSettings?.resolution || pluginApi?.manifest?.metadata?.defaultSettings?.resolution || "original"
function saveSettings() {
if (!pluginApi) {
Logger.e("ScreenRecorder", "Cannot save: pluginApi is null")
return
Logger.e("ScreenRecorder", "Cannot save: pluginApi is null");
return;
}
pluginApi.pluginSettings.hideInactive = root.editHideInactive
@@ -92,6 +70,7 @@ ColumnLayout {
pluginApi.pluginSettings.directory = root.editDirectory
pluginApi.pluginSettings.filenamePattern = root.editFilenamePattern
pluginApi.pluginSettings.frameRate = root.editFrameRate
pluginApi.pluginSettings.customFrameRate = root.editCustomFrameRate
pluginApi.pluginSettings.audioCodec = root.editAudioCodec
pluginApi.pluginSettings.videoCodec = root.editVideoCodec
pluginApi.pluginSettings.quality = root.editQuality
@@ -102,9 +81,9 @@ ColumnLayout {
pluginApi.pluginSettings.videoSource = root.editVideoSource
pluginApi.pluginSettings.resolution = root.editResolution
pluginApi.saveSettings()
pluginApi.saveSettings();
Logger.i("ScreenRecorder", "Settings saved successfully")
Logger.i("ScreenRecorder", "Settings saved successfully");
}
// Icon Color
NComboBox {
@@ -181,16 +160,25 @@ ColumnLayout {
NComboBox {
label: pluginApi.tr("settings.video.source")
description: pluginApi.tr("settings.video.source-desc")
model: [
{
"key": "portal",
"name": pluginApi.tr("settings.video.sources-portal")
},
{
"key": "screen",
"name": pluginApi.tr("settings.video.sources-screen")
model: {
let options = [
{
"key": "portal",
"name": pluginApi.tr("settings.video.sources-portal")
},
{
"key": "screen",
"name": pluginApi.tr("settings.video.sources-screen")
}
];
if (CompositorService.isHyprland) {
options.push({
"key": "focused-monitor",
"name": pluginApi.tr("settings.video.sources-focused-monitor")
});
}
]
return options;
}
currentKey: root.editVideoSource
onSelected: key => root.editVideoSource = key
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.videoSource || "portal"
@@ -201,22 +189,6 @@ ColumnLayout {
label: pluginApi.tr("settings.video.frame-rate")
description: pluginApi.tr("settings.video.frame-rate-desc")
model: [
{
"key": "5",
"name": "5 FPS"
},
{
"key": "10",
"name": "10 FPS"
},
{
"key": "15",
"name": "15 FPS"
},
{
"key": "20",
"name": "20 FPS"
},
{
"key": "30",
"name": "30 FPS"
@@ -225,25 +197,13 @@ ColumnLayout {
"key": "60",
"name": "60 FPS"
},
{
"key": "100",
"name": "100 FPS"
},
{
"key": "120",
"name": "120 FPS"
},
{
"key": "144",
"name": "144 FPS"
},
{
"key": "165",
"name": "165 FPS"
},
{
"key": "240",
"name": "240 FPS"
"key": "custom",
"name": pluginApi.tr("settings.video.frame-rate-custom")
}
]
currentKey: root.editFrameRate
@@ -251,6 +211,26 @@ ColumnLayout {
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.frameRate || "60"
}
// Custom Frame Rate
NTextInput {
visible: root.editFrameRate === "custom"
label: pluginApi.tr("settings.video.custom-frame-rate")
description: pluginApi.tr("settings.video.custom-frame-rate-desc")
placeholderText: "60"
text: root.editCustomFrameRate
onTextChanged: {
// Only allow numeric values
var numeric = text.replace(/[^0-9]/g, '')
if (numeric !== text) {
text = numeric
}
if (numeric) {
root.editCustomFrameRate = numeric
}
}
Layout.fillWidth: true
}
// Video Quality
NComboBox {
label: pluginApi.tr("settings.video.quality")
@@ -284,25 +264,46 @@ ColumnLayout {
description: pluginApi.tr("settings.video.codec-desc")
model: {
let options = [
{"key": "h264", "name": "H264"},
{"key": "hevc", "name": "HEVC"},
{"key": "av1", "name": "AV1"},
{"key": "vp8", "name": "VP8"},
{"key": "vp9", "name": "VP9"}
]
// Only add HDR options if source is 'screen'
if (root.editVideoSource === "screen") {
options.push({"key": "hevc_hdr", "name": "HEVC HDR"})
options.push({"key": "av1_hdr", "name": "AV1 HDR"})
{
"key": "h264",
"name": "H264"
},
{
"key": "hevc",
"name": "HEVC"
},
{
"key": "av1",
"name": "AV1"
},
{
"key": "vp8",
"name": "VP8"
},
{
"key": "vp9",
"name": "VP9"
}
];
// Only add HDR options if source is 'screen' or 'focused-monitor'
if (root.editVideoSource === "screen" || root.editVideoSource === "focused-monitor") {
options.push({
"key": "hevc_hdr",
"name": "HEVC HDR"
});
options.push({
"key": "av1_hdr",
"name": "AV1 HDR"
});
}
return options
return options;
}
currentKey: root.editVideoCodec
onSelected: key => {
root.editVideoCodec = key
root.editVideoCodec = key;
// If an HDR codec is selected, change the colorRange to full
if (key.includes("_hdr")) {
root.editColorRange = "full"
root.editColorRange = "full";
}
}
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.videoCodec || "h264"
@@ -310,8 +311,8 @@ ColumnLayout {
Connections {
target: root
function onEditVideoSourceChanged() {
if (root.editVideoSource !== "screen" && (root.editVideoCodec === "av1_hdr" || root.editVideoCodec === "hevc_hdr")) {
root.editVideoCodec = "h264"
if (root.editVideoSource !== "screen" && root.editVideoSource !== "focused-monitor" && (root.editVideoCodec === "av1_hdr" || root.editVideoCodec === "hevc_hdr")) {
root.editVideoCodec = "h264";
}
}
}
@@ -435,7 +436,7 @@ ColumnLayout {
initialPath: root.editDirectory || Quickshell.env("HOME") + "/Videos"
onAccepted: paths => {
if (paths.length > 0) {
root.editDirectory = paths[0]
root.editDirectory = paths[0];
}
}
}

View File

@@ -13,7 +13,8 @@
"started": "Aufnahme gestartet.",
"stop-recording": "Bildschirmaufnahme (Aufnahme stoppen)",
"stopping": "Aufnahme stoppen…",
"open-file": "Datei öffnen"
"open-file": "Datei öffnen",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Bildschirmrekorder",
"settings": {
@@ -52,7 +53,10 @@
"color-range-full": "Vollständig",
"color-range-limited": "Begrenzt",
"frame-rate": "Bildrate",
"frame-rate-custom": "Benutzerdefiniert",
"frame-rate-desc": "Ziel-Bildrate für Bildschirmaufnahmen",
"custom-frame-rate": "Benutzerdefinierte Bildrate",
"custom-frame-rate-desc": "Geben Sie einen benutzerdefinierten Bildratenwert ein (FPS)",
"quality": "Videoqualität",
"quality-desc": "Höhere Qualität führt zu größeren Dateien",
"quality-high": "Hoch",
@@ -66,7 +70,8 @@
"source-desc": "Portal wird empfohlen, bei Artefakten versuchen Sie Bildschirm",
"sources-portal": "Portal",
"sources-screen": "Bildschirm",
"title": "Videoeinstellungen"
"title": "Videoeinstellungen",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -13,7 +13,8 @@
"started": "Recording started.",
"stop-recording": "Screen Recorder (stop recording)",
"stopping": "Stopping recording…",
"open-file": "Open file"
"open-file": "Open file",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Screen Recorder",
"settings": {
@@ -52,7 +53,10 @@
"color-range-full": "Full",
"color-range-limited": "Limited",
"frame-rate": "Frame rate",
"frame-rate-custom": "Custom",
"frame-rate-desc": "Target frame rate for screen recordings",
"custom-frame-rate": "Custom frame rate",
"custom-frame-rate-desc": "Enter a custom frame rate value (FPS)",
"quality": "Video quality",
"quality-desc": "Higher quality results in larger file sizes",
"quality-high": "High",
@@ -66,6 +70,7 @@
"source-desc": "Portal is recommended, if you get artifacts try Screen",
"sources-portal": "Portal",
"sources-screen": "Screen",
"sources-focused-monitor": "Focused monitor (Hyprland)",
"title": "Video Settings"
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Grabador de pantalla (empezar a grabar)",
"started": "Grabación iniciada.",
"stop-recording": "Grabador de pantalla (detener grabación)",
"stopping": "Deteniendo la grabación…"
"stopping": "Deteniendo la grabación…",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Grabadora de Pantalla",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Completo",
"color-range-limited": "Limitado",
"frame-rate": "Tasa de fotogramas",
"frame-rate-custom": "Personalizado",
"frame-rate-desc": "Tasa de fotogramas objetivo para grabaciones de pantalla",
"custom-frame-rate": "Tasa de fotogramas personalizada",
"custom-frame-rate-desc": "Ingrese un valor de tasa de fotogramas personalizado (FPS)",
"quality": "Calidad de video",
"quality-desc": "Mayor calidad resulta en archivos más grandes",
"quality-high": "Alto",
@@ -63,7 +67,8 @@
"source-desc": "Se recomienda Portal, si obtiene artefactos intente Pantalla",
"sources-portal": "Portal",
"sources-screen": "Pantalla",
"title": "Configuración de Video"
"title": "Configuración de Video",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Enregistreur d'écran (démarrer l'enregistrement)",
"started": "Enregistrement démarré.",
"stop-recording": "Enregistreur d'écran (arrêter l'enregistrement)",
"stopping": "Arrêt de l'enregistrement..."
"stopping": "Arrêt de l'enregistrement...",
"monitor-not-found": "Impossible de détecter l'écran sous le curseur."
},
"name": "Enregistreur d'Écran",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Complet / Plein",
"color-range-limited": "Limité(e)",
"frame-rate": "Fréquence d'images",
"frame-rate-custom": "Personnalisé",
"frame-rate-desc": "Fréquence d'images cible pour les enregistrements d'écran",
"custom-frame-rate": "Fréquence d'images personnalisée",
"custom-frame-rate-desc": "Entrez une valeur de fréquence d'images personnalisée (FPS)",
"quality": "Qualité vidéo",
"quality-desc": "Une qualité supérieure résulte en des fichiers plus volumineux",
"quality-high": "Haut/Haute",
@@ -63,6 +67,7 @@
"source-desc": "Portal est recommandé, si vous obtenez des artefacts essayez Écran",
"sources-portal": "Portail",
"sources-screen": "Écran",
"sources-focused-monitor": "Écran sous le curseur (Hyprland)",
"title": "Paramètres Vidéo"
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Képernyőrögzítő (felvétel indítása)",
"started": "A felvétel elindult.",
"stop-recording": "Képernyőfelvevő (felvétel leállítása)",
"stopping": "A felvétel leállítása…"
"stopping": "A felvétel leállítása…",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Képernyőfelvevő",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Teljes",
"color-range-limited": "Korlátozott",
"frame-rate": "Képkockasebesség",
"frame-rate-custom": "Egyéni",
"frame-rate-desc": "Képernyőfelvételek célzott képkockasebessége",
"custom-frame-rate": "Egyéni képkockasebesség",
"custom-frame-rate-desc": "Adjon meg egyéni képkockasebesség értéket (FPS)",
"quality": "Videóminőség",
"quality-desc": "A jobb minőség nagyobb fájlméretet eredményez",
"quality-high": "Magas",
@@ -63,7 +67,8 @@
"source-desc": "A Portal ajánlott, ha hibákat tapasztalsz, próbáld a Screen-t",
"sources-portal": "Portál",
"sources-screen": "Képernyő",
"title": "Videóbeállítások"
"title": "Videóbeállítások",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "P錄影 (開始錄影)",
"started": "Aufnahme gestartet.",
"stop-recording": "Skjáupptökutæki (hætta upptöku)",
"stopping": "Nagpapatigil ng pagrekord..."
"stopping": "Nagpapatigil ng pagrekord...",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Registratore Schermo",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Puno",
"color-range-limited": "Limitato",
"frame-rate": "Frequenza fotogrammi",
"frame-rate-custom": "Personalizzato",
"frame-rate-desc": "Frequenza fotogrammi target per le registrazioni dello schermo",
"custom-frame-rate": "Frequenza fotogrammi personalizzata",
"custom-frame-rate-desc": "Inserisci un valore personalizzato di frequenza fotogrammi (FPS)",
"quality": "Qualità video",
"quality-desc": "Qualità superiore risulta in file più grandi",
"quality-high": "Alta",
@@ -63,7 +67,8 @@
"source-desc": "Portal è consigliato, se si ottengono artefatti prova Schermo",
"sources-portal": "Portale",
"sources-screen": "Skjerm",
"title": "Impostazioni Video"
"title": "Impostazioni Video",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -13,7 +13,8 @@
"started": "録画を開始しました。",
"stop-recording": "画面録画(停止)",
"stopping": "録画を停止しています…",
"open-file": "ファイルを開く"
"open-file": "ファイルを開く",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "画面レコーダー",
"settings": {
@@ -52,7 +53,10 @@
"color-range-full": "フル",
"color-range-limited": "限定",
"frame-rate": "フレームレート",
"frame-rate-custom": "カスタム",
"frame-rate-desc": "画面録画の目標フレームレート",
"custom-frame-rate": "カスタムフレームレート",
"custom-frame-rate-desc": "カスタムフレームレート値を入力してくださいFPS",
"quality": "ビデオ品質",
"quality-desc": "品質が高いほど、ファイルサイズが大きくなります",
"quality-high": "高",
@@ -66,7 +70,8 @@
"source-desc": "ポータルが推奨されます。アーティファクトが発生する場合は「スクリーン」を試してください",
"sources-portal": "ポータル",
"sources-screen": "スクリーン",
"title": "ビデオ設定"
"title": "ビデオ設定",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Qeydkerê ekranê (dest bi qeydkirinê bike)",
"started": "Qeydkirin dest pê kir.",
"stop-recording": "Tomarkera dîmenderê (tomarkirinê bide sekinandin)",
"stopping": "Qeydkirin radiweste…"
"stopping": "Qeydkirin radiweste…",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Qeydkerê Ekranê",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Tije",
"color-range-limited": "Sînorkirî",
"frame-rate": "Rêjeya çarçoveyê",
"frame-rate-custom": "Kesane",
"frame-rate-desc": "Rêjeya çarçoveya hedefê ji bo qeydên ekranê",
"custom-frame-rate": "Rêjeya çarçoveya kesane",
"custom-frame-rate-desc": "Nirxek rêjeya çarçoveya kesane binivîse (FPS)",
"quality": "Kalîteya vîdyoyê",
"quality-desc": "Encamên bi kalîte bilindtir dibin sedema mezinahiyên pelan ên mezintir",
"quality-high": "Bilind",
@@ -63,7 +67,8 @@
"source-desc": "Portal tê pêşniyarkirin, eger tu artefaktan bibînî Screen biceribîne",
"sources-portal": "Portal",
"sources-screen": "Dîmende",
"title": "Mîhengên Vîdeoyê"
"title": "Mîhengên Vîdeoyê",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Schermrecorder (opname starten)",
"started": "Opname gestart.",
"stop-recording": "Schermrecorder (opname stoppen)",
"stopping": "Opname stoppen…"
"stopping": "Opname stoppen…",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Schermrecorder",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Volledig",
"color-range-limited": "Beperkt",
"frame-rate": "Framesnelheid",
"frame-rate-custom": "Aangepast",
"frame-rate-desc": "Doelframesnelheid voor schermopnames",
"custom-frame-rate": "Aangepaste framesnelheid",
"custom-frame-rate-desc": "Voer een aangepaste framesnelheid in (FPS)",
"quality": "Videokwaliteit",
"quality-desc": "Hogere kwaliteit resulteert in grotere bestanden",
"quality-high": "Hoog",
@@ -63,7 +67,8 @@
"source-desc": "Portal wordt aanbevolen, probeer Scherm als u artefacten krijgt",
"sources-portal": "Portaal",
"sources-screen": "Scherm",
"title": "Video-instellingen"
"title": "Video-instellingen",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Rejestrator ekranu (rozpocznij nagrywanie)",
"started": "Rozpoczęto nagrywanie.",
"stop-recording": "Rejestrator ekranu (zatrzymaj nagrywanie)",
"stopping": "Zatrzymywanie nagrywania…"
"stopping": "Zatrzymywanie nagrywania…",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Rejestrator ekranu",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Pełny",
"color-range-limited": "Ograniczone",
"frame-rate": "Liczba klatek na sekundę",
"frame-rate-custom": "Niestandardowa",
"frame-rate-desc": "Docelowa liczba klatek na sekundę dla nagrań ekranu",
"custom-frame-rate": "Niestandardowa liczba klatek",
"custom-frame-rate-desc": "Wprowadź niestandardową liczbę klatek na sekundę (FPS)",
"quality": "Jakość wideo",
"quality-desc": "Wyższa jakość skutkuje większymi rozmiarami plików",
"quality-high": "Wysoki",
@@ -63,7 +67,8 @@
"source-desc": "Zalecany jest Portal, jeśli występują artefakty, spróbuj opcji Ekran",
"sources-portal": "Portal",
"sources-screen": "Ekran",
"title": "Ustawienia wideo"
"title": "Ustawienia wideo",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Gravador de tela (iniciar gravação)",
"started": "Gravação iniciada.",
"stop-recording": "Gravador de tela (parar gravação)",
"stopping": "Parando a gravação..."
"stopping": "Parando a gravação...",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Gravador de Tela",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Completo",
"color-range-limited": "Limitado",
"frame-rate": "Taxa de quadros",
"frame-rate-custom": "Personalizado",
"frame-rate-desc": "Taxa de quadros alvo para gravações de tela",
"custom-frame-rate": "Taxa de quadros personalizada",
"custom-frame-rate-desc": "Insira um valor personalizado de taxa de quadros (FPS)",
"quality": "Qualidade de vídeo",
"quality-desc": "Maior qualidade resulta em arquivos maiores",
"quality-high": "Alto(a)",
@@ -63,7 +67,8 @@
"source-desc": "Portal é recomendado, se obtiver artefatos tente Tela",
"sources-portal": "Portal",
"sources-screen": "Tela",
"title": "Configurações de Vídeo"
"title": "Configurações de Vídeo",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Запись экрана (начать запись)",
"started": "Запись началась.",
"stop-recording": "Запись экрана (остановить запись)",
"stopping": "Остановка записи…"
"stopping": "Остановка записи…",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Запись Экрана",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Полный",
"color-range-limited": "Ограниченный",
"frame-rate": "Частота кадров",
"frame-rate-custom": "Пользовательская",
"frame-rate-desc": "Целевая частота кадров для записи экрана",
"custom-frame-rate": "Пользовательская частота кадров",
"custom-frame-rate-desc": "Введите пользовательское значение частоты кадров (FPS)",
"quality": "Качество видео",
"quality-desc": "Более высокое качество приводит к большим размерам файлов",
"quality-high": "Высокий",
@@ -63,7 +67,8 @@
"source-desc": "Рекомендуется Portal, если появляются артефакты попробуйте Экран",
"sources-portal": "Портал",
"sources-screen": "Экран",
"title": "Настройки Видео"
"title": "Настройки Видео",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Ekran kaydedici (kayda başla)",
"started": "Kayıt başladı.",
"stop-recording": "Ekran kaydedici (kaydı durdur)",
"stopping": "Kaydı durdurma…"
"stopping": "Kaydı durdurma…",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Ekran Kaydedici",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Tam",
"color-range-limited": "Sınırlı",
"frame-rate": "Kare hızı",
"frame-rate-custom": "Özel",
"frame-rate-desc": "Ekran kayıtları için hedef kare hızı",
"custom-frame-rate": "Özel kare hızı",
"custom-frame-rate-desc": "Özel bir kare hızı değeri girin (FPS)",
"quality": "Video kalitesi",
"quality-desc": "Daha yüksek kalite daha büyük dosya boyutlarına yol açar",
"quality-high": "Yüksek",
@@ -63,7 +67,8 @@
"source-desc": "Portal önerilir, artefaktlar alırsanız Ekran'ı deneyin",
"sources-portal": "Portal",
"sources-screen": "Ekran",
"title": "Video Ayarları"
"title": "Video Ayarları",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -12,7 +12,8 @@
"start-recording": "Запис екрана (почати запис)",
"started": "Запис розпочато.",
"stop-recording": "Запис екрана (зупинити запис)",
"stopping": "Зупинка запису…"
"stopping": "Зупинка запису…",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "Записувач Екрану",
"settings": {
@@ -49,7 +50,10 @@
"color-range-full": "Повний",
"color-range-limited": "Обмежений",
"frame-rate": "Частота кадрів",
"frame-rate-custom": "Користувацька",
"frame-rate-desc": "Цільова частота кадрів для запису екрану",
"custom-frame-rate": "Користувацька частота кадрів",
"custom-frame-rate-desc": "Введіть користувацьке значення частоти кадрів (FPS)",
"quality": "Якість відео",
"quality-desc": "Вища якість призводить до більших розмірів файлів",
"quality-high": "Високий",
@@ -63,7 +67,8 @@
"source-desc": "Рекомендується Portal, якщо з'являються артефакти спробуйте Екран",
"sources-portal": "Портал",
"sources-screen": "Екран",
"title": "Налаштування Відео"
"title": "Налаштування Відео",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -13,7 +13,8 @@
"started": "已开始录制。",
"stop-recording": "屏幕录像(停止录制)",
"stopping": "停止录制…",
"open-file": "打开文件"
"open-file": "打开文件",
"monitor-not-found": "Could not detect the monitor under cursor."
},
"name": "屏幕录制器",
"settings": {
@@ -52,7 +53,10 @@
"color-range-full": "全范围",
"color-range-limited": "有限",
"frame-rate": "帧率",
"frame-rate-custom": "自定义",
"frame-rate-desc": "屏幕录制的目标帧率",
"custom-frame-rate": "自定义帧率",
"custom-frame-rate-desc": "输入自定义帧率值FPS",
"quality": "视频质量",
"quality-desc": "更高的质量会导致更大的文件大小",
"quality-high": "高",
@@ -66,7 +70,8 @@
"source-desc": "推荐使用桌面门户 (Portal),如果出现画面异常请尝试屏幕",
"sources-portal": "桌面门户",
"sources-screen": "屏幕",
"title": "视频设置"
"title": "视频设置",
"sources-focused-monitor": "Focused monitor (Hyprland)"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "screen-recorder",
"name": "Screen Recorder",
"version": "1.1.7",
"version": "1.2.0",
"minNoctaliaVersion": "3.6.0",
"author": "Noctalia Team",
"official": true,