inital commit
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var pluginApi: null
|
||||
|
||||
property ShellScreen screen
|
||||
property string widgetId: ""
|
||||
property string section: ""
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
|
||||
|
||||
property bool micActive: false
|
||||
property bool camActive: false
|
||||
property bool scrActive: false
|
||||
property var micApps: []
|
||||
property var camApps: []
|
||||
property var scrApps: []
|
||||
|
||||
property var cfg: pluginApi?.pluginSettings || ({})
|
||||
property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
|
||||
|
||||
property bool hideInactive: cfg.hideInactive ?? defaults.hideInactive
|
||||
property bool removeMargins: cfg.removeMargins ?? defaults.removeMargins
|
||||
property int iconSpacing: cfg.iconSpacing || Style.marginXS
|
||||
|
||||
readonly property color activeColor: Color.mPrimary
|
||||
readonly property color inactiveColor: Qt.alpha(Color.mOnSurfaceVariant, 0.3)
|
||||
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
|
||||
implicitWidth: isVertical ? Style.capsuleHeight : Math.round(layout.implicitWidth + margins)
|
||||
implicitHeight: isVertical ? Math.round(layout.implicitHeight + margins) : Style.capsuleHeight
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: Style.radiusM
|
||||
color: Style.capsuleColor
|
||||
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 = [];
|
||||
|
||||
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") : "";
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
var tooltipText = buildTooltip();
|
||||
if (tooltipText) {
|
||||
TooltipService.show(root, tooltipText, BarService.getTooltipDirection());
|
||||
}
|
||||
}
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# 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.
|
||||
- **RemoveMargins**: If enabled, removes all outer margins of the widget.
|
||||
- **Icon Spacing**: Controls the horizontal/vertical spacing between the icons.
|
||||
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,88 @@
|
||||
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
|
||||
property bool removeMargins: cfg.removeMargins ?? defaults.removeMargins
|
||||
property int iconSpacing: cfg.iconSpacing || Style.marginXS
|
||||
|
||||
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: function (checked) {
|
||||
root.hideInactive = checked;
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: pluginApi?.tr("settings.removeMargins.label")
|
||||
description: pluginApi?.tr("settings.removeMargins.desc")
|
||||
|
||||
checked: root.removeMargins
|
||||
onToggled: function (checked) {
|
||||
root.removeMargins = checked;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
if (!pluginApi) {
|
||||
Logger.e("PrivacyIndicator", "Cannot save settings: pluginApi is null");
|
||||
return;
|
||||
}
|
||||
|
||||
pluginApi.pluginSettings.hideInactive = root.hideInactive;
|
||||
pluginApi.pluginSettings.iconSpacing = root.iconSpacing;
|
||||
pluginApi.pluginSettings.removeMargins = root.removeMargins;
|
||||
|
||||
pluginApi.saveSettings();
|
||||
|
||||
Logger.i("PrivacyIndicator", "Settings saved successfully");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"settings": {
|
||||
"hideInactive": {
|
||||
"desc": "Mikrofon-, Kamera- und Bildschirmsymbole ausblenden, wenn sie inaktiv sind.",
|
||||
"label": "Inaktive Zustände ausblenden"
|
||||
},
|
||||
"iconSpacing": {
|
||||
"desc": "Den Abstand zwischen den Symbolen festlegen.",
|
||||
"label": "Symbolabstand"
|
||||
},
|
||||
"removeMargins": {
|
||||
"desc": "Alle äußeren Ränder des Widgets entfernen.",
|
||||
"label": "Ränder entfernen"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Kamera: {apps}",
|
||||
"mic-on": "Mikrofon: {apps}",
|
||||
"screen-on": "Bildschirmfreigabe: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"settings": {
|
||||
"hideInactive": {
|
||||
"desc": "Hide microphone, camera, and screen icons when there are inactive.",
|
||||
"label": "Hide inactive states"
|
||||
},
|
||||
"iconSpacing": {
|
||||
"desc": "Set the spacing between the icons.",
|
||||
"label": "Icon spacing"
|
||||
},
|
||||
"removeMargins": {
|
||||
"desc": "Remove all outer margins of the widget.",
|
||||
"label": "Remove margins"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Camera: {apps}",
|
||||
"mic-on": "Microphone: {apps}",
|
||||
"screen-on": "Screen sharing: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Cámara: {apps}",
|
||||
"mic-on": "Micrófono: {apps}",
|
||||
"screen-on": "Compartir pantalla: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"settings": {
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Caméra: {apps}",
|
||||
"mic-on": "Microphone: {apps}",
|
||||
"screen-on": "Partage d'écran: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Kamera: {apps}",
|
||||
"mic-on": "Mikrofonoa: {apps}",
|
||||
"screen-on": "Ekran-partaĝado: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"settings": {
|
||||
"hideInactive": {
|
||||
"desc": "マイク・カメラ・画面共有のアイコンを、非アクティブなときは非表示にします。",
|
||||
"label": "非アクティブ状態を非表示"
|
||||
},
|
||||
"iconSpacing": {
|
||||
"desc": "アイコン同士の間隔を設定します。",
|
||||
"label": "アイコン間隔"
|
||||
},
|
||||
"removeMargins": {
|
||||
"desc": "ウィジェットの外側の余白をすべて削除します。",
|
||||
"label": "余白を削除"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "カメラ: {apps}",
|
||||
"mic-on": "マイク: {apps}",
|
||||
"screen-on": "画面共有: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Camera: {apps}",
|
||||
"mic-on": "Microfoon: {apps}",
|
||||
"screen-on": "Schermdeling: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Câmera: {apps}",
|
||||
"mic-on": "Microfone: {apps}",
|
||||
"screen-on": "Compartilhamento de tela: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"settings": {
|
||||
"hideInactive": {
|
||||
"desc": "Скрывать значки микрофона, камеры и экрана, когда они неактивны.",
|
||||
"label": "Скрывать неактивные состояния"
|
||||
},
|
||||
"iconSpacing": {
|
||||
"desc": "Задать расстояние между значками.",
|
||||
"label": "Интервал между значками"
|
||||
},
|
||||
"removeMargins": {
|
||||
"desc": "Удаляет все внешние отступы виджета.",
|
||||
"label": "Убрать отступы"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Камера: {apps}",
|
||||
"mic-on": "Микрофон: {apps}",
|
||||
"screen-on": "Демонстрация экрана: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Kamera: {apps}",
|
||||
"mic-on": "Mikrofon: {apps}",
|
||||
"screen-on": "Ekran paylaşımı: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"settings": {
|
||||
"hideInactive": {
|
||||
"desc": "Приховувати значки мікрофона, камери та екрана, коли вони неактивні.",
|
||||
"label": "Приховувати неактивні стани"
|
||||
},
|
||||
"iconSpacing": {
|
||||
"desc": "Встановити відстань між значками.",
|
||||
"label": "Інтервал між значками"
|
||||
},
|
||||
"removeMargins": {
|
||||
"desc": "Видаляє всі зовнішні відступи віджета.",
|
||||
"label": "Прибрати відступи"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "Камера: {apps}",
|
||||
"mic-on": "Мікрофон: {apps}",
|
||||
"screen-on": "Демонстрація екрана: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"settings": {
|
||||
"hideInactive": {
|
||||
"desc": "在麦克风、摄像头和屏幕图标处于非活动状态时将其隐藏。",
|
||||
"label": "隐藏非活动状态"
|
||||
},
|
||||
"iconSpacing": {
|
||||
"desc": "设置图标之间的间距。",
|
||||
"label": "图标间距"
|
||||
},
|
||||
"removeMargins": {
|
||||
"desc": "移除小部件所有外部边距。",
|
||||
"label": "移除边距"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"cam-on": "摄像头: {apps}",
|
||||
"mic-on": "麦克风: {apps}",
|
||||
"screen-on": "屏幕共享: {apps}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": "privacy-indicator",
|
||||
"name": "Privacy Indicator",
|
||||
"version": "1.0.10",
|
||||
"minNoctaliaVersion": "3.6.0",
|
||||
"author": "Noctalia Team <team@noctalia.dev>",
|
||||
"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.",
|
||||
"entryPoints": {
|
||||
"barWidget": "BarWidget.qml",
|
||||
"settings": "Settings.qml"
|
||||
},
|
||||
"dependencies": {
|
||||
"plugins": []
|
||||
},
|
||||
"metadata": {
|
||||
"defaultSettings": {
|
||||
"hideInactive": false,
|
||||
"removeMargins": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"hideInactive": true,
|
||||
"iconSpacing": 4,
|
||||
"removeMargins": false
|
||||
}
|
||||
Reference in New Issue
Block a user