inital commit

This commit is contained in:
2025-12-28 10:41:44 -06:00
commit e7426264e7
119 changed files with 4953 additions and 0 deletions

View File

@@ -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
}
}
}
}

View File

@@ -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

View File

@@ -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");
}
}

View File

@@ -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}"
}
}

View File

@@ -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}"
}
}

View File

@@ -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}"
}
}

View File

@@ -0,0 +1,21 @@
{
"settings": {
"hideInactive": {
"desc": "Masquer les icônes du micro, de la caméra et de lécran lorsquils sont inactifs.",
"label": "Masquer les états inactifs"
},
"iconSpacing": {
"desc": "Définir lespacement 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}"
}
}

View File

@@ -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}"
}
}

View File

@@ -0,0 +1,21 @@
{
"settings": {
"hideInactive": {
"desc": "マイク・カメラ・画面共有のアイコンを、非アクティブなときは非表示にします。",
"label": "非アクティブ状態を非表示"
},
"iconSpacing": {
"desc": "アイコン同士の間隔を設定します。",
"label": "アイコン間隔"
},
"removeMargins": {
"desc": "ウィジェットの外側の余白をすべて削除します。",
"label": "余白を削除"
}
},
"tooltip": {
"cam-on": "カメラ: {apps}",
"mic-on": "マイク: {apps}",
"screen-on": "画面共有: {apps}"
}
}

View File

@@ -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}"
}
}

View File

@@ -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}"
}
}

View File

@@ -0,0 +1,21 @@
{
"settings": {
"hideInactive": {
"desc": "Скрывать значки микрофона, камеры и экрана, когда они неактивны.",
"label": "Скрывать неактивные состояния"
},
"iconSpacing": {
"desc": "Задать расстояние между значками.",
"label": "Интервал между значками"
},
"removeMargins": {
"desc": "Удаляет все внешние отступы виджета.",
"label": "Убрать отступы"
}
},
"tooltip": {
"cam-on": "Камера: {apps}",
"mic-on": "Микрофон: {apps}",
"screen-on": "Демонстрация экрана: {apps}"
}
}

View File

@@ -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}"
}
}

View File

@@ -0,0 +1,21 @@
{
"settings": {
"hideInactive": {
"desc": "Приховувати значки мікрофона, камери та екрана, коли вони неактивні.",
"label": "Приховувати неактивні стани"
},
"iconSpacing": {
"desc": "Встановити відстань між значками.",
"label": "Інтервал між значками"
},
"removeMargins": {
"desc": "Видаляє всі зовнішні відступи віджета.",
"label": "Прибрати відступи"
}
},
"tooltip": {
"cam-on": "Камера: {apps}",
"mic-on": "Мікрофон: {apps}",
"screen-on": "Демонстрація екрана: {apps}"
}
}

View File

@@ -0,0 +1,21 @@
{
"settings": {
"hideInactive": {
"desc": "在麦克风、摄像头和屏幕图标处于非活动状态时将其隐藏。",
"label": "隐藏非活动状态"
},
"iconSpacing": {
"desc": "设置图标之间的间距。",
"label": "图标间距"
},
"removeMargins": {
"desc": "移除小部件所有外部边距。",
"label": "移除边距"
}
},
"tooltip": {
"cam-on": "摄像头: {apps}",
"mic-on": "麦克风: {apps}",
"screen-on": "屏幕共享: {apps}"
}
}

View File

@@ -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

View File

@@ -0,0 +1,5 @@
{
"hideInactive": true,
"iconSpacing": 4,
"removeMargins": false
}

View File

@@ -0,0 +1,98 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Widgets
import qs.Services.UI
Rectangle {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
property bool hovered: false
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
implicitWidth: isVertical ? Style.capsuleHeight : layout.implicitWidth + Style.marginS * 2
implicitHeight: isVertical ? layout.implicitHeight + Style.marginS * 2 : Style.capsuleHeight
color: root.hovered ? Color.mHover : Style.capsuleColor
radius: Style.radiusM
border.color: Style.capsuleBorderColor
border.width: Style.capsuleBorderWidth
property string currentIconName: pluginApi?.pluginSettings?.currentIconName || pluginApi?.manifest?.metadata?.defaultSettings?.currentIconName
property bool hideOnZero: pluginApi?.pluginSettings.hideOnZero || pluginApi?.manifest?.metadata.defaultSettings?.hideOnZero
readonly property bool isVisible: (root.pluginApi?.mainInstance?.updateCount > 0) || !root.hideOnZero
visible: root.isVisible
// also set opacity to zero when invisible as we use opacity to hide the barWidgetLoader
opacity: root.isVisible ? 1.0 : 0.0
//
// ------ Widget ------
//
Item {
id: layout
anchors.centerIn: parent
implicitWidth: grid.implicitWidth
implicitHeight: grid.implicitHeight
GridLayout {
id: grid
columns: root.isVertical ? 1 : 2
rowSpacing: Style.marginS
columnSpacing: Style.marginS
NIcon {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
icon: root.currentIconName
color: root.hovered ? Color.mOnHover : Color.mOnSurface
}
NText {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
text: root.pluginApi?.mainInstance?.updateCount.toString()
color: root.hovered ? Color.mOnHover : Color.mOnSurface
pointSize: Style.fontSizeS
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: root.pluginApi?.mainInstance?.updateCount > 0 ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
root.pluginApi?.mainInstance?.startDoSystemUpdate();
}
onEntered: {
root.hovered = true;
buildTooltip();
}
onExited: {
root.hovered = false;
TooltipService.hide();
}
}
}
function buildTooltip() {
const updateCount = root.pluginApi?.mainInstance?.updateCount
if (updateCount === 0) {
TooltipService.show(root, pluginApi?.tr("tooltip.noUpdatesAvailable"), BarService.getTooltipDirection());
} else {
TooltipService.show(root, pluginApi?.tr("tooltip.updatesAvailable"), BarService.getTooltipDirection());
}
}
}

View File

@@ -0,0 +1,126 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
Item {
id: root
property var pluginApi: null
readonly property url updaterJson: Qt.resolvedUrl("file://" + Settings.configDir + "/plugins/update-count/updaterConfigs.json")
readonly property int minutesToMillis: 60_000
readonly property int updateIntervalMinutes: pluginApi?.pluginSettings.updateIntervalMinutes || pluginApi?.manifest?.metadata.defaultSettings?.updateIntervalMinutes || 30
readonly property string updateTerminalCommand: pluginApi?.pluginSettings.updateTerminalCommand || pluginApi?.manifest?.metadata.defaultSettings?.updateTerminalCommand || ""
property int updateCount: 0
property var updater: null
property var customUpdater: ({
name: "custom",
cmdGetNumUpdates: pluginApi?.pluginSettings.customCmdGetNumUpdates || "",
cmdDoSystemUpdate: pluginApi?.pluginSettings.customCmdDoSystemUpdate || ""
})
//
// ------ Initialization ------
//
property var updaters: []
property int checkIndex: 0
property var current: null
FileView {
id: updaterFile
path: root.updaterJson
onLoaded: {
try {
root.updaters = JSON.parse(updaterFile.text());
root.runAllCmdChecks();
} catch (e) {
Logger.e("UpdateCount", "JSON Error in", root.updaterJson, ":", e);
}
}
}
function runAllCmdChecks() {
if (!root.updaters || root.updaters.length === 0) {
return;
}
root.checkIndex = 0;
root.checkNext();
}
function checkNext() {
if (root.checkIndex >= root.updaters.length) {
startGetNumUpdates();
return;
}
root.current = root.updaters[root.checkIndex++];
cmdCheckProc.command = ["sh", "-c", root.current.cmdCheck];
cmdCheckProc.running = true;
}
Process {
id: cmdCheckProc
onExited: function (exitCode, exitStatus) {
if (exitCode === 0) {
root.updater = root.current
Logger.i("UpdateCount", `Initialization finished. Detected updater: ${root.updater.name}.`);
root.startGetNumUpdates()
} else {
root.checkNext();
}
}
}
//
// ------ Get number of updates ------
//
Timer {
id: timerGetNumUpdates
interval: root.updateIntervalMinutes * root.minutesToMillis
running: true
repeat: true
onTriggered: root.startGetNumUpdates()
}
function startGetNumUpdates() {
const cmd = root.customUpdater.cmdGetNumUpdates || root.updater.cmdGetNumUpdates || "exit 1"
getNumUpdates.command = ["sh", "-c", cmd]
getNumUpdates.running = true;
}
Process {
id: getNumUpdates
stdout: StdioCollector {
onStreamFinished: {
var count = parseInt(text.trim());
root.updateCount = isNaN(count) ? -1 : count;
if (root.updateCount >= 0) {
Logger.i("UpdateCount", `Updates available: ${root.updateCount}`);
} else {
Logger.e("UpdateCount", `getNumUpdates return '${text.trim()}' cannot be parsed into int`);
}
}
}
}
//
// ------ Start update ------
//
function startDoSystemUpdate() {
const cmd = root.customUpdater.cmdDoSystemUpdate || root.updater.cmdDoSystemUpdate || "echo 'No update cmd found.'"
const term = root.updateTerminalCommand.trim();
const fullCmd = (term.indexOf("{}") !== -1) ? term.replace("{}", cmd) : term + " " + cmd;
Quickshell.execDetached(["sh", "-c", fullCmd]);
Logger.i("UpdateCount", `Executed update command: ${fullCmd}`);
}
}

View File

@@ -0,0 +1,52 @@
# Update Count Plugin
A compact Noctalia bar widget that periodically checks for available system updates and displays the current update count. Clicking the widget spawns a terminal to execute the configured system update command.
## Features
- **Periodic Update Checks**: Polls at a configurable interval and updates the displayed count.
- **Package Manager Detection**: Supports common update-check/update flows for supported distros (see Requirements).
- **Click to Update**: Launches a terminal to execute the configured system update command.
- **Custom Commands**: Optional custom commands for both “get number of updates” and “do system update”.
## Installation
This plugin is part of the `noctalia-plugins` repository.
## Configuration
Configure the plugin in Noctalia settings:
- **Hide on Zero Updates**: Hides the widget when the available update count is `0`.
- **Update Check Interval**: Polling interval used for update checks.
- **Icon Identifier / Select Icon**: View and change the currently used icon name.
- **Custom Update Count Command**: Shell command that must output a single integer.
- **Custom System Update Command**: Shell command used to perform the update.
- **Terminal Emulator**: Command used to spawn a terminal for the update action.
- If the value contains `{}`, it will be replaced with the update command.
- If `{}` is not present, the update command is appended.
## Usage
- **Display**: Shows an icon and the current update count.
- **Click**: Spawns the configured terminal command and executes the system update command.
- **Hover**: Shows a tooltip indicating whether updates are available or not.
## Requirements
- **Noctalia Shell**: 3.6.0 or later.
- **Update backend**: one of the supported package managers defined in `updaterConfigs.json` (recommended, but not required).
## Technical Details
- **Updater selection**:
- Built-in update handling is driven by `updaterConfigs.json`, which defines the commands used to:
- determine the number of available updates (`cmdGetNumUpdates`), and
- perform the system update (`cmdDoSystemUpdate`).
- At runtime, the plugin selects the **first available** updater whose required executable checks succeed.
- If no updater matches, the plugin relies on the **custom command** settings.
- **Supported package managers / tools**:
- Arch Linux: `pacman`; `yay`, `paru` (AUR helpers)
- Fedora: `dnf`
- Void Linux: `xbps`

View File

@@ -0,0 +1,193 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
property var pluginApi: null
property int updateIntervalMinutes: pluginApi?.pluginSettings?.updateIntervalMinutes || pluginApi?.manifest?.metadata?.defaultSettings?.updateIntervalMinutes
property string updateTerminalCommand: pluginApi?.pluginSettings?.updateTerminalCommand || pluginApi?.manifest?.metadata.defaultSettings?.updateTerminalCommand
property string currentIconName: pluginApi?.pluginSettings?.currentIconName || pluginApi?.manifest?.metadata?.defaultSettings?.currentIconName
property bool hideOnZero: pluginApi?.pluginSettings?.hideOnZero || pluginApi?.manifest?.metadata?.defaultSettings?.hideOnZero
property string customCmdGetNumUpdates: pluginApi?.pluginSettings.customCmdGetNumUpdates || ""
property string customCmdDoSystemUpdate: pluginApi?.pluginSettings.customCmdDoSystemUpdate || ""
spacing: Style.marginL
Component.onCompleted: {
Logger.i("UpdateCount", "Settings UI loaded");
}
//
// ------ General ------
//
NToggle {
id: widgetSwitch
label: pluginApi?.tr("settings.hideWidget.label")
description: pluginApi?.tr("settings.hideWidget.desc")
checked: root.hideOnZero
onToggled: function (checked) {
root.hideOnZero = checked;
}
}
RowLayout {
spacing: Style.marginL
NLabel {
label: pluginApi?.tr("settings.currentIconName.label")
description: pluginApi?.tr("settings.currentIconName.desc")
}
NText {
text: root.currentIconName
color: Settings.data.colorSchemes.darkMode ? Color.mPrimary : Color.mOnPrimary
}
NIcon {
icon: root.currentIconName
color: Settings.data.colorSchemes.darkMode ? Color.mPrimary : Color.mOnPrimary
}
NButton {
text: pluginApi?.tr("settings.changeIcon.label")
onClicked: {
Logger.i("UpdateCount", "Icon selector button clicked.");
changeIcon.open();
}
}
NIconPicker {
id: changeIcon
onIconSelected: function (icon) {
root.currentIconName = icon;
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginL
NLabel {
label: pluginApi?.tr("settings.updateInterval.label")
description: pluginApi?.tr("settings.updateInterval.desc")
}
NSlider {
from: 5
to: 300
value: root.updateIntervalMinutes
stepSize: 5
onValueChanged: {
root.updateIntervalMinutes = value;
}
}
NText {
text: root.updateIntervalMinutes.toString().padStart(3, " ") + " minutes"
color: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
}
}
NDivider {
visible: true
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.bottomMargin: Style.marginL
}
//
// ------ Custom commands ------
//
NTextInput {
label: pluginApi?.tr("settings.customCmdGetNumUpdates.label")
description: pluginApi?.tr("settings.customCmdGetNumUpdates.desc")
placeholderText: pluginApi?.tr("settings.customCmdGetNumUpdates.placeholder")
text: root.customCmdGetNumUpdates
onTextChanged: root.customCmdGetNumUpdates = text
}
NTextInput {
label: pluginApi?.tr("settings.customCmdDoSystemUpdate.label")
description: pluginApi?.tr("settings.customCmdDoSystemUpdate.desc")
placeholderText: pluginApi?.tr("settings.customCmdDoSystemUpdate.placeholder")
text: root.customCmdDoSystemUpdate
onTextChanged: root.customCmdDoSystemUpdate = text
}
NTextInput {
Layout.fillWidth: true
label: pluginApi?.tr("settings.terminal.label")
description: pluginApi?.tr("settings.terminal.desc")
placeholderText: pluginApi?.tr("settings.terminal.placeholder")
text: root.updateTerminalCommand
onTextChanged: root.updateTerminalCommand = text
}
NDivider {
visible: true
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.bottomMargin: Style.marginL
}
//
// ------ Information ------
//
NLabel {
label: pluginApi?.tr("settings.currentCommands.label")
}
ColumnLayout {
RowLayout {
NText {
Layout.fillWidth: true
text: pluginApi?.tr("settings.currentNumUpdatesCmd.label")
color: Settings.data.colorSchemes.darkMode ? Color.mSecondary : Color.mOnSecondary
}
NText {
text: root.customCmdGetNumUpdates || pluginApi?.mainInstance?.updater.cmdGetNumUpdates || "NA"
color: Settings.data.colorSchemes.darkMode ? Color.mTertiary : Color.mOnTertiary
}
}
RowLayout {
NText {
Layout.fillWidth: true
text: pluginApi?.tr("settings.currentUpdateCmd.label")
color: Settings.data.colorSchemes.darkMode ? Color.mSecondary : Color.mOnSecondary
}
NText {
text: root.customCmdDoSystemUpdate || pluginApi?.mainInstance?.updater.cmdDoSystemUpdate || "NA"
color: Settings.data.colorSchemes.darkMode ? Color.mTertiary : Color.mOnTertiary
}
}
}
function saveSettings() {
if (!pluginApi) {
Logger.e("UpdateCount", "Cannot save settings: pluginApi is null");
return;
}
pluginApi.pluginSettings.updateIntervalMinutes = root.updateIntervalMinutes;
pluginApi.pluginSettings.updateTerminalCommand = root.updateTerminalCommand || pluginApi?.manifest?.metadata.defaultSettings?.updateTerminalCommand;
pluginApi.pluginSettings.currentIconName = root.currentIconName;
pluginApi.pluginSettings.hideOnZero = root.hideOnZero;
pluginApi.pluginSettings.customCmdGetNumUpdates = root.customCmdGetNumUpdates;
pluginApi.pluginSettings.customCmdDoSystemUpdate = root.customCmdDoSystemUpdate;
pluginApi.saveSettings();
pluginApi?.mainInstance?.startGetNumUpdates();
Logger.i("UpdateCount", "Settings saved successfully");
pluginApi.closePanel(root.screen);
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Icon auswählen"
},
"currentCommands": {
"label": "Aktuell konfigurierte Befehle"
},
"currentIconName": {
"desc": "Der aktuell konfigurierte Icon-Name.",
"label": "Icon-Kennung"
},
"currentNumUpdatesCmd": {
"label": "Update-Anzahl"
},
"currentUpdateCmd": {
"label": "System-Update"
},
"customCmdDoSystemUpdate": {
"desc": "Shell-Befehl zum Ausführen des System-Updates.",
"label": "Benutzerdefinierter System-Update-Befehl",
"placeholder": "z. B. yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Shell-Befehl, der eine einzelne Ganzzahl als Anzahl verfügbarer Updates ausgeben muss.",
"label": "Benutzerdefinierter Befehl für Update-Anzahl",
"placeholder": "z. B. yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Blendet das Widget aus, wenn die Anzahl verfügbarer Updates 0 ist.",
"label": "Bei 0 Updates ausblenden"
},
"terminal": {
"desc": "Terminalbefehl zum Ausführen des System-Update-Befehls. Optional: {} für den Update-Befehl.",
"label": "Terminal-Emulator",
"placeholder": "z. B. foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Abfrageintervall für Update-Prüfungen",
"label": "Intervall für Update-Prüfung"
}
},
"tooltip": {
"noUpdatesAvailable": "Keine Updates gefunden",
"updatesAvailable": "Klicken, um den konfigurierten System-Update-Befehl auszuführen"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Select Icon"
},
"currentCommands": {
"label": "Currently configured commands"
},
"currentIconName": {
"desc": "Currently configured icon name.",
"label": "Icon Identifier"
},
"currentNumUpdatesCmd": {
"label": "Update count"
},
"currentUpdateCmd": {
"label": "System update"
},
"customCmdDoSystemUpdate": {
"desc": "Shell command used to perform the system update.",
"label": "Custom System Update Command",
"placeholder": "e.g., yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Shell command that must output a single integer representing the number of available updates.",
"label": "Custom Update Count Command",
"placeholder": "e.g., yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Hide the widget when the available update count is 0.",
"label": "Hide on Zero Updates"
},
"terminal": {
"desc": "Terminal command used to execute the system update command. Optional: {} for update command.",
"label": "Terminal Emulator",
"placeholder": "e.g., foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Polling interval for update checks",
"label": "Update Check Interval"
}
},
"tooltip": {
"noUpdatesAvailable": "No updates detected",
"updatesAvailable": "Click to run the configured system update command"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Seleccionar icono"
},
"currentCommands": {
"label": "Comandos configurados actualmente"
},
"currentIconName": {
"desc": "Nombre de icono configurado actualmente.",
"label": "Identificador de icono"
},
"currentNumUpdatesCmd": {
"label": "Recuento de actualizaciones"
},
"currentUpdateCmd": {
"label": "Actualización del sistema"
},
"customCmdDoSystemUpdate": {
"desc": "Comando de shell para ejecutar la actualización del sistema.",
"label": "Comando personalizado de actualización",
"placeholder": "p. ej., yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Comando de shell que debe imprimir un único entero con el número de actualizaciones disponibles.",
"label": "Comando personalizado de recuento",
"placeholder": "p. ej., yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Oculta el widget cuando el número de actualizaciones disponibles es 0.",
"label": "Ocultar con 0 actualizaciones"
},
"terminal": {
"desc": "Comando del terminal usado para ejecutar el comando de actualización del sistema. Opcional: {} para el comando de actualización.",
"label": "Emulador de terminal",
"placeholder": "p. ej., foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Intervalo de sondeo para las comprobaciones de actualizaciones",
"label": "Intervalo de comprobación"
}
},
"tooltip": {
"noUpdatesAvailable": "No se detectaron actualizaciones",
"updatesAvailable": "Haz clic para ejecutar el comando de actualización del sistema configurado"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Choisir licône"
},
"currentCommands": {
"label": "Commandes actuellement configurées"
},
"currentIconName": {
"desc": "Nom dicône actuellement configuré.",
"label": "Identifiant dicône"
},
"currentNumUpdatesCmd": {
"label": "Nombre de mises à jour"
},
"currentUpdateCmd": {
"label": "Mise à jour système"
},
"customCmdDoSystemUpdate": {
"desc": "Commande shell utilisée pour effectuer la mise à jour du système.",
"label": "Commande personnalisée de mise à jour",
"placeholder": "ex. yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Commande shell devant afficher un seul entier correspondant au nombre de mises à jour disponibles.",
"label": "Commande personnalisée de comptage",
"placeholder": "ex. yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Masque le widget lorsque le nombre de mises à jour disponibles est 0.",
"label": "Masquer si 0 mise à jour"
},
"terminal": {
"desc": "Commande du terminal utilisée pour exécuter la commande de mise à jour du système. Optionnel : {} pour la commande de mise à jour.",
"label": "Émulateur de terminal",
"placeholder": "ex. foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Intervalle de sondage pour les vérifications de mises à jour",
"label": "Intervalle de vérification"
}
},
"tooltip": {
"noUpdatesAvailable": "Aucune mise à jour détectée",
"updatesAvailable": "Cliquer pour exécuter la commande de mise à jour système configurée"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Seleziona icona"
},
"currentCommands": {
"label": "Comandi attualmente configurati"
},
"currentIconName": {
"desc": "Nome icona attualmente configurato.",
"label": "Identificatore icona"
},
"currentNumUpdatesCmd": {
"label": "Conteggio aggiornamenti"
},
"currentUpdateCmd": {
"label": "Aggiornamento di sistema"
},
"customCmdDoSystemUpdate": {
"desc": "Comando shell usato per effettuare laggiornamento di sistema.",
"label": "Comando personalizzato di aggiornamento",
"placeholder": "es., yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Comando shell che deve stampare un singolo intero che rappresenta il numero di aggiornamenti disponibili.",
"label": "Comando personalizzato per conteggio",
"placeholder": "es., yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Nasconde il widget quando il numero di aggiornamenti disponibili è 0.",
"label": "Nascondi con 0 aggiornamenti"
},
"terminal": {
"desc": "Comando del terminale usato per eseguire il comando di aggiornamento di sistema. Opzionale: {} per il comando di aggiornamento.",
"label": "Emulatore di terminale",
"placeholder": "es., foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Intervallo di polling per i controlli aggiornamenti",
"label": "Intervallo di verifica"
}
},
"tooltip": {
"noUpdatesAvailable": "Nessun aggiornamento rilevato",
"updatesAvailable": "Clicca per eseguire il comando di aggiornamento di sistema configurato"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "アイコンを選択"
},
"currentCommands": {
"label": "現在の設定コマンド"
},
"currentIconName": {
"desc": "現在設定されているアイコン名です。",
"label": "アイコン識別子"
},
"currentNumUpdatesCmd": {
"label": "更新件数"
},
"currentUpdateCmd": {
"label": "システム更新"
},
"customCmdDoSystemUpdate": {
"desc": "システム更新を実行するためのシェルコマンドです。",
"label": "システム更新のカスタムコマンド",
"placeholder": "例: yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "利用可能な更新数を表す整数を 1 つ出力する必要があります。",
"label": "更新件数のカスタムコマンド",
"placeholder": "例: yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "利用可能な更新数が 0 の場合にウィジェットを非表示にします。",
"label": "更新 0 件で非表示"
},
"terminal": {
"desc": "システム更新コマンドを実行するためのターミナルコマンド。任意: 更新コマンド用に {} を使用します。",
"label": "ターミナルエミュレーター",
"placeholder": "例: foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "更新チェックのポーリング間隔",
"label": "更新チェック間隔"
}
},
"tooltip": {
"noUpdatesAvailable": "更新は検出されませんでした",
"updatesAvailable": "クリックして設定済みのシステム更新コマンドを実行"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Pictogram kiezen"
},
"currentCommands": {
"label": "Huidig geconfigureerde commandos"
},
"currentIconName": {
"desc": "Huidig geconfigureerde pictogramnaam.",
"label": "Pictogram-ID"
},
"currentNumUpdatesCmd": {
"label": "Update-aantal"
},
"currentUpdateCmd": {
"label": "Systeemupdate"
},
"customCmdDoSystemUpdate": {
"desc": "Shell-commando om de systeemupdate uit te voeren.",
"label": "Aangepast systeemupdatecommando",
"placeholder": "bijv. yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Shell-commando dat één geheel getal moet uitvoereren met het aantal beschikbare updates.",
"label": "Aangepast commando voor update-aantal",
"placeholder": "bijv. yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Verbergt de widget wanneer het aantal beschikbare updates 0 is.",
"label": "Verbergen bij 0 updates"
},
"terminal": {
"desc": "Terminalcommando om het systeemupdatecommando uit te voeren. Optioneel: {} voor het updatecommando.",
"label": "Terminalemulator",
"placeholder": "bijv. foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Poll-interval voor updatecontroles",
"label": "Controle-interval"
}
},
"tooltip": {
"noUpdatesAvailable": "Geen updates gevonden",
"updatesAvailable": "Klik om het geconfigureerde systeemupdatecommando uit te voeren"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Selecionar ícone"
},
"currentCommands": {
"label": "Comandos atualmente configurados"
},
"currentIconName": {
"desc": "Nome do ícone atualmente configurado.",
"label": "Identificador do ícone"
},
"currentNumUpdatesCmd": {
"label": "Contagem de atualizações"
},
"currentUpdateCmd": {
"label": "Atualização do sistema"
},
"customCmdDoSystemUpdate": {
"desc": "Comando de shell usado para efetuar a atualização do sistema.",
"label": "Comando personalizado de atualização do sistema",
"placeholder": "ex.: yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Comando de shell que deve imprimir um único inteiro representando o número de atualizações disponíveis.",
"label": "Comando personalizado para contagem de atualizações",
"placeholder": "ex.: yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Oculta o widget quando o número de atualizações disponíveis é 0.",
"label": "Ocultar com 0 atualizações"
},
"terminal": {
"desc": "Comando de terminal usado para executar o comando de atualização do sistema. Opcional: {} para o comando de atualização.",
"label": "Emulador de terminal",
"placeholder": "ex.: foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Intervalo de polling para verificações de atualizações",
"label": "Intervalo de verificação de atualizações"
}
},
"tooltip": {
"noUpdatesAvailable": "Nenhuma atualização detetada",
"updatesAvailable": "Clique para executar o comando de atualização do sistema configurado"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Выбрать значок"
},
"currentCommands": {
"label": "Текущие настроенные команды"
},
"currentIconName": {
"desc": "Текущее настроенное имя значка.",
"label": "Идентификатор значка"
},
"currentNumUpdatesCmd": {
"label": "Количество обновлений"
},
"currentUpdateCmd": {
"label": "Обновление системы"
},
"customCmdDoSystemUpdate": {
"desc": "Команда оболочки для выполнения обновления системы.",
"label": "Пользовательская команда обновления",
"placeholder": "например: yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Команда оболочки должна вывести одно целое число — количество доступных обновлений.",
"label": "Пользовательская команда подсчёта",
"placeholder": "например: yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Скрывает виджет, когда количество доступных обновлений равно 0.",
"label": "Скрывать при 0 обновлений"
},
"terminal": {
"desc": "Команда терминала для выполнения команды обновления системы. Необязательно: {} для команды обновления.",
"label": "Эмулятор терминала",
"placeholder": "например: foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Интервал опроса для проверки обновлений",
"label": "Интервал проверки"
}
},
"tooltip": {
"noUpdatesAvailable": "Обновления не обнаружены",
"updatesAvailable": "Нажмите, чтобы выполнить настроенную команду обновления системы"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Simge seç"
},
"currentCommands": {
"label": "Şu anda yapılandırılmış komutlar"
},
"currentIconName": {
"desc": "Şu anda yapılandırılmış simge adı.",
"label": "Simge tanımlayıcı"
},
"currentNumUpdatesCmd": {
"label": "Güncelleme sayısı"
},
"currentUpdateCmd": {
"label": "Sistem güncellemesi"
},
"customCmdDoSystemUpdate": {
"desc": "Sistem güncellemesini gerçekleştirmek için kullanılan kabuk komutu.",
"label": "Özel sistem güncelleme komutu",
"placeholder": "örn., yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Mevcut güncellemelerin sayısını temsil eden tek bir tamsayı çıktılaması gereken kabuk komutu.",
"label": "Özel güncelleme sayısı komutu",
"placeholder": "örn., yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Mevcut güncelleme sayısı 0 olduğunda widget'ı gizler.",
"label": "0 güncellemede gizle"
},
"terminal": {
"desc": "Sistem güncelleme komutunu çalıştırmak için kullanılan terminal komutu. İsteğe bağlı: güncelleme komutu için {}.",
"label": "Terminal emülatörü",
"placeholder": "örn., foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Güncelleme denetimleri için yoklama aralığı",
"label": "Güncelleme denetim aralığı"
}
},
"tooltip": {
"noUpdatesAvailable": "Güncelleme algılanmadı",
"updatesAvailable": "Yapılandırılmış sistem güncelleme komutunu çalıştırmak için tıklayın"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "Вибрати іконку"
},
"currentCommands": {
"label": "Поточні налаштовані команди"
},
"currentIconName": {
"desc": "Поточна налаштована назва іконки.",
"label": "Ідентифікатор іконки"
},
"currentNumUpdatesCmd": {
"label": "Кількість оновлень"
},
"currentUpdateCmd": {
"label": "Оновлення системи"
},
"customCmdDoSystemUpdate": {
"desc": "Команда оболонки для виконання оновлення системи.",
"label": "Користувацька команда оновлення системи",
"placeholder": "напр., yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Команда оболонки має вивести одне ціле число, що представляє кількість доступних оновлень.",
"label": "Користувацька команда підрахунку оновлень",
"placeholder": "напр., yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "Приховує віджет, коли кількість доступних оновлень дорівнює 0.",
"label": "Приховувати за 0 оновлень"
},
"terminal": {
"desc": "Команда термінала для виконання команди оновлення системи. Необов’язково: {} для команди оновлення.",
"label": "Емулятор термінала",
"placeholder": "напр., foot -e, hyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "Інтервал опитування для перевірок оновлень",
"label": "Інтервал перевірки оновлень"
}
},
"tooltip": {
"noUpdatesAvailable": "Оновлень не виявлено",
"updatesAvailable": "Натисніть, щоб запустити налаштовану команду оновлення системи"
}
}

View File

@@ -0,0 +1,47 @@
{
"settings": {
"changeIcon": {
"label": "选择图标"
},
"currentCommands": {
"label": "当前配置的命令"
},
"currentIconName": {
"desc": "当前配置的图标名称。",
"label": "图标标识"
},
"currentNumUpdatesCmd": {
"label": "更新数量"
},
"currentUpdateCmd": {
"label": "系统更新"
},
"customCmdDoSystemUpdate": {
"desc": "用于执行系统更新的 Shell 命令。",
"label": "自定义系统更新命令",
"placeholder": "例如yay -Syu"
},
"customCmdGetNumUpdates": {
"desc": "Shell 命令必须输出一个整数,表示可用更新的数量。",
"label": "自定义更新数量命令",
"placeholder": "例如yay -Quq 2>/dev/null | wc -l"
},
"hideWidget": {
"desc": "当可用更新数为 0 时隐藏该小部件。",
"label": "零更新时隐藏"
},
"terminal": {
"desc": "用于执行系统更新命令的终端命令。可选:使用 {} 代表更新命令占位符。",
"label": "终端模拟器",
"placeholder": "例如foot -ehyprctl dispatch exec '[float] kitty -e {}'"
},
"updateInterval": {
"desc": "更新检查的轮询间隔",
"label": "检查间隔"
}
},
"tooltip": {
"noUpdatesAvailable": "未检测到更新",
"updatesAvailable": "点击运行已配置的系统更新命令"
}
}

View File

@@ -0,0 +1,26 @@
{
"id": "update-count",
"name": "Update Count",
"version": "1.0.5",
"minNoctaliaVersion": "3.6.0",
"author": "BukoMoon",
"license": "GPLv3",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "Checks for system updates and shows the update count. Click to run update command in a terminal.",
"entryPoints": {
"main": "Main.qml",
"barWidget": "BarWidget.qml",
"settings": "Settings.qml"
},
"dependencies": {
"plugins": []
},
"metadata": {
"defaultSettings": {
"updateIntervalMinutes": 30,
"updateTerminalCommand": "foot -e",
"currentIconName": "world-download",
"hideOnZero": false
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

View File

@@ -0,0 +1,8 @@
{
"updateIntervalMinutes": 30,
"updateTerminalCommand": "alacritty -e",
"currentIconName": "world-download",
"hideOnZero": true,
"customCmdGetNumUpdates": "expr $(xbps-install -Mnu 2>&1 | grep -v '^$' | wc -l) + $(flatpak remote-ls --updates | wc -l)",
"customCmdDoSystemUpdate": "~/update.sh"
}

View File

@@ -0,0 +1,32 @@
[
{
"name": "yay",
"cmdCheck": "command -v yay >/dev/null 2>&1",
"cmdGetNumUpdates": "yay -Sy >/dev/null 2>&1; yay -Quq 2>/dev/null | wc -l",
"cmdDoSystemUpdate": "yay -Syu"
},
{
"name": "paru",
"cmdCheck": "command -v paru >/dev/null 2>&1",
"cmdGetNumUpdates": "paru -Sy >/dev/null 2>&1; paru -Quq 2>/dev/null | wc -l",
"cmdDoSystemUpdate": "paru -Syu"
},
{
"name": "pacman",
"cmdCheck": "command -v pacman >/dev/null 2>&1",
"cmdGetNumUpdates": "pacman -Quq 2>/dev/null | wc -l",
"cmdDoSystemUpdate": "sudo pacman -Syu"
},
{
"name": "dnf",
"cmdCheck": "command -v dnf >/dev/null 2>&1",
"cmdGetNumUpdates": "dnf check-update -q | grep -c ^[a-z0-9]",
"cmdDoSystemUpdate": "sudo dnf upgrade"
},
{
"name": "xbps",
"cmdCheck": "command -v xbps-install >/dev/null 2>&1",
"cmdGetNumUpdates": "xbps-install -Mnu 2>&1 | grep -v '^$' | wc -l",
"cmdDoSystemUpdate": "sudo xbps-install -Su"
}
]