1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 14:02:53 -05:00
Files
DankMaterialShell/Services/CompositorService.qml
2025-08-27 02:34:46 +03:00

216 lines
7.2 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Singleton {
id: root
// Compositor detection
property bool isHyprland: false
property bool isNiri: false
property bool uwsmActive: false
property string compositor: "unknown"
readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
readonly property string niriSocket: Quickshell.env("NIRI_SOCKET")
property bool useNiriSorting: isNiri && NiriService
property bool useHyprlandSorting: false
property var sortedToplevels: {
if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) {
return []
}
// Only use niri sorting when both compositor is niri AND niri service is ready
if (useNiriSorting) {
return NiriService.sortToplevels(ToplevelManager.toplevels.values)
}
if (isHyprland) {
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
const sortedHyprland = hyprlandToplevels.sort((a, b) => {
// Sort by monitor first
if (a.monitor && b.monitor) {
const monitorCompare = a.monitor.name.localeCompare(b.monitor.name)
if (monitorCompare !== 0) return monitorCompare
}
// Then by workspace
if (a.workspace && b.workspace) {
const workspaceCompare = a.workspace.id - b.workspace.id
if (workspaceCompare !== 0) return workspaceCompare
}
if (a.lastIpcObject && b.lastIpcObject && a.lastIpcObject.at && b.lastIpcObject.at) {
const aX = a.lastIpcObject.at[0]
const bX = b.lastIpcObject.at[0]
const aY = a.lastIpcObject.at[1]
const bY = b.lastIpcObject.at[1]
const xCompare = aX - bX
if (Math.abs(xCompare) > 10) return xCompare
return aY - bY
}
if (a.lastIpcObject && !b.lastIpcObject) return -1
if (!a.lastIpcObject && b.lastIpcObject) return 1
if (a.title && b.title) {
return a.title.localeCompare(b.title)
}
return 0
})
// Return the wayland Toplevel objects
return sortedHyprland.map(hyprToplevel => hyprToplevel.wayland).filter(wayland => wayland !== null)
}
// For other compositors or when services aren't ready yet, return unsorted toplevels
return ToplevelManager.toplevels.values
}
Component.onCompleted: {
detectCompositor()
uwsmCheck.running = true
}
function filterCurrentWorkspace(toplevels, screen){
if (useNiriSorting) {
return NiriService.filterCurrentWorkspace(toplevels, screen)
}
if (isHyprland) {
return filterHyprlandCurrentWorkspace(toplevels, screen)
}
return toplevels
}
function filterHyprlandCurrentWorkspace(toplevels, screenName) {
if (!toplevels || toplevels.length === 0 || !Hyprland.toplevels) {
return toplevels
}
var currentWorkspaceId = null
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
for (var i = 0; i < hyprlandToplevels.length; i++) {
var hyprToplevel = hyprlandToplevels[i]
if (hyprToplevel.monitor && hyprToplevel.monitor.name === screenName && hyprToplevel.workspace) {
if (hyprToplevel.activated) {
currentWorkspaceId = hyprToplevel.workspace.id
break
}
if (currentWorkspaceId === null) {
currentWorkspaceId = hyprToplevel.workspace.id
}
}
}
if (currentWorkspaceId === null && Hyprland.workspaces) {
const workspaces = Array.from(Hyprland.workspaces.values)
for (var k = 0; k < workspaces.length; k++) {
var workspace = workspaces[k]
if (workspace.monitor && workspace.monitor === screenName) {
if (Hyprland.focusedWorkspace && workspace.id === Hyprland.focusedWorkspace.id) {
currentWorkspaceId = workspace.id
break
}
if (currentWorkspaceId === null) {
currentWorkspaceId = workspace.id
}
}
}
}
if (currentWorkspaceId === null) {
return toplevels
}
return toplevels.filter(toplevel => {
for (var j = 0; j < hyprlandToplevels.length; j++) {
var hyprToplevel = hyprlandToplevels[j]
if (hyprToplevel.wayland === toplevel) {
return hyprToplevel.workspace && hyprToplevel.workspace.id === currentWorkspaceId
}
}
return false
})
}
function detectCompositor() {
// Check for Hyprland first
if (hyprlandSignature && hyprlandSignature.length > 0) {
isHyprland = true
isNiri = false
compositor = "hyprland"
console.log("CompositorService: Detected Hyprland")
return
}
// Check for Niri
if (niriSocket && niriSocket.length > 0) {
// Verify the socket actually exists
niriSocketCheck.running = true
} else {
// No compositor detected, default to Niri
isHyprland = false
isNiri = false
compositor = "unknown"
console.warn("CompositorService: No compositor detected")
}
}
Process {
id: niriSocketCheck
command: ["test", "-S", root.niriSocket]
onExited: exitCode => {
if (exitCode === 0) {
root.isNiri = true
root.isHyprland = false
root.compositor = "niri"
console.log("CompositorService: Detected Niri with socket:", root.niriSocket)
} else {
root.isHyprland = false
root.isNiri = true
root.compositor = "niri"
console.warn("CompositorService: Niri socket check failed, defaulting to Niri anyway")
}
}
}
function logout() {
if (uwsmActive) {
Quickshell.execDetached(["uwsm", "stop"])
return
}
if (isNiri) {
NiriService.quit()
return
}
// Hyprland fallback
Hyprland.dispatch("exit")
}
Process {
id: uwsmCheck
// `uwsm check is-active` returns 0 if in uwsm-managed session, 1 otherwise
command: ["uwsm", "check", "is-active"]
running: false
onExited: function(exitCode) {
uwsmActive = exitCode === 0
}
}
}