This commit is contained in:
2026-02-17 21:43:15 -06:00
parent e9f3ab00a7
commit 1432d75bb6
22 changed files with 1088 additions and 243 deletions

View File

@@ -2,10 +2,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Commons
import qs.Modules.Bar.Extras
import qs.Services.UI
import qs.Widgets
@@ -26,12 +23,15 @@ Item {
readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName)
readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName)
property bool micActive: false
property bool camActive: false
property bool scrActive: false
property var micApps: []
property var camApps: []
property var scrApps: []
// Access main instance for state
readonly property var mainInstance: pluginApi?.mainInstance
property bool micActive: mainInstance ? mainInstance.micActive : false
property bool camActive: mainInstance ? mainInstance.camActive : false
property bool scrActive: mainInstance ? mainInstance.scrActive : false
property var micApps: mainInstance ? mainInstance.micApps : []
property var camApps: mainInstance ? mainInstance.camApps : []
property var scrApps: mainInstance ? mainInstance.scrApps : []
property var cfg: pluginApi?.pluginSettings || ({})
property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
@@ -63,156 +63,6 @@ Item {
visible: root.isVisible
opacity: root.isVisible ? 1.0 : 0.0
PwObjectTracker {
objects: Pipewire.ready ? Pipewire.nodes.values : []
}
Process {
id: cameraCheckProcess
running: false
command: ["sh", "-c", "for dev in /dev/video*; do [ -e \"$dev\" ] && [ -n \"$(find /proc/[0-9]*/fd/ -lname \"$dev\" 2>/dev/null | head -n1)\" ] && echo \"active\" && exit 0; done; exit 1"]
onExited: (code, status) => {
var isActive = code === 0;
root.camActive = isActive;
if (isActive) {
cameraAppsProcess.running = true;
} else {
root.camApps = [];
}
}
}
Process {
id: cameraAppsProcess
running: false
command: ["sh", "-c", "for dev in /dev/video*; do [ -e \"$dev\" ] && for fd in /proc/[0-9]*/fd/*; do [ -L \"$fd\" ] && [ \"$(readlink \"$fd\" 2>/dev/null)\" = \"$dev\" ] && ps -p \"$(echo \"$fd\" | cut -d/ -f3)\" -o comm= 2>/dev/null; done; done | sort -u | tr '\\n' ',' | sed 's/,$//'"]
onExited: (code, status) => {
if (stdout) {
var appsString = stdout.trim();
var apps = appsString.length > 0 ? appsString.split(',') : [];
root.camApps = apps;
} else {
root.camApps = [];
}
}
}
Timer {
interval: 1000
repeat: true
running: true
triggeredOnStart: true
onTriggered: updatePrivacyState()
}
function hasNodeLinks(node, links) {
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link && (link.source === node || link.target === node)) {
return true;
}
}
return false;
}
function getAppName(node) {
return node.properties["application.name"] || node.nickname || node.name || "";
}
function updateMicrophoneState(nodes, links) {
var appNames = [];
var isActive = false;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (!node || !node.isStream || !node.audio || node.isSink) continue;
if (!hasNodeLinks(node, links) || !node.properties) continue;
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 (appName && appNames.indexOf(appName) === -1) {
appNames.push(appName);
}
}
}
root.micActive = isActive;
root.micApps = appNames;
}
function updateCameraState() {
cameraCheckProcess.running = true;
}
function isScreenShareNode(node) {
if (!node.properties) {
return false;
}
var mediaClass = node.properties["media.class"] || "";
if (mediaClass.indexOf("Audio") >= 0) {
return false;
}
if (mediaClass.indexOf("Video") === -1) {
return false;
}
var mediaName = (node.properties["media.name"] || "").toLowerCase();
if (mediaName.match(/^(xdph-streaming|gsr-default|game capture|screen|desktop|display|cast|webrtc|v4l2)/) ||
mediaName === "gsr-default_output" ||
mediaName.match(/screen-cast|screen-capture|desktop-capture|monitor-capture|window-capture|game-capture/i)) {
return true;
}
return false;
}
function updateScreenShareState(nodes, links) {
var appNames = [];
var isActive = false;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (!node || !hasNodeLinks(node, links) || !node.properties) continue;
if (isScreenShareNode(node)) {
isActive = true;
var appName = getAppName(node);
if (appName && appNames.indexOf(appName) === -1) {
appNames.push(appName);
}
}
}
root.scrActive = isActive;
root.scrApps = appNames;
}
function updatePrivacyState() {
if (!Pipewire.ready) return;
var nodes = Pipewire.nodes.values || [];
var links = Pipewire.links.values || [];
updateMicrophoneState(nodes, links);
updateCameraState();
updateScreenShareState(nodes, links);
}
function buildTooltip() {
var parts = [];
@@ -301,12 +151,14 @@ Item {
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
acceptedButtons: Qt.RightButton | Qt.LeftButton
hoverEnabled: true
onClicked: function (mouse) {
if (mouse.button === Qt.RightButton) {
PanelService.showContextMenu(contextMenu, root, screen);
} else if (mouse.button === Qt.LeftButton) {
if (pluginApi) pluginApi.openPanel(root.screen, root);
}
}

View File

@@ -0,0 +1,282 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Commons
import qs.Services.UI
Item {
id: root
property var pluginApi: null
// --- Logic extracted from BarWidget.qml ---
property bool micActive: false
property bool camActive: false
property bool scrActive: false
property var micApps: []
property var camApps: []
property var scrApps: []
property var accessHistory: []
// Previous states for history tracking
property var _prevMicApps: []
property var _prevCamApps: []
property var _prevScrApps: []
PwObjectTracker {
objects: Pipewire.ready ? Pipewire.nodes.values : []
}
Process {
id: cameraDetectionProcess
running: false
command: ["sh", "-c", "for dev in /sys/class/video4linux/video*; do [ -e \"$dev/name\" ] && grep -qv 'Metadata' \"$dev/name\" && dev_name=$(basename \"$dev\") && find /proc/[0-9]*/fd -lname \"/dev/$dev_name\" 2>/dev/null; done | cut -d/ -f3 | xargs -r ps -o comm= -p | sort -u | tr '\\n' ',' | sed 's/,$//'"]
stdout: StdioCollector {
onStreamFinished: {
var appsString = this.text.trim();
var apps = appsString.length > 0 ? appsString.split(',') : [];
root.camApps = apps;
root.camActive = apps.length > 0;
}
}
}
Timer {
interval: 1000
repeat: true
running: true
triggeredOnStart: true
onTriggered: updatePrivacyState()
}
function hasNodeLinks(node, links) {
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link && (link.source === node || link.target === node)) return true;
}
return false;
}
function getAppName(node) {
return node.properties["application.name"] || node.nickname || node.name || "";
}
function updateMicrophoneState(nodes, links) {
var appNames = [];
var isActive = false;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (!node || !node.isStream || !node.audio || node.isSink) continue;
if (!hasNodeLinks(node, links) || !node.properties) continue;
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 (appName && appNames.indexOf(appName) === -1) appNames.push(appName);
}
}
root.micActive = isActive;
root.micApps = appNames;
}
function updateCameraState() {
cameraDetectionProcess.running = true;
}
function isScreenShareNode(node) {
if (!node.properties) return false;
var mediaClass = node.properties["media.class"] || "";
if (mediaClass.indexOf("Audio") >= 0) return false;
if (mediaClass.indexOf("Video") === -1) return false;
var mediaName = (node.properties["media.name"] || "").toLowerCase();
if (mediaName.match(/^(xdph-streaming|gsr-default|game capture|screen|desktop|display|cast|webrtc|v4l2)/) ||
mediaName === "gsr-default_output" ||
mediaName.match(/screen-cast|screen-capture|desktop-capture|monitor-capture|window-capture|game-capture/i)) {
return true;
}
return false;
}
function updateScreenShareState(nodes, links) {
var appNames = [];
var isActive = false;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (!node || !hasNodeLinks(node, links) || !node.properties) continue;
if (isScreenShareNode(node)) {
isActive = true;
var appName = getAppName(node);
if (appName && appNames.indexOf(appName) === -1) appNames.push(appName);
}
}
root.scrActive = isActive;
root.scrApps = appNames;
}
function updatePrivacyState() {
if (!Pipewire.ready) return;
var nodes = Pipewire.nodes.values || [];
var links = Pipewire.links.values || [];
updateMicrophoneState(nodes, links);
updateCameraState();
updateScreenShareState(nodes, links);
}
// --- History Persistence ---
property string stateFile: ""
property bool isLoaded: false
Component.onCompleted: {
// Setup state file path
Qt.callLater(() => {
if (typeof Settings !== 'undefined' && Settings.cacheDir) {
stateFile = Settings.cacheDir + "privacy-history.json";
historyFileView.path = stateFile;
}
});
}
FileView {
id: historyFileView
printErrors: false
watchChanges: false
adapter: JsonAdapter {
id: adapter
property var history: []
}
onLoaded: {
root.isLoaded = true;
if (adapter.history) {
// Restore history
root.accessHistory = adapter.history;
}
}
onLoadFailed: error => {
// If file doesn't exist (error 2), we are ready to save new data
if (error === 2) {
root.isLoaded = true;
} else {
console.error("PrivacyIndicator: Failed to load history file:", error);
root.isLoaded = true; // Try to continue anyway
}
}
}
function saveHistory() {
if (!stateFile || !isLoaded) return;
adapter.history = root.accessHistory;
// Ensure cache directory exists and save
try {
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]);
Qt.callLater(() => {
try {
historyFileView.writeAdapter();
} catch (e) {
console.error("PrivacyIndicator: Failed to save history", e);
}
});
} catch (e) {
console.error("PrivacyIndicator: Failed to save history", e);
}
}
function addToHistory(app, type, icon, colorKey, action) {
var time = new Date().toLocaleTimeString(Qt.locale(), Locale.ShortFormat);
var entry = {
"appName": app,
"type": type,
"icon": icon,
"colorKey": colorKey,
"time": time,
"timestamp": Date.now(),
"action": action // "started" or "stopped"
};
var newHistory = [entry].concat(accessHistory);
if (newHistory.length > 50) newHistory = newHistory.slice(0, 50); // Increased limit as we have more entries now
accessHistory = newHistory;
saveHistory();
}
function clearHistory() {
accessHistory = [];
saveHistory();
}
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++) {
var app = newApps[i];
if (!oldApps || oldApps.indexOf(app) === -1) {
addToHistory(app, type, icon, colorKey, "started");
}
}
}
// Check for removed apps (Stopped)
if (oldApps) {
for (var j = 0; j < oldApps.length; j++) {
var oldApp = oldApps[j];
if (!newApps || newApps.indexOf(oldApp) === -1) {
addToHistory(oldApp, type, icon, colorKey, "stopped");
}
}
}
}
// Get active color from settings or default
property var cfg: pluginApi?.pluginSettings || ({})
property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
property string activeColorKey: cfg.activeColor ?? defaults.activeColor ?? "primary"
onMicAppsChanged: {
checkAppChanges(micApps, _prevMicApps, "Microphone", "microphone", activeColorKey);
_prevMicApps = micApps;
}
// Helper to detect activation edge
property bool oldMicActive: false
onMicActiveChanged: {
if (micActive && (!oldMicActive)) {
ToastService.showNotice(pluginApi?.tr("toast.mic-on") || "Microphone is active", "", "microphone");
}
oldMicActive = micActive
}
property bool oldCamActive: false
onCamActiveChanged: {
if (camActive && !oldCamActive) {
ToastService.showNotice(pluginApi?.tr("toast.cam-on") || "Camera is active", "", "camera");
}
oldCamActive = camActive
}
onCamAppsChanged: {
checkAppChanges(camApps, _prevCamApps, "Camera", "camera", activeColorKey);
_prevCamApps = camApps;
}
property bool oldScrActive: false
onScrActiveChanged: {
if (scrActive && !oldScrActive) {
ToastService.showNotice(pluginApi?.tr("toast.screen-on") || "Screen sharing is active", "", "screen-share");
}
oldScrActive = scrActive
}
onScrAppsChanged: {
checkAppChanges(scrApps, _prevScrApps, "Screen", "screen-share", activeColorKey);
_prevScrApps = scrApps;
}
}

View File

@@ -0,0 +1,169 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
Item {
id: root
property var pluginApi: null
// Standard panel properties
readonly property var geometryPlaceholder: panelContainer
property real contentPreferredWidth: 320 * Style.uiScaleRatio
property real contentPreferredHeight: 450 * Style.uiScaleRatio
readonly property var mainInstance: pluginApi?.mainInstance
Rectangle {
id: panelContainer
anchors.fill: parent
color: "transparent"
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
// Header Box
NBox {
Layout.fillWidth: true
Layout.preferredHeight: headerRow.implicitHeight + Style.marginM * 2
RowLayout {
id: headerRow
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginS
NIcon {
icon: "shield-check"
color: Color.mPrimary
pointSize: Style.fontSizeL
}
NText {
Layout.fillWidth: true
text: pluginApi?.tr("history.title") || "Access History"
font.weight: Style.fontWeightBold
pointSize: Style.fontSizeL
color: Color.mOnSurface
}
NIconButton {
icon: "trash"
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
if (mainInstance) mainInstance.clearHistory();
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
NScrollView {
id: scrollView
anchors.fill: parent
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
ColumnLayout {
width: scrollView.availableWidth
spacing: Style.marginS
Repeater {
model: mainInstance ? mainInstance.accessHistory : []
delegate: Rectangle {
Layout.fillWidth: true
implicitHeight: 56 * Style.uiScaleRatio
radius: Style.radiusM
color: Color.mSurfaceVariant
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
Rectangle {
width: 32 * Style.uiScaleRatio
height: 32 * Style.uiScaleRatio
radius: width/2
color: Qt.alpha(iconColor, 0.1)
readonly property color iconColor: Color.resolveColorKey(modelData.colorKey || "primary")
NIcon {
anchors.centerIn: parent
icon: modelData.icon
color: parent.iconColor
pointSize: Style.fontSizeM
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
NText {
Layout.fillWidth: true
text: modelData.appName
elide: Text.ElideRight
font.weight: Style.fontWeightBold
pointSize: Style.fontSizeM
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NText {
text: modelData.time
color: Qt.alpha(Color.mOnSurface, 0.7)
pointSize: Style.fontSizeS
}
NText {
text: "•"
color: Qt.alpha(Color.mOnSurface, 0.3)
pointSize: Style.fontSizeS
}
NText {
text: {
const action = modelData.action || "started";
return pluginApi?.tr("history.action." + action) || action;
}
color: (modelData.action || "started") === "stopped" ? Color.resolveColorKey("error") : Color.resolveColorKey("primary")
font.weight: Style.fontWeightBold
pointSize: Style.fontSizeS
}
}
}
}
}
}
// Empty state
NText {
Layout.alignment: Qt.AlignHCenter
visible: (!mainInstance || mainInstance.accessHistory.length === 0)
text: pluginApi?.tr("history.empty") || "No recent access"
color: Qt.alpha(Color.mOnSurface, 0.5)
pointSize: Style.fontSizeM
Layout.topMargin: Style.marginL
}
Item { Layout.fillHeight: true } // spacer
}
}
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Alle äußeren Ränder des Widgets entfernen.",
"label": "Ränder entfernen"
},
"activeColor": {
"desc": "Farbe der Symbole, wenn sie aktiv sind.",
"label": "Aktive Farbsymbol"
},
"inactiveColor": {
"desc": "Farbe der Symbole, wenn sie inaktiv sind.",
"label": "Inaktive Farbsymbol"
}
},
"tooltip": {
"cam-on": "Kamera: {apps}",
"mic-on": "Mikrofon: {apps}",
"screen-on": "Bildschirmfreigabe: {apps}"
},
"toast": {
"cam-on": "Kamera ist aktiv",
"mic-on": "Mikrofon ist aktiv",
"screen-on": "Bildschirmfreigabe ist aktiv"
},
"menu": {
"settings": "Widget-Einstellungen"
},
"history": {
"title": "Zugriffsverlauf",
"empty": "Kein kürzlicher Zugriff",
"clear": "Leeren",
"action": {
"started": "Gestartet",
"stopped": "Beendet"
}
}
}
}

View File

@@ -28,5 +28,19 @@
"cam-on": "Camera: {apps}",
"mic-on": "Microphone: {apps}",
"screen-on": "Screen sharing: {apps}"
},
"toast": {
"cam-on": "Camera is active",
"mic-on": "Microphone is active",
"screen-on": "Screen sharing is active"
},
"history": {
"title": "Access History",
"empty": "No recent access",
"clear": "Clear",
"action": {
"started": "Started",
"stopped": "Stopped"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Quita todos los márgenes externos del widget.",
"label": "Quitar márgenes"
},
"activeColor": {
"desc": "Color de los iconos cuando están activos.",
"label": "Color de icono activo"
},
"inactiveColor": {
"desc": "Color de los iconos cuando están inactivos.",
"label": "Color de icono inactivo"
}
},
"tooltip": {
"cam-on": "Cámara: {apps}",
"mic-on": "Micrófono: {apps}",
"screen-on": "Compartir pantalla: {apps}"
},
"toast": {
"cam-on": "La cámara está activa",
"mic-on": "El micrófono está activo",
"screen-on": "Compartir pantalla está activo"
},
"menu": {
"settings": "Configuración del widget"
},
"history": {
"title": "Historial de acceso",
"empty": "Sin accesos recientes",
"clear": "Borrar",
"action": {
"started": "Iniciado",
"stopped": "Detenido"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Supprime toutes les marges extérieures du widget.",
"label": "Supprimer les marges"
},
"activeColor": {
"desc": "Couleur des icônes lorsqu'elles sont actives.",
"label": "Couleur d'icône active"
},
"inactiveColor": {
"desc": "Couleur des icônes lorsqu'elles sont inactives.",
"label": "Couleur d'icône inactive"
}
},
"tooltip": {
"cam-on": "Caméra: {apps}",
"mic-on": "Microphone: {apps}",
"screen-on": "Partage d'écran: {apps}"
},
"toast": {
"cam-on": "La caméra est active",
"mic-on": "Le microphone est actif",
"screen-on": "Le partage d'écran est actif"
},
"menu": {
"settings": "Paramètres du widget"
},
"history": {
"title": "Historique d'accès",
"empty": "Aucun accès récent",
"clear": "Effacer",
"action": {
"started": "Démarré",
"stopped": "Arrêté"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Távolítsd el a widget összes külső margóját.",
"label": "Margók eltávolítása"
},
"activeColor": {
"desc": "Az ikonok színe, amikor aktívak.",
"label": "Aktív ikon színe"
},
"inactiveColor": {
"desc": "Az ikonok színe, amikor inaktívak.",
"label": "Inaktív ikon színe"
}
},
"tooltip": {
"cam-on": "Kamera: {apps}",
"mic-on": "Mikrofon: {apps}",
"screen-on": "Képernyőmegosztás: {apps}"
},
"toast": {
"cam-on": "A kamera aktív",
"mic-on": "A mikrofon aktív",
"screen-on": "Képernyőmegosztás aktív"
},
"menu": {
"settings": "Widget beállítások"
},
"history": {
"title": "Hozzáférési előzmények",
"empty": "Nincs nemrégiben történt hozzáférés",
"clear": "Törlés",
"action": {
"started": "Elindítva",
"stopped": "Leállítva"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Rimuove tutti i margini esterni del widget.",
"label": "Rimuovi margini"
},
"activeColor": {
"desc": "Colore delle icone quando sono attive.",
"label": "Colore icona attiva"
},
"inactiveColor": {
"desc": "Colore delle icone quando sono inattive.",
"label": "Colore icona inattiva"
}
},
"tooltip": {
"cam-on": "Kamera: {apps}",
"mic-on": "Mikrofonoa: {apps}",
"screen-on": "Ekran-partaĝado: {apps}"
},
"toast": {
"cam-on": "La fotocamera è attiva",
"mic-on": "Il microfono è attivo",
"screen-on": "La condivisione dello schermo è attiva"
},
"menu": {
"settings": "Impostazioni widget"
},
"history": {
"title": "Cronologia accessi",
"empty": "Nessun accesso recente",
"clear": "Pulisci",
"action": {
"started": "Iniziato",
"stopped": "Terminato"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "ウィジェットの外側の余白をすべて削除します。",
"label": "余白を削除"
},
"activeColor": {
"desc": "アクティブ時のアイコンの色。",
"label": "アクティブ時の色"
},
"inactiveColor": {
"desc": "非アクティブ時のアイコンの色。",
"label": "非アクティブ時の色"
}
},
"tooltip": {
"cam-on": "カメラ: {apps}",
"mic-on": "マイク: {apps}",
"screen-on": "画面共有: {apps}"
},
"toast": {
"cam-on": "カメラがアクティブです",
"mic-on": "マイクがアクティブです",
"screen-on": "画面共有がアクティブです"
},
"menu": {
"settings": "ウィジェット設定"
},
"history": {
"title": "アクセス履歴",
"empty": "最近のアクセスはありません",
"clear": "クリア",
"action": {
"started": "開始",
"stopped": "停止"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Hemû marjînalên derveyî yên widgetê rake.",
"label": "Derdestên derxînin"
},
"activeColor": {
"desc": "Color of the icons when active.",
"label": "Active icon color"
},
"inactiveColor": {
"desc": "Color of the icons when inactive.",
"label": "Inactive icon color"
}
},
"tooltip": {
"cam-on": "Kamera: {apps}",
"mic-on": "Mîkrofon: {apps}",
"screen-on": "Parvekirina ekranê: {apps}"
},
"toast": {
"cam-on": "Kamera çalak e",
"mic-on": "Mîkrofon çalak e",
"screen-on": "Parvekirina ekranê çalak e"
},
"menu": {
"settings": "Widget settings"
},
"history": {
"title": "Access History",
"empty": "No recent access",
"clear": "Clear",
"action": {
"started": "Started",
"stopped": "Stopped"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Verwijdert alle buitenste marges van de widget.",
"label": "Marges verwijderen"
},
"activeColor": {
"desc": "Kleur van de pictogrammen wanneer ze actief zijn.",
"label": "Actieve pictogramkleur"
},
"inactiveColor": {
"desc": "Kleur van de pictogrammen wanneer ze inactief zijn.",
"label": "Inactieve pictogramkleur"
}
},
"tooltip": {
"cam-on": "Camera: {apps}",
"mic-on": "Microfoon: {apps}",
"screen-on": "Schermdeling: {apps}"
},
"toast": {
"cam-on": "Camera is actief",
"mic-on": "Microfoon is actief",
"screen-on": "Scherm delen is actief"
},
"menu": {
"settings": "Widget instellingen"
},
"history": {
"title": "Toegangsgeschiedenis",
"empty": "Geen recente toegang",
"clear": "Wissen",
"action": {
"started": "Gestart",
"stopped": "Gestopt"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Usuń wszystkie zewnętrzne marginesy widżetu.",
"label": "Usuń marginesy"
},
"activeColor": {
"desc": "Kolor ikon, gdy są aktywne.",
"label": "Kolor aktywnej ikony"
},
"inactiveColor": {
"desc": "Kolor ikon, gdy są nieaktywne.",
"label": "Kolor nieaktywnej ikony"
}
},
"tooltip": {
"cam-on": "Kamera: {apps}",
"mic-on": "Mikrofon: {apps}",
"screen-on": "Udostępnianie ekranu: {apps}"
},
"toast": {
"cam-on": "Kamera jest aktywna",
"mic-on": "Mikrofon jest aktywny",
"screen-on": "Udostępnianie ekranu jest aktywne"
},
"menu": {
"settings": "Ustawienia widżetu"
},
"history": {
"title": "Historia dostępu",
"empty": "Brak ostatnich dostępów",
"clear": "Wyczyść",
"action": {
"started": "Rozpoczęto",
"stopped": "Zatrzymano"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Remove todas as margens externas do widget.",
"label": "Remover margens"
},
"activeColor": {
"desc": "Cor dos ícones quando ativos.",
"label": "Cor do ícone ativo"
},
"inactiveColor": {
"desc": "Cor dos ícones quando inativos.",
"label": "Cor do ícone inativo"
}
},
"tooltip": {
"cam-on": "Câmera: {apps}",
"mic-on": "Microfone: {apps}",
"screen-on": "Compartilhamento de tela: {apps}"
},
"toast": {
"cam-on": "A câmera está ativa",
"mic-on": "O microfone está ativo",
"screen-on": "O compartilhamento de tela está ativo"
},
"menu": {
"settings": "Configurações do widget"
},
"history": {
"title": "Histórico de acesso",
"empty": "Sem acessos recentes",
"clear": "Limpar",
"action": {
"started": "Iniciado",
"stopped": "Interrompido"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Удаляет все внешние отступы виджета.",
"label": "Убрать отступы"
},
"activeColor": {
"desc": "Цвет иконок при активации.",
"label": "Цвет активной иконки"
},
"inactiveColor": {
"desc": "Цвет иконок в спокойном состоянии.",
"label": "Цвет неактивной иконки"
}
},
"tooltip": {
"cam-on": "Камера: {apps}",
"mic-on": "Микрофон: {apps}",
"screen-on": "Демонстрация экрана: {apps}"
},
"toast": {
"cam-on": "Камера активна",
"mic-on": "Микрофон активен",
"screen-on": "Демонстрация экрана активна"
},
"menu": {
"settings": "Настройки виджета"
},
"history": {
"title": "История доступа",
"empty": "Нет недавних доступов",
"clear": "Очистить",
"action": {
"started": "Начато",
"stopped": "Остановлено"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Widgetın tüm dış kenar boşluklarını kaldırır.",
"label": "Kenarlıkları kaldır"
},
"activeColor": {
"desc": "Aktif olduğunda simgelerin rengi.",
"label": "Aktif simge rengi"
},
"inactiveColor": {
"desc": "Devre dışı olduğunda simgelerin rengi.",
"label": "Pasif simge rengi"
}
},
"tooltip": {
"cam-on": "Kamera: {apps}",
"mic-on": "Mikrofon: {apps}",
"screen-on": "Ekran paylaşımı: {apps}"
},
"toast": {
"cam-on": "Kamera aktif",
"mic-on": "Mikrofon aktif",
"screen-on": "Ekran paylaşımı aktif"
},
"menu": {
"settings": "Bileşen ayarları"
},
"history": {
"title": "Erişim Geçmişi",
"empty": "Yakın zamanda erişim yok",
"clear": "Temizle",
"action": {
"started": "Başlatıldı",
"stopped": "Durduruldu"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "Видаляє всі зовнішні відступи віджета.",
"label": "Прибрати відступи"
},
"activeColor": {
"desc": "Колір іконок при активації.",
"label": "Колір активної іконки"
},
"inactiveColor": {
"desc": "Колір іконок у спокійному стані.",
"label": "Колір неактивної іконки"
}
},
"tooltip": {
"cam-on": "Камера: {apps}",
"mic-on": "Мікрофон: {apps}",
"screen-on": "Демонстрація екрана: {apps}"
},
"toast": {
"cam-on": "Камера активна",
"mic-on": "Мікрофон активний",
"screen-on": "Демонстрація екрана активна"
},
"menu": {
"settings": "Налаштування віджета"
},
"history": {
"title": "Історія доступу",
"empty": "Немає недавніх доступів",
"clear": "Очистити",
"action": {
"started": "Розпочато",
"stopped": "Зупинено"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "移除小部件所有外部边距。",
"label": "移除边距"
},
"activeColor": {
"desc": "激活图标的颜色。",
"label": "激活图标颜色"
},
"inactiveColor": {
"desc": "未激活图标的颜色。",
"label": "未激活图标颜色"
}
},
"tooltip": {
"cam-on": "摄像头: {apps}",
"mic-on": "麦克风: {apps}",
"screen-on": "屏幕共享: {apps}"
},
"toast": {
"cam-on": "摄像头已激活",
"mic-on": "麦克风已激活",
"screen-on": "屏幕共享已激活"
},
"menu": {
"settings": "小部件设置"
},
"history": {
"title": "访问历史",
"empty": "无最近访问",
"clear": "清除",
"action": {
"started": "已开始",
"stopped": "已停止"
}
}
}
}

View File

@@ -11,11 +11,36 @@
"removeMargins": {
"desc": "移除小工具外面的所有邊距",
"label": "移除邊距"
},
"activeColor": {
"desc": "圖示啟用時的顏色。",
"label": "啟用圖示顏色"
},
"inactiveColor": {
"desc": "圖示未啟用時的顏色。",
"label": "未啟用圖示顏色"
}
},
"tooltip": {
"cam-on": "攝影機: {apps}",
"mic-on": "麥克風: {apps}",
"screen-on": "螢幕分享: {apps}"
},
"toast": {
"cam-on": "攝影機已啟用",
"mic-on": "麥克風已啟用",
"screen-on": "螢幕分享已啟用"
},
"menu": {
"settings": "小工具設定"
},
"history": {
"title": "存取紀錄",
"empty": "無最近存取",
"clear": "清除",
"action": {
"started": "已開始",
"stopped": "已停止"
}
}
}
}

View File

@@ -1,16 +1,22 @@
{
"id": "privacy-indicator",
"name": "Privacy Indicator",
"version": "1.1.0",
"version": "1.2.4",
"minNoctaliaVersion": "3.6.0",
"author": "Noctalia Team",
"official": true,
"license": "MIT",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "A privacy indicator widget that shows when microphone, camera or screen sharing is active.",
"tags": ["Bar", "Privacy", "Indicator"],
"tags": [
"Bar",
"Privacy",
"Indicator"
],
"entryPoints": {
"main": "Main.qml",
"barWidget": "BarWidget.qml",
"panel": "Panel.qml",
"settings": "Settings.qml"
},
"dependencies": {
@@ -24,4 +30,4 @@
"inactiveColor": "none"
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -9,6 +9,7 @@
"density": "default",
"enableClipPreview": true,
"enableClipboardHistory": true,
"enableSessionSearch": true,
"enableSettingsSearch": true,
"enableWindowsSearch": true,
"iconMode": "tabler",
@@ -41,7 +42,7 @@
"backgroundOpacity": 0.93,
"barType": "simple",
"capsuleColorKey": "none",
"capsuleOpacity": 1,
"capsuleOpacity": 0.36,
"density": "comfortable",
"displayMode": "always_visible",
"floating": false,
@@ -52,7 +53,7 @@
"marginVertical": 5,
"monitors": [
],
"outerCorners": false,
"outerCorners": true,
"position": "top",
"screenOverrides": [
],
@@ -62,29 +63,20 @@
"widgets": {
"center": [
{
"compactMode": false,
"compactShowAlbumArt": true,
"compactShowVisualizer": false,
"hideMode": "hidden",
"hideWhenIdle": false,
"id": "MediaMini",
"maxWidth": 250,
"panelShowAlbumArt": true,
"panelShowVisualizer": true,
"scrollingMode": "hover",
"showAlbumArt": true,
"showArtistFirst": true,
"showProgressRing": true,
"showVisualizer": true,
"textColor": "none",
"useFixedWidth": false,
"visualizerType": "linear"
"clockColor": "none",
"customFont": "Adwaita Sans",
"formatHorizontal": "HH:mm:ss t",
"formatVertical": "HH mm - dd MM",
"id": "Clock",
"tooltipFormat": "HH:mm ddd, MMM dd",
"useCustomFont": false
}
],
"left": [
{
"colorizeSystemIcon": "none",
"enableColorization": false,
"generalTooltipText": "",
"hideMode": "alwaysExpanded",
"icon": "rocket",
"id": "CustomButton",
@@ -100,7 +92,9 @@
"parseJson": false,
"rightClickExec": "",
"rightClickUpdateText": false,
"showExecTooltip": true,
"showIcon": true,
"showTextTooltip": true,
"textCollapse": "",
"textCommand": "",
"textIntervalMs": 3000,
@@ -127,7 +121,6 @@
"labelMode": "name",
"occupiedColor": "secondary",
"pillSize": 0.6,
"reverseScroll": false,
"showApplications": false,
"showBadge": true,
"showLabelsOnlyWhenOccupied": true,
@@ -136,21 +129,12 @@
{
"clockColor": "none",
"customFont": "",
"formatHorizontal": "HH:mm ddd, MMM dd",
"formatHorizontal": "ddd, MMM dd",
"formatVertical": "HH mm - dd MM",
"id": "Clock",
"tooltipFormat": "HH:mm ddd, MMM dd",
"useCustomFont": false
},
{
"defaultSettings": {
"currentIconName": "world-download",
"hideOnZero": false,
"updateIntervalMinutes": 30,
"updateTerminalCommand": "foot -e"
},
"id": "plugin:update-count"
},
{
"compactMode": false,
"diskPath": "/",
@@ -169,7 +153,8 @@
"showNetworkStats": false,
"showSwapUsage": false,
"textColor": "none",
"useMonospaceFont": true
"useMonospaceFont": true,
"usePadding": false
},
{
"defaultSettings": {
@@ -186,17 +171,13 @@
"id": "plugin:network-indicator"
},
{
"colorizeIcons": true,
"hideMode": "hidden",
"iconScale": 1,
"id": "Taskbar",
"maxTaskbarWidth": 40,
"onlyActiveWorkspaces": false,
"onlySameOutput": true,
"showPinnedApps": true,
"showTitle": false,
"smartWidth": true,
"titleWidth": 120
"defaultSettings": {
"currentIconName": "world-download",
"hideOnZero": false,
"updateIntervalMinutes": 30,
"updateTerminalCommand": "foot -e"
},
"id": "plugin:update-count"
}
],
"right": [
@@ -214,6 +195,7 @@
{
"colorizeSystemIcon": "none",
"enableColorization": false,
"generalTooltipText": "",
"hideMode": "alwaysExpanded",
"icon": "eye",
"id": "CustomButton",
@@ -229,7 +211,9 @@
"parseJson": false,
"rightClickExec": "",
"rightClickUpdateText": false,
"showExecTooltip": true,
"showIcon": true,
"showTextTooltip": true,
"textCollapse": "",
"textCommand": "",
"textIntervalMs": 3000,
@@ -250,6 +234,7 @@
"textColor": "none"
},
{
"applyToAllMonitors": false,
"displayMode": "onhover",
"iconColor": "none",
"id": "Brightness",
@@ -351,7 +336,7 @@
"id": "media-sysmon-card"
},
{
"enabled": false,
"enabled": true,
"id": "brightness-card"
}
],
@@ -389,11 +374,153 @@
{
"name": "DP-3",
"widgets": [
{
"clockColor": "none",
"clockStyle": "minimal",
"customFont": "",
"format": "HH:mm\\nd MMMM yyyy",
"id": "Clock",
"roundedCorners": true,
"scale": 1.1838477631085023,
"showBackground": true,
"useCustomFont": false,
"x": 20,
"y": 60
},
{
"hideMode": "visible",
"id": "MediaPlayer",
"roundedCorners": true,
"scale": 1.1461354014452196,
"showAlbumArt": true,
"showBackground": true,
"showButtons": true,
"showVisualizer": true,
"visualizerType": "mirrored",
"x": 20,
"y": 160
},
{
"id": "Weather",
"scale": 1,
"showBackground": true,
"x": 240,
"y": 60
}
]
},
{
"name": "DP-5",
"widgets": [
{
"hideMode": "visible",
"id": "MediaPlayer",
"roundedCorners": true,
"scale": 1.4949747468305836,
"showAlbumArt": true,
"showBackground": true,
"showButtons": true,
"showVisualizer": true,
"visualizerType": "mirrored",
"x": 40,
"y": 200
},
{
"id": "Weather",
"scale": 1.3394112549695427,
"showBackground": true,
"x": 320,
"y": 80
},
{
"clockColor": "none",
"clockStyle": "minimal",
"customFont": "",
"format": "HH:mm\\nd MMMM yyyy",
"id": "Clock",
"roundedCorners": true,
"scale": 1.5562573345334174,
"showBackground": true,
"useCustomFont": false,
"x": 40,
"y": 80
}
]
},
{
"name": "HDMI-A-1",
"widgets": [
{
"clockColor": "none",
"clockStyle": "minimal",
"customFont": "",
"format": "HH:mm\\nd MMMM yyyy",
"id": "Clock",
"roundedCorners": true,
"scale": 1.1744196726926817,
"showBackground": true,
"useCustomFont": false,
"x": 20,
"y": 60
},
{
"id": "Weather",
"scale": 1,
"showBackground": true,
"x": 240,
"y": 60
},
{
"hideMode": "visible",
"id": "MediaPlayer",
"roundedCorners": true,
"scale": 1.1461354014452199,
"showAlbumArt": true,
"showBackground": true,
"showButtons": true,
"showVisualizer": true,
"visualizerType": "mirrored",
"x": 20,
"y": 160
}
]
},
{
"name": "eDP-1",
"widgets": [
{
"clockColor": "none",
"clockStyle": "minimal",
"customFont": "",
"format": "HH:mm\\nd MMMM yyyy",
"id": "Clock",
"roundedCorners": true,
"scale": 1.1461354014452199,
"showBackground": true,
"useCustomFont": false,
"x": 20,
"y": 60
},
{
"hideMode": "visible",
"id": "MediaPlayer",
"roundedCorners": true,
"scale": 1.15084944665313,
"showAlbumArt": true,
"showBackground": true,
"showButtons": true,
"showVisualizer": true,
"visualizerType": "mirrored",
"x": 20,
"y": 160
},
{
"id": "Weather",
"scale": 1,
"showBackground": true,
"x": 240,
"y": 60
}
]
}
]
@@ -403,19 +530,25 @@
"backgroundOpacity": 1,
"colorizeIcons": true,
"deadOpacity": 0.6,
"displayMode": "exclusive",
"enabled": false,
"displayMode": "always_visible",
"dockType": "static",
"enabled": true,
"floatingRatio": 1,
"inactiveIndicators": false,
"inactiveIndicators": true,
"monitors": [
"DP-5"
],
"onlySameOutput": false,
"pinnedApps": [
"firefox",
"com.discordapp.Discord",
"com.moonlight_stream.Moonlight",
"Element"
],
"pinnedStatic": true,
"position": "bottom",
"size": 0.77
"position": "top",
"showFrameIndicator": true,
"sitOnFrame": false,
"size": 1.2
},
"general": {
"allowPanelsOnScreenWithoutBar": true,
@@ -446,6 +579,9 @@
"keyLeft": [
"Left"
],
"keyRemove": [
"Del"
],
"keyRight": [
"Right"
],
@@ -456,12 +592,13 @@
"language": "",
"lockOnSuspend": true,
"lockScreenAnimations": true,
"lockScreenBlur": 0.5,
"lockScreenBlur": 0.8,
"lockScreenCountdownDuration": 10000,
"lockScreenMonitors": [
],
"lockScreenTint": 0,
"radiusRatio": 1,
"reverseScroll": false,
"scaleRatio": 1,
"screenRadiusRatio": 1,
"shadowDirection": "bottom_right",
@@ -499,10 +636,12 @@
"weatherShowEffects": true
},
"network": {
"airplaneModeEnabled": false,
"bluetoothDetailsViewMode": "grid",
"bluetoothHideUnnamedDevices": false,
"bluetoothRssiPollIntervalMs": 10000,
"bluetoothRssiPollingEnabled": false,
"disableDiscoverability": false,
"wifiDetailsViewMode": "list",
"wifiEnabled": true
},
@@ -517,9 +656,12 @@
},
"notifications": {
"backgroundOpacity": 1,
"clearDismissed": true,
"criticalUrgencyDuration": 15,
"density": "default",
"enableBatteryToast": true,
"enableKeyboardLayoutToast": true,
"enableMarkdown": true,
"enableMediaToast": false,
"enabled": true,
"location": "top_right",
@@ -552,7 +694,8 @@
"enabledTypes": [
0,
1,
2
2,
3
],
"location": "top_right",
"monitors": [
@@ -574,70 +717,71 @@
"command": "",
"countdownEnabled": false,
"enabled": true,
"keybind": ""
"keybind": "1"
},
{
"action": "suspend",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": ""
"keybind": "2"
},
{
"action": "hibernate",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": ""
"keybind": "3"
},
{
"action": "reboot",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": ""
"keybind": "4"
},
{
"action": "logout",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": ""
"keybind": "5"
},
{
"action": "shutdown",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "6"
},
{
"action": "rebootToUefi",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": ""
}
],
"showHeader": true,
"showNumberLabels": true
"showKeybinds": true
},
"settingsVersion": 51,
"settingsVersion": 53,
"systemMonitor": {
"batteryCriticalThreshold": 5,
"batteryWarningThreshold": 20,
"cpuCriticalThreshold": 90,
"cpuPollingInterval": 1000,
"cpuWarningThreshold": 80,
"criticalColor": "",
"diskAvailCriticalThreshold": 10,
"diskAvailWarningThreshold": 20,
"diskCriticalThreshold": 90,
"diskPollingInterval": 3000,
"diskWarningThreshold": 80,
"enableDgpuMonitoring": false,
"externalMonitor": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor",
"gpuCriticalThreshold": 90,
"gpuPollingInterval": 3000,
"gpuWarningThreshold": 80,
"loadAvgPollingInterval": 3000,
"memCriticalThreshold": 90,
"memPollingInterval": 1000,
"memWarningThreshold": 80,
"networkPollingInterval": 1000,
"swapCriticalThreshold": 90,
"swapWarningThreshold": 80,
"tempCriticalThreshold": 90,
@@ -690,6 +834,8 @@
"directory": "/home/aiden/Pictures/Wallpapers",
"enableMultiMonitorDirectories": true,
"enabled": true,
"favorites": [
],
"fillColor": "#000000",
"fillMode": "crop",
"hideWallpaperFilenames": false,
@@ -707,6 +853,7 @@
"randomIntervalSec": 600,
"setWallpaperOnAllMonitors": false,
"showHiddenFiles": false,
"skipStartupTransition": false,
"solidColor": "#1a1a2e",
"sortOrder": "name",
"transitionDuration": 1500,