diff options
| author | seraphim <18ycm9tx@anonaddy.me> | 2026-07-31 14:03:07 +0700 |
|---|---|---|
| committer | seraphim <18ycm9tx@anonaddy.me> | 2026-07-31 14:03:07 +0700 |
| commit | 4835f660f9435fa64336daaaae1ea3cab1dfbee6 (patch) | |
| tree | 45524b7f7cdb6009fa85558cea44678b2b563a43 /noctalia/.config/plugins/privacy-indicator/Main.qml | |
| parent | 59226408d8bc950d7ec8c21623f28b5f7de8fce0 (diff) | |
mouse
Diffstat (limited to 'noctalia/.config/plugins/privacy-indicator/Main.qml')
| -rw-r--r-- | noctalia/.config/plugins/privacy-indicator/Main.qml | 316 |
1 files changed, 0 insertions, 316 deletions
diff --git a/noctalia/.config/plugins/privacy-indicator/Main.qml b/noctalia/.config/plugins/privacy-indicator/Main.qml deleted file mode 100644 index 29d94a0..0000000 --- a/noctalia/.config/plugins/privacy-indicator/Main.qml +++ /dev/null @@ -1,316 +0,0 @@ -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: [] - - // Get active color from settings or default - property var cfg: pluginApi?.pluginSettings || ({}) - 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 ?? "" - property string camFilterRegex: cfg.camFilterRegex ?? defaults.camFilterRegex ?? "" - - 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(',') : []; - - var filterRegex = null; - if (root.camFilterRegex && root.camFilterRegex.length > 0) { - try { - filterRegex = new RegExp(root.camFilterRegex); - } catch (e) { - Logger.w("PrivacyIndicator: Invalid camFilterRegex:", root.camFilterRegex); - } - } - - var appNames = []; - for (var i = 0; i < apps.length; i++) { - var appName = apps[i]; - if (filterRegex && appName && filterRegex.test(appName)) continue; - if (appName && appNames.indexOf(appName) === -1) appNames.push(appName); - } - - root.camApps = appNames; - root.camActive = appNames.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; - - 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; - 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; - - var appName = getAppName(node); - if (filterRegex && appName && filterRegex.test(appName)) continue; - - isActive = true; - 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"); - } - } - } - } - - - onMicAppsChanged: { - checkAppChanges(micApps, _prevMicApps, "Microphone", "microphone", activeColorKey); - _prevMicApps = micApps; - } - // Helper to detect activation edge - property bool oldMicActive: false - onMicActiveChanged: { - if (enableToast && micActive && !oldMicActive) { - ToastService.showNotice(pluginApi?.tr("toast.mic-on"), "", "microphone"); - } - oldMicActive = micActive - } - - property bool oldCamActive: false - onCamActiveChanged: { - if (enableToast && camActive && !oldCamActive) { - ToastService.showNotice(pluginApi?.tr("toast.cam-on"), "", "camera"); - } - oldCamActive = camActive - } - onCamAppsChanged: { - checkAppChanges(camApps, _prevCamApps, "Camera", "camera", activeColorKey); - _prevCamApps = camApps; - } - - property bool oldScrActive: false - onScrActiveChanged: { - if (enableToast && scrActive && !oldScrActive) { - ToastService.showNotice(pluginApi?.tr("toast.screen-on"), "", "screen-share"); - } - oldScrActive = scrActive - } - onScrAppsChanged: { - checkAppChanges(scrApps, _prevScrApps, "Screen", "screen-share", activeColorKey); - _prevScrApps = scrApps; - } - - -} |
