quickshell and hyprland additions

This commit is contained in:
2026-03-15 13:56:00 +02:00
parent c9c27d1554
commit 1ad06b82a6
509 changed files with 68371 additions and 19 deletions

View File

@@ -0,0 +1,242 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
pragma Singleton
Singleton {
id: niriItem
signal stateChanged()
property string title: ""
property bool isFullscreen: false
property string layout: "Tiled"
property int focusedWorkspaceId: 1
property var workspaces: []
property var workspaceCache: ({})
property var windows: [] // tracked windows
property bool initialized: false
property int screenW: 0
property int screenH: 0
property real screenScale: 1
function changeWorkspace(id) {
sendSocketCommand(niriCommandSocket, {
"Action": {
"focus_workspace": {
"reference": { "Id": id }
}
}
})
dispatchProc.command = ["niri", "msg", "action", "focus-workspace", id.toString()]
dispatchProc.running = true
}
function changeWorkspaceRelative(delta) {
const cmd = delta > 0 ? "focus-workspace-down" : "focus-workspace-up"
dispatchProc.command = ["niri", "msg", "action", cmd]
dispatchProc.running = true
}
function isWorkspaceOccupied(id) {
for (const ws of workspaces)
if (ws.id === id) return true
return false
}
function focusedWindowForWorkspace(id) {
// focused window in workspace
for (const win of windows) {
if (win.workspaceId === id && win.isFocused) {
return { class: win.appId, title: win.title }
}
}
// fallback: any window in workspace
for (const win of windows) {
if (win.workspaceId === id) {
return { class: win.appId, title: win.title }
}
}
return null
}
function sendSocketCommand(sock, command) {
if (sock.connected)
sock.write(JSON.stringify(command) + "\n")
}
function startEventStream() {
sendSocketCommand(niriEventStream, "EventStream")
}
function updateWorkspaces() {
sendSocketCommand(niriCommandSocket, "Workspaces")
}
function updateWindows() {
sendSocketCommand(niriCommandSocket, "Windows")
}
function updateFocusedWindow() {
sendSocketCommand(niriCommandSocket, "FocusedWindow")
}
function recollectWorkspaces(workspacesData) {
const list = []
workspaceCache = {}
for (const ws of workspacesData) {
const data = {
id: ws.idx !== undefined ? ws.idx + 1 : ws.id,
internalId: ws.id,
idx: ws.idx,
name: ws.name || "",
output: ws.output || "",
isFocused: ws.is_focused === true,
isActive: ws.is_active === true
}
list.push(data)
workspaceCache[ws.id] = data
if (data.isFocused)
focusedWorkspaceId = data.id
}
list.sort((a, b) => a.id - b.id)
workspaces = list
stateChanged()
}
function recollectWindows(windowsData) {
const list = []
for (const win of windowsData) {
list.push({
appId: win.app_id || "",
title: win.title || "",
workspaceId: win.workspace_id,
isFocused: win.is_focused === true
})
}
windows = list
stateChanged()
}
function recollectFocusedWindow(win) {
if (win && win.title) {
title = win.title
isFullscreen = win.is_fullscreen || false
layout = "Tiled"
} else {
title = "~"
isFullscreen = false
layout = "Tiled"
}
stateChanged()
}
Component.onCompleted: {
if (Quickshell.env("NIRI_SOCKET")) {
niriCommandSocket.connected = true
niriEventStream.connected = true
initialized = true
}
}
Process {
id: niriOutputsProc
command: ["niri", "msg", "outputs"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const lines = this.text.split("\n")
let sizeRe = /Logical size:\s*(\d+)x(\d+)/
let scaleRe = /Scale:\s*([\d.]+)/
for (const line of lines) {
let m
if ((m = sizeRe.exec(line))) {
screenW = parseInt(m[1], 10)
screenH = parseInt(m[2], 10)
} else if ((m = scaleRe.exec(line))) {
screenScale = parseFloat(m[1])
}
}
stateChanged()
}
}
}
Socket {
id: niriCommandSocket
path: Quickshell.env("NIRI_SOCKET") || ""
connected: false
onConnectedChanged: {
if (connected) {
updateWorkspaces()
updateWindows()
updateFocusedWindow()
}
}
parser: SplitParser {
onRead: (line) => {
if (!line.trim()) return
try {
const data = JSON.parse(line)
if (data?.Ok) {
const res = data.Ok
if (res.Workspaces) recollectWorkspaces(res.Workspaces)
else if (res.Windows) recollectWindows(res.Windows)
else if (res.FocusedWindow) recollectFocusedWindow(res.FocusedWindow)
}
} catch (e) {
console.warn("Niri socket parse error:", e)
}
}
}
}
Socket {
id: niriEventStream
path: Quickshell.env("NIRI_SOCKET") || ""
connected: false
onConnectedChanged: {
if (connected)
startEventStream()
}
parser: SplitParser {
onRead: (data) => {
if (!data.trim()) return
try {
const event = JSON.parse(data.trim())
if (event.WorkspacesChanged)
recollectWorkspaces(event.WorkspacesChanged.workspaces)
else if (event.WorkspaceActivated)
updateWorkspaces()
else if (
event.WindowFocusChanged ||
event.WindowOpenedOrChanged ||
event.WindowClosed
)
updateWindows()
} catch (e) {
console.warn("Niri event stream parse error:", e)
}
}
}
}
Process { id: dispatchProc }
}