diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 3a706d10..85d94e98 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1277,6 +1277,20 @@ "label": "Ausblenden, wenn keine Medien wiedergegeben werden", "description": "Wenn aktiviert, wird der Visualizer ausgeblendet, sofern keine Wiedergabe läuft." } + }, + "lock-keys": { + "show-caps-lock": { + "description": "Caps Lock Status anzeigen.", + "label": "Feststelltaste" + }, + "show-num-lock": { + "description": "Num-Lock-Status anzeigen.", + "label": "Num-Taste" + }, + "show-scroll-lock": { + "description": "Scroll-Lock-Status anzeigen.", + "label": "Rollenfeststelltaste" + } } } }, diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 50a4a211..d8f16c14 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1260,6 +1260,20 @@ "label": "Hide when no media is playing", "description": "When enabled, the visualizer is hidden unless a player is actively playing." } + }, + "lock-keys": { + "show-caps-lock": { + "label": "Caps Lock", + "description": "Display caps lock status." + }, + "show-num-lock": { + "label": "Num Lock", + "description": "Display num lock status." + }, + "show-scroll-lock": { + "label": "Scroll Lock", + "description": "Display scroll lock status." + } } } }, diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 381d458a..44016655 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1260,6 +1260,20 @@ "label": "Ocultar cuando no se reproduce", "description": "Si está activado, el visualizador se oculta salvo que haya reproducción activa." } + }, + "lock-keys": { + "show-caps-lock": { + "description": "Mostrar el estado de Bloq Mayús.", + "label": "Bloq Mayús" + }, + "show-num-lock": { + "description": "Mostrar el estado de bloqueo numérico.", + "label": "Bloq Num" + }, + "show-scroll-lock": { + "description": "Mostrar el estado de Bloq Despl.", + "label": "Bloq Despl" + } } } }, diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 82b990c0..783e94ec 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1260,6 +1260,20 @@ "label": "Masquer lorsqu'aucun média n'est en lecture", "description": "Si activé, le visualiseur est masqué sauf lorsqu'un lecteur est en lecture." } + }, + "lock-keys": { + "show-caps-lock": { + "description": "Afficher l'état du verrouillage majuscule.", + "label": "Verr Maj" + }, + "show-num-lock": { + "description": "Afficher l'état du verrouillage numérique.", + "label": "Verr Num" + }, + "show-scroll-lock": { + "description": "Afficher l'état du verrouillage du défilement.", + "label": "Verr Maj" + } } } }, diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 80917f1d..7cb7c3cf 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1260,6 +1260,20 @@ "label": "Ocultar quando não houver reprodução", "description": "Quando ativado, o visualizador fica oculto a menos que haja reprodução ativa." } + }, + "lock-keys": { + "show-caps-lock": { + "description": "Exibir o status do Caps Lock.", + "label": "Caps Lock" + }, + "show-num-lock": { + "description": "Exibir o status do Num Lock.", + "label": "Bloq Num" + }, + "show-scroll-lock": { + "description": "Exibir o status do Scroll Lock.", + "label": "Scroll Lock" + } } } }, diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 59a36cd3..fdf2893c 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1260,6 +1260,20 @@ "label": "无媒体播放时隐藏", "description": "启用后,除非正在播放媒体,否则隐藏可视化显示。" } + }, + "lock-keys": { + "show-caps-lock": { + "description": "显示大写锁定状态。", + "label": "大写锁定" + }, + "show-num-lock": { + "description": "显示数字锁定键状态。", + "label": "数字锁定" + }, + "show-scroll-lock": { + "description": "显示滚动锁定状态。", + "label": "滚动锁定" + } } } }, diff --git a/Modules/Bar/Widgets/LockKeys.qml b/Modules/Bar/Widgets/LockKeys.qml new file mode 100644 index 00000000..952b310a --- /dev/null +++ b/Modules/Bar/Widgets/LockKeys.qml @@ -0,0 +1,97 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import qs.Commons +import qs.Modules.Settings +import qs.Services +import qs.Widgets +//import qs.Modules.Bar.Extras + +Rectangle { + id: root + + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property string barPosition: Settings.data.bar.position + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + + readonly property bool showCaps: (widgetSettings.showCapsLock !== undefined) ? widgetSettings.showCapsLock : widgetMetadata.showCapsLock + readonly property bool showNum: (widgetSettings.showNumLock !== undefined) ? widgetSettings.showNumLock : widgetMetadata.showNumLock + readonly property bool showScroll: (widgetSettings.showScrollLock !== undefined) ? widgetSettings.showScrollLock : widgetMetadata.showScrollLock + + implicitWidth: isVertical ? Style.capsuleHeight : Math.round(layout.implicitWidth + Style.marginM * 2) + implicitHeight: isVertical ? Math.round(layout.implicitHeight + Style.marginM * 2) : Style.capsuleHeight + + Layout.alignment: Qt.AlignVCenter + + radius: Style.radiusM + color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent + + Item { + id: layout + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + + implicitWidth: rowLayout.visible ? rowLayout.implicitWidth : colLayout.implicitWidth + implicitHeight: rowLayout.visible ? rowLayout.implicitHeight : colLayout.implicitHeight + + RowLayout { + id: rowLayout + visible: !root.isVertical + spacing: 0 + + NIcon { + visible: root.showCaps + icon: "letter-c" + color: LockKeysService.capsLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + } + NIcon { + visible: root.showNum + icon: "letter-n" + color: LockKeysService.numLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + } + NIcon { + visible: root.showScroll + icon: "letter-s" + color: LockKeysService.scrollLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + } + } + + ColumnLayout { + id: colLayout + visible: root.isVertical + spacing: 0 + + NIcon { + visible: root.showCaps + icon: "letter-c" + color: LockKeysService.capsLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + } + NIcon { + visible: root.showNum + icon: "letter-n" + color: LockKeysService.numLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + } + NIcon { + visible: root.showScroll + icon: "letter-s" + color: LockKeysService.scrollLockOn ? Color.mTertiary : Qt.alpha(Color.mOnSurfaceVariant, 0.3) + } + } + } +} diff --git a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml index d43facb8..b6169eee 100644 --- a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml @@ -128,6 +128,7 @@ Popup { "ControlCenter": "WidgetSettings/ControlCenterSettings.qml", "CustomButton": "WidgetSettings/CustomButtonSettings.qml", "KeyboardLayout": "WidgetSettings/KeyboardLayoutSettings.qml", + "LockKeys": "WidgetSettings/LockKeysSettings.qml", "MediaMini": "WidgetSettings/MediaMiniSettings.qml", "Microphone": "WidgetSettings/MicrophoneSettings.qml", "NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml", diff --git a/Modules/Settings/Bar/WidgetSettings/LockKeysSettings.qml b/Modules/Settings/Bar/WidgetSettings/LockKeysSettings.qml new file mode 100644 index 00000000..fb32205b --- /dev/null +++ b/Modules/Settings/Bar/WidgetSettings/LockKeysSettings.qml @@ -0,0 +1,49 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueShowCapsLock: widgetData.showCapsLock !== undefined ? widgetData.showCapsLock : widgetMetadata.showCapsLock + property bool valueShowNumLock: widgetData.showNumLock !== undefined ? widgetData.showNumLock : widgetMetadata.showNumLock + property bool valueShowScrollLock: widgetData.showScrollLock !== undefined ? widgetData.showScrollLock : widgetMetadata.showScrollLock + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.showCapsLock = valueShowCapsLock + settings.showNumLock = valueShowNumLock + settings.showScrollLock = valueShowScrollLock + return settings + } + + NToggle { + label: I18n.tr("bar.widget-settings.lock-keys.show-caps-lock.label") + description: I18n.tr("bar.widget-settings.lock-keys.show-caps-lock.description") + checked: valueShowCapsLock + onToggled: checked => valueShowCapsLock = checked + } + + NToggle { + label: I18n.tr("bar.widget-settings.lock-keys.show-num-lock.label") + description: I18n.tr("bar.widget-settings.lock-keys.show-num-lock.description") + checked: valueShowNumLock + onToggled: checked => valueShowNumLock = checked + } + + NToggle { + label: I18n.tr("bar.widget-settings.lock-keys.show-scroll-lock.label") + description: I18n.tr("bar.widget-settings.lock-keys.show-scroll-lock.description") + checked: valueShowScrollLock + onToggled: checked => valueShowScrollLock = checked + } +} diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index e8b2b993..dce92880 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -21,6 +21,7 @@ Singleton { "DarkMode": darkMode, "KeepAwake": keepAwakeComponent, "KeyboardLayout": keyboardLayoutComponent, + "LockKeys": lockKeysComponent, "MediaMini": mediaMiniComponent, "Microphone": microphoneComponent, "NightLight": nightLightComponent, @@ -98,6 +99,12 @@ Singleton { "allowUserSettings": true, "displayMode": "onhover" }, + "LockKeys": { + "allowUserSettings": true, + "showCapsLock": true, + "showNumLock": true, + "showScrollLock": true + }, "MediaMini": { "allowUserSettings": true, "hideMode": "hidden", @@ -193,6 +200,9 @@ Singleton { property Component keepAwakeComponent: Component { KeepAwake {} } + property Component lockKeysComponent: Component { + LockKeys {} + } property Component mediaMiniComponent: Component { MediaMini {} } diff --git a/Services/LockKeysService.qml b/Services/LockKeysService.qml new file mode 100644 index 00000000..2a8c6150 --- /dev/null +++ b/Services/LockKeysService.qml @@ -0,0 +1,85 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Singleton { + id: root + + property bool capsLockOn: false + property bool numLockOn: false + property bool scrollLockOn: false + + signal capsLockChanged(bool active) + signal numLockChanged(bool active) + signal scrollLockChanged(bool active) + + Process { + id: stateCheckProcess + + property string checkCommand: " \ +caps=0; cat /sys/class/leds/input*::capslock/brightness 2>/dev/null | grep -q 1 && caps=1; echo \"caps:${caps}\"; \ +num=0; cat /sys/class/leds/input*::numlock/brightness 2>/dev/null | grep -q 1 && num=1; echo \"num:${num}\"; \ +scroll=0; cat /sys/class/leds/input*::scrolllock/brightness 2>/dev/null | grep -q 1 && scroll=1; echo \"scroll:${scroll}\"; \ +" +command: ["sh", "-c", stateCheckProcess.checkCommand] + +stdout: StdioCollector { + onStreamFinished: { + var lines = this.text.trim().split('\n') + for (var i = 0; i < lines.length; i++) { + var parts = lines[i].split(':') + if (parts.length === 2) { + var key = parts[0] + var newState = (parts[1] === '1') + + if (key === "caps") { + if (root.capsLockOn !== newState) { + root.capsLockOn = newState + root.capsLockChanged(newState) + Logger.i("LockKeysService", "Caps Lock:", capsLockOn) + } + } else if (key === "num") { + if (root.numLockOn !== newState) { + root.numLockOn = newState + root.numLockChanged(newState) + Logger.i("LockKeysService", "Num Lock:", numLockOn) + } + } else if (key === "scroll") { + if (root.scrollLockOn !== newState) { + root.scrollLockOn = newState + root.scrollLockChanged(newState) + Logger.i("LockKeysService", "Scroll Lock:", scrollLockOn) + } + } + } + } + } +} +stderr: StdioCollector { + onStreamFinished: { + if (this.text.trim().length > 0) + Logger.i("LockKeysService", "Error running state check:", this.text.trim()) + } +} + } + + Timer { + id: pollTimer + interval: 200 + running: true + repeat: true + onTriggered: { + if (!stateCheckProcess.running) { + stateCheckProcess.running = true + } + } + } + + Component.onCompleted: { + Logger.i("LockKeysService", "Service started, performing initial state check.") + stateCheckProcess.running = true + } +}