mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 09:36:46 +00:00
243 lines
6.7 KiB
QML
243 lines
6.7 KiB
QML
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 }
|
|
}
|