diff --git a/Modules/Settings/TopBarTab.qml b/Modules/Settings/TopBarTab.qml index 4456940d..002d642c 100644 --- a/Modules/Settings/TopBarTab.qml +++ b/Modules/Settings/TopBarTab.qml @@ -140,6 +140,11 @@ Item { "icon": "network_check", "warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined, "enabled": DgopService.dgopAvailable + }, { + "id": "keyboard_layout_name", + "text": "Keyboard Layout Name", + "description": "Displays the active keyboard layout and allows switching", + "icon": "keyboard", }] property var defaultLeftWidgets: [{ "id": "launcherButton", diff --git a/Modules/Settings/WidgetsTab.qml b/Modules/Settings/WidgetsTab.qml index abb2b70b..69ba55a1 100644 --- a/Modules/Settings/WidgetsTab.qml +++ b/Modules/Settings/WidgetsTab.qml @@ -138,6 +138,11 @@ Item { "icon": "network_check", "warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined, "enabled": DgopService.dgopAvailable + }, { + "id": "keyboard_layout_name", + "text": "Keyboard Layout Name", + "description": "Displays the active keyboard layout and allows switching", + "icon": "keyboard", }] property var defaultLeftWidgets: [{ "id": "launcherButton", diff --git a/Modules/TopBar/KeyboardLayoutName.qml b/Modules/TopBar/KeyboardLayoutName.qml new file mode 100644 index 00000000..95e23591 --- /dev/null +++ b/Modules/TopBar/KeyboardLayoutName.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Controls +import qs.Common +import qs.Services +import qs.Widgets +import qs.Modules.ProcessList + +Rectangle { + id: root + + readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30)) + + width: contentRow.implicitWidth + horizontalPadding * 2 + height: widgetHeight + radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius + + color: { + if (SettingsData.topBarNoBackground) return "transparent" + const baseColor = mouseArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover + return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, + baseColor.a * Theme.widgetTransparency) + } + + MouseArea { + id: mouseArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + NiriService.cycleKeyboardLayout() + } + } + + Row { + id: contentRow + + anchors.centerIn: parent + spacing: Theme.spacingS + + StyledText { + text: NiriService.getCurrentKeyboardLayoutName() + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } +} diff --git a/Modules/TopBar/TopBar.qml b/Modules/TopBar/TopBar.qml index 84d10227..13a12d9f 100644 --- a/Modules/TopBar/TopBar.qml +++ b/Modules/TopBar/TopBar.qml @@ -337,6 +337,8 @@ PanelWindow { return true case "network_speed_monitor": return DgopService.dgopAvailable + case "keyboard_layout_name": + return true default: return false } @@ -386,6 +388,8 @@ PanelWindow { return separatorComponent case "network_speed_monitor": return networkComponent + case "keyboard_layout_name": + return keyboardLayoutNameComponent default: return null } @@ -1109,6 +1113,12 @@ PanelWindow { opacity: 0.3 } } + + Component { + id: keyboardLayoutNameComponent + + KeyboardLayoutName {} + } } } } diff --git a/Services/NiriService.qml b/Services/NiriService.qml index a485f9db..aa2e306b 100644 --- a/Services/NiriService.qml +++ b/Services/NiriService.qml @@ -26,6 +26,10 @@ Singleton { // Overview state property bool inOverview: false + // Keyboard layout state + property int currentKeyboardLayoutIndex: 0 + property var keyboardLayoutNames: [] + // Internal state (not exposed to external components) property string configValidationOutput: "" property bool hasInitialConnection: false @@ -191,6 +195,10 @@ Singleton { handleOverviewChanged(event.OverviewOpenedOrClosed) } else if (event.ConfigLoaded) { handleConfigLoaded(event.ConfigLoaded) + } else if (event.KeyboardLayoutsChanged) { + handleKeyboardLayoutsChanged(event.KeyboardLayoutsChanged) + } else if (event.KeyboardLayoutSwitched) { + handleKeyboardLayoutSwitched(event.KeyboardLayoutSwitched) } } @@ -373,6 +381,15 @@ Singleton { } } + function handleKeyboardLayoutsChanged(data) { + keyboardLayoutNames = data.keyboard_layouts.names + currentKeyboardLayoutIndex = data.keyboard_layouts.current_idx + } + + function handleKeyboardLayoutSwitched(data) { + currentKeyboardLayoutIndex = data.idx + } + Process { id: validateProcess command: ["niri", "validate"] @@ -441,7 +458,25 @@ Singleton { return 1 } + function getCurrentKeyboardLayoutName() { + if (currentKeyboardLayoutIndex >= 0 + && currentKeyboardLayoutIndex < keyboardLayoutNames.length) { + return keyboardLayoutNames[currentKeyboardLayoutIndex] + } + return "" + } + + + function cycleKeyboardLayout() { + return send({ + "Action": { + "SwitchLayout": { + "layout": "Next" + } + } + }) + } function quit() { return send({