summaryrefslogtreecommitdiff
path: root/noctalia/.config/plugins/privacy-indicator/Main.qml
diff options
context:
space:
mode:
Diffstat (limited to 'noctalia/.config/plugins/privacy-indicator/Main.qml')
-rw-r--r--noctalia/.config/plugins/privacy-indicator/Main.qml316
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;
- }
-
-
-}