mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 09:36:46 +00:00
quickshell and hyprland additions
This commit is contained in:
216
.config/quickshell/nucleus-shell/services/Hyprland.qml
Executable file
216
.config/quickshell/nucleus-shell/services/Hyprland.qml
Executable file
@@ -0,0 +1,216 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// true if Hyprland is running, false otherwise
|
||||
readonly property bool isHyprland: Compositor.require("hyprland")
|
||||
|
||||
// reactive Hyprland data, only valid if Hyprland is running
|
||||
signal stateChanged()
|
||||
readonly property var toplevels: isHyprland ? Hyprland.toplevels : []
|
||||
readonly property var workspaces: isHyprland ? Hyprland.workspaces : []
|
||||
readonly property var monitors: isHyprland ? Hyprland.monitors : []
|
||||
readonly property Toplevel activeToplevel: isHyprland ? ToplevelManager.activeToplevel : null
|
||||
readonly property HyprlandWorkspace focusedWorkspace: isHyprland ? Hyprland.focusedWorkspace : null
|
||||
readonly property HyprlandMonitor focusedMonitor: isHyprland ? Hyprland.focusedMonitor : null
|
||||
readonly property int focusedWorkspaceId: focusedWorkspace?.id ?? 1
|
||||
property real screenW: focusedMonitor ? focusedMonitor.width : 0
|
||||
property real screenH: focusedMonitor ? focusedMonitor.height : 0
|
||||
property real screenScale: focusedMonitor ? focusedMonitor.scale : 1
|
||||
|
||||
// parsed hyprctl data, defaults are empty
|
||||
property var windowList: []
|
||||
property var windowByAddress: ({})
|
||||
property var addresses: []
|
||||
property var layers: ({})
|
||||
property var monitorsInfo: []
|
||||
property var workspacesInfo: []
|
||||
property var workspaceById: ({})
|
||||
property var workspaceIds: []
|
||||
property var activeWorkspaceInfo: null
|
||||
property string keyboardLayout: "?"
|
||||
|
||||
// dispatch a command to Hyprland, no-op if not running
|
||||
function dispatch(request: string): void {
|
||||
if (!isHyprland) return
|
||||
Hyprland.dispatch(request)
|
||||
}
|
||||
|
||||
// switch workspace safely
|
||||
function changeWorkspace(targetWorkspaceId) {
|
||||
if (!isHyprland || !targetWorkspaceId) return
|
||||
root.dispatch("workspace " + targetWorkspaceId)
|
||||
}
|
||||
|
||||
// find most recently focused window in a workspace
|
||||
function focusedWindowForWorkspace(workspaceId) {
|
||||
if (!isHyprland) return null
|
||||
const wsWindows = root.windowList.filter(w => w.workspace.id === workspaceId)
|
||||
if (wsWindows.length === 0) return null
|
||||
return wsWindows.reduce((best, win) => {
|
||||
const bestFocus = best?.focusHistoryID ?? Infinity
|
||||
const winFocus = win?.focusHistoryID ?? Infinity
|
||||
return winFocus < bestFocus ? win : best
|
||||
}, null)
|
||||
}
|
||||
|
||||
// check if a workspace has any windows
|
||||
function isWorkspaceOccupied(id: int): bool {
|
||||
if (!isHyprland) return false
|
||||
return Hyprland.workspaces.values.find(w => w?.id === id)?.lastIpcObject.windows > 0 || false
|
||||
}
|
||||
|
||||
// update all hyprctl processes
|
||||
function updateAll() {
|
||||
if (!isHyprland) return
|
||||
getClients.running = true
|
||||
getLayers.running = true
|
||||
getMonitors.running = true
|
||||
getWorkspaces.running = true
|
||||
getActiveWorkspace.running = true
|
||||
}
|
||||
|
||||
// largest window in a workspace
|
||||
function biggestWindowForWorkspace(workspaceId) {
|
||||
if (!isHyprland) return null
|
||||
const windowsInThisWorkspace = root.windowList.filter(w => w.workspace.id === workspaceId)
|
||||
return windowsInThisWorkspace.reduce((maxWin, win) => {
|
||||
const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0)
|
||||
const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0)
|
||||
return winArea > maxArea ? win : maxWin
|
||||
}, null)
|
||||
}
|
||||
|
||||
// refresh keyboard layout
|
||||
function refreshKeyboardLayout() {
|
||||
if (!isHyprland) return
|
||||
hyprctlDevices.running = true
|
||||
}
|
||||
|
||||
// only create hyprctl processes if Hyprland is running
|
||||
Component.onCompleted: {
|
||||
if (isHyprland) {
|
||||
updateAll()
|
||||
refreshKeyboardLayout()
|
||||
}
|
||||
}
|
||||
|
||||
// process to get keyboard layout
|
||||
Process {
|
||||
id: hyprctlDevices
|
||||
running: false
|
||||
command: ["hyprctl", "devices", "-j"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const devices = JSON.parse(this.text)
|
||||
const keyboard = devices.keyboards.find(k => k.main) || devices.keyboards[0]
|
||||
root.keyboardLayout = keyboard?.active_keymap?.toUpperCase()?.slice(0, 2) ?? "?"
|
||||
} catch (err) {
|
||||
console.error("Failed to parse keyboard layout:", err)
|
||||
root.keyboardLayout = "?"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getClients
|
||||
running: false
|
||||
command: ["hyprctl", "clients", "-j"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
root.windowList = JSON.parse(this.text)
|
||||
let tempWinByAddress = {}
|
||||
for (let win of root.windowList) tempWinByAddress[win.address] = win
|
||||
root.windowByAddress = tempWinByAddress
|
||||
root.addresses = root.windowList.map(w => w.address)
|
||||
} catch (e) {
|
||||
console.error("Failed to parse clients:", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getMonitors
|
||||
running: false
|
||||
command: ["hyprctl", "monitors", "-j"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try { root.monitorsInfo = JSON.parse(this.text) }
|
||||
catch (e) { console.error("Failed to parse monitors:", e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getLayers
|
||||
running: false
|
||||
command: ["hyprctl", "layers", "-j"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try { root.layers = JSON.parse(this.text) }
|
||||
catch (e) { console.error("Failed to parse layers:", e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getWorkspaces
|
||||
running: false
|
||||
command: ["hyprctl", "workspaces", "-j"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
root.workspacesInfo = JSON.parse(this.text)
|
||||
let map = {}
|
||||
for (let ws of root.workspacesInfo) map[ws.id] = ws
|
||||
root.workspaceById = map
|
||||
root.workspaceIds = root.workspacesInfo.map(ws => ws.id)
|
||||
} catch (e) { console.error("Failed to parse workspaces:", e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: getActiveWorkspace
|
||||
running: false
|
||||
command: ["hyprctl", "activeworkspace", "-j"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try { root.activeWorkspaceInfo = JSON.parse(this.text) }
|
||||
catch (e) { console.error("Failed to parse active workspace:", e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only connect to Hyprland events if running
|
||||
Connections {
|
||||
target: isHyprland ? Hyprland : null
|
||||
function onRawEvent(event) {
|
||||
if (!isHyprland || event.name.endsWith("v2")) return
|
||||
|
||||
if (event.name.includes("activelayout"))
|
||||
refreshKeyboardLayout()
|
||||
else if (event.name.includes("mon"))
|
||||
Hyprland.refreshMonitors()
|
||||
else if (event.name.includes("workspace") || event.name.includes("window"))
|
||||
Hyprland.refreshWorkspaces()
|
||||
else
|
||||
Hyprland.refreshToplevels()
|
||||
|
||||
updateAll()
|
||||
root.stateChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user