mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Services in subfolder + cleanup/fixes
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property string currentLayout: I18n.tr("system.unknown-layout")
|
||||
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 = extractLayoutCode(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 = extractLayoutCode(mainKeyboard.active_keymap)
|
||||
} else {
|
||||
root.currentLayout = "Unknown"
|
||||
}
|
||||
} catch (e) {
|
||||
root.currentLayout = "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process for X11 systems using setxkbmap
|
||||
Process {
|
||||
id: x11LayoutProcess
|
||||
running: false
|
||||
command: ["setxkbmap", "-query"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const lines = text.split('\n')
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('layout:')) {
|
||||
const layout = line.split(':')[1].trim()
|
||||
root.currentLayout = layout
|
||||
return
|
||||
}
|
||||
}
|
||||
root.currentLayout = "Unknown"
|
||||
} catch (e) {
|
||||
root.currentLayout = "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process for general Wayland using localectl (systemd)
|
||||
Process {
|
||||
id: localectlProcess
|
||||
running: false
|
||||
command: ["localectl", "status"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const lines = text.split('\n')
|
||||
for (const line of lines) {
|
||||
if (line.includes("X11 Layout:")) {
|
||||
const layout = line.split(':')[1].trim()
|
||||
if (layout && layout !== "n/a") {
|
||||
root.currentLayout = layout
|
||||
return
|
||||
}
|
||||
}
|
||||
if (line.includes("VC Keymap:")) {
|
||||
const keymap = line.split(':')[1].trim()
|
||||
if (keymap && keymap !== "n/a") {
|
||||
root.currentLayout = extractLayoutCode(keymap)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
root.currentLayout = "Unknown"
|
||||
} catch (e) {
|
||||
root.currentLayout = "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process for generic keyboard layout detection using gsettings (GNOME-based)
|
||||
Process {
|
||||
id: gsettingsProcess
|
||||
running: false
|
||||
command: ["gsettings", "get", "org.gnome.desktop.input-sources", "current"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const currentIndex = parseInt(text.trim())
|
||||
gsettingsSourcesProcess.running = true
|
||||
} catch (e) {
|
||||
fallbackToLocalectl()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gsettingsSourcesProcess
|
||||
running: false
|
||||
command: ["gsettings", "get", "org.gnome.desktop.input-sources", "sources"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
// Parse the sources array and extract layout codes
|
||||
const sourcesText = text.trim()
|
||||
const matches = sourcesText.match(/\('xkb', '([^']+)'\)/g)
|
||||
if (matches && matches.length > 0) {
|
||||
// Get the first layout as default
|
||||
const layoutMatch = matches[0].match(/\('xkb', '([^']+)'\)/)
|
||||
if (layoutMatch) {
|
||||
root.currentLayout = layoutMatch[1].split('+')[0] // Take first part before any variants
|
||||
}
|
||||
} else {
|
||||
fallbackToLocalectl()
|
||||
}
|
||||
} catch (e) {
|
||||
fallbackToLocalectl()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackToLocalectl() {
|
||||
localectlProcess.running = true
|
||||
}
|
||||
|
||||
// Extract layout code from various format strings using Commons data
|
||||
function extractLayoutCode(layoutString) {
|
||||
if (!layoutString)
|
||||
return "Unknown"
|
||||
|
||||
const str = layoutString.toLowerCase()
|
||||
|
||||
// If it's already a short code (2-3 chars), return as-is
|
||||
if (/^[a-z]{2,3}(\+.*)?$/.test(str)) {
|
||||
return str.split('+')[0]
|
||||
}
|
||||
|
||||
// Extract from parentheses like "English (US)"
|
||||
const parenMatch = str.match(/\(([a-z]{2,3})\)/)
|
||||
if (parenMatch) {
|
||||
return parenMatch[1]
|
||||
}
|
||||
|
||||
// Check for exact matches or partial matches in language map from Commons
|
||||
const entries = Object.entries(KeyboardLayout.languageMap)
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
const lang = entries[i][0]
|
||||
const code = entries[i][1]
|
||||
if (str.includes(lang)) {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing matches, try first 2-3 characters if they look like a code
|
||||
const codeMatch = str.match(/^([a-z]{2,3})/)
|
||||
return codeMatch ? codeMatch[1] : "unknown"
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("KeyboardLayout", "Service started")
|
||||
updateLayout()
|
||||
}
|
||||
|
||||
function updateLayout() {
|
||||
// Try compositor-specific methods first
|
||||
if (CompositorService.isHyprland) {
|
||||
hyprlandLayoutProcess.running = true
|
||||
} else if (CompositorService.isNiri) {
|
||||
niriLayoutProcess.running = true
|
||||
} else {
|
||||
// Try detection methods in order of preference
|
||||
if (Qt.platform.os === "linux") {
|
||||
// Check if we're in X11 or Wayland
|
||||
const sessionType = Qt.application.arguments.find(arg => arg.includes("QT_QPA_PLATFORM")) || process.env.XDG_SESSION_TYPE
|
||||
|
||||
if (sessionType && sessionType.includes("xcb") || process.env.DISPLAY) {
|
||||
// X11 system
|
||||
x11LayoutProcess.running = true
|
||||
} else {
|
||||
// Wayland or unknown - try gsettings first, then localectl
|
||||
gsettingsProcess.running = true
|
||||
}
|
||||
} else {
|
||||
currentLayout = "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user