KeyboardLayout: update on keyboard layout change events (hyprland)

This commit is contained in:
Ala Alkhafaji
2025-11-10 23:54:37 +01:00
parent 9921e1a09f
commit 43ff69238a
3 changed files with 74 additions and 29 deletions
+72 -1
View File
@@ -3,6 +3,7 @@ import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import qs.Commons
import qs.Services.Keyboard
Item {
id: root
@@ -43,6 +44,7 @@ Item {
safeUpdateWorkspaces()
safeUpdateWindows()
queryDisplayScales()
queryKeyboardLayout()
})
initialized = true
Logger.i("HyprlandService", "Service started")
@@ -56,7 +58,6 @@ Item {
hyprlandMonitorsProcess.running = true
}
// Hyprland monitors process for display scale detection
// Hyprland monitors process for display scale detection
Process {
id: hyprlandMonitorsProcess
@@ -112,6 +113,50 @@ Item {
}
}
}
function queryKeyboardLayout() {
hyprlandDevicesProcess.running = true
}
// Hyprland devices process for keyboard layout detection
Process {
id: hyprlandDevicesProcess
running: false
command: ["hyprctl", "devices", "-j"]
property string accumulatedOutput: ""
stdout: SplitParser {
onRead: function (line) {
// Accumulate lines instead of parsing each one
hyprlandDevicesProcess.accumulatedOutput += line
}
}
onExited: function (exitCode) {
if (exitCode !== 0 || !accumulatedOutput) {
Logger.e("HyprlandService", "Failed to query devices, exit code:", exitCode)
accumulatedOutput = ""
return
}
try {
const devicesData = JSON.parse(accumulatedOutput)
for (const keyboard of devicesData.keyboards) {
if (keyboard.main) {
const layoutName = keyboard.active_keymap
KeyboardLayoutService.setCurrentLayout(layoutName)
Logger.d("HyprlandService", "Keyboard layout switched:", layoutName)
}
}
} catch (e) {
Logger.e("HyprlandService", "Failed to parse devices:", e)
} finally {
// Clear accumulated output for next query
accumulatedOutput = ""
}
}
}
// Safe update wrapper
function safeUpdate() {
safeUpdateWindows()
@@ -330,6 +375,27 @@ Item {
return defaultValue
}
function handleActiveLayoutEvent(ev) {
try {
let beforeParenthesis
const parenthesisPos = ev.lastIndexOf('(')
if (parenthesisPos === -1) {
beforeParenthesis = ev
} else {
beforeParenthesis = ev.substring(0, parenthesisPos)
}
const layoutNameStart = beforeParenthesis.lastIndexOf(',') + 1
const layoutName = ev.substring(layoutNameStart)
KeyboardLayoutService.setCurrentLayout(layoutName)
Logger.d("HyprlandService", "Keyboard layout switched:", layoutName)
} catch (e) {
Logger.e("HyprlandService", "Error handling activelayout:", e)
}
}
// Connections to Hyprland
Connections {
target: Hyprland.workspaces
@@ -358,9 +424,14 @@ Item {
updateTimer.restart()
const monitorsEvents = ["configreloaded", "monitoradded", "monitorremoved", "monitoraddedv2", "monitorremovedv2"]
if (monitorsEvents.includes(event.name)) {
Qt.callLater(queryDisplayScales)
}
if (event.name == "activelayout") {
handleActiveLayoutEvent(event.data)
}
}
}
-3
View File
@@ -49,7 +49,6 @@ Item {
niriOutputsProcess.running = true
}
// Niri outputs process for display scale detection
Process {
id: niriOutputsProcess
@@ -452,6 +451,4 @@ Item {
Logger.e("NiriService", "Failed to logout:", e)
}
}
}
+2 -25
View File
@@ -25,29 +25,6 @@ Singleton {
}
}
// 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 = extractLayoutCode(mainKeyboard.active_keymap)
} else {
root.currentLayout = I18n.tr("system.unknown-layout")
}
} catch (e) {
root.currentLayout = I18n.tr("system.unknown-layout")
}
}
}
}
// Process for X11 systems using setxkbmap
Process {
id: x11LayoutProcess
@@ -229,9 +206,9 @@ Singleton {
function updateLayout() {
// Try compositor-specific methods first
if (CompositorService.isHyprland) {
hyprlandLayoutProcess.running = true
} else if (CompositorService.isNiri) {
// do nothing, niri calls setCurrentLayout
} else {
// Try detection methods in order of preference
if (Qt.platform.os === "linux") {