diff --git a/Commons/WidgetLoader.qml b/Commons/WidgetLoader.qml index 17ef9bf..872f64d 100644 --- a/Commons/WidgetLoader.qml +++ b/Commons/WidgetLoader.qml @@ -65,7 +65,7 @@ QtObject { // This is where you should add your Modules/Bar/Widgets/ // so it gets registered in the BarTab function discoverAvailableWidgets() { - const widgetFiles = ["ActiveWindow", "Battery", "Bluetooth", "Brightness", "Clock", "MediaMini", "NotificationHistory", "PowerProfile", "ScreenRecorderIndicator", "SidePanelToggle", "SystemMonitor", "Tray", "Volume", "WiFi", "Workspace"] + const widgetFiles = ["ActiveWindow", "Battery", "Bluetooth", "Brightness", "Clock", "KeyboardLayout", "MediaMini", "NotificationHistory", "PowerProfile", "ScreenRecorderIndicator", "SidePanelToggle", "SystemMonitor", "Tray", "Volume", "WiFi", "Workspace"] const availableWidgets = [] diff --git a/Modules/Bar/Widgets/KeyboardLayout.qml b/Modules/Bar/Widgets/KeyboardLayout.qml new file mode 100644 index 0000000..fd1e4fb --- /dev/null +++ b/Modules/Bar/Widgets/KeyboardLayout.qml @@ -0,0 +1,34 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import Quickshell.Io +import qs.Commons +import qs.Services +import qs.Widgets + +Item { + id: root + + width: pill.width + height: pill.height + + // Use the shared service for keyboard layout + property string currentLayout: KeyboardLayoutService.currentLayout + + NPill { + id: pill + icon: "keyboard_alt" + iconCircleColor: Color.mPrimary + collapsedIconColor: Color.mOnSurface + autoHide: false // Important to be false so we can hover as long as we want + text: currentLayout + tooltipText: "Keyboard Layout: " + currentLayout + + onClicked: { + // You could open keyboard settings here if needed + // For now, just show the current layout + } + } + + +} diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 5f41bd7..f5ded6e 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -163,6 +163,13 @@ Loader { } } + // Keyboard layout indicator component + Item { + id: keyboardLayout + + property string currentLayout: (typeof KeyboardLayoutService !== 'undefined' && KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown" + } + // Wallpaper image Image { id: lockBgImage @@ -580,6 +587,25 @@ Loader { font.weight: Style.fontWeightBold } } + + // Keyboard layout indicator + Row { + spacing: Style.marginS * scaling + + NText { + text: keyboardLayout.currentLayout + color: Color.mOnSurface + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + } + + NIcon { + text: "keyboard_alt" + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurface + } + } } } diff --git a/Services/KeyboardLayoutService.qml b/Services/KeyboardLayoutService.qml new file mode 100644 index 0000000..c66e5c9 --- /dev/null +++ b/Services/KeyboardLayoutService.qml @@ -0,0 +1,112 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import qs.Commons +import qs.Services + +Singleton { + id: root + + property string currentLayout: "Unknown" + property int updateInterval: 1000 // Update every second + + // Timer to periodically update the layout + Timer { + id: updateTimer + interval: updateInterval + running: true + repeat: true + onTriggered: { + updateLayout() + } + } + + // Process to get current keyboard layout using niri msg (Wayland native) + Process { + id: niriLayoutProcess + running: false + command: ["niri", "msg", "-j", "keyboard-layouts"] + stdout: StdioCollector { + onStreamFinished: { + try { + const data = JSON.parse(text) + const layoutName = data.names[data.current_idx] + root.currentLayout = mapLayoutNameToCode(layoutName) + } catch (e) { + root.currentLayout = "Unknown" + } + } + } + } + + // Process to get current keyboard layout using hyprctl (Hyprland) + Process { + id: hyprlandLayoutProcess + running: false + command: ["hyprctl", "-j", "devices"] + stdout: StdioCollector { + onStreamFinished: { + try { + const data = JSON.parse(text) + // Find the main keyboard and get its active keymap + const mainKeyboard = data.keyboards.find(kb => kb.main === true) + if (mainKeyboard && mainKeyboard.active_keymap) { + root.currentLayout = mapLayoutNameToCode(mainKeyboard.active_keymap) + } else { + root.currentLayout = "Unknown" + } + } catch (e) { + root.currentLayout = "Unknown" + } + } + } + } + + // Layout name to ISO code mapping + property var layoutMap: { + "German": "de", + "English (US)": "us", + "English (UK)": "gb", + "French": "fr", + "Spanish": "es", + "Italian": "it", + "Portuguese (Brazil)": "br", + "Portuguese": "pt", + "Russian": "ru", + "Polish": "pl", + "Swedish": "se", + "Norwegian": "no", + "Danish": "dk", + "Finnish": "fi", + "Hungarian": "hu", + "Turkish": "tr", + "Czech": "cz", + "Slovak": "sk", + "Japanese": "jp", + "Korean": "kr", + "Chinese": "cn" + } + + // Map layout names to ISO codes + function mapLayoutNameToCode(layoutName) { + return layoutMap[layoutName] || layoutName // fallback to raw name if not found + } + + Component.onCompleted: { + Logger.log("KeyboardLayout", "Service started") + updateLayout() + } + + function updateLayout() { + if (CompositorService.isHyprland) { + hyprlandLayoutProcess.running = true + } else if (CompositorService.isNiri) { + niriLayoutProcess.running = true + } else { + currentLayout = "Unknown" + } + } +}