forked from Shinonome/dots-hyprland
bar: layout indicator: more proper layout parsing
This commit is contained in:
@@ -416,12 +416,15 @@ Scope {
|
|||||||
color: rightSidebarButton.colText
|
color: rightSidebarButton.colText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Label {
|
Loader {
|
||||||
|
active: HyprlandXkb.layoutCodes.length > 1
|
||||||
|
visible: active
|
||||||
Layout.rightMargin: indicatorsRowLayout.realSpacing
|
Layout.rightMargin: indicatorsRowLayout.realSpacing
|
||||||
text: LayoutService.currentLayout
|
sourceComponent: StyledText {
|
||||||
visible: LayoutService.currentLayout !== ""
|
text: HyprlandXkb.currentLayoutCode
|
||||||
font.pixelSize: Appearance.font.pixelSize.larger - 3
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
color: rightSidebarButton.colText
|
color: rightSidebarButton.colText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MaterialSymbol {
|
MaterialSymbol {
|
||||||
Layout.rightMargin: indicatorsRowLayout.realSpacing
|
Layout.rightMargin: indicatorsRowLayout.realSpacing
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes the active Hyprland Xkb keyboard layout name and code for indicators.
|
||||||
|
*/
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
// You can read these
|
||||||
|
property list<string> layoutCodes: []
|
||||||
|
property var cachedLayoutCodes: ({})
|
||||||
|
property string currentLayoutName: ""
|
||||||
|
property string currentLayoutCode: ""
|
||||||
|
// For the service
|
||||||
|
property string targetDeviceName: "hl-virtual-keyboard"
|
||||||
|
property var baseLayoutFilePath: "/usr/share/X11/xkb/rules/base.lst"
|
||||||
|
property bool needsLayoutRefresh: false
|
||||||
|
|
||||||
|
// Update the layout code according to the layout name (Hyprland gives the name not the code)
|
||||||
|
onCurrentLayoutNameChanged: root.updateLayoutCode()
|
||||||
|
function updateLayoutCode() {
|
||||||
|
if (cachedLayoutCodes.hasOwnProperty(currentLayoutName)) {
|
||||||
|
root.currentLayoutCode = cachedLayoutCodes[currentLayoutName];
|
||||||
|
} else {
|
||||||
|
getLayoutProc.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the layout code from the base.lst file by grabbing the line with the current layout name
|
||||||
|
Process {
|
||||||
|
id: getLayoutProc
|
||||||
|
command: ["cat", root.baseLayoutFilePath]
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: layoutCollector
|
||||||
|
|
||||||
|
onStreamFinished: {
|
||||||
|
const lines = layoutCollector.text.split("\n");
|
||||||
|
const targetDescription = root.currentLayoutName;
|
||||||
|
const foundLine = lines.find(line => {
|
||||||
|
// Skip comment lines and empty lines
|
||||||
|
if (!line.trim() || line.trim().startsWith('!'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Match: key + whitespace + description
|
||||||
|
const match = line.match(/^\s*(\S+)\s+(.+)$/);
|
||||||
|
if (match && match[2] === targetDescription) {
|
||||||
|
root.cachedLayoutCodes[match[2]] = match[1];
|
||||||
|
root.currentLayoutCode = match[1];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// console.log("[HyprlandXkb] Found line:", foundLine);
|
||||||
|
// console.log("[HyprlandXkb] Layout:", root.currentLayoutName, "| Code:", root.currentLayoutCode);
|
||||||
|
// console.log("[HyprlandXkb] Cached layout codes:", JSON.stringify(root.cachedLayoutCodes, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find out available layouts and current active layout. Should only be necessary on init
|
||||||
|
Process {
|
||||||
|
id: fetchLayoutsProc
|
||||||
|
running: true
|
||||||
|
command: ["hyprctl", "-j", "devices"]
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: devicesCollector
|
||||||
|
onStreamFinished: {
|
||||||
|
const parsedOutput = JSON.parse(devicesCollector.text);
|
||||||
|
const hyprlandKeyboard = parsedOutput["keyboards"].find(kb => kb.name === root.targetDeviceName);
|
||||||
|
root.layoutCodes = hyprlandKeyboard["layout"].split(",");
|
||||||
|
root.currentLayoutName = hyprlandKeyboard["active_keymap"];
|
||||||
|
// console.log("[HyprlandXkb] Fetched | Layouts (multiple: " + (root.layouts.length > 1) + "): "
|
||||||
|
// + root.layouts.join(", ") + " | Active: " + root.currentLayoutName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the layout name when it changes
|
||||||
|
Connections {
|
||||||
|
target: Hyprland
|
||||||
|
function onRawEvent(event) {
|
||||||
|
if (event.name === "activelayout") {
|
||||||
|
// We're triggering refresh here because Hyprland virtual kb after a config reload disappears
|
||||||
|
// from `hyprctl devices` and it only comes back at the next activelayout event.
|
||||||
|
if (root.needsLayoutRefresh) {
|
||||||
|
root.needsLayoutRefresh = false;
|
||||||
|
fetchLayoutsProc.running = true;
|
||||||
|
}
|
||||||
|
// Update when layout might have changed
|
||||||
|
const dataString = event.data;
|
||||||
|
// console.log("[HyprlandXkb] Received raw event:", event.name, "with data:", dataString);
|
||||||
|
if (!dataString.startsWith(root.targetDeviceName))
|
||||||
|
return;
|
||||||
|
root.currentLayoutName = dataString.split(",")[1];
|
||||||
|
} else if (event.name == "configreloaded") {
|
||||||
|
// Mark layout code list to be updated when config is reloaded
|
||||||
|
root.needsLayoutRefresh = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
pragma Singleton
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell.Hyprland
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: layoutService
|
|
||||||
|
|
||||||
property string currentLayout: "" // This is empty on startup. We could default it to "en", but we don't know the user's configured layout order (e.g. "en,ru" vs "ru,en").
|
|
||||||
// I haven't found a way to query the initial layout from QML without external bash scripts, so this is the safest compromise for now.
|
|
||||||
|
|
||||||
function parseLayout(fullLayoutName) {
|
|
||||||
if (!fullLayoutName) return;
|
|
||||||
|
|
||||||
const shortName = fullLayoutName.substring(0, 2).toLowerCase();
|
|
||||||
|
|
||||||
if (currentLayout !== shortName) {
|
|
||||||
currentLayout = shortName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRawEvent(event) {
|
|
||||||
if (event.name === "activelayout") {
|
|
||||||
const dataString = event.data;
|
|
||||||
const layoutInfo = dataString.split(",");
|
|
||||||
const fullLayoutName = layoutInfo[layoutInfo.length - 1];
|
|
||||||
|
|
||||||
parseLayout(fullLayoutName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
Hyprland.rawEvent.connect(handleRawEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user