diff --git a/environment.d/10-path.conf b/environment.d/10-path.conf new file mode 100644 index 0000000..c186183 --- /dev/null +++ b/environment.d/10-path.conf @@ -0,0 +1 @@ +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin diff --git a/foot/foot.ini b/foot/foot.ini index ee26c9e..33c216c 100644 --- a/foot/foot.ini +++ b/foot/foot.ini @@ -1,3 +1,6 @@ +[main] +include=~/.config/foot/themes/noctalia + # -*- conf -*- # shell=$SHELL (if set, otherwise user's default shell from /etc/passwd) diff --git a/foot/themes/noctalia b/foot/themes/noctalia new file mode 100644 index 0000000..8c4ad91 --- /dev/null +++ b/foot/themes/noctalia @@ -0,0 +1,22 @@ +[colors-dark] +foreground=ebdbb2 +background=282828 +regular0=282828 +regular1=cc241d +regular2=98971a +regular3=d79921 +regular4=458588 +regular5=b16286 +regular6=689d6a +regular7=a89984 +bright0=928374 +bright1=fb4934 +bright2=b8bb26 +bright3=fabd2f +bright4=83a598 +bright5=d3869b +bright6=8ec07c +bright7=ebdbb2 +selection-foreground=ebdbb2 +selection-background=665c54 +cursor=282828 ebdbb2 diff --git a/link.sh b/link.sh index ca3ac03..7c7abd2 100755 --- a/link.sh +++ b/link.sh @@ -16,3 +16,5 @@ ln -s ~/Documents/dotfiles/zathura ~/.config/ ln -s ~/Documents/dotfiles/opencode ~/.config/ ln -s ~/Documents/dotfiles/.zshrc ~/ ln -s ~/Documents/dotfiles/.gitconfig ~/ +mkdir -p ~/.config/environment.d +ln -s ~/Documents/dotfiles/environment.d/10-path.conf ~/.config/environment.d/10-path.conf diff --git a/noctalia/plugins.json b/noctalia/plugins.json index 04b0a22..146b536 100644 --- a/noctalia/plugins.json +++ b/noctalia/plugins.json @@ -7,6 +7,10 @@ } ], "states": { + "privacy-indicator": { + "enabled": true, + "sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins" + } }, "version": 2 } diff --git a/noctalia/plugins/privacy-indicator/BarWidget.qml b/noctalia/plugins/privacy-indicator/BarWidget.qml new file mode 100644 index 0000000..bdebd41 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/BarWidget.qml @@ -0,0 +1,175 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Item { + id: root + + property var pluginApi: null + + 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 : "" + readonly property string barPosition: Settings.getBarPositionForScreen(screenName) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property real barHeight: Style.getBarHeightForScreen(screenName) + readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName) + readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName) + + // 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 || ({}) + + property bool hideInactive: cfg.hideInactive ?? defaults.hideInactive ?? false + property bool enableToast: cfg.enableToast ?? defaults.enableToast ?? true + property bool removeMargins: cfg.removeMargins ?? defaults.removeMargins ?? false + property int iconSpacing: cfg.iconSpacing ?? defaults.iconSpacing ?? 4 + property string activeColorKey: cfg.activeColor ?? defaults.activeColor ?? "primary" + property string inactiveColorKey: cfg.inactiveColor ?? defaults.inactiveColor ?? "none" + + readonly property color activeColor: Color.resolveColorKey(activeColorKey) + readonly property color inactiveColor: inactiveColorKey === "none" ? Qt.alpha(Color.mOnSurfaceVariant, 0.3) : Color.resolveColorKey(inactiveColorKey) + readonly property color micColor: micActive ? activeColor : inactiveColor + readonly property color camColor: camActive ? activeColor : inactiveColor + readonly property color scrColor: scrActive ? activeColor : inactiveColor + + readonly property bool isVisible: !hideInactive || micActive || camActive || scrActive + + property real margins: removeMargins ? 0 : Style.marginM * 2 + + readonly property real contentWidth: isVertical ? Style.capsuleHeight : Math.round(layout.implicitWidth + margins) + readonly property real contentHeight: isVertical ? Math.round(layout.implicitHeight + margins) : Style.capsuleHeight + + implicitWidth: contentWidth + implicitHeight: contentHeight + + Layout.alignment: Qt.AlignVCenter + visible: root.isVisible + opacity: root.isVisible ? 1.0 : 0.0 + + function buildTooltip() { + var parts = []; + + if (micActive && micApps.length > 0) { + parts.push("Mic: " + micApps.join(", ")); + } + + if (camActive && camApps.length > 0) { + parts.push("Cam: " + camApps.join(", ")); + } + + if (scrActive && scrApps.length > 0) { + parts.push("Screen sharing: " + scrApps.join(", ")); + } + + return parts.length > 0 ? parts.join("\n") : ""; + } + + Rectangle { + id: visualCapsule + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) + width: root.contentWidth + height: root.contentHeight + radius: Style.radiusM + color: Style.capsuleColor + border.color: Style.capsuleBorderColor + border.width: Style.capsuleBorderWidth + + Item { + id: layout + + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + + implicitWidth: iconsLayout.implicitWidth + implicitHeight: iconsLayout.implicitHeight + + GridLayout { + id: iconsLayout + + columns: root.isVertical ? 1 : 3 + rows: root.isVertical ? 3 : 1 + + rowSpacing: root.iconSpacing + columnSpacing: root.iconSpacing + + NIcon { + visible: micActive || !root.hideInactive + icon: micActive ? "microphone" : "microphone-off" + color: root.micColor + } + NIcon { + visible: camActive || !root.hideInactive + icon: camActive ? "camera" : "camera-off" + color: root.camColor + } + NIcon { + visible: scrActive || !root.hideInactive + icon: scrActive ? "screen-share" : "screen-share-off" + color: root.scrColor + } + } + } + } + + NPopupContextMenu { + id: contextMenu + + model: [ + { + "label": pluginApi?.tr("menu.settings"), + "action": "settings", + "icon": "settings" + }, + ] + + onTriggered: function (action) { + contextMenu.close(); + PanelService.closeContextMenu(screen); + if (action === "settings") { + BarService.openPluginSettings(root.screen, pluginApi.manifest); + } + } + } + + MouseArea { + anchors.fill: parent + 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); + } + } + + onEntered: { + var tooltipText = buildTooltip(); + if (tooltipText) { + TooltipService.show(root, tooltipText, BarService.getTooltipDirection()); + } + } + onExited: TooltipService.hide() + } +} diff --git a/noctalia/plugins/privacy-indicator/Main.qml b/noctalia/plugins/privacy-indicator/Main.qml new file mode 100644 index 0000000..0c64564 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/Main.qml @@ -0,0 +1,316 @@ +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++) { + 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; + } + + +} diff --git a/noctalia/plugins/privacy-indicator/Panel.qml b/noctalia/plugins/privacy-indicator/Panel.qml new file mode 100644 index 0000000..0cfcc22 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/Panel.qml @@ -0,0 +1,170 @@ +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 + readonly property bool allowAttach: true + + 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") + 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") + color: Qt.alpha(Color.mOnSurface, 0.5) + pointSize: Style.fontSizeM + Layout.topMargin: Style.marginL + } + + Item { Layout.fillHeight: true } // spacer + } + } + } + } + } +} diff --git a/noctalia/plugins/privacy-indicator/README.md b/noctalia/plugins/privacy-indicator/README.md new file mode 100644 index 0000000..d192040 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/README.md @@ -0,0 +1,47 @@ +# Privacy Indicator Plugin + +A privacy indicator widget that monitors and displays when microphone, camera, or screen sharing is active on your system. + +## Features + +- **Microphone Monitoring**: Detects active microphone usage via Pipewire +- **Camera Monitoring**: Detects active camera usage by checking `/dev/video*` devices +- **Screen Sharing Detection**: Monitors screen sharing sessions via Pipewire +- **Visual Indicators**: Shows icons that change color based on active state + - Active: Primary color + - Inactive: Semi-transparent variant color +- **App Information**: Tooltip displays which applications are using each resource +- **Adaptive Layout**: Automatically adjusts layout for horizontal or vertical bar positions + +## Configuration + +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. +- **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 + +The widget displays three icons in the bar: +- **Microphone**: Shows when any app is using the microphone +- **Camera**: Shows when any app is accessing the camera +- **Screen Share**: Shows when screen sharing is active + +Hover over the widget to see a tooltip listing which applications are using each resource. + +## Requirements + +- Noctalia Shell 3.6.0 or higher +- Pipewire (for microphone and screen sharing detection) +- Access to `/dev/video*` devices (for camera detection) + +## Technical Details + +- Updates privacy state every second +- Uses Pipewire API to monitor audio/video streams +- Checks `/proc/[0-9]*/fd/` for camera device access +- Detects screen sharing by analyzing Pipewire node properties and media class diff --git a/noctalia/plugins/privacy-indicator/Settings.qml b/noctalia/plugins/privacy-indicator/Settings.qml new file mode 100644 index 0000000..84d77e7 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/Settings.qml @@ -0,0 +1,141 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + id: root + + property var pluginApi: null + + property var cfg: pluginApi?.pluginSettings || ({}) + property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({}) + + property bool hideInactive: cfg.hideInactive ?? defaults.hideInactive ?? false + property bool enableToast: cfg.enableToast ?? defaults.enableToast ?? true + property bool removeMargins: cfg.removeMargins ?? defaults.removeMargins ?? false + 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 + property string camFilterRegex: cfg.camFilterRegex ?? defaults.camFilterRegex + + spacing: Style.marginL + + Component.onCompleted: { + Logger.i("PrivacyIndicator", "Settings UI loaded"); + } + + ColumnLayout { + spacing: Style.marginM + Layout.fillWidth: true + + NToggle { + label: pluginApi?.tr("settings.hideInactive.label") + description: pluginApi?.tr("settings.hideInactive.desc") + + checked: root.hideInactive + onToggled: checked => { + root.hideInactive = checked; + } + } + + NToggle { + label: pluginApi?.tr("settings.enableToast.label") + description: pluginApi?.tr("settings.enableToast.desc") + + checked: root.enableToast + onToggled: checked => { + root.enableToast = checked; + } + } + + NToggle { + label: pluginApi?.tr("settings.removeMargins.label") + description: pluginApi?.tr("settings.removeMargins.desc") + + checked: root.removeMargins + onToggled: checked => { + root.removeMargins = checked; + } + } + + NColorChoice { + label: pluginApi?.tr("settings.activeColor.label") + description: pluginApi?.tr("settings.activeColor.desc") + currentKey: root.activeColor + onSelected: key => root.activeColor = key + } + + NColorChoice { + label: pluginApi?.tr("settings.inactiveColor.label") + description: pluginApi?.tr("settings.inactiveColor.desc") + currentKey: root.inactiveColor + onSelected: key => root.inactiveColor = key + noneColor: Qt.alpha(Color.mOnSurfaceVariant, 0.3) + noneOnColor: Qt.alpha(Color.mOnSurface, 0.7) + } + + NComboBox { + label: pluginApi?.tr("settings.iconSpacing.label") + description: pluginApi?.tr("settings.iconSpacing.desc") + + model: { + const labels = ["XXS", "XS", "S", "M", "L", "XL"]; + const values = [Style.marginXXS, Style.marginXS, Style.marginS, Style.marginM, Style.marginL, Style.marginXL]; + + const result = []; + for (var i = 0; i < labels.length; ++i) { + const v = values[i]; + result.push({ + key: v.toFixed(0), + name: `${labels[i]} (${v}px)` + }); + } + return result; + } + + // INFO: From my understanding, the toFixed(0) shouldn't be needed here and there, but without the + // current key does not show when opening the settings window. + currentKey: root.iconSpacing.toFixed(0) + onSelected: key => root.iconSpacing = key + } + + NTextInput { + Layout.fillWidth: true + label: pluginApi?.tr("settings.micFilterRegex.label") + description: pluginApi?.tr("settings.micFilterRegex.desc") + placeholderText: "effect_input.rnnoise|easyeffects" + text: root.micFilterRegex + onTextChanged: root.micFilterRegex = text + } + + NTextInput { + Layout.fillWidth: true + label: pluginApi?.tr("settings.camFilterRegex.label") + description: pluginApi?.tr("settings.camFilterRegex.desc") + placeholderText: "droidcam" + text: root.camFilterRegex + onTextChanged: root.camFilterRegex = text + } + } + + function saveSettings() { + if (!pluginApi) { + Logger.e("PrivacyIndicator", "Cannot save settings: pluginApi is null"); + return; + } + + pluginApi.pluginSettings.hideInactive = root.hideInactive; + pluginApi.pluginSettings.enableToast = root.enableToast; + pluginApi.pluginSettings.iconSpacing = root.iconSpacing; + pluginApi.pluginSettings.removeMargins = root.removeMargins; + pluginApi.pluginSettings.activeColor = root.activeColor; + pluginApi.pluginSettings.inactiveColor = root.inactiveColor; + pluginApi.pluginSettings.micFilterRegex = root.micFilterRegex; + + pluginApi.saveSettings(); + + Logger.i("PrivacyIndicator", "Settings saved successfully"); + } +} diff --git a/noctalia/plugins/privacy-indicator/i18n/de.json b/noctalia/plugins/privacy-indicator/i18n/de.json new file mode 100644 index 0000000..b25635e --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/de.json @@ -0,0 +1,54 @@ +{ + "menu": { + "settings": "Widget Einstellungen" + }, + "settings": { + "activeColor": { + "desc": "Farbe der Symbole, wenn sie aktiv sind.", + "label": "Aktive Farbsymbol" + }, + "hideInactive": { + "desc": "Mikrofon-, Kamera- und Bildschirmsymbole ausblenden, wenn sie inaktiv sind.", + "label": "Inaktive Zustände ausblenden" + }, + "enableToast": { + "desc": "Zeige Toast Mitteilungen an, wenn sich einer der Zustände ändert.", + "label": "Aktiviere Toast Mitteilungen" + }, + "inactiveColor": { + "desc": "Farbe der Symbole, wenn sie inaktiv sind.", + "label": "Inaktive Farbsymbol" + }, + "iconSpacing": { + "desc": "Den Abstand zwischen den Symbolen festlegen.", + "label": "Symbolabstand" + }, + "removeMargins": { + "desc": "Alle äußeren Ränder des Widgets entfernen.", + "label": "Ränder entfernen" + }, + "micFilterRegex": { + "desc": "Regex Muster zum Herausfiltern von Mikrofon-Apps. Entsprechende Apps werden vollständig von der Erkennung ausgeschlossen.", + "label": "Regex für Mikrofonfilter" + } + }, + "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" + }, + "history": { + "title": "Zugriffsverlauf", + "empty": "Kein kürzlicher Zugriff", + "clear": "Leeren", + "action": { + "started": "Gestartet", + "stopped": "Beendet" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/en.json b/noctalia/plugins/privacy-indicator/i18n/en.json new file mode 100644 index 0000000..fa65418 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/en.json @@ -0,0 +1,58 @@ +{ + "menu": { + "settings": "Widget settings" + }, + "settings": { + "activeColor": { + "desc": "Color of the icons when active.", + "label": "Active icon color" + }, + "hideInactive": { + "desc": "Hide microphone, camera, and screen icons when there are inactive.", + "label": "Hide inactive states" + }, + "enableToast": { + "desc": "Show toast messages when one of the states changes.", + "label": "Enable toast notifications" + }, + "inactiveColor": { + "desc": "Color of the icons when inactive.", + "label": "Inactive icon color" + }, + "iconSpacing": { + "desc": "Set the spacing between the icons.", + "label": "Icon spacing" + }, + "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" + }, + "camFilterRegex": { + "desc": "Regex pattern to filter out camera applications. Matching apps are completely excluded from detection.", + "label": "Camera filter regex" + } + }, + "tooltip": { + "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" + } + } +} diff --git a/noctalia/plugins/privacy-indicator/i18n/es.json b/noctalia/plugins/privacy-indicator/i18n/es.json new file mode 100644 index 0000000..93a48a7 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/es.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Ocultar los iconos de micrófono, cámara y pantalla cuando estén inactivos.", + "label": "Ocultar estados inactivos" + }, + "iconSpacing": { + "desc": "Configurar el espacio entre los iconos.", + "label": "Espaciado de iconos" + }, + "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" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/fr.json b/noctalia/plugins/privacy-indicator/i18n/fr.json new file mode 100644 index 0000000..9053131 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/fr.json @@ -0,0 +1,54 @@ +{ + "menu": { + "settings": "Paramètres du widget" + }, + "settings": { + "activeColor": { + "desc": "Couleur des icônes lorsqu'elles sont actives.", + "label": "Couleur d'icône active" + }, + "hideInactive": { + "desc": "Masquer les icônes du micro, de la caméra et de l’écran lorsqu’ils sont inactifs.", + "label": "Masquer les états inactifs" + }, + "enableToast": { + "desc": "Afficher des messages toast lorsque l'un des états change.", + "label": "Activer les notifications toast" + }, + "inactiveColor": { + "desc": "Couleur des icônes lorsqu'elles sont inactives.", + "label": "Couleur d'icône inactive" + }, + "iconSpacing": { + "desc": "Définir l’espacement entre les icônes.", + "label": "Espacement des icônes" + }, + "removeMargins": { + "desc": "Supprime toutes les marges extérieures du widget.", + "label": "Supprimer les marges" + }, + "micFilterRegex": { + "desc": "Motif Regex pour filtrer les applications de microphone. Les applications correspondantes sont complètement exclues de la détection.", + "label": "Regex de filtrage du microphone" + } + }, + "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" + }, + "history": { + "title": "Historique d'accès", + "empty": "Aucun accès récent", + "clear": "Effacer", + "action": { + "started": "Démarré", + "stopped": "Arrêté" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/hu.json b/noctalia/plugins/privacy-indicator/i18n/hu.json new file mode 100644 index 0000000..4a7ba8c --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/hu.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Mikrofon, kamera és képernyő ikonok elrejtése, ha éppen nincsenek használatban.", + "label": "Inaktív állapotok elrejtése" + }, + "iconSpacing": { + "desc": "Állítsa be az ikonok közötti távolságot.", + "label": "Ikon távolság" + }, + "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" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/it.json b/noctalia/plugins/privacy-indicator/i18n/it.json new file mode 100644 index 0000000..d0eee79 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/it.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Nascondi le icone di microfono, fotocamera e schermo quando sono inattive.", + "label": "Nascondi stati inattivi" + }, + "iconSpacing": { + "desc": "Imposta la distanza tra le icone.", + "label": "Spaziatura delle icone" + }, + "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" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/ja.json b/noctalia/plugins/privacy-indicator/i18n/ja.json new file mode 100644 index 0000000..ba61aeb --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/ja.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "マイク・カメラ・画面共有のアイコンを、非アクティブなときは非表示にします。", + "label": "非アクティブ状態を非表示" + }, + "iconSpacing": { + "desc": "アイコン同士の間隔を設定します。", + "label": "アイコン間隔" + }, + "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": "停止" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/ku.json b/noctalia/plugins/privacy-indicator/i18n/ku.json new file mode 100644 index 0000000..6a58f84 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/ku.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Îkonên mîkrofon, kamera û ekranê dema ku neçalak bin veşêre.", + "label": "Rewşa neçalak veşêre" + }, + "iconSpacing": { + "desc": "Cihê di navbera îkonan de diyar bike.", + "label": "Dûrahiya îkonan" + }, + "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" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/nl.json b/noctalia/plugins/privacy-indicator/i18n/nl.json new file mode 100644 index 0000000..d0b031d --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/nl.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Verberg de pictogrammen voor microfoon, camera en scherm wanneer ze inactief zijn.", + "label": "Inactieve status verbergen" + }, + "iconSpacing": { + "desc": "Stel de afstand tussen de pictogrammen in.", + "label": "Pictogramafstand" + }, + "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" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/pl.json b/noctalia/plugins/privacy-indicator/i18n/pl.json new file mode 100644 index 0000000..9e05d3a --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/pl.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Ukryj ikony mikrofonu, kamery i ekranu, gdy są nieaktywne.", + "label": "Ukryj nieaktywne stany" + }, + "iconSpacing": { + "desc": "Ustaw odstęp między ikonami.", + "label": "Odstępy ikon" + }, + "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" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/pt.json b/noctalia/plugins/privacy-indicator/i18n/pt.json new file mode 100644 index 0000000..068a528 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/pt.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Oculta os ícones de microfone, câmera e tela quando estiverem inativos.", + "label": "Ocultar estados inativos" + }, + "iconSpacing": { + "desc": "Define o espaçamento entre os ícones.", + "label": "Espaçamento dos ícones" + }, + "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" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/ru.json b/noctalia/plugins/privacy-indicator/i18n/ru.json new file mode 100644 index 0000000..152e7d8 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/ru.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Скрывать значки микрофона, камеры и экрана, когда они неактивны.", + "label": "Скрывать неактивные состояния" + }, + "iconSpacing": { + "desc": "Задать расстояние между значками.", + "label": "Интервал между значками" + }, + "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": "Остановлено" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/tr.json b/noctalia/plugins/privacy-indicator/i18n/tr.json new file mode 100644 index 0000000..016f1be --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/tr.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Mikrofon, kamera ve ekran simgelerini pasif olduklarında gizle.", + "label": "Pasif durumları gizle" + }, + "iconSpacing": { + "desc": "Simgeler arasındaki boşluğu ayarla.", + "label": "Simge aralığı" + }, + "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" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/uk-UA.json b/noctalia/plugins/privacy-indicator/i18n/uk-UA.json new file mode 100644 index 0000000..5b872a7 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/uk-UA.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "Приховувати значки мікрофона, камери та екрана, коли вони неактивні.", + "label": "Приховувати неактивні стани" + }, + "iconSpacing": { + "desc": "Встановити відстань між значками.", + "label": "Інтервал між значками" + }, + "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": "Зупинено" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/vi.json b/noctalia/plugins/privacy-indicator/i18n/vi.json new file mode 100644 index 0000000..6cd6d76 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/vi.json @@ -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" + } + } +} diff --git a/noctalia/plugins/privacy-indicator/i18n/zh-CN.json b/noctalia/plugins/privacy-indicator/i18n/zh-CN.json new file mode 100644 index 0000000..5665d78 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/zh-CN.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "在麦克风、摄像头和屏幕图标处于非活动状态时将其隐藏。", + "label": "隐藏非活动状态" + }, + "iconSpacing": { + "desc": "设置图标之间的间距。", + "label": "图标间距" + }, + "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": "已停止" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/i18n/zh-TW.json b/noctalia/plugins/privacy-indicator/i18n/zh-TW.json new file mode 100644 index 0000000..febeadc --- /dev/null +++ b/noctalia/plugins/privacy-indicator/i18n/zh-TW.json @@ -0,0 +1,46 @@ +{ + "settings": { + "hideInactive": { + "desc": "當麥克風, 攝影機及螢幕分享沒有啟動時就直接隱藏", + "label": "隱藏未啟動的狀態" + }, + "iconSpacing": { + "desc": "設定圖示之間的留空", + "label": "圖示間距" + }, + "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": "已停止" + } + } +} \ No newline at end of file diff --git a/noctalia/plugins/privacy-indicator/manifest.json b/noctalia/plugins/privacy-indicator/manifest.json new file mode 100644 index 0000000..e7b25b1 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/manifest.json @@ -0,0 +1,37 @@ +{ + "id": "privacy-indicator", + "name": "Privacy Indicator", + "version": "1.2.9", + "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" + ], + "entryPoints": { + "main": "Main.qml", + "barWidget": "BarWidget.qml", + "panel": "Panel.qml", + "settings": "Settings.qml" + }, + "dependencies": { + "plugins": [] + }, + "metadata": { + "defaultSettings": { + "hideInactive": false, + "enableToast": true, + "removeMargins": false, + "iconSpacing": 4, + "activeColor": "primary", + "inactiveColor": "none", + "micFilterRegex": "", + "camFilterRegex": "" + } + } +} diff --git a/noctalia/plugins/privacy-indicator/preview.png b/noctalia/plugins/privacy-indicator/preview.png new file mode 100644 index 0000000..0db7b7c Binary files /dev/null and b/noctalia/plugins/privacy-indicator/preview.png differ diff --git a/noctalia/plugins/privacy-indicator/settings.json b/noctalia/plugins/privacy-indicator/settings.json new file mode 100644 index 0000000..d55d301 --- /dev/null +++ b/noctalia/plugins/privacy-indicator/settings.json @@ -0,0 +1,9 @@ +{ + "hideInactive": true, + "enableToast": true, + "removeMargins": false, + "iconSpacing": 4, + "activeColor": "primary", + "inactiveColor": "none", + "micFilterRegex": "" +} diff --git a/noctalia/settings.json b/noctalia/settings.json index 8cc657f..e09ba7c 100644 --- a/noctalia/settings.json +++ b/noctalia/settings.json @@ -86,6 +86,18 @@ "id": "Clock", "tooltipFormat": "HH:mm ddd, MMM dd", "useCustomFont": false + }, + { + "defaultSettings": { + "activeColor": "primary", + "enableToast": true, + "hideInactive": false, + "iconSpacing": 4, + "inactiveColor": "none", + "micFilterRegex": "", + "removeMargins": false + }, + "id": "plugin:privacy-indicator" } ], "left": [ @@ -123,6 +135,13 @@ } ], "right": [ + { + "displayMode": "onhover", + "iconColor": "none", + "id": "Volume", + "middleClickCommand": "pwvucontrol || pavucontrol", + "textColor": "none" + }, { "blacklist": [ ], @@ -306,7 +325,7 @@ "autoStartAuth": false, "avatarImage": "/home/user/.face", "boxRadiusRatio": 1, - "clockFormat": "hh\\nmm", + "clockFormat": "HH:mm ddd MMM d ", "clockStyle": "custom", "compactLockScreen": false, "dimmerOpacity": 0.23, @@ -349,13 +368,13 @@ ], "lockScreenTint": 0, "passwordChars": false, - "radiusRatio": 1, + "radiusRatio": 0, "reverseScroll": false, "scaleRatio": 1, "screenRadiusRatio": 1, - "shadowDirection": "bottom_right", - "shadowOffsetX": 2, - "shadowOffsetY": 3, + "shadowDirection": "center", + "shadowOffsetX": 0, + "shadowOffsetY": 0, "showChangelogOnStartup": true, "showHibernateOnLockScreen": false, "showScreenCorners": false, @@ -379,15 +398,15 @@ "customCommands": "[]", "enabled": true, "fadeDuration": 5, - "lockCommand": "", - "lockTimeout": 660, + "lockCommand": "qs -c noctalia-shell ipc call session lock", + "lockTimeout": 300, "resumeLockCommand": "", - "resumeScreenOffCommand": "", + "resumeScreenOffCommand": "swaymsg \"output * dpms on\"", "resumeSuspendCommand": "", - "screenOffCommand": "", - "screenOffTimeout": 600, - "suspendCommand": "", - "suspendTimeout": 1800 + "screenOffCommand": "swaymsg \"output * dpms off\"", + "screenOffTimeout": 360, + "suspendCommand": "systemctl suspend", + "suspendTimeout": 900 }, "location": { "analogClockInCalendar": false, @@ -419,7 +438,7 @@ "autoSchedule": true, "dayTemp": "6500", "enabled": true, - "forced": false, + "forced": true, "manualSunrise": "06:30", "manualSunset": "18:30", "nightTemp": "4000" @@ -435,7 +454,7 @@ "density": "default", "enableBatteryToast": true, "enableKeyboardLayoutToast": true, - "enableMarkdown": false, + "enableMarkdown": true, "enableMediaToast": false, "enabled": true, "location": "top_right", @@ -479,7 +498,7 @@ "notifyUpdates": true }, "sessionMenu": { - "countdownDuration": 10000, + "countdownDuration": 5000, "enableCountdown": true, "largeButtonsLayout": "single-row", "largeButtonsStyle": true, @@ -571,6 +590,26 @@ }, "templates": { "activeTemplates": [ + { + "enabled": true, + "id": "gtk" + }, + { + "enabled": true, + "id": "qt" + }, + { + "enabled": true, + "id": "foot" + }, + { + "enabled": true, + "id": "btop" + }, + { + "enabled": true, + "id": "zathura" + } ], "enableUserTheming": false }, @@ -580,10 +619,10 @@ "fontDefaultScale": 1, "fontFixed": "monospace", "fontFixedScale": 1, - "panelBackgroundOpacity": 0.93, + "panelBackgroundOpacity": 1, "panelsAttachedToBar": true, "scrollbarAlwaysVisible": true, - "settingsPanelMode": "attached", + "settingsPanelMode": "centered", "settingsPanelSideBarCardStyle": false, "tooltipsEnabled": true, "translucentWidgets": false diff --git a/sway/config b/sway/config index b3faae4..fa88f68 100644 --- a/sway/config +++ b/sway/config @@ -1,54 +1,27 @@ # Variabili di base set $mod Mod4 -# set $left h -# set $down j -# set $up k -# set $right l set $term foot -# Launcher: wofi (sostituisce wmenu-run) -# set $menu wofi -S drun -p Search -I -b -i - -# Wallpaper e output -# set $wallpaper ~/Nextcloud/wallpapers/laptop/hiroishi_nagasai.png -# set $benq_wallpaper ~/Nextcloud/wallpapers/laptop/hiroishi_nagasai.png set $laptop eDP-1 set $benq DP-9 -# Color Scheme (Gruvbox) -set $gruvbox_bg_dark #282828 -set $gruvbox_bg_light #504945 -set $gruvbox_fg_dark #ebdbb2 -set $gruvbox_fg_light #d5c4a1 -set $gruvbox_red #cc241d -set $gruvbox_green #98971a -set $gruvbox_yellow #d79921 -set $gruvbox_blue #458588 -set $gruvbox_purple #b16286 -set $gruvbox_aqua #689d6a -set $gruvbox_orange #d65d0e - -# Impostazione del wallpaper e configurazione output -# output * bg $wallpaper fill # Imposta il profilo ICC, risoluzione, posizione e scala per il display laptop output $laptop color_profile icc ~/Documents/dotfiles/sway/FRAMEWORK_13_BOE_ICC/BOE_CQ_______NE135FBM_N41_03.icm output $laptop resolution 2256x1504 position 0,0 output $laptop scale 1.3 # Configure BenQ display -# output $benq bg $benq_wallpaper fill + output $laptop color_profile icc ~/Documents/dotfiles/sway/BENQ_PD2706QN_ICC/'Display P3.icc' output $benq resolution 2560x1440@99.990Hz position 0,0 output $laptop scale 1.3 # Gestione degli eventi del coperchio -bindswitch --reload --locked lid:on output $laptop disable -bindswitch --reload --locked lid:off output $laptop enable +bindswitch --reload --locked lid:on exec ~/Documents/dotfiles/sway/scripts/lid.sh on $laptop +bindswitch --reload --locked lid:off exec ~/Documents/dotfiles/sway/scripts/lid.sh off $laptop -# Configurazione dell'inattività: lock e DPMS -# Delegata a noctalia-shell (abilitalo nelle impostazioni di Noctalia -> Idle) input type:touchpad { - dwt enabled # Disable while typing - RIATTIVA QUESTA OPZIONE + dwt disabled # Disable while typing - RIATTIVA QUESTA OPZIONE tap enabled # Enable tap-to-click natural_scroll enabled # Natural scrolling middle_emulation enabled # Emulate middle mouse button - RIATTIVA QUESTA OPZIONE @@ -74,7 +47,7 @@ bindsym $mod+Return exec $term # Chiudi finestra focalizzata bindsym $mod+Shift+q kill -set $ipc qs -c noctalia-shell ipc call +set $ipc /usr/sbin/qs -c noctalia-shell ipc call # Avvia il launcher bindsym $mod+d exec $ipc launcher toggle @@ -166,9 +139,9 @@ bindsym --locked XF86MonBrightnessUp exec $ipc brightness increase bindsym --locked XF86MonBrightnessDown exec $ipc brightness decrease # Controlli multimediali -bindsym XF86AudioPlay exec ipc call media playPause -bindsym XF86AudioNext exec ipc call media next -bindsym XF86AudioPrev exec ipc call media previous +bindsym XF86AudioPlay exec $ipc media playPause +bindsym XF86AudioNext exec $ipc media next +bindsym XF86AudioPrev exec $ipc media previous bindsym $mod+Shift+h exec handy --toggle-transcription bindsym $mod+v exec $ipc launcher clipboard @@ -201,7 +174,7 @@ gaps outer 2 gaps inner 3 # Impostazioni dei colori per il focus delle finestre -client.focused $gruvbox_orange $gruvbox_orange $gruvbox_bg_dark $gruvbox_orange +# client.focused $gruvbox_orange $gruvbox_orange $gruvbox_bg_dark $gruvbox_orange hide_edge_borders smart ### Impostazioni GTK per tema scuro @@ -246,16 +219,13 @@ exec dbus-update-activation-environment WAYLAND_DISPLAY DISPLAY XDG_CURRENT_DESK ### Notifiche Desktop e Clip History # exec swaync # exec mako -exec wl-paste --type text --watch cliphist store -exec wl-paste --type image --watch cliphist store - ### Applet di rete exec nm-applet --indicator exec handy --start-hidden # speek to text exec wl-paste --type text --watch cliphist store # Stores only text data exec wl-paste --type image --watch cliphist store # Stores only image data -exec_always pkill qs; qs -d -c noctalia-shell +exec_always sh -c '/usr/bin/pkill -x qs || true; exec /usr/sbin/qs -d -c noctalia-shell' # comando per capire come si chiama l'id della app per le regole delle finestre # swaymsg -t get_tree @@ -280,3 +250,5 @@ for_window [title="Picture in picture"] floating enable, sticky enable for_window [title="Save File"] floating enable for_window [app_id="firefox" title="Firefox — Sharing Indicator"] kill + +include ~/.config/sway/noctalia diff --git a/sway/noctalia b/sway/noctalia new file mode 100644 index 0000000..a79352c --- /dev/null +++ b/sway/noctalia @@ -0,0 +1,18 @@ +set $primary #b8bb26 +set $on_primary #282828 +set $secondary #fabd2f +set $tertiary #83a598 +set $error #fb4934 +set $surface #282828 +set $on_surface #fbf1c7 +set $on_surface_variant #ebdbb2 +set $outline #786f6b + +## Window Colours +# class border backgr. text indicator child_border +client.focused $primary $surface $on_surface $primary $primary +client.focused_inactive $outline $surface $on_surface_variant $outline $outline +client.unfocused $outline $surface $on_surface_variant $outline $outline +client.urgent $error $surface $on_surface $error $error +client.placeholder $surface $surface $on_surface_variant $surface $surface +client.background $surface diff --git a/sway/scripts/lid.sh b/sway/scripts/lid.sh new file mode 100755 index 0000000..2603e0c --- /dev/null +++ b/sway/scripts/lid.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +action="$1" +internal="${2:-eDP-1}" + +active_count="$(swaymsg -t get_outputs | python3 -c 'import json,sys; print(sum(1 for o in json.load(sys.stdin) if o.get("active")))' 2>/dev/null)" + +if [ -z "$active_count" ]; then + active_count=1 +fi + +case "$action" in + on) + if [ "$active_count" -le 1 ]; then + qs -c noctalia-shell ipc call session lock + sleep 1 + systemctl suspend + else + swaymsg "output $internal disable" + fi + ;; + off) + swaymsg "output $internal enable" + ;; +esac diff --git a/zathura/noctaliarc b/zathura/noctaliarc new file mode 100644 index 0000000..13ef7df --- /dev/null +++ b/zathura/noctaliarc @@ -0,0 +1,31 @@ +set default-bg "rgba(40, 40, 40, 0.8)" +set default-fg "#fbf1c7" +set recolor-lightcolor "rgba(0,0,0,0)" +set recolor-darkcolor "#fbf1c7" + +set statusbar-bg "#282828" +set statusbar-fg "#b8bb26" +set inputbar-bg "#282828" +set inputbar-fg "#b8bb26" + +set notification-bg "#282828" +set notification-fg "#b8bb26" +set notification-error-bg "#282828" +set notification-error-fg "#fb4934" +set notification-warning-bg "#f2f4be" +set notification-warning-fg "#7d0d00" + +set index-bg "rgba(0,0,0,0)" +set index-fg "#fbf1c7" +set index-active-bg "#b8bb26" +set index-active-fg "#282828" +set highlight-color "rgba(242, 244, 190, 0.5)" +set highlight-active-color "rgba(232, 233, 149, 0.5)" +set highlight-fg "#282828" + +set completion-bg "#282828" +set completion-fg "#b8bb26" +set completion-group-bg "#282828" +set completion-group-fg "#b8bb26" +set completion-highlight-fg "#282828" +set completion-highlight-bg "#b8bb26"