Files
noctalia-shell/Services/CompositorService.qml
T
2025-10-03 14:49:31 +02:00

230 lines
5.9 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import qs.Commons
import qs.Services
Singleton {
id: root
// Compositor detection
property bool isHyprland: false
property bool isNiri: false
// Generic workspace and window data
property ListModel workspaces: ListModel {}
property ListModel windows: ListModel {}
property int focusedWindowIndex: -1
// Generic events
signal workspaceChanged
signal activeWindowChanged
signal windowListChanged
// Backend service loader
property var backend: null
Component.onCompleted: {
detectCompositor()
}
function detectCompositor() {
const hyprlandSignature = Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
if (hyprlandSignature && hyprlandSignature.length > 0) {
isHyprland = true
isNiri = false
backendLoader.sourceComponent = hyprlandComponent
} else {
// Default to Niri
isHyprland = false
isNiri = true
backendLoader.sourceComponent = niriComponent
}
}
Loader {
id: backendLoader
onLoaded: {
if (item) {
root.backend = item
setupBackendConnections()
backend.initialize()
}
}
}
// Hyprland backend component
Component {
id: hyprlandComponent
HyprlandService {
id: hyprlandBackend
}
}
// Niri backend component
Component {
id: niriComponent
NiriService {
id: niriBackend
}
}
function setupBackendConnections() {
if (!backend)
return
// Connect backend signals to facade signals
backend.workspaceChanged.connect(() => {
// Sync workspaces when they change
syncWorkspaces()
// Forward the signal
workspaceChanged()
})
backend.activeWindowChanged.connect(() => {
// Sync active window when it changes
// TODO: Avoid re-syncing all windows
syncWindows()
// Forward the signal
activeWindowChanged()
})
backend.windowListChanged.connect(() => {
// Sync windows when they change
syncWindows()
// Forward the signal
windowListChanged()
})
// Property bindings
backend.focusedWindowIndexChanged.connect(() => {
focusedWindowIndex = backend.focusedWindowIndex
})
// Initial sync
syncWorkspaces()
syncWindows()
focusedWindowIndex = backend.focusedWindowIndex
}
function syncWorkspaces() {
workspaces.clear()
const ws = backend.workspaces
for (var i = 0; i < ws.count; i++) {
workspaces.append(ws.get(i))
}
// Emit signal to notify listeners that workspace list has been updated
workspacesChanged()
}
function syncWindows() {
windows.clear()
const ws = backend.windows
for (var i = 0; i < ws.length; i++) {
windows.append(ws[i])
}
// Emit signal to notify listeners that workspace list has been updated
windowListChanged()
}
// Get focused window
function getFocusedWindow() {
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) {
return windows.get(focusedWindowIndex)
}
return null
}
// Get focused window title
function getFocusedWindowTitle() {
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) {
return windows.get(focusedWindowIndex).title || ""
}
return ""
}
// Generic workspace switching
function switchToWorkspace(workspaceId) {
if (backend && backend.switchToWorkspace) {
backend.switchToWorkspace(workspaceId)
} else {
Logger.warn("Compositor", "No backend available for workspace switching")
}
}
// Get current workspace
function getCurrentWorkspace() {
for (var i = 0; i < workspaces.count; i++) {
const ws = workspaces.get(i)
if (ws.isFocused) {
return ws
}
}
return null
}
// Get active workspaces
function getActiveWorkspaces() {
const activeWorkspaces = []
for (var i = 0; i < workspaces.count; i++) {
const ws = workspaces.get(i)
if (ws.isActive) {
activeWorkspaces.push(ws)
}
}
return activeWorkspaces
}
// Set focused window
function focusWindow(windowId) {
if (backend && backend.focusWindow) {
backend.focusWindow(windowId)
} else {
Logger.warn("Compositor", "No backend available for window focus")
}
}
// Close window
function closeWindow(windowId) {
if (backend && backend.closeWindow) {
backend.closeWindow(windowId)
} else {
Logger.warn("Compositor", "No backend available for window closing")
}
}
// Session management
function logout() {
if (backend && backend.logout) {
backend.logout()
} else {
Logger.warn("Compositor", "No backend available for logout")
}
}
function shutdown() {
Quickshell.execDetached(["shutdown", "-h", "now"])
}
function reboot() {
Quickshell.execDetached(["reboot"])
}
function suspend() {
Quickshell.execDetached(["systemctl", "suspend"])
}
function lockAndSuspend() {
try {
if (PanelService && PanelService.lockScreen && !PanelService.lockScreen.active) {
PanelService.lockScreen.active = true
}
} catch (e) {
Logger.warn("Compositor", "Failed to activate lock screen before suspend: " + e)
}
// Queue suspend to the next event loop cycle to allow lock UI to render
Qt.callLater(suspend)
}
}