mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-11 07:52:50 -05:00
compositor service & use toplevels instead of niri data
This commit is contained in:
84
Services/CompositorService.qml
Normal file
84
Services/CompositorService.qml
Normal file
@@ -0,0 +1,84 @@
|
||||
pragma Singleton
|
||||
|
||||
pragma ComponentBehavior
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Compositor detection
|
||||
property bool isHyprland: false
|
||||
property bool isNiri: 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
|
||||
|
||||
// Unified sorted toplevels - automatically chooses sorting based on compositor
|
||||
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)
|
||||
}
|
||||
|
||||
// For non-niri compositors or when niri isn't ready yet, return unsorted toplevels
|
||||
return ToplevelManager.toplevels.values
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("CompositorService: Starting detection...")
|
||||
detectCompositor()
|
||||
}
|
||||
|
||||
function detectCompositor() {
|
||||
// Check for Hyprland first
|
||||
if (hyprlandSignature && hyprlandSignature.length > 0) {
|
||||
isHyprland = true
|
||||
isNiri = false
|
||||
compositor = "hyprland"
|
||||
console.log("CompositorService: Detected Hyprland with signature:", hyprlandSignature)
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pragma ComponentBehavior
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -22,45 +23,19 @@ Singleton {
|
||||
|
||||
// Window management
|
||||
property var windows: []
|
||||
property int focusedWindowIndex: -1
|
||||
property string focusedWindowTitle: "(No active window)"
|
||||
property string focusedWindowId: ""
|
||||
|
||||
// Overview state
|
||||
property bool inOverview: false
|
||||
|
||||
// Config validation
|
||||
// Internal state (not exposed to external components)
|
||||
property string configValidationOutput: ""
|
||||
property bool hasInitialConnection: false
|
||||
|
||||
signal windowOpenedOrChanged(var windowData)
|
||||
|
||||
// Feature availability
|
||||
property bool niriAvailable: false
|
||||
|
||||
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
|
||||
|
||||
Component.onCompleted: checkNiriAvailability()
|
||||
|
||||
Process {
|
||||
id: niriCheck
|
||||
command: ["test", "-S", root.socketPath]
|
||||
|
||||
onExited: exitCode => {
|
||||
root.niriAvailable = exitCode === 0
|
||||
if (root.niriAvailable) {
|
||||
eventStreamSocket.connected = true
|
||||
fetchOutputs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkNiriAvailability() {
|
||||
niriCheck.running = true
|
||||
}
|
||||
|
||||
function fetchOutputs() {
|
||||
if (niriAvailable) {
|
||||
if (CompositorService.isNiri) {
|
||||
outputsProcess.running = true
|
||||
}
|
||||
}
|
||||
@@ -98,7 +73,7 @@ Singleton {
|
||||
Socket {
|
||||
id: eventStreamSocket
|
||||
path: root.socketPath
|
||||
connected: false
|
||||
connected: CompositorService.isNiri
|
||||
|
||||
onConnectionStateChanged: {
|
||||
if (connected) {
|
||||
@@ -121,7 +96,7 @@ Singleton {
|
||||
Socket {
|
||||
id: requestSocket
|
||||
path: root.socketPath
|
||||
connected: root.niriAvailable
|
||||
connected: CompositorService.isNiri
|
||||
}
|
||||
|
||||
function sortWindowsByLayout(windowList) {
|
||||
@@ -203,8 +178,6 @@ Singleton {
|
||||
handleWindowsChanged(event.WindowsChanged)
|
||||
} else if (event.WindowClosed) {
|
||||
handleWindowClosed(event.WindowClosed)
|
||||
} else if (event.WindowFocusChanged) {
|
||||
handleWindowFocusChanged(event.WindowFocusChanged)
|
||||
} else if (event.WindowOpenedOrChanged) {
|
||||
handleWindowOpenedOrChanged(event.WindowOpenedOrChanged)
|
||||
} else if (event.WindowLayoutsChanged) {
|
||||
@@ -279,10 +252,6 @@ Singleton {
|
||||
// This is crucial for handling floating window close scenarios
|
||||
if (data.active_window_id !== null
|
||||
&& data.active_window_id !== undefined) {
|
||||
focusedWindowId = String(data.active_window_id)
|
||||
focusedWindowIndex = windows.findIndex(
|
||||
w => w.id == data.active_window_id)
|
||||
|
||||
// Create new windows array with updated focus states to trigger property change
|
||||
let updatedWindows = []
|
||||
for (var i = 0; i < windows.length; i++) {
|
||||
@@ -295,13 +264,8 @@ Singleton {
|
||||
updatedWindows.push(updatedWindow)
|
||||
}
|
||||
windows = updatedWindows
|
||||
|
||||
updateFocusedWindow()
|
||||
} else {
|
||||
// No active window in this workspace
|
||||
focusedWindowId = ""
|
||||
focusedWindowIndex = -1
|
||||
|
||||
// Create new windows array with cleared focus states for this workspace
|
||||
let updatedWindows = []
|
||||
for (var i = 0; i < windows.length; i++) {
|
||||
@@ -315,42 +279,15 @@ Singleton {
|
||||
updatedWindows.push(updatedWindow)
|
||||
}
|
||||
windows = updatedWindows
|
||||
|
||||
updateFocusedWindow()
|
||||
}
|
||||
}
|
||||
|
||||
function handleWindowsChanged(data) {
|
||||
windows = sortWindowsByLayout(data.windows)
|
||||
|
||||
// Extract focused window from initial state
|
||||
var focusedWindow = windows.find(w => w.is_focused)
|
||||
if (focusedWindow) {
|
||||
focusedWindowId = String(focusedWindow.id)
|
||||
focusedWindowIndex = windows.findIndex(
|
||||
w => w.id === focusedWindow.id)
|
||||
} else {
|
||||
focusedWindowId = ""
|
||||
focusedWindowIndex = -1
|
||||
}
|
||||
|
||||
updateFocusedWindow()
|
||||
}
|
||||
|
||||
function handleWindowClosed(data) {
|
||||
windows = windows.filter(w => w.id !== data.id)
|
||||
updateFocusedWindow()
|
||||
}
|
||||
|
||||
function handleWindowFocusChanged(data) {
|
||||
if (data.id) {
|
||||
focusedWindowId = data.id
|
||||
focusedWindowIndex = windows.findIndex(w => w.id === data.id)
|
||||
} else {
|
||||
focusedWindowId = ""
|
||||
focusedWindowIndex = -1
|
||||
}
|
||||
updateFocusedWindow()
|
||||
}
|
||||
|
||||
function handleWindowOpenedOrChanged(data) {
|
||||
@@ -368,14 +305,6 @@ Singleton {
|
||||
windows = sortWindowsByLayout([...windows, window])
|
||||
}
|
||||
|
||||
if (window.is_focused) {
|
||||
focusedWindowId = window.id
|
||||
focusedWindowIndex = windows.findIndex(w => w.id === window.id)
|
||||
}
|
||||
|
||||
updateFocusedWindow()
|
||||
|
||||
windowOpenedOrChanged(window)
|
||||
}
|
||||
|
||||
function handleWindowLayoutsChanged(data) {
|
||||
@@ -477,17 +406,8 @@ Singleton {
|
||||
currentOutputWorkspaces = outputWs
|
||||
}
|
||||
|
||||
function updateFocusedWindow() {
|
||||
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) {
|
||||
var focusedWin = windows[focusedWindowIndex]
|
||||
focusedWindowTitle = focusedWin.title || "(Unnamed window)"
|
||||
} else {
|
||||
focusedWindowTitle = "(No active window)"
|
||||
}
|
||||
}
|
||||
|
||||
function send(request) {
|
||||
if (!niriAvailable || !requestSocket.connected)
|
||||
if (!CompositorService.isNiri || !requestSocket.connected)
|
||||
return false
|
||||
requestSocket.write(JSON.stringify(request) + "\n")
|
||||
return true
|
||||
@@ -518,25 +438,7 @@ Singleton {
|
||||
return 1
|
||||
}
|
||||
|
||||
function focusWindow(windowId) {
|
||||
return send({
|
||||
"Action": {
|
||||
"FocusWindow": {
|
||||
"id": windowId
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function closeWindow(windowId) {
|
||||
return send({
|
||||
"Action": {
|
||||
"CloseWindow": {
|
||||
"id": windowId
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function quit() {
|
||||
return send({
|
||||
@@ -548,58 +450,66 @@ Singleton {
|
||||
})
|
||||
}
|
||||
|
||||
function getWindowsByAppId(appId) {
|
||||
if (!appId)
|
||||
return []
|
||||
return windows.filter(w => w.app_id && w.app_id.toLowerCase(
|
||||
) === appId.toLowerCase())
|
||||
|
||||
|
||||
function sortToplevels(toplevels) {
|
||||
if (!toplevels || toplevels.length === 0 || !CompositorService.isNiri || windows.length === 0) {
|
||||
return [...toplevels]
|
||||
}
|
||||
|
||||
// Create a map to match toplevels to niri windows
|
||||
// We'll match by appId and title since toplevels don't have numeric IDs
|
||||
var toplevelToNiriMap = {}
|
||||
|
||||
for (var i = 0; i < toplevels.length; i++) {
|
||||
var toplevel = toplevels[i]
|
||||
if (!toplevel.appId) continue
|
||||
|
||||
// Find matching niri window by appId and optionally title
|
||||
for (var j = 0; j < windows.length; j++) {
|
||||
var niriWindow = windows[j]
|
||||
|
||||
// Match by appId
|
||||
if (niriWindow.app_id === toplevel.appId) {
|
||||
// If title also matches or niri window has no title, use this match
|
||||
if (!niriWindow.title || niriWindow.title === toplevel.title) {
|
||||
toplevelToNiriMap[i] = {
|
||||
niriIndex: j,
|
||||
niriWindow: niriWindow
|
||||
}
|
||||
break
|
||||
}
|
||||
// If we found appId match but no title match yet, store as fallback
|
||||
if (!(i in toplevelToNiriMap)) {
|
||||
toplevelToNiriMap[i] = {
|
||||
niriIndex: j,
|
||||
niriWindow: niriWindow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort toplevels using niri's ordering
|
||||
return [...toplevels].sort((a, b) => {
|
||||
var aIndex = toplevels.indexOf(a)
|
||||
var bIndex = toplevels.indexOf(b)
|
||||
|
||||
var aNiri = toplevelToNiriMap[aIndex]
|
||||
var bNiri = toplevelToNiriMap[bIndex]
|
||||
|
||||
// If both have niri data, use niri ordering
|
||||
if (aNiri && bNiri) {
|
||||
return aNiri.niriIndex - bNiri.niriIndex
|
||||
}
|
||||
|
||||
// If only one has niri data, prioritize it
|
||||
if (aNiri && !bNiri) return -1
|
||||
if (!aNiri && bNiri) return 1
|
||||
|
||||
// If neither has niri data, keep original toplevel order
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
function getRunningAppIds() {
|
||||
var appIds = new Set()
|
||||
windows.forEach(w => {
|
||||
if (w.app_id) {
|
||||
appIds.add(w.app_id.toLowerCase())
|
||||
}
|
||||
})
|
||||
return Array.from(appIds)
|
||||
}
|
||||
|
||||
function getRunningAppIdsOrdered() {
|
||||
// Get unique app IDs in order they appear in the Niri layout
|
||||
// Windows are sorted by workspace and then by position within the workspace
|
||||
var sortedWindows = [...windows].sort((a, b) => {
|
||||
// If both have layout info, sort by position
|
||||
if (a.layout && b.layout
|
||||
&& a.layout.pos_in_scrolling_layout
|
||||
&& b.layout.pos_in_scrolling_layout) {
|
||||
var aPos = a.layout.pos_in_scrolling_layout
|
||||
var bPos = b.layout.pos_in_scrolling_layout
|
||||
|
||||
// First compare workspace index
|
||||
if (aPos[0] !== bPos[0]) {
|
||||
return aPos[0] - bPos[0]
|
||||
}
|
||||
// Then compare position within workspace
|
||||
return aPos[1] - bPos[1]
|
||||
}
|
||||
// Fallback to window ID if no layout info
|
||||
return a.id - b.id
|
||||
})
|
||||
|
||||
var appIds = []
|
||||
var seenApps = new Set()
|
||||
|
||||
sortedWindows.forEach(w => {
|
||||
if (w.app_id) {
|
||||
var lowerAppId = w.app_id.toLowerCase()
|
||||
if (!seenApps.has(lowerAppId)) {
|
||||
appIds.push(lowerAppId)
|
||||
seenApps.add(lowerAppId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return appIds
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user