This commit is contained in:
2026-04-06 08:31:38 -05:00
parent aff58a1214
commit 5a84a8f294
41 changed files with 1211 additions and 104 deletions

View File

@@ -1,8 +1,10 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
import qs.Services.UI
import qs.Services.System
Item {
@@ -13,6 +15,8 @@ Item {
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
// ---------- Configuration ----------
@@ -29,7 +33,7 @@ Item {
property color colorSilent: root.useCustomColors && cfg.colorSilent || Color.mSurfaceVariant
property color colorTx: root.useCustomColors && cfg.colorTx || Color.mSecondary
property color colorRx: root.useCustomColors && cfg.colorRx || Color.mPrimary
property color colorText: root.useCustomColors && cfg.colorText || Qt.alpha(Color.mOnSurfaceVariant, 0.3)
property color colorText: root.useCustomColors && cfg.colorText || Color.mOnSurfaceVariant
property int byteThresholdActive: cfg.byteThresholdActive || defaults.byteThresholdActive || 1024
property real fontSizeModifier: cfg.fontSizeModifier || defaults.fontSizeModifier || 1
@@ -107,6 +111,39 @@ Item {
}
}
// ---------- Interaction ----------
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onPressed: mouse => {
if (mouse.button == Qt.RightButton)
PanelService.showContextMenu(contextMenu, root, screen);
}
NPopupContextMenu {
id: contextMenu
model: [
{
"label": I18n.tr("actions.widget-settings"),
"action": "widget-settings",
"icon": "settings"
},
]
onTriggered: action => {
contextMenu.close();
PanelService.closeContextMenu(screen);
if (action === "widget-settings") {
BarService.openPluginSettings(screen, pluginApi.manifest);
}
}
}
}
// ---------- Utilities ----------
function convertBytes(bytesPerSecond) {

View File

@@ -1,13 +1,17 @@
{
"id": "network-indicator",
"name": "Network Indicator",
"version": "1.0.5",
"version": "1.0.7",
"minNoctaliaVersion": "3.7.5",
"author": "tonigineer",
"license": "MIT",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "A `lively` network traffic indicator.",
"tags": ["Bar", "Network", "Indicator"],
"tags": [
"Bar",
"Network",
"Indicator"
],
"entryPoints": {
"barWidget": "BarWidget.qml",
"settings": "Settings.qml"

View File

@@ -14,6 +14,8 @@ Item {
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
// Bar positioning properties
readonly property string screenName: screen ? screen.name : ""

View File

@@ -10,7 +10,7 @@ Item {
property var pluginApi: null
// --- Logic extracted from BarWidget.qml ---
property bool micActive: false
property bool camActive: false
property bool scrActive: false
@@ -19,7 +19,7 @@ Item {
property var scrApps: []
property var accessHistory: []
// Previous states for history tracking
property var _prevMicApps: []
property var _prevCamApps: []
@@ -30,6 +30,7 @@ Item {
property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
property bool enableToast: cfg.enableToast ?? defaults.enableToast ?? true
property string activeColorKey: cfg.activeColor ?? defaults.activeColor ?? "primary"
property string micFilterRegex: cfg.micFilterRegex ?? defaults.micFilterRegex ?? ""
PwObjectTracker {
objects: Pipewire.ready ? Pipewire.nodes.values : []
@@ -73,6 +74,16 @@ Item {
function updateMicrophoneState(nodes, links) {
var appNames = [];
var isActive = false;
var filterRegex = null;
if (root.micFilterRegex && root.micFilterRegex.length > 0) {
try {
filterRegex = new RegExp(root.micFilterRegex);
} catch (e) {
Logger.w("PrivacyIndicator: Invalid micFilterRegex:", root.micFilterRegex);
}
}
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (!node || !node.isStream || !node.audio || node.isSink) continue;
@@ -80,8 +91,11 @@ Item {
var mediaClass = node.properties["media.class"] || "";
if (mediaClass === "Stream/Input/Audio") {
if (node.properties["stream.capture.sink"] === "true") continue;
isActive = true;
var appName = getAppName(node);
if (filterRegex && appName && filterRegex.test(appName)) continue;
isActive = true;
if (appName && appNames.indexOf(appName) === -1) appNames.push(appName);
}
}
@@ -133,7 +147,7 @@ Item {
}
// --- History Persistence ---
property string stateFile: ""
property bool isLoaded: false
@@ -164,7 +178,7 @@ Item {
root.accessHistory = adapter.history;
}
}
onLoadFailed: error => {
// If file doesn't exist (error 2), we are ready to save new data
if (error === 2) {
@@ -178,9 +192,9 @@ Item {
function saveHistory() {
if (!stateFile || !isLoaded) return;
adapter.history = root.accessHistory;
// Ensure cache directory exists and save
try {
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]);
@@ -220,7 +234,7 @@ Item {
function checkAppChanges(newApps, oldApps, type, icon, colorKey) {
if (!newApps && !oldApps) return;
// Check for new apps (Started)
if (newApps) {
for (var i = 0; i < newApps.length; i++) {
@@ -230,7 +244,7 @@ Item {
}
}
}
// Check for removed apps (Stopped)
if (oldApps) {
for (var j = 0; j < oldApps.length; j++) {
@@ -279,6 +293,6 @@ Item {
checkAppChanges(scrApps, _prevScrApps, "Screen", "screen-share", activeColorKey);
_prevScrApps = scrApps;
}
}

View File

@@ -18,8 +18,10 @@ A privacy indicator widget that monitors and displays when microphone, camera, o
Access the plugin settings in Noctalia to configure the following options:
- **Hide Inactive States**: If enabled, microphone, camera, and screen icons are hidden whenever they are inactive. Only active states are shown.
- **RemoveMargins**: If enabled, removes all outer margins of the widget.
- **Remove Margins**: If enabled, removes all outer margins of the widget.
- **Icon Spacing**: Controls the horizontal/vertical spacing between the icons.
- **Active/Inactive Icon Color**: Customize the colors for active and inactive states.
- **Microphone Filter Regex**: Regex pattern to filter out specific microphone applications. Matching apps are completely excluded from detection (they won't trigger the indicator or appear in tooltips). Use `|` to specify multiple patterns, e.g., `effect_input.rnnoise|easyeffects`.
## Usage

View File

@@ -17,6 +17,7 @@ ColumnLayout {
property int iconSpacing: cfg.iconSpacing ?? defaults.iconSpacing ?? 4
property string activeColor: cfg.activeColor ?? defaults.activeColor ?? "primary"
property string inactiveColor: cfg.inactiveColor ?? defaults.inactiveColor ?? "none"
property string micFilterRegex: cfg.micFilterRegex ?? defaults.micFilterRegex
spacing: Style.marginL
@@ -46,7 +47,7 @@ ColumnLayout {
onToggled: checked => {
root.enableToast = checked;
}
}
}
NToggle {
label: pluginApi?.tr("settings.removeMargins.label")
@@ -98,6 +99,15 @@ ColumnLayout {
currentKey: root.iconSpacing.toFixed(0)
onSelected: key => root.iconSpacing = key
}
NTextInput {
Layout.fillWidth: true
label: pluginApi?.tr("settings.micFilterRegex.label") || "Microphone filter regex"
description: pluginApi?.tr("settings.micFilterRegex.desc") || "Regex pattern to filter out microphone applications"
placeholderText: "effect_input.rnnoise|easyeffects"
text: root.micFilterRegex
onTextChanged: root.micFilterRegex = text
}
}
function saveSettings() {
@@ -112,6 +122,7 @@ ColumnLayout {
pluginApi.pluginSettings.removeMargins = root.removeMargins;
pluginApi.pluginSettings.activeColor = root.activeColor;
pluginApi.pluginSettings.inactiveColor = root.inactiveColor;
pluginApi.pluginSettings.micFilterRegex = root.micFilterRegex;
pluginApi.saveSettings();

View File

@@ -26,6 +26,10 @@
"removeMargins": {
"desc": "Remove all outer margins of the widget.",
"label": "Remove margins"
},
"micFilterRegex": {
"desc": "Regex pattern to filter out microphone applications. Matching apps are completely excluded from detection.",
"label": "Microphone filter regex"
}
},
"tooltip": {

View File

@@ -0,0 +1,54 @@
{
"menu": {
"settings": "Cài đặt tiện ích"
},
"settings": {
"activeColor": {
"desc": "Màu của biểu tượng khi đang hoạt động.",
"label": "Màu biểu tượng khi hoạt động"
},
"hideInactive": {
"desc": "Ẩn biểu tượng micro, camera và màn hình khi không hoạt động.",
"label": "Ẩn trạng thái không hoạt động"
},
"enableToast": {
"desc": "Hiển thị thông báo khi một trạng thái thay đổi.",
"label": "Bật thông báo"
},
"inactiveColor": {
"desc": "Màu của biểu tượng khi không hoạt động.",
"label": "Màu biểu tượng khi không hoạt động"
},
"iconSpacing": {
"desc": "Thiết lập khoảng cách giữa các biểu tượng.",
"label": "Khoảng cách biểu tượng"
},
"removeMargins": {
"desc": "Loại bỏ toàn bộ lề ngoài của widget.",
"label": "Xóa lề"
},
"micFilterRegex": {
"desc": "Biểu thức chính quy để lọc các ứng dụng sử dụng micro. Các ứng dụng khớp sẽ bị loại khỏi việc phát hiện.",
"label": "Regex lọc micro"
}
},
"tooltip": {
"cam-on": "Camera: {apps}",
"mic-on": "Micro: {apps}",
"screen-on": "Chia sẻ màn hình: {apps}"
},
"toast": {
"cam-on": "Camera đang hoạt động",
"mic-on": "Micro đang hoạt động",
"screen-on": "Chia sẻ màn hình đang hoạt động"
},
"history": {
"title": "Lịch sử truy cập",
"empty": "Không có truy cập gần đây",
"clear": "Xóa",
"action": {
"started": "Bắt đầu",
"stopped": "Dừng"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "privacy-indicator",
"name": "Privacy Indicator",
"version": "1.2.5",
"version": "1.2.7",
"minNoctaliaVersion": "3.6.0",
"author": "Noctalia Team",
"official": true,
@@ -29,7 +29,8 @@
"removeMargins": false,
"iconSpacing": 4,
"activeColor": "primary",
"inactiveColor": "none"
"inactiveColor": "none",
"micFilterRegex": ""
}
}
}
}

View File

@@ -13,6 +13,8 @@ NIconButton {
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
// Bar positioning properties
readonly property string screenName: screen ? screen.name : ""
@@ -33,7 +35,7 @@ NIconButton {
readonly property string iconColorKey: cfg.iconColor ?? defaults.iconColor ?? "none"
readonly property color iconColor: Color.resolveColorKey(iconColorKey)
readonly property bool shouldShow: !hideInactive || (mainInstance?.isRecording ?? false) || (mainInstance?.isPending ?? false)
readonly property bool shouldShow: !hideInactive || (mainInstance?.isRecording ?? false) || (mainInstance?.isPending ?? false) || (mainInstance?.isReplaying ?? false)
visible: true
opacity: shouldShow ? 1.0 : 0.0
@@ -59,8 +61,8 @@ NIconButton {
baseSize: root.capsuleHeight
applyUiScale: false
customRadius: Style.radiusL
colorBg: (mainInstance?.isRecording || mainInstance?.isPending) ? Color.mPrimary : Style.capsuleColor
colorFg: (mainInstance?.isRecording || mainInstance?.isPending) ? Color.mOnPrimary : root.iconColor
colorBg: (mainInstance?.isRecording || mainInstance?.isPending) ? Color.mPrimary : (mainInstance?.isReplaying ? Qt.alpha(Color.mSecondary, 0.25) : Style.capsuleColor)
colorFg: (mainInstance?.isRecording || mainInstance?.isPending) ? Color.mOnPrimary : (mainInstance?.isReplaying ? Color.mSecondary : root.iconColor)
colorBorder: "transparent"
colorBorderHover: "transparent"
border.color: Style.capsuleBorderColor
@@ -92,13 +94,39 @@ NIconButton {
NPopupContextMenu {
id: contextMenu
model: [
{
model: {
var items = [];
// Replay controls (only when replay is enabled)
if (mainInstance?.replayEnabled) {
if (mainInstance?.isReplaying) {
items.push({
"label": pluginApi.tr("messages.save-replay"),
"action": "save-replay",
"icon": "device-floppy"
});
items.push({
"label": pluginApi.tr("messages.stop-replay"),
"action": "stop-replay",
"icon": "stop"
});
} else {
items.push({
"label": pluginApi.tr("messages.start-replay"),
"action": "start-replay",
"icon": "player-play"
});
}
}
items.push({
"label": I18n.tr("actions.widget-settings"),
"action": "widget-settings",
"icon": "settings"
},
]
});
return items;
}
onTriggered: action => {
contextMenu.close();
@@ -106,6 +134,12 @@ NIconButton {
if (action === "widget-settings") {
BarService.openPluginSettings(screen, pluginApi.manifest);
} else if (action === "start-replay" && mainInstance) {
mainInstance.startReplay();
} else if (action === "stop-replay" && mainInstance) {
mainInstance.stopReplay();
} else if (action === "save-replay" && mainInstance) {
mainInstance.saveReplay();
}
}
}

View File

@@ -12,8 +12,8 @@ NIconButton {
enabled: mainInstance?.isAvailable ?? false
icon: "camera-video"
tooltipText: mainInstance?.buildTooltip()
colorFg: mainInstance?.isRecording ? Color.mOnPrimary : Color.mPrimary
colorBg: mainInstance?.isRecording ? Color.mPrimary : Style.capsuleColor
colorFg: mainInstance?.isRecording ? Color.mOnPrimary : (mainInstance?.isReplaying ? Color.mSecondary : Color.mPrimary)
colorBg: mainInstance?.isRecording ? Color.mPrimary : (mainInstance?.isReplaying ? Qt.alpha(Color.mSecondary, 0.25) : Style.capsuleColor)
onClicked: {
if (pluginApi && pluginApi.mainInstance) {
pluginApi.mainInstance.toggleRecording();

View File

@@ -19,6 +19,10 @@ Item {
property string detectedMonitor: ""
property bool usePrimeRun: false
// Replay state
property bool isReplaying: false
property bool isReplayPending: false
// Single reusable Process object
Process {
id: checker
@@ -54,6 +58,30 @@ Item {
root.stopRecording();
}
}
function toggleReplay() {
if (root.isAvailable) {
root.toggleReplay();
}
}
function startReplay() {
if (root.isAvailable && !root.isReplaying && !root.isReplayPending) {
root.startReplay();
}
}
function stopReplay() {
if (root.isReplaying || root.isReplayPending) {
root.stopReplay();
}
}
function saveReplay() {
if (root.isReplaying) {
root.saveReplay();
}
}
}
// Settings shortcuts
@@ -71,6 +99,26 @@ Item {
readonly property string audioSource: pluginApi?.pluginSettings?.audioSource || "default_output"
readonly property string videoSource: pluginApi?.pluginSettings?.videoSource || "portal"
readonly property string resolution: pluginApi?.pluginSettings?.resolution || "original"
readonly property bool restorePortalSession: pluginApi?.pluginSettings?.restorePortalSession ?? false
// Replay settings shortcuts
readonly property bool replayEnabled: pluginApi?.pluginSettings?.replayEnabled ?? false
readonly property string replayDuration: pluginApi?.pluginSettings?.replayDuration || "30"
readonly property string customReplayDuration: pluginApi?.pluginSettings?.customReplayDuration || "30"
readonly property string replayStorage: pluginApi?.pluginSettings?.replayStorage || "ram"
readonly property var codecResolutionLimits: ({
"h264": "4096x4096"
})
function buildResolutionFlag() {
if (resolution !== "original") {
return `-s ${resolution}`;
}
var maxResolution = codecResolutionLimits[videoCodec];
return maxResolution ? `-s ${maxResolution}` : "";
}
function buildTooltip() {
if (!isAvailable) {
@@ -85,6 +133,10 @@ Item {
return pluginApi.tr("messages.stop-recording");
}
if (isReplaying) {
return pluginApi.tr("messages.replay-active");
}
return pluginApi.tr("messages.start-recording");
}
@@ -240,8 +292,9 @@ Item {
})();
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 resolutionFlag = buildResolutionFlag();
var restoreFlag = restorePortalSession ? "-restore-portal-session yes" : "";
var flags = `-w ${source} -f ${actualFrameRate} -k ${videoCodec} ${audioFlags} -q ${quality} -cursor ${showCursor ? "yes" : "no"} -cr ${colorRange} ${resolutionFlag} ${restoreFlag} -o "${outputPath}"`;
var primePrefix = primeRun ? "prime-run " : "";
var command = `
_gpuscreenrecorder_flatpak_installed() {
@@ -272,7 +325,7 @@ Item {
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 '^(/nix/store/.*-gpu-screen-recorder|gpu-screen-recorder)' || pkill -SIGINT -f '^com.dec05eba.gpu_screen_recorder'"]);
isRecording = false;
isPending = false;
@@ -323,9 +376,10 @@ Item {
// 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);
const filteredError = filterStderr(stderr);
if (filteredError.length > 0 && !wasCancelled) {
ToastService.showError(pluginApi.tr("messages.failed-start"), truncateForToast(filteredError));
Logger.e("ScreenRecorder", filteredError);
}
}
} else if (isRecording || hasActiveRecording) {
@@ -341,11 +395,12 @@ Item {
}
} else {
// Don't show error if user intentionally cancelled
const filteredError = filterStderr(stderr);
if (!wasCancelled) {
if (stderr.length > 0) {
ToastService.showError(pluginApi.tr("messages.failed-start"), truncateForToast(stderr));
Logger.e("ScreenRecorder", stderr);
} else {
if (filteredError.length > 0) {
ToastService.showError(pluginApi.tr("messages.failed-start"), truncateForToast(filteredError));
Logger.e("ScreenRecorder", filteredError);
} else if (exitCode !== 0) {
ToastService.showError(pluginApi.tr("messages.failed-start"), pluginApi.tr("messages.failed-general"));
}
}
@@ -455,7 +510,247 @@ 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 '^(/nix/store/.*-gpu-screen-recorder|gpu-screen-recorder)' 2>/dev/null || pkill -9 -f '^com.dec05eba.gpu_screen_recorder' 2>/dev/null || true"]);
}
}
// ─── Replay Buffer ───────────────────────────────────────────────────
function toggleReplay() {
(isReplaying || isReplayPending) ? stopReplay() : startReplay();
}
function startReplay() {
if (!isAvailable) return;
if (isReplaying || isReplayPending) return;
if (!replayEnabled) return;
isReplayPending = true;
replayPortalCheckProcess.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)"]
});
}
function launchReplay() {
// 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}"';
replayMonitorDetectProcess.exec({
"command": ["sh", "-c", script]
});
return;
}
launchReplayWithSource(videoSource, false);
}
function launchReplayWithSource(source, primeRun) {
var videoDir = Settings.preprocessPath(directory);
if (!videoDir) {
videoDir = Quickshell.env("HOME") + "/Videos";
}
// Ensure directory ends with / for gpu-screen-recorder
if (videoDir && !videoDir.endsWith("/")) {
videoDir += "/";
}
var actualDuration = (replayDuration === "custom") ? customReplayDuration : replayDuration;
var actualFrameRate = (frameRate === "custom") ? customFrameRate : frameRate;
var resolutionFlag = buildResolutionFlag();
const audioFlags = (() => {
if (audioSource === "none") {
return "";
}
if (audioSource === "both") {
return `-ac ${audioCodec} -a "default_output|default_input"`;
}
return `-ac ${audioCodec} -a ${audioSource}`;
})();
var restoreFlag = restorePortalSession ? "-restore-portal-session yes" : "";
var flags = `-w ${source} -c mp4 -f ${actualFrameRate} -k ${videoCodec} ${audioFlags} -q ${quality} -cursor ${showCursor ? "yes" : "no"} -cr ${colorRange} ${resolutionFlag} -r ${actualDuration} -replay-storage ${replayStorage} ${restoreFlag} -o "${videoDir}"`;
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
${primePrefix}gpu-screen-recorder ${flags}
elif command -v flatpak >/dev/null 2>&1 && _gpuscreenrecorder_flatpak_installed; then
${primePrefix}flatpak run --command=gpu-screen-recorder --file-forwarding com.dec05eba.gpu_screen_recorder ${flags}
else
echo "GPU_SCREEN_RECORDER_NOT_INSTALLED"
fi`;
replayProcess.exec({
"command": ["sh", "-c", command]
});
replayPendingTimer.running = true;
}
function stopReplay() {
if (!isReplaying && !isReplayPending) return;
// Send SIGINT to stop the replay daemon
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f '^(/nix/store/.*-gpu-screen-recorder|gpu-screen-recorder).*-r ' || pkill -SIGINT -f '^com.dec05eba.gpu_screen_recorder.*-r '"]);
isReplaying = false;
isReplayPending = false;
replayPendingTimer.running = false;
replayMonitorTimer.running = false;
ToastService.showNotice(pluginApi.tr("messages.replay-stopped"), "", "info");
// Force kill after 3 seconds, just in case
replayKillTimer.running = true;
}
function saveReplay() {
if (!isReplaying) return;
// Send SIGUSR1 to save the replay buffer
Quickshell.execDetached(["sh", "-c", "pkill -SIGUSR1 -f '^(/nix/store/.*-gpu-screen-recorder|gpu-screen-recorder).*-r ' || pkill -SIGUSR1 -f '^com.dec05eba.gpu_screen_recorder.*-r '"]);
}
// Replay Process
Process {
id: replayProcess
stdout: SplitParser {
onRead: data => {
// gpu-screen-recorder outputs the saved file path to stdout on each SIGUSR1 save
var savedPath = String(data).trim();
if (savedPath && savedPath.length > 0 && !savedPath.startsWith("GPU_SCREEN_RECORDER")) {
ToastService.showNotice(
pluginApi.tr("messages.replay-saved"),
savedPath, "video", 3000,
pluginApi.tr("messages.open-file"),
() => openFile(savedPath)
);
if (copyToClipboard) {
copyFileToClipboard(savedPath);
}
}
}
}
stderr: StdioCollector {}
onExited: function(exitCode, exitStatus) {
const stderr = String(replayProcess.stderr.text || "").trim();
const wasCancelled = isCancelledByUser("", stderr);
const filteredError = filterStderr(stderr);
if (isReplayPending) {
isReplayPending = false;
replayPendingTimer.running = false;
if (exitCode !== 0 && filteredError.length > 0 && !wasCancelled) {
ToastService.showError(pluginApi.tr("messages.replay-failed"), truncateForToast(filteredError));
Logger.e("ScreenRecorder", "Replay: " + filteredError);
}
} else if (isReplaying) {
isReplaying = false;
replayMonitorTimer.running = false;
if (exitCode !== 0 && !wasCancelled) {
if (filteredError.length > 0) {
ToastService.showError(pluginApi.tr("messages.replay-failed"), truncateForToast(filteredError));
Logger.e("ScreenRecorder", "Replay: " + filteredError);
}
}
}
}
}
// Filter stderr to only include actual errors (starting with gsr error: or gsr fatal:)
function filterStderr(text) {
if (!text) return "";
const lines = text.split("\n");
const errorLines = lines.filter(line => {
const lower = line.toLowerCase();
if (lower.includes("gsr info:") || lower.includes("gsr notice:") || lower.includes("(error: none)")) return false;
return lower.includes("gsr error:") || lower.includes("gsr fatal:") || lower.includes("failed to") || lower.includes("error:");
});
if (errorLines.length === 0) return "";
return errorLines.join("\n").trim();
}
// Portal check for replay
Process {
id: replayPortalCheckProcess
onExited: function(exitCode, exitStatus) {
if (exitCode === 0) {
launchReplay();
} else {
isReplayPending = false;
ToastService.showError(pluginApi.tr("messages.no-portals"), pluginApi.tr("messages.no-portals-desc"));
}
}
}
// Monitor detection for replay on Hyprland
Process {
id: replayMonitorDetectProcess
stdout: StdioCollector {}
stderr: StdioCollector {}
onExited: function(exitCode, exitStatus) {
const output = String(replayMonitorDetectProcess.stdout.text || "").trim();
if (exitCode !== 0 || output === "MONITOR_NOT_FOUND" || !output) {
isReplayPending = false;
ToastService.showError(pluginApi.tr("messages.replay-failed"), pluginApi.tr("messages.monitor-not-found"));
return;
}
const parts = output.split(":");
const monitorName = parts[0];
const primeRun = parts.length > 1 && parts[1] === "1";
Logger.i("ScreenRecorder", "Replay: Detected monitor: " + monitorName + (primeRun ? " (prime-run)" : ""));
launchReplayWithSource(monitorName, primeRun);
}
}
Timer {
id: replayPendingTimer
interval: 2000
running: false
repeat: false
onTriggered: {
if (root.isReplayPending && replayProcess.running) {
root.isReplayPending = false;
root.isReplaying = true;
replayMonitorTimer.running = true;
ToastService.showNotice(pluginApi.tr("messages.replay-started"), "", "info");
} else if (root.isReplayPending) {
root.isReplayPending = false;
}
}
}
Timer {
id: replayMonitorTimer
interval: 2000
running: false
repeat: true
onTriggered: {
if (!replayProcess.running && root.isReplaying) {
root.isReplaying = false;
running = false;
}
}
}
Timer {
id: replayKillTimer
interval: 3000
running: false
repeat: false
onTriggered: {
Quickshell.execDetached(["sh", "-c", "pkill -9 -f '^(/nix/store/.*-gpu-screen-recorder|gpu-screen-recorder).*-r ' 2>/dev/null || pkill -9 -f '^com.dec05eba.gpu_screen_recorder.*-r ' 2>/dev/null || true"]);
}
}
}

View File

@@ -5,10 +5,11 @@ Hardware-accelerated screen recording for Noctalia using [gpu-screen-recorder](h
## Features
- Hardware-accelerated screen recording
- **Replay buffer** — continuously capture and save the last N seconds on demand
- Customizable video codecs (H264, HEVC, AV1, VP8, VP9, HDR variants)
- Audio recording with multiple sources (system output, microphone, both, or none)
- Adjustable frame rates (30-240 FPS)
- Configurable output resolution (Full HD, 4K, QHD, HD, or original)
- Configurable output resolution (Full HD, WUXGA, 4K, QHD, HD, or original)
- Configurable output directory
- Optional clipboard copy after recording
- Optional cursor recording
@@ -47,12 +48,18 @@ Configure the plugin through the settings panel:
- **Video Quality**: Medium, High, Very High, or Ultra
- **Video Codec**: H264, HEVC, AV1, VP8, VP9 (+ HDR variants for screen source)
- **Color Range**: Limited (recommended) or Full
- **Resolution**: Output resolution limit (1920x1080, 2560x1440, 3840x2160, 1280x720, or Original)
- **Resolution**: Output resolution limit (1920x1080, 1920x1200, 2560x1440, 3840x2160, 1280x720, or Original)
- **Audio Source**: None, System Output, Microphone, or Both
- **Audio Codec**: Opus (recommended) or AAC
- **Show Cursor**: Include mouse cursor in recording
- **Copy to Clipboard**: Automatically copy file after recording
#### Replay
- **Enable Replay Buffer**: Toggle the replay buffer feature on/off
- **Replay Duration**: Buffer length (15s, 30s, 60s, 2 min, 5 min, or custom)
- **Replay Storage**: Store buffer in RAM (faster, recommended) or Disk
### IPC Commands
Control the screen recorder via IPC for keybindings or scripts:
@@ -66,6 +73,18 @@ qs -c noctalia-shell ipc call plugin:screen-recorder start
# Explicitly stop recording
qs -c noctalia-shell ipc call plugin:screen-recorder stop
# Start the replay buffer
qs -c noctalia-shell ipc call plugin:screen-recorder startReplay
# Save the replay buffer (last N seconds)
qs -c noctalia-shell ipc call plugin:screen-recorder saveReplay
# Stop the replay buffer
qs -c noctalia-shell ipc call plugin:screen-recorder stopReplay
# Toggle replay buffer on/off
qs -c noctalia-shell ipc call plugin:screen-recorder toggleReplay
```
## Video Codecs

View File

@@ -59,12 +59,38 @@ ColumnLayout {
property string editResolution: pluginApi?.pluginSettings?.resolution || pluginApi?.manifest?.metadata?.defaultSettings?.resolution || "original"
property bool editRestorePortalSession: pluginApi?.pluginSettings?.restorePortalSession ?? pluginApi?.manifest?.metadata?.defaultSettings?.restorePortalSession ?? false
// Replay settings
property bool editReplayEnabled: pluginApi?.pluginSettings?.replayEnabled ?? pluginApi?.manifest?.metadata?.defaultSettings?.replayEnabled ?? false
readonly property var _validReplayDurations: ["15", "30", "60", "120", "300", "custom"]
readonly property string _rawReplayDuration:
pluginApi?.pluginSettings?.replayDuration ||
pluginApi?.manifest?.metadata?.defaultSettings?.replayDuration ||
"30"
property string editReplayDuration:
_validReplayDurations.includes(_rawReplayDuration) ? _rawReplayDuration : "custom"
property string editCustomReplayDuration:
_validReplayDurations.includes(_rawReplayDuration)
? (pluginApi?.pluginSettings?.customReplayDuration ||
pluginApi?.manifest?.metadata?.defaultSettings?.customReplayDuration ||
"30")
: _rawReplayDuration
property string editReplayStorage: pluginApi?.pluginSettings?.replayStorage || pluginApi?.manifest?.metadata?.defaultSettings?.replayStorage || "ram"
function saveSettings() {
if (!pluginApi) {
Logger.e("ScreenRecorder", "Cannot save: pluginApi is null");
if (!pluginApi || !pluginApi.pluginSettings) {
Logger.e("ScreenRecorder", "Cannot save: pluginApi or pluginSettings is null");
return;
}
// Core settings
pluginApi.pluginSettings.hideInactive = root.editHideInactive
pluginApi.pluginSettings.iconColor = root.editIconColor
pluginApi.pluginSettings.directory = root.editDirectory
@@ -80,6 +106,14 @@ ColumnLayout {
pluginApi.pluginSettings.audioSource = root.editAudioSource
pluginApi.pluginSettings.videoSource = root.editVideoSource
pluginApi.pluginSettings.resolution = root.editResolution
pluginApi.pluginSettings.restorePortalSession = root.editRestorePortalSession
// Replay settings
pluginApi.pluginSettings.replayEnabled = root.editReplayEnabled
pluginApi.pluginSettings.replayDuration = root.editReplayDuration
pluginApi.pluginSettings.customReplayDuration = root.editCustomReplayDuration
pluginApi.pluginSettings.replayStorage = root.editReplayStorage
pluginApi.saveSettings();
@@ -125,7 +159,7 @@ ColumnLayout {
label: pluginApi.tr("settings.general.show-cursor")
description: pluginApi.tr("settings.general.show-cursor-description")
checked: root.editShowCursor
onToggled: root.editShowCursor = checked
onToggled: c => root.editShowCursor = c
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.showCursor ?? true
}
@@ -134,7 +168,7 @@ ColumnLayout {
label: pluginApi.tr("settings.general.copy-to-clipboard")
description: pluginApi.tr("settings.general.copy-to-clipboard-description")
checked: root.editCopyToClipboard
onToggled: root.editCopyToClipboard = checked
onToggled: c => root.editCopyToClipboard = c
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.copyToClipboard ?? false
}
@@ -143,10 +177,85 @@ ColumnLayout {
label: pluginApi.tr("settings.general.hide-when-inactive")
description: pluginApi.tr("settings.general.hide-when-inactive-description")
checked: root.editHideInactive
onToggled: root.editHideInactive = checked
onToggled: c => root.editHideInactive = c
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.hideInactive ?? false
}
// Restore Portal Session
NToggle {
label: pluginApi.tr("settings.general.restore-portal-session")
description: pluginApi.tr("settings.general.restore-portal-session-description")
checked: root.editRestorePortalSession
onToggled: c => root.editRestorePortalSession = c
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.restorePortalSession ?? false
}
NDivider {
Layout.fillWidth: true
}
// Replay Settings
ColumnLayout {
spacing: Style.marginL
Layout.fillWidth: true
NToggle {
label: pluginApi.tr("settings.replay.enable")
description: pluginApi.tr("settings.replay.enable-desc")
checked: root.editReplayEnabled
onToggled: c => root.editReplayEnabled = c
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.replayEnabled ?? false
}
NComboBox {
visible: root.editReplayEnabled
label: pluginApi.tr("settings.replay.duration")
description: pluginApi.tr("settings.replay.duration-desc")
model: [
{ "key": "15", "name": "15s" },
{ "key": "30", "name": "30s" },
{ "key": "60", "name": "60s" },
{ "key": "120", "name": "2 min" },
{ "key": "300", "name": "5 min" },
{ "key": "custom", "name": pluginApi.tr("settings.video.frame-rate-custom") }
]
currentKey: root.editReplayDuration
onSelected: key => root.editReplayDuration = key
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.replayDuration || "30"
}
NTextInput {
visible: root.editReplayEnabled && root.editReplayDuration === "custom"
label: pluginApi.tr("settings.replay.custom-duration")
description: pluginApi.tr("settings.replay.custom-duration-desc")
placeholderText: "30"
text: root.editCustomReplayDuration
onTextChanged: {
var numeric = text.replace(/[^0-9]/g, '')
if (numeric !== text) {
text = numeric
}
if (numeric) {
root.editCustomReplayDuration = numeric
}
}
Layout.fillWidth: true
}
NComboBox {
visible: root.editReplayEnabled
label: pluginApi.tr("settings.replay.storage")
description: pluginApi.tr("settings.replay.storage-desc")
model: [
{ "key": "ram", "name": pluginApi.tr("settings.replay.storage-ram") },
{ "key": "disk", "name": pluginApi.tr("settings.replay.storage-disk") }
]
currentKey: root.editReplayStorage
onSelected: key => root.editReplayStorage = key
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.replayStorage || "ram"
}
}
NDivider {
Layout.fillWidth: true
}
@@ -350,6 +459,10 @@ ColumnLayout {
"key": "1920x1080",
"name": "1920x1080 (Full HD)"
},
{
"key": "1920x1200",
"name": "1920x1200 (WUXGA)"
},
{
"key": "2560x1440",
"name": "2560x1440 (QHD)"

View File

@@ -14,7 +14,15 @@
"stop-recording": "Bildschirmaufnahme (Aufnahme stoppen)",
"stopping": "Aufnahme stoppen…",
"open-file": "Datei öffnen",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Could not detect the monitor under cursor.",
"replay-saved": "Replay gespeichert.",
"replay-started": "Replay-Puffer gestartet.",
"replay-stopped": "Replay-Puffer gestoppt.",
"replay-failed": "Fehler beim Starten des Replay-Puffers.",
"replay-active": "Bildschirmaufnahme (Replay aktiv)",
"save-replay": "Replay-Puffer speichern",
"start-replay": "Replay-Puffer starten",
"stop-replay": "Replay-Puffer stoppen"
},
"name": "Bildschirmrekorder",
"settings": {
@@ -40,10 +48,24 @@
"hide-when-inactive-description": "Widget in Statusleiste ausblenden, wenn keine Aufnahme stattfindet",
"output-folder": "Ausgabeordner",
"output-folder-description": "Ordner, in dem Bildschirmaufnahmen gespeichert werden",
"restore-portal-session": "Portal-Sitzung wiederherstellen",
"restore-portal-session-description": "Stellt die vorherige Portal-Sitzung für nachfolgende Aufnahmen automatisch wieder her (deaktiviert die Auswahlaufforderung)",
"show-cursor": "Cursor anzeigen",
"show-cursor-description": "Mauszeiger im Video aufnehmen",
"title": "Allgemein"
},
"replay": {
"enable": "Replay-Puffer aktivieren",
"enable-desc": "Ein rollender Puffer der letzten Bildschirmaktivität beibehalten, der bei Bedarf gespeichert werden kann",
"duration": "Replay-Dauer",
"duration-desc": "Länge des Replay-Puffers in Sekunden",
"custom-duration": "Benutzerdefinierte Replay-Dauer",
"custom-duration-desc": "Geben Sie eine benutzerdefinierte Replay-Puffer-Dauer in Sekunden ein",
"storage": "Replay-Speicher",
"storage-desc": "Wo der Replay-Puffer gespeichert werden soll",
"storage-ram": "RAM (empfohlen)",
"storage-disk": "Festplatte"
},
"title": "Bildschirmrekorder-Einstellungen",
"video": {
"codec": "Video-Codec",

View File

@@ -14,7 +14,15 @@
"stop-recording": "Screen Recorder (stop recording)",
"stopping": "Stopping recording…",
"open-file": "Open file",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Could not detect the monitor under cursor.",
"replay-saved": "Replay saved.",
"replay-started": "Replay buffer started.",
"replay-stopped": "Replay buffer stopped.",
"replay-failed": "Failed to start replay buffer.",
"replay-active": "Screen Recorder (replay active)",
"save-replay": "Save replay buffer",
"start-replay": "Start replay buffer",
"stop-replay": "Stop replay buffer"
},
"name": "Screen Recorder",
"settings": {
@@ -40,10 +48,24 @@
"hide-when-inactive-description": "Hide the bar indicator when not recording",
"output-folder": "Output folder",
"output-folder-description": "Folder where screen recordings will be saved",
"restore-portal-session": "Restore portal session",
"restore-portal-session-description": "Automatically restore the previous portal session for subsequent recordings (disables the selection prompt)",
"show-cursor": "Show cursor",
"show-cursor-description": "Record mouse cursor in the video",
"title": "General"
},
"replay": {
"enable": "Enable replay buffer",
"enable-desc": "Keep a rolling buffer of recent screen activity that can be saved on demand",
"duration": "Replay duration",
"duration-desc": "Length of the replay buffer in seconds",
"custom-duration": "Custom replay duration",
"custom-duration-desc": "Enter a custom replay buffer duration in seconds",
"storage": "Replay storage",
"storage-desc": "Where to store the replay buffer",
"storage-ram": "RAM (recommended)",
"storage-disk": "Disk"
},
"title": "Screen Recorder Settings",
"video": {
"codec": "Video codec",

View File

@@ -13,7 +13,15 @@
"started": "Grabación iniciada.",
"stop-recording": "Grabador de pantalla (detener grabación)",
"stopping": "Deteniendo la grabación…",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "No se pudo detectar el monitor bajo el cursor.",
"replay-saved": "Replay guardado.",
"replay-started": "Búfer de replay iniciado.",
"replay-stopped": "Búfer de replay detenido.",
"replay-failed": "Error al iniciar el búfer de replay.",
"replay-active": "Grabador de pantalla (replay activo)",
"save-replay": "Guardar búfer de replay",
"start-replay": "Iniciar búfer de replay",
"stop-replay": "Detener búfer de replay"
},
"name": "Grabadora de Pantalla",
"settings": {
@@ -37,10 +45,24 @@
"copy-to-clipboard-description": "Copia el archivo al portapapeles después de que termine la grabación.",
"output-folder": "Carpeta de salida",
"output-folder-description": "Carpeta donde se guardarán las grabaciones de pantalla",
"restore-portal-session": "Restaurar sesión del portal",
"restore-portal-session-description": "Restaurar automáticamente la sesión anterior del portal para grabaciones posteriores (desactiva el aviso de selección)",
"show-cursor": "Mostrar cursor",
"show-cursor-description": "Grabar el cursor del ratón en el video",
"title": "General"
},
"replay": {
"enable": "Activar búfer de replay",
"enable-desc": "Mantener un búfer rodante de la actividad reciente de la pantalla que se puede guardar a pedido",
"duration": "Duración del replay",
"duration-desc": "Longitud del búfer de replay en segundos",
"custom-duration": "Duración de replay personalizada",
"custom-duration-desc": "Ingrese una duración de búfer de replay personalizada en segundos",
"storage": "Almacenamiento de replay",
"storage-desc": "Dónde almacenar el búfer de replay",
"storage-ram": "RAM (recomendado)",
"storage-disk": "Disco"
},
"title": "Configuración de Grabadora de Pantalla",
"video": {
"codec": "Códec de video",

View File

@@ -13,7 +13,16 @@
"started": "Enregistrement démarré.",
"stop-recording": "Enregistreur d'écran (arrêter l'enregistrement)",
"stopping": "Arrêt de l'enregistrement...",
"monitor-not-found": "Impossible de détecter l'écran sous le curseur."
"open-file": "Ouvrir le fichier",
"monitor-not-found": "Impossible de détecter l'écran sous le curseur.",
"replay-saved": "Replay enregistré.",
"replay-started": "Tampon de replay démarré.",
"replay-stopped": "Tampon de replay arrêté.",
"replay-failed": "Échec du démarrage du tampon de replay.",
"replay-active": "Enregistreur d'écran (replay actif)",
"save-replay": "Enregistrer le tampon de replay",
"start-replay": "Démarrer le tampon de replay",
"stop-replay": "Arrêter le tampon de replay"
},
"name": "Enregistreur d'Écran",
"settings": {
@@ -35,12 +44,28 @@
"general": {
"copy-to-clipboard": "Copier dans le presse-papiers",
"copy-to-clipboard-description": "Copier le fichier dans le presse-papiers une fois l'enregistrement terminé.",
"hide-when-inactive": "Masquer lorsqu'inactif",
"hide-when-inactive-description": "Masquer l'indicateur de la barre lorsqu'il n'y a pas d'enregistrement",
"output-folder": "Dossier de sortie",
"output-folder-description": "Dossier où les enregistrements d'écran seront enregistrés",
"restore-portal-session": "Restaurer la session du portail",
"restore-portal-session-description": "Restaurer automatiquement la session du portail précédente pour les enregistrements ultérieurs (désactive l'invite de sélection)",
"show-cursor": "Afficher le curseur",
"show-cursor-description": "Enregistrer le curseur de la souris dans la vidéo",
"title": "Général"
},
"replay": {
"enable": "Activer le tampon de replay",
"enable-desc": "Conserver un tampon roulant de l'activité récente de l'écran qui peut être enregistré à la demande",
"duration": "Durée du replay",
"duration-desc": "Longueur du tampon de replay en secondes",
"custom-duration": "Durée de replay personnalisée",
"custom-duration-desc": "Saisissez une durée de tampon de replay personnalisée en secondes",
"storage": "Stockage du replay",
"storage-desc": "Où stocker le tampon de replay",
"storage-ram": "RAM (recommandé)",
"storage-disk": "Disque"
},
"title": "Paramètres de l'Enregistreur d'Écran",
"video": {
"codec": "Codec vidéo",

View File

@@ -13,7 +13,15 @@
"started": "A felvétel elindult.",
"stop-recording": "Képernyőfelvevő (felvétel leállítása)",
"stopping": "A felvétel leállítása…",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Nem sikerült felismerni a kurzor alatti monitort.",
"replay-saved": "Replay elmentve.",
"replay-started": "Replay puffer elindítva.",
"replay-stopped": "Replay puffer leállítva.",
"replay-failed": "Nem sikerült elindítani a replay puffert.",
"replay-active": "Képernyőrögzítő (replay aktív)",
"save-replay": "Replay puffer mentése",
"start-replay": "Replay puffer indítása",
"stop-replay": "Replay puffer leállítása"
},
"name": "Képernyőfelvevő",
"settings": {
@@ -36,11 +44,25 @@
"copy-to-clipboard": "Másolás a vágólapra",
"copy-to-clipboard-description": "A felvétel befejezése után másolja a fájlt a vágólapra.",
"output-folder": "Kimeneti mappa",
"output-folder-description": "Mappa, ahová a képernyőfelvételek mentésre kerülnek",
"output-folder-description": "A mappa, ahová a képernyőfelvételek mentésre kerülnek",
"restore-portal-session": "Portál munkamenet visszaállítása",
"restore-portal-session-description": "Az előző portál munkamenet automatikus visszaállítása a következő felvételekhez (kikapcsolja a választóablakot)",
"show-cursor": "Kurzor megjelenítése",
"show-cursor-description": "Egérkurzor rögzítése a videóban",
"title": "Általános"
},
"replay": {
"enable": "Replay puffer engedélyezése",
"enable-desc": "Tartson egy gördülő puffert a legutóbbi képernyőaktivitásról, amely igény szerint elmenthető",
"duration": "Replay időtartama",
"duration-desc": "A replay puffer hossza másodpercben",
"custom-duration": "Egyéni replay időtartam",
"custom-duration-desc": "Adjon meg egy egyéni replay puffer időtartamot másodpercben",
"storage": "Replay tároló",
"storage-desc": "Hol tárolja a replay puffert",
"storage-ram": "RAM (ajánlott)",
"storage-disk": "Lemez"
},
"title": "Képernyőrögzítő beállítások",
"video": {
"codec": "Videó kodek",

View File

@@ -13,7 +13,15 @@
"started": "Aufnahme gestartet.",
"stop-recording": "Skjáupptökutæki (hætta upptöku)",
"stopping": "Nagpapatigil ng pagrekord...",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Impossibile rilevare il monitor sotto il cursore.",
"replay-saved": "Replay salvato.",
"replay-started": "Buffer di replay avviato.",
"replay-stopped": "Buffer di replay interrotto.",
"replay-failed": "Impossibile avviare il buffer di replay.",
"replay-active": "Registratore schermo (replay attivo)",
"save-replay": "Salva buffer di replay",
"start-replay": "Avvia buffer di replay",
"stop-replay": "Interrompi buffer di replay"
},
"name": "Registratore Schermo",
"settings": {
@@ -37,10 +45,24 @@
"copy-to-clipboard-description": "Kopyahin ang file sa clipboard pagkatapos matapos ang pag-record.",
"output-folder": "Cartella di output",
"output-folder-description": "Cartella dove verranno salvate le registrazioni dello schermo",
"restore-portal-session": "Restore portal session",
"restore-portal-session-description": "Automatically restore the previous portal session for subsequent recordings (disables the selection prompt)",
"show-cursor": "Mostra cursore",
"show-cursor-description": "Registra il cursore del mouse nel video",
"title": "Generale"
},
"replay": {
"enable": "Abilita buffer di replay",
"enable-desc": "Mantiene un buffer ciclico dell'attività recente dello schermo che può essere salvato a richiesta",
"duration": "Durata replay",
"duration-desc": "Lunghezza del buffer di replay in secondi",
"custom-duration": "Durata replay personalizzata",
"custom-duration-desc": "Inserisci una durata personalizzata del buffer di replay in secondi",
"storage": "Archiviazione replay",
"storage-desc": "Dove archiviare il buffer di replay",
"storage-ram": "RAM (consigliato)",
"storage-disk": "Disco"
},
"title": "Impostazioni Registratore Schermo",
"video": {
"codec": "Codec video",

View File

@@ -14,7 +14,15 @@
"stop-recording": "画面録画(停止)",
"stopping": "録画を停止しています…",
"open-file": "ファイルを開く",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "カーソル下のモニターを検出できませんでした。",
"replay-saved": "リプレイが保存されました。",
"replay-started": "リプレイバッファが開始されました。",
"replay-stopped": "リプレイバッファが停止されました。",
"replay-failed": "リプレイバッファの開始に失敗しました。",
"replay-active": "スクリーンレコーダー (リプレイ有効)",
"save-replay": "リプレイバッファを保存",
"start-replay": "リプレイバッファを開始",
"stop-replay": "リプレイバッファを停止"
},
"name": "画面レコーダー",
"settings": {
@@ -40,10 +48,24 @@
"hide-when-inactive-description": "録画していないときはウィジェットを隠す",
"output-folder": "出力フォルダー",
"output-folder-description": "画面録画が保存されるフォルダー",
"restore-portal-session": "ポータルセッションを復元する",
"restore-portal-session-description": "以降の録画のために前のポータルセッションを自動的に復元します(選択プロンプトを無効にします)",
"show-cursor": "カーソルを表示",
"show-cursor-description": "録画にマウスカーソルを記録する",
"title": "全般"
},
"replay": {
"enable": "リプレイバッファを有効にする",
"enable-desc": "最近の画面アクティビティのローリングバッファを保持し、必要に応じて保存できるようにします",
"duration": "リプレイ時間",
"duration-desc": "リプレイバッファの長さ(秒)",
"custom-duration": "カスタムリプレイ時間",
"custom-duration-desc": "カスタムリプレイバッファの時間を秒単位で入力してください",
"storage": "リプレイストレージ",
"storage-desc": "リプレイバッファの保存先",
"storage-ram": "RAM (推奨)",
"storage-disk": "ディスク"
},
"title": "画面レコーダー設定",
"video": {
"codec": "ビデオコーデック",

View File

@@ -13,7 +13,15 @@
"started": "Qeydkirin dest pê kir.",
"stop-recording": "Tomarkera dîmenderê (tomarkirinê bide sekinandin)",
"stopping": "Qeydkirin radiweste…",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Monitora bin nîşanderê nehat dîtin.",
"replay-saved": "Vegerandin hat qeydkirin.",
"replay-started": "Depo vegerandinê dest pê kir.",
"replay-stopped": "Depo vegerandinê sekinî.",
"replay-failed": "Destpêkirina depo vegerandinê bi ser neket.",
"replay-active": "Qeydkerê dîmenderê (vegerandin çalak e)",
"save-replay": "Depo vegerandinê qeyd bike",
"start-replay": "Depo vegerandinê bide destpêkirin",
"stop-replay": "Depo vegerandinê bide sekinandin"
},
"name": "Qeydkerê Ekranê",
"settings": {
@@ -36,11 +44,25 @@
"copy-to-clipboard": "Kopyayî ser clipboardê bike",
"copy-to-clipboard-description": "Piştî qedandina tomarkirinê pelê kopî klîpbordê bike.",
"output-folder": "Cilda peldanka ya derketinê",
"output-folder-description": "Cihê ku tomarên ekranê dê lê bên tomarkirin",
"show-cursor": "Nîşan bide mişk",
"output-folder-description": "Peldanka ku qeydên ekranê lê werên rizgarkirin",
"restore-portal-session": "Vegerandina rûniştina portalê",
"restore-portal-session-description": "Ji bo qeydên paşê bixweber rûniştina portalê ya berê vedigerîne (pêşniyara hilbijartinê neçalak dike)",
"show-cursor": "Nîşanderê nîşan bide",
"show-cursor-description": "Qeydê nîşankerê mişkê di vîdeoyê de",
"title": "Giştî"
},
"replay": {
"enable": "Depo vegerandinê çalak bike",
"enable-desc": "Depoyek gerok a çalakiyên dîmenderê bihêle ku dikare li ser daxwazê were qeydkirin",
"duration": "Dama vegerandinê",
"duration-desc": "Dirêjahiya depo vegerandinê bi çirkeyan",
"custom-duration": "Dama vegerandinê ya xwerû",
"custom-duration-desc": "Dama depo vegerandinê ya xwerû bi çirkeyan binivîse",
"storage": "Depoya vegerandinê",
"storage-desc": "Depo vegerandinê li ku were hilanîn",
"storage-ram": "RAM (tê pêşniyarkirin)",
"storage-disk": "Dîsk"
},
"title": "Mîhengên Qeydkerê Dîmenderê",
"video": {
"codec": "Kodêka vîdyoyî",

View File

@@ -13,7 +13,15 @@
"started": "Opname gestart.",
"stop-recording": "Schermrecorder (opname stoppen)",
"stopping": "Opname stoppen…",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Kon de monitor onder de cursor niet detecteren.",
"replay-saved": "Replay opgeslagen.",
"replay-started": "Replay-buffer gestart.",
"replay-stopped": "Replay-buffer gestopt.",
"replay-failed": "Fout bij starten van replay-buffer.",
"replay-active": "Schermrecorder (replay actief)",
"save-replay": "Replay-buffer opslaan",
"start-replay": "Replay-buffer starten",
"stop-replay": "Replay-buffer stoppen"
},
"name": "Schermrecorder",
"settings": {
@@ -37,10 +45,24 @@
"copy-to-clipboard-description": "Kopieer het bestand naar het klembord nadat de opname is voltooid.",
"output-folder": "Uitvoermap",
"output-folder-description": "Map waar schermopnames worden opgeslagen",
"restore-portal-session": "Portalsessie herstellen",
"restore-portal-session-description": "Herstel automatisch de vorige portalsessie voor volgende opnamen (schakelt het selectievenster uit)",
"show-cursor": "Cursor tonen",
"show-cursor-description": "Muiscursor in de video opnemen",
"title": "Algemeen"
},
"replay": {
"enable": "Replay-buffer inschakelen",
"enable-desc": "Een rollende buffer van recente schermactiviteit bijhouden die op verzoek kan worden opgeslagen",
"duration": "Replay-duur",
"duration-desc": "Lengte van de replay-buffer in seconden",
"custom-duration": "Aangepaste replay-duur",
"custom-duration-desc": "Voer een aangepaste replay-bufferduur in seconden in",
"storage": "Replay-opslag",
"storage-desc": "Waar de replay-buffer moet worden opgeslagen",
"storage-ram": "RAM (aanbevolen)",
"storage-disk": "Schijf"
},
"title": "Schermrecorder Instellingen",
"video": {
"codec": "Video-codec",

View File

@@ -13,7 +13,15 @@
"started": "Rozpoczęto nagrywanie.",
"stop-recording": "Rejestrator ekranu (zatrzymaj nagrywanie)",
"stopping": "Zatrzymywanie nagrywania…",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Nie udało się wykryć monitora pod kursorem.",
"replay-saved": "Powtórka zapisana.",
"replay-started": "Bufor powtórki uruchomiony.",
"replay-stopped": "Bufor powtórki zatrzymany.",
"replay-failed": "Nie udało się uruchomić bufora powtórki.",
"replay-active": "Rejestrator ekranu (powtórka aktywna)",
"save-replay": "Zapisz bufor powtórki",
"start-replay": "Uruchom bufor powtórki",
"stop-replay": "Zatrzymaj bufor powtórki"
},
"name": "Rejestrator ekranu",
"settings": {
@@ -37,10 +45,24 @@
"copy-to-clipboard-description": "Kopiuj plik do schowka po zakończeniu nagrywania.",
"output-folder": "Folder wyjściowy",
"output-folder-description": "Folder, w którym będą zapisywane nagrania ekranu",
"restore-portal-session": "Przywróć sesję portalu",
"restore-portal-session-description": "Automatycznie przywracaj poprzednią sesję portalu dla kolejnych nagrań (wyłącza monit o wybór)",
"show-cursor": "Pokaż kursor",
"show-cursor-description": "Nagrywaj kursor myszy na filmie",
"title": "Ogólne"
},
"replay": {
"enable": "Włącz bufor powtórki",
"enable-desc": "Utrzymuj bufor cykliczny ostatniej aktywności ekranu, który można zapisać na żądanie",
"duration": "Czas trwania powtórki",
"duration-desc": "Długość bufora powtórki w sekundach",
"custom-duration": "Niestandardowy czas trwania powtórki",
"custom-duration-desc": "Wprowadź niestandardowy czas trwania bufora powtórki w sekundach",
"storage": "Przechowywanie powtórki",
"storage-desc": "Gdzie przechowywać bufor powtórki",
"storage-ram": "RAM (zalecane)",
"storage-disk": "Dysk"
},
"title": "Ustawienia rejestratora ekranu",
"video": {
"codec": "Kodek wideo",

View File

@@ -13,7 +13,15 @@
"started": "Gravação iniciada.",
"stop-recording": "Gravador de tela (parar gravação)",
"stopping": "Parando a gravação...",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Não foi possível detetar o monitor sob o cursor.",
"replay-saved": "Replay salvo.",
"replay-started": "Buffer de replay iniciado.",
"replay-stopped": "Buffer de replay parado.",
"replay-failed": "Falha ao iniciar o buffer de replay.",
"replay-active": "Gravador de ecrã (replay ativo)",
"save-replay": "Salvar buffer de replay",
"start-replay": "Iniciar buffer de replay",
"stop-replay": "Parar buffer de replay"
},
"name": "Gravador de Tela",
"settings": {
@@ -37,10 +45,24 @@
"copy-to-clipboard-description": "Copiar o arquivo para a área de transferência após a gravação terminar.",
"output-folder": "Pasta de saída",
"output-folder-description": "Pasta onde as gravações de tela serão salvas",
"restore-portal-session": "Restaurar sessão do portal",
"restore-portal-session-description": "Restaurar automaticamente a sessão anterior do portal para gravações subsequentes (desativa o aviso de seleção)",
"show-cursor": "Mostrar cursor",
"show-cursor-description": "Gravar o cursor do mouse no vídeo",
"title": "Geral"
},
"replay": {
"enable": "Ativar buffer de replay",
"enable-desc": "Manter um buffer rotativo da atividade recente do ecrã que pode ser salvo sob demanda",
"duration": "Duração do replay",
"duration-desc": "Comprimento do buffer de replay em segundos",
"custom-duration": "Duração de replay personalizada",
"custom-duration-desc": "Insira uma duração de buffer de replay personalizada em segundos",
"storage": "Armazenamento do replay",
"storage-desc": "Onde armazenar o buffer de replay",
"storage-ram": "RAM (recomendado)",
"storage-disk": "Disco"
},
"title": "Configurações do Gravador de Tela",
"video": {
"codec": "Codec de vídeo",

View File

@@ -13,7 +13,15 @@
"started": "Запись началась.",
"stop-recording": "Запись экрана (остановить запись)",
"stopping": "Остановка записи…",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Не удалось обнаружить монитор под курсором.",
"replay-saved": "Повтор сохранен.",
"replay-started": "Буфер повтора запущен.",
"replay-stopped": "Буфер повтора остановлен.",
"replay-failed": "Не удалось запустить буфер повтора.",
"replay-active": "Запис экрана (повтор активен)",
"save-replay": "Сохранить буфер повтора",
"start-replay": "Запустить буфер повтора",
"stop-replay": "Остановить буфер повтора"
},
"name": "Запись Экрана",
"settings": {
@@ -37,10 +45,24 @@
"copy-to-clipboard-description": "Скопировать файл в буфер обмена после завершения записи.",
"output-folder": "Папка вывода",
"output-folder-description": "Папка, в которую будут сохраняться записи экрана",
"restore-portal-session": "Восстановить сеанс портала",
"restore-portal-session-description": "Автоматически восстанавливать предыдущий сеанс портала для последующих записей (отключает запрос выбора)",
"show-cursor": "Показать курсор",
"show-cursor-description": "Записывать курсор мыши в видео",
"title": "Общие"
},
"replay": {
"enable": "Включить буфер повтора",
"enable-desc": "Хранить скользящий буфер недавней активности экрана, который можно сохранить по требованию",
"duration": "Длительность повтора",
"duration-desc": "Длина буфера повтора в секундах",
"custom-duration": "Пользовательская длительность повтора",
"custom-duration-desc": "Введите пользовательскую длительность буфера повтора в секундах",
"storage": "Хранилище повтора",
"storage-desc": "Где хранить буфер повтора",
"storage-ram": "ОЗУ (рекомендуется)",
"storage-disk": "Диск"
},
"title": "Настройки Записи Экрана",
"video": {
"codec": "Видеокодек",

View File

@@ -13,7 +13,15 @@
"started": "Kayıt başladı.",
"stop-recording": "Ekran kaydedici (kaydı durdur)",
"stopping": "Kaydı durdurma…",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "İmleç altındaki monitör algılanamadı.",
"replay-saved": "Tekrar kaydedildi.",
"replay-started": "Tekrar arabelleği başlatıldı.",
"replay-stopped": "Tekrar arabelleği durduruldu.",
"replay-failed": "Tekrar arabelleği başlatılamadı.",
"replay-active": "Ekran Kaydedici (tekrar etkin)",
"save-replay": "Tekrar arabelleğini kaydet",
"start-replay": "Tekrar arabelleğini başlat",
"stop-replay": "Tekrar arabelleğini durdur"
},
"name": "Ekran Kaydedici",
"settings": {
@@ -37,10 +45,24 @@
"copy-to-clipboard-description": "Kayıt bittikten sonra dosyayı panoya kopyala.",
"output-folder": ıktı klasörü",
"output-folder-description": "Ekran kayıtlarının kaydedileceği klasör",
"restore-portal-session": "Portal oturumunu geri yükle",
"restore-portal-session-description": "Sonraki kayıtlar için önceki portal oturumunu otomatik olarak geri yükle (seçim penceresini devre dışı bırakır)",
"show-cursor": "İmleci göster",
"show-cursor-description": "Videoda fare imlecini kaydet",
"title": "Genel"
},
"replay": {
"enable": "Tekrar arabelleğini etkinleştir",
"enable-desc": "İsteğe bağlı olarak kaydedilebilen son ekran etkinliğinin döngüsel bir arabelleğini tutun",
"duration": "Tekrar süresi",
"duration-desc": "Saniye cinsinden tekrar arabelleğinin uzunluğu",
"custom-duration": "Özel tekrar süresi",
"custom-duration-desc": "Saniye cinsinden özel bir tekrar arabelleği süresi girin",
"storage": "Tekrar depolama",
"storage-desc": "Tekrar arabelleğinin nerede saklanacağı",
"storage-ram": "RAM (önerilir)",
"storage-disk": "Disk"
},
"title": "Ekran Kaydedici Ayarları",
"video": {
"codec": "Video codec",

View File

@@ -13,7 +13,15 @@
"started": "Запис розпочато.",
"stop-recording": "Запис екрана (зупинити запис)",
"stopping": "Зупинка запису…",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "Не вдалося виявити монітор під курсором.",
"replay-saved": "Повтор збережено.",
"replay-started": "Буфер повтору запущено.",
"replay-stopped": "Буфер повтору зупинено.",
"replay-failed": "Не вдалося запустити буфер повтору.",
"replay-active": "Запис екрана (повтор активний)",
"save-replay": "Зберегти буфер повтору",
"start-replay": "Запустити буфер повтору",
"stop-replay": "Зупинити буфер повтору"
},
"name": "Записувач Екрану",
"settings": {
@@ -36,11 +44,25 @@
"copy-to-clipboard": "Копіювати до буфера обміну",
"copy-to-clipboard-description": "Копіювати файл у буфер обміну після завершення запису.",
"output-folder": "Папка виводу",
"output-folder-description": "Папка, де зберігатимуться записи екрану",
"output-folder-description": "Папка, у якій будуть зберігатися записи екрана",
"restore-portal-session": "Відновити сеанс порталу",
"restore-portal-session-description": "Автоматично відновлювати попередній сеанс порталу для наступних записів (вимикає запит вибору)",
"show-cursor": "Показати курсор",
"show-cursor-description": "Записувати курсор миші у відео",
"title": "Загальні"
},
"replay": {
"enable": "Увімкнути буфер повтору",
"enable-desc": "Зберігати циклічний буфер останньої активності екрана, який можна зберегти на вимогу",
"duration": "Тривалість повтору",
"duration-desc": "Довжина буфера повтору в секундах",
"custom-duration": "Своя тривалість повтору",
"custom-duration-desc": "Введіть власну тривалість буфера повтору в секундах",
"storage": "Сховище повтору",
"storage-desc": "Де зберігати буфер повтору",
"storage-ram": "ОЗП (рекомендовано)",
"storage-disk": "Диск"
},
"title": "Налаштування Записувача Екрану",
"video": {
"codec": "Відеокодек",

View File

@@ -0,0 +1,99 @@
{
"description": "Ghi màn hình bằng phần cứng với các tùy chỉnh video và âm thanh",
"messages": {
"failed-general": "Trình ghi đã thoát với lỗi.",
"failed-gpu": "gpu-screen-recorder đã thoát bất ngờ.",
"failed-start": "Không thể bắt đầu ghi.",
"no-portals": "Desktop portal chưa chạy.",
"no-portals-desc": "Hãy khởi động xdg-desktop-portal và portal compositor (wlr/hyprland/gnome/kde).",
"not-installed": "Chưa cài đặt gpu-screen-recorder.",
"not-installed-desc": "Vui lòng cài đặt gpu-screen-recorder để sử dụng tính năng ghi màn hình.",
"saved": "Đã lưu bản ghi.",
"start-recording": "Trình ghi màn hình (bắt đầu ghi)",
"started": "Đã bắt đầu ghi.",
"stop-recording": "Trình ghi màn hình (dừng ghi)",
"stopping": "Đang dừng ghi…",
"open-file": "Mở tệp",
"monitor-not-found": "Không thể xác định màn hình dưới con trỏ.",
"replay-saved": "Đã lưu replay.",
"replay-started": "Đã bắt đầu bộ đệm replay.",
"replay-stopped": "Đã dừng bộ đệm replay.",
"replay-failed": "Không thể bắt đầu bộ đệm replay.",
"replay-active": "Trình ghi màn hình (replay đang hoạt động)",
"save-replay": "Lưu bộ đệm replay",
"start-replay": "Bắt đầu bộ đệm replay",
"stop-replay": "Dừng bộ đệm replay"
},
"name": "Trình ghi màn hình",
"settings": {
"audio": {
"audio-sources-both": "Âm thanh hệ thống + micro",
"audio-sources-microphone-input": "Micro",
"audio-sources-none": "Không có âm thanh",
"audio-sources-system-output": "Âm thanh hệ thống",
"codec": "Codec âm thanh",
"codec-desc": "Khuyến nghị dùng Opus để có hiệu năng tốt và kích thước nhỏ",
"source": "Nguồn âm thanh",
"source-desc": "Nguồn âm thanh sẽ được ghi",
"title": "Cài đặt âm thanh"
},
"filename-pattern": {
"description": "Mẫu tên tệp sử dụng định dạng ngày giờ (ví dụ: recording_yyyyMMdd_HHmmss)",
"label": "Mẫu tên tệp"
},
"general": {
"copy-to-clipboard": "Sao chép vào clipboard",
"copy-to-clipboard-description": "Sao chép tệp vào clipboard sau khi ghi xong.",
"hide-when-inactive": "Ẩn khi không hoạt động",
"hide-when-inactive-description": "Ẩn chỉ báo trên thanh khi không ghi",
"output-folder": "Thư mục lưu",
"output-folder-description": "Thư mục lưu các bản ghi màn hình",
"restore-portal-session": "Khôi phục phiên portal",
"restore-portal-session-description": "Tự động khôi phục phiên portal trước đó cho lần ghi tiếp theo (tắt hộp thoại chọn)",
"show-cursor": "Hiển thị con trỏ",
"show-cursor-description": "Ghi lại con trỏ chuột trong video",
"title": "Chung"
},
"replay": {
"enable": "Bật replay buffer",
"enable-desc": "Lưu tạm hoạt động màn hình gần đây để có thể lưu lại khi cần",
"duration": "Thời lượng replay",
"duration-desc": "Độ dài bộ đệm replay tính bằng giây",
"custom-duration": "Thời lượng tùy chỉnh",
"custom-duration-desc": "Nhập thời lượng replay tùy chỉnh (giây)",
"storage": "Lưu trữ replay",
"storage-desc": "Nơi lưu bộ đệm replay",
"storage-ram": "RAM (khuyến nghị)",
"storage-disk": "Đĩa"
},
"title": "Cài đặt trình ghi màn hình",
"video": {
"codec": "Codec video",
"codec-desc": "h264 là codec phổ biến nhất",
"color-range": "Dải màu",
"color-range-desc": "Khuyến nghị dùng Limited để tương thích tốt hơn",
"color-range-full": "Đầy đủ",
"color-range-limited": "Giới hạn",
"frame-rate": "Tốc độ khung hình",
"frame-rate-custom": "Tùy chỉnh",
"frame-rate-desc": "Tốc độ khung hình mục tiêu khi ghi",
"custom-frame-rate": "Tốc độ khung hình tùy chỉnh",
"custom-frame-rate-desc": "Nhập giá trị FPS tùy chỉnh",
"quality": "Chất lượng video",
"quality-desc": "Chất lượng cao hơn sẽ tạo tệp lớn hơn",
"quality-high": "Cao",
"quality-medium": "Trung bình",
"quality-ultra": "Siêu cao",
"quality-very-high": "Rất cao",
"resolution": "Độ phân giải",
"resolution-desc": "Giới hạn độ phân giải đầu ra. Mặc định dùng độ phân giải gốc",
"resolution-original": "Mặc định",
"source": "Nguồn video",
"source-desc": "Khuyến nghị dùng Portal, nếu lỗi hiển thị thì thử Screen",
"sources-portal": "Portal",
"sources-screen": "Màn hình",
"sources-focused-monitor": "Màn hình đang focus (Hyprland)",
"title": "Cài đặt video"
}
}
}

View File

@@ -14,7 +14,15 @@
"stop-recording": "屏幕录像(停止录制)",
"stopping": "停止录制…",
"open-file": "打开文件",
"monitor-not-found": "Could not detect the monitor under cursor."
"monitor-not-found": "无法检测到光标下的显示器。",
"replay-saved": "回放已保存。",
"replay-started": "回放缓冲已启动。",
"replay-stopped": "回放缓冲已停止。",
"replay-failed": "启动回放缓冲失败。",
"replay-active": "屏幕录像机(回放活动中)",
"save-replay": "保存回放缓冲",
"start-replay": "启动回放缓冲",
"stop-replay": "停止回放缓冲"
},
"name": "屏幕录制器",
"settings": {
@@ -37,13 +45,27 @@
"copy-to-clipboard": "复制到剪贴板",
"copy-to-clipboard-description": "录制结束后将文件复制到剪贴板。",
"output-folder": "输出文件夹",
"output-folder-description": "屏幕录制将保存的文件夹",
"output-folder-description": "屏幕录保存的文件夹",
"restore-portal-session": "恢复门户会话",
"restore-portal-session-description": "自动恢复之前的门户会话以用于后续录制(禁用选择提示)",
"show-cursor": "显示光标",
"show-cursor-description": "在视频中录制鼠标光标",
"hide-when-inactive": "非活动时隐藏",
"hide-when-inactive-description": "未录制时隐藏栏指示器",
"title": "常规"
},
"replay": {
"enable": "启用回放缓冲",
"enable-desc": "保持最近屏幕活动的滚动缓冲,可根据需要保存",
"duration": "回放时长",
"duration-desc": "回放缓冲的长度(秒)",
"custom-duration": "自定义回放时长",
"custom-duration-desc": "输入自定义回放缓冲时长(秒)",
"storage": "回放存储",
"storage-desc": "回放缓冲存储位置",
"storage-ram": "内存 (推荐)",
"storage-disk": "磁盘"
},
"title": "屏幕录制器设置",
"video": {
"codec": "视频编解码器",

View File

@@ -1,7 +1,7 @@
{
"id": "screen-recorder",
"name": "Screen Recorder",
"version": "1.2.0",
"version": "1.3.7",
"minNoctaliaVersion": "3.6.0",
"author": "Noctalia Team",
"official": true,
@@ -36,7 +36,12 @@
"copyToClipboard": false,
"audioSource": "default_output",
"videoSource": "portal",
"resolution": "original"
"resolution": "original",
"replayEnabled": false,
"replayDuration": "30",
"customReplayDuration": "30",
"replayStorage": "ram",
"restorePortalSession": false
}
}
}

View File

@@ -13,6 +13,8 @@ Item {
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property bool hovered: false
// Bar positioning properties
@@ -82,13 +84,9 @@ Item {
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: root.pluginApi?.mainInstance?.updateCount > 0 ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (root.pluginApi?.mainInstance?.updateCount > 0)
root.pluginApi?.mainInstance?.startDoSystemUpdate();
}
onEntered: {
root.hovered = true;
buildTooltip();
@@ -98,15 +96,53 @@ Item {
root.hovered = false;
TooltipService.hide();
}
onPressed: mouse => {
TooltipService.hide();
if (mouse.button == Qt.LeftButton && root.pluginApi?.mainInstance?.updateCount > 0)
root.pluginApi?.mainInstance?.startDoSystemUpdate();
else if (mouse.button == Qt.RightButton)
PanelService.showContextMenu(contextMenu, root, screen);
}
NPopupContextMenu {
id: contextMenu
model: [
{
"label": "Update",
"action": "run-update-cmd",
"icon": "arrow-up-from-arc",
"enabled": root.pluginApi?.mainInstance?.updateCount > 0
},
{
"label": I18n.tr("actions.widget-settings"),
"action": "widget-settings",
"icon": "settings"
},
]
onTriggered: action => {
contextMenu.close();
PanelService.closeContextMenu(screen);
if (action === "run-update-cmd")
root.pluginApi?.mainInstance?.startDoSystemUpdate();
else if (action === "widget-settings") {
BarService.openPluginSettings(screen, pluginApi.manifest);
}
}
}
}
function buildTooltip() {
const updateCount = root.pluginApi?.mainInstance?.updateCount
if (updateCount === 0) {
TooltipService.show(root, pluginApi?.tr("tooltip.noUpdatesAvailable"), BarService.getTooltipDirection());
TooltipService.show(root, pluginApi?.tr("tooltip.noUpdatesAvailable"), BarService.getTooltipDirection(root.screenName));
} else {
TooltipService.show(root, pluginApi?.tr("tooltip.updatesAvailable"), BarService.getTooltipDirection());
TooltipService.show(root, pluginApi?.tr("tooltip.updatesAvailable"), BarService.getTooltipDirection(root.screenName));
}
}
}

View File

@@ -1,13 +1,16 @@
{
"id": "update-count",
"name": "Update Count",
"version": "1.0.12",
"version": "1.0.14",
"minNoctaliaVersion": "3.6.0",
"author": "BukoMoon",
"license": "GPLv3",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "Checks for system updates and shows the update count. Click to run update command in a terminal.",
"tags": ["Bar", "System"],
"tags": [
"Bar",
"System"
],
"entryPoints": {
"main": "Main.qml",
"barWidget": "BarWidget.qml",

View File

@@ -8,7 +8,9 @@
"customLaunchPrefixEnabled": false,
"density": "default",
"enableClipPreview": true,
"enableClipboardChips": true,
"enableClipboardHistory": true,
"enableClipboardSmartIcons": true,
"enableSessionSearch": true,
"enableSettingsSearch": true,
"enableWindowsSearch": true,
@@ -23,7 +25,6 @@
"showIconBackground": true,
"sortByMostUsed": true,
"terminalCommand": "alacritty -e",
"useApp2Unit": false,
"viewMode": "list"
},
"audio": {
@@ -31,6 +32,7 @@
],
"preferredPlayer": "",
"spectrumFrameRate": 30,
"spectrumMirrored": true,
"visualizerType": "linear",
"volumeFeedback": false,
"volumeFeedbackSoundFile": "",
@@ -48,7 +50,6 @@
"density": "comfortable",
"displayMode": "always_visible",
"enableExclusionZoneInset": true,
"floating": false,
"fontScale": 1,
"frameRadius": 12,
"frameThickness": 8,
@@ -189,20 +190,6 @@
"useMonospaceFont": true,
"usePadding": false
},
{
"defaultSettings": {
"arrowType": "caret",
"byteThresholdActive": 1024,
"fontSizeModifier": 1,
"forceMegabytes": false,
"iconSizeModifier": 1,
"minWidth": 0,
"showNumbers": true,
"spacingInbetween": 0,
"useCustomColors": false
},
"id": "plugin:network-indicator"
},
{
"defaultSettings": {
"currentIconName": "world-download",
@@ -336,6 +323,7 @@
"monitorForColors": "",
"predefinedScheme": "Catppuccin",
"schedulingMode": "off",
"syncGsettings": true,
"useWallpaperColors": false
},
"controlCenter": {
@@ -572,16 +560,19 @@
"indicatorColor": "primary",
"indicatorOpacity": 0.6,
"indicatorThickness": 3,
"launcherIcon": "",
"launcherIconColor": "none",
"launcherPosition": "end",
"launcherUseDistroLogo": false,
"monitors": [
],
"onlySameOutput": false,
"pinnedApps": [
"Alacritty",
"com.moonlight_stream.Moonlight",
"com.discordapp.Discord",
"org.keepassxc.KeePassXC",
"Alacritty",
"Firefox",
"com.discordapp.Discord",
"firefox"
],
"pinnedStatic": true,
@@ -653,9 +644,11 @@
"showHibernateOnLockScreen": true,
"showScreenCorners": false,
"showSessionButtonsOnLockScreen": true,
"smoothScrollEnabled": true,
"telemetryEnabled": true
},
"hooks": {
"colorGeneration": "",
"darkModeChange": "",
"enabled": false,
"performanceModeDisabled": "",
@@ -682,6 +675,7 @@
},
"location": {
"analogClockInCalendar": false,
"autoLocate": false,
"firstDayOfWeek": -1,
"hideWeatherCityName": false,
"hideWeatherTimezone": false,
@@ -692,10 +686,10 @@
"use12hourFormat": false,
"useFahrenheit": false,
"weatherEnabled": true,
"weatherShowEffects": true
"weatherShowEffects": true,
"weatherTaliaMascotAlways": false
},
"network": {
"airplaneModeEnabled": false,
"bluetoothAutoConnect": true,
"bluetoothDetailsViewMode": "grid",
"bluetoothHideUnnamedDevices": false,
@@ -703,8 +697,7 @@
"bluetoothRssiPollingEnabled": false,
"disableDiscoverability": false,
"networkPanelView": "wifi",
"wifiDetailsViewMode": "list",
"wifiEnabled": true
"wifiDetailsViewMode": "list"
},
"nightLight": {
"autoSchedule": false,
@@ -768,7 +761,8 @@
"overlayLayer": true
},
"plugins": {
"autoUpdate": false
"autoUpdate": false,
"notifyUpdates": true
},
"sessionMenu": {
"countdownDuration": 10000,
@@ -824,13 +818,20 @@
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "7"
},
{
"action": "userspaceReboot",
"command": "",
"countdownEnabled": true,
"enabled": false,
"keybind": ""
}
],
"showHeader": true,
"showKeybinds": true
},
"settingsVersion": 57,
"settingsVersion": 59,
"systemMonitor": {
"batteryCriticalThreshold": 5,
"batteryWarningThreshold": 20,
@@ -903,6 +904,7 @@
"fillColor": "#000000",
"fillMode": "crop",
"hideWallpaperFilenames": false,
"linkLightAndDarkWallpapers": true,
"monitorDirectories": [
{
"directory": "/home/aiden/Pictures/Wallpapers",
@@ -922,7 +924,15 @@
"sortOrder": "name",
"transitionDuration": 1500,
"transitionEdgeSmoothness": 0.05,
"transitionType": "random",
"transitionType": [
"fade",
"disc",
"stripes",
"wipe",
"pixelate",
"honeycomb"
],
"useOriginalImages": false,
"useSolidColor": false,
"useWallhaven": false,
"viewMode": "single",