Watch
1
0
Fork
You've already forked souveraine
0
souveraine/surfaces/quickshell/ii-base/services/HyprlandXkb.qml
Fimeg 8f42fc953d publish: the public projection begins here
This is a projection, not a development branch. The tree above was constructed
from the internal source named below under a manifest that decides which paths
may leave, then scanned as a whole tree rather than as a series of patches, and
only then published.

Public history starts here because the history before it was not admissible,
and neither was the tree. What used to stand in this repository included a
rescue copy of another machine, a directory of phone handoffs, deployment
wired to one house, and a submodule pointing at a forge no stranger can reach.
None of that was ever the product. It stays in the private forge, which is
allowed to hold the whole working organism, and this is what was deliberately
sent out instead.

Three mechanisms produced this tree, in decreasing order of trust. A top-level
path the manifest does not name never arrives at all, which is the one that
catches directories nobody has thought of yet. Named internal files inside
admitted roots are dropped. A short, reviewed table replaces deployment
defaults that a public build must not carry -- an endpoint aimed at one LAN, a
VPN profile belonging to one phone, packaging built from one checkout path.

Everything after this commit is an ordinary publication with the same three
trailers, so a force push stops being routine and starts meaning that
something deliberate happened. The trailers bind the projection to its source
without pretending the public SHA is the private one: same lineage, different
tree, and the record says so.

Source-Sha: 8f27b1e76a8fef560a336aba18e6990713ff1047
Policy-Sha: 6b261d2f3e6e1fb19874846ba4bb1dfe15565d25b8618c1c1afba0419c101d27
Tree-Digest: 18ec3563c5e5ef9a414993a9f6734b251ff9ed3cd56eebdd6cac01e45c6e3067
2026-09-04 15:55:48 -04:00

119 lines
4.8 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import qs.modules.common
/**
* 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 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 layout: (whitespace + ) key + whitespace + description
const matchLayout = line.match(/^\s*(\S+)\s+(.+)$/);
if (matchLayout && matchLayout[2] === targetDescription) {
root.cachedLayoutCodes[matchLayout[2]] = matchLayout[1];
root.currentLayoutCode = matchLayout[1];
return true;
}
// Match variant: (whitespace + ) variant + whitespace + key + whitespace + description
const matchVariant = line.match(/^\s*(\S+)\s+(\S+)\s+(.+)$/);
if (matchVariant && matchVariant[3] === targetDescription) {
const complexLayout = matchVariant[2] + matchVariant[1];
root.cachedLayoutCodes[matchVariant[3]] = complexLayout;
root.currentLayoutCode = complexLayout;
return true;
}
return false;
});
// 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.main === true);
root.layoutCodes = hyprlandKeyboard["layout"].split(",");
root.currentLayoutName = hyprlandKeyboard["active_keymap"];
// console.log("[HyprlandXkb] Fetched | Layouts (multiple: " + (root.layoutCodes.length > 1) + "): "
// + root.layoutCodes.join(", ") + " | Active: " + root.currentLayoutName);
}
}
}
// Update the layout name when it changes
Connections {
target: Hyprland
function onRawEvent(event) {
if (event.name === "activelayout") {
if (root.needsLayoutRefresh) {
root.needsLayoutRefresh = false;
fetchLayoutsProc.running = true;
}
// If there's only one layout, the updated layout is always the same
if (root.layoutCodes.length <= 1) return;
// Update when layout might have changed
const dataString = event.data;
root.currentLayoutName = dataString.substring(dataString.indexOf(",") + 1);
// Update layout for on-screen keyboard (osk)
Config.options.osk.layout = root.currentLayoutName;
} else if (event.name == "configreloaded") {
// Mark layout code list to be updated when config is reloaded
root.needsLayoutRefresh = true;
}
}
}
}