mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-03 20:32:07 -04:00
compositor service & use toplevels instead of niri data
This commit is contained in:
@@ -381,7 +381,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hasNamedWorkspaces() {
|
function hasNamedWorkspaces() {
|
||||||
if (typeof NiriService === "undefined" || !NiriService.niriAvailable)
|
if (typeof NiriService === "undefined" || !CompositorService.isNiri)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||||
@@ -394,7 +394,7 @@ Singleton {
|
|||||||
|
|
||||||
function getNamedWorkspaces() {
|
function getNamedWorkspaces() {
|
||||||
var namedWorkspaces = []
|
var namedWorkspaces = []
|
||||||
if (typeof NiriService === "undefined" || !NiriService.niriAvailable)
|
if (typeof NiriService === "undefined" || !CompositorService.isNiri)
|
||||||
return namedWorkspaces
|
return namedWorkspaces
|
||||||
|
|
||||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||||
|
|||||||
@@ -625,6 +625,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (typeof Colors !== "undefined")
|
if (typeof Colors !== "undefined")
|
||||||
Colors.colorsUpdated.connect(root.onColorsUpdated)
|
Colors.colorsUpdated.connect(root.onColorsUpdated)
|
||||||
|
|||||||
@@ -16,24 +16,18 @@ PanelWindow {
|
|||||||
|
|
||||||
property var modelData
|
property var modelData
|
||||||
property var contextMenu
|
property var contextMenu
|
||||||
property var windowsMenu
|
|
||||||
property bool autoHide: SettingsData.dockAutoHide
|
property bool autoHide: SettingsData.dockAutoHide
|
||||||
property real backgroundTransparency: SettingsData.dockTransparency
|
property real backgroundTransparency: SettingsData.dockTransparency
|
||||||
|
|
||||||
property bool contextMenuOpen: (contextMenu && contextMenu.visible
|
property bool contextMenuOpen: (contextMenu && contextMenu.visible
|
||||||
&& contextMenu.screen === modelData)
|
&& contextMenu.screen === modelData)
|
||||||
|| (windowsMenu && windowsMenu.visible
|
|
||||||
&& windowsMenu.screen === modelData)
|
|
||||||
property bool windowIsFullscreen: {
|
property bool windowIsFullscreen: {
|
||||||
if (!NiriService.focusedWindowId || !NiriService.niriAvailable)
|
if (!ToplevelManager.activeToplevel)
|
||||||
return false
|
|
||||||
var focusedWindow = NiriService.windows.find(
|
|
||||||
w => w.id === NiriService.focusedWindowId)
|
|
||||||
if (!focusedWindow)
|
|
||||||
return false
|
return false
|
||||||
|
var activeWindow = ToplevelManager.activeToplevel
|
||||||
var fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
|
var fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
|
||||||
return fullscreenApps.some(app => focusedWindow.app_id
|
return fullscreenApps.some(app => activeWindow.appId
|
||||||
&& focusedWindow.app_id.toLowerCase(
|
&& activeWindow.appId.toLowerCase(
|
||||||
).includes(app))
|
).includes(app))
|
||||||
}
|
}
|
||||||
property bool reveal: (!autoHide || dockMouseArea.containsMouse
|
property bool reveal: (!autoHide || dockMouseArea.containsMouse
|
||||||
@@ -142,7 +136,6 @@ PanelWindow {
|
|||||||
anchors.bottomMargin: 4
|
anchors.bottomMargin: 4
|
||||||
|
|
||||||
contextMenu: dock.contextMenu
|
contextMenu: dock.contextMenu
|
||||||
windowsMenu: dock.windowsMenu
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ Item {
|
|||||||
|
|
||||||
property var appData
|
property var appData
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
property var windowsMenu: null
|
|
||||||
property var dockApps: null
|
property var dockApps: null
|
||||||
property int index: -1
|
property int index: -1
|
||||||
property bool longPressing: false
|
property bool longPressing: false
|
||||||
@@ -203,9 +202,10 @@ Item {
|
|||||||
["gtk-launch", appData.appId])
|
["gtk-launch", appData.appId])
|
||||||
}
|
}
|
||||||
} else if (appData.type === "window") {
|
} else if (appData.type === "window") {
|
||||||
// Focus the specific window
|
// Focus the specific window using toplevel
|
||||||
if (appData.windowId)
|
if (appData.toplevelObject) {
|
||||||
NiriService.focusWindow(appData.windowId)
|
appData.toplevelObject.activate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (mouse.button === Qt.MiddleButton) {
|
} else if (mouse.button === Qt.MiddleButton) {
|
||||||
if (appData && appData.appId) {
|
if (appData && appData.appId) {
|
||||||
@@ -301,8 +301,8 @@ Item {
|
|||||||
if (!appData)
|
if (!appData)
|
||||||
return "transparent"
|
return "transparent"
|
||||||
|
|
||||||
// For window type, check if focused
|
// For window type, check if focused using reactive property
|
||||||
if (appData.type === "window" && appData.isFocused)
|
if (appData.type === "window" && appData.toplevelObject && appData.toplevelObject.activated)
|
||||||
return Theme.primary
|
return Theme.primary
|
||||||
|
|
||||||
// For running apps, show dimmer indicator
|
// For running apps, show dimmer indicator
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -9,7 +10,6 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
property var windowsMenu: null
|
|
||||||
property bool requestDockShow: false
|
property bool requestDockShow: false
|
||||||
property int pinnedAppCount: 0
|
property int pinnedAppCount: 0
|
||||||
|
|
||||||
@@ -62,15 +62,17 @@ Item {
|
|||||||
"isPinned"// Use -1 instead of null
|
"isPinned"// Use -1 instead of null
|
||||||
: true,
|
: true,
|
||||||
"isRunning": false,
|
"isRunning": false,
|
||||||
"isFocused": false
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
root.pinnedAppCount = pinnedApps.length
|
root.pinnedAppCount = pinnedApps.length
|
||||||
|
|
||||||
|
// Get sorted toplevels from CompositorService
|
||||||
|
var sortedToplevels = CompositorService.sortedToplevels
|
||||||
|
|
||||||
// Add separator between pinned and running if both exist
|
// Add separator between pinned and running if both exist
|
||||||
if (pinnedApps.length > 0
|
if (pinnedApps.length > 0
|
||||||
&& NiriService.windows.length > 0) {
|
&& sortedToplevels.length > 0) {
|
||||||
items.push({
|
items.push({
|
||||||
"type": "separator",
|
"type": "separator",
|
||||||
"appId": "__SEPARATOR__",
|
"appId": "__SEPARATOR__",
|
||||||
@@ -85,31 +87,23 @@ Item {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second section: Running windows (sorted by display->workspace->position)
|
// Second section: Running windows (sorted using Theme.sortToplevels)
|
||||||
// NiriService.windows is already sorted by sortWindowsByLayout
|
sortedToplevels.forEach(toplevel => {
|
||||||
NiriService.windows.forEach(window => {
|
|
||||||
// Limit window title length for tooltip
|
// Limit window title length for tooltip
|
||||||
var title = window.title
|
var title = toplevel.title || "(Unnamed)"
|
||||||
|| "(Unnamed)"
|
|
||||||
if (title.length > 50) {
|
if (title.length > 50) {
|
||||||
title = title.substring(
|
title = title.substring(0, 47) + "..."
|
||||||
0, 47) + "..."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this window is focused - compare as numbers
|
|
||||||
var isFocused = window.id
|
|
||||||
== NiriService.focusedWindowId
|
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
"type": "window",
|
"type": "window",
|
||||||
"appId": window.app_id
|
"appId": toplevel.appId || "",
|
||||||
|| "",
|
"windowId": -1, // Toplevel doesn't have numeric ID
|
||||||
"windowId": window.id || -1,
|
|
||||||
"windowTitle": title,
|
"windowTitle": title,
|
||||||
"workspaceId": window.workspace_id || -1,
|
"workspaceId": -1, // Will be handled by sorting
|
||||||
"isPinned": false,
|
"isPinned": false,
|
||||||
"isRunning": true,
|
"isRunning": true,
|
||||||
"isFocused": isFocused
|
"toplevelObject": toplevel
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -146,7 +140,6 @@ Item {
|
|||||||
|
|
||||||
appData: model
|
appData: model
|
||||||
contextMenu: root.contextMenu
|
contextMenu: root.contextMenu
|
||||||
windowsMenu: root.windowsMenu
|
|
||||||
dockApps: root
|
dockApps: root
|
||||||
index: model.index
|
index: model.index
|
||||||
|
|
||||||
@@ -159,18 +152,13 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: NiriService
|
target: CompositorService
|
||||||
function onWindowsChanged() {
|
function onSortedToplevelsChanged() {
|
||||||
dockModel.updateModel()
|
|
||||||
}
|
|
||||||
function onWindowOpenedOrChanged() {
|
|
||||||
dockModel.updateModel()
|
|
||||||
}
|
|
||||||
function onFocusedWindowIdChanged() {
|
|
||||||
dockModel.updateModel()
|
dockModel.updateModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SessionData
|
target: SessionData
|
||||||
function onPinnedAppsChanged() {
|
function onPinnedAppsChanged() {
|
||||||
|
|||||||
@@ -187,59 +187,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: !!(root.appData && root.appData.windows
|
visible: root.appData && root.appData.type === "window"
|
||||||
&& root.appData.windows.count > 0)
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
|
||||||
Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.appData
|
|
||||||
&& root.appData.windows ? root.appData.windows : null
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
required property var model
|
|
||||||
width: menuColumn.width
|
|
||||||
height: 28
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: windowArea.containsMouse ? Qt.rgba(
|
|
||||||
Theme.primary.r,
|
|
||||||
Theme.primary.g,
|
|
||||||
Theme.primary.b,
|
|
||||||
0.12) : "transparent"
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: model.title || "Untitled Window"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: windowArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
NiriService.focusWindow(model.id)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: !!(root.appData && root.appData.windows
|
|
||||||
&& root.appData.windows.count > 1)
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 1
|
height: 1
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
@@ -247,12 +195,19 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: !!(root.appData && root.appData.windows
|
visible: root.appData && root.appData.type === "window"
|
||||||
&& root.appData.windows.count > 1)
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
||||||
|
Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: root.appData && root.appData.type === "window"
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 28
|
height: 28
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: closeAllArea.containsMouse ? Qt.rgba(
|
color: closeArea.containsMouse ? Qt.rgba(
|
||||||
Theme.error.r,
|
Theme.error.r,
|
||||||
Theme.error.g,
|
Theme.error.g,
|
||||||
Theme.error.b,
|
Theme.error.b,
|
||||||
@@ -264,25 +219,22 @@ PanelWindow {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Close All Windows"
|
text: "Close Window"
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: closeAllArea.containsMouse ? Theme.error : Theme.surfaceText
|
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
font.weight: Font.Normal
|
font.weight: Font.Normal
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
wrapMode: Text.NoWrap
|
wrapMode: Text.NoWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: closeAllArea
|
id: closeArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.appData || !root.appData.windows)
|
if (root.appData && root.appData.toplevelObject) {
|
||||||
return
|
root.appData.toplevelObject.close()
|
||||||
for (var i = 0; i < root.appData.windows.count; i++) {
|
|
||||||
var window = root.appData.windows.get(i)
|
|
||||||
NiriService.closeWindow(window.id)
|
|
||||||
}
|
}
|
||||||
root.close()
|
root.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
|
||||||
import qs.Services
|
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property bool showWindowsMenu: false
|
|
||||||
property var appData: null
|
|
||||||
property var anchorItem: null
|
|
||||||
property real dockVisibleHeight: 40
|
|
||||||
property int margin: 10
|
|
||||||
|
|
||||||
function showForButton(button, data, dockHeight) {
|
|
||||||
anchorItem = button
|
|
||||||
appData = data
|
|
||||||
dockVisibleHeight = dockHeight || 40
|
|
||||||
|
|
||||||
var dockWindow = button.Window.window
|
|
||||||
if (dockWindow) {
|
|
||||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
|
||||||
var s = Quickshell.screens[i]
|
|
||||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
|
||||||
root.screen = s
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showWindowsMenu = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
showWindowsMenu = false
|
|
||||||
}
|
|
||||||
|
|
||||||
screen: Quickshell.screens[0]
|
|
||||||
|
|
||||||
visible: showWindowsMenu
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
color: "transparent"
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
property point anchorPos: Qt.point(screen.width / 2, screen.height - 100)
|
|
||||||
|
|
||||||
onAnchorItemChanged: updatePosition()
|
|
||||||
onVisibleChanged: if (visible)
|
|
||||||
updatePosition()
|
|
||||||
|
|
||||||
function updatePosition() {
|
|
||||||
if (!anchorItem) {
|
|
||||||
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockWindow = anchorItem.Window.window
|
|
||||||
if (!dockWindow) {
|
|
||||||
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
|
|
||||||
|
|
||||||
var actualDockHeight = root.dockVisibleHeight // fallback
|
|
||||||
|
|
||||||
function findDockBackground(item) {
|
|
||||||
if (item.objectName === "dockBackground") {
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
for (var i = 0; i < item.children.length; i++) {
|
|
||||||
var found = findDockBackground(item.children[i])
|
|
||||||
if (found)
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockBackground = findDockBackground(dockWindow.contentItem)
|
|
||||||
if (dockBackground) {
|
|
||||||
actualDockHeight = dockBackground.height
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockBottomMargin = 16 // The dock has bottom margin
|
|
||||||
var buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
|
|
||||||
|
|
||||||
var dockContentWidth = dockWindow.width
|
|
||||||
var screenWidth = root.screen.width
|
|
||||||
var dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
|
|
||||||
var buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2
|
|
||||||
|
|
||||||
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: menuContainer
|
|
||||||
|
|
||||||
width: Math.min(600, Math.max(
|
|
||||||
250,
|
|
||||||
windowColumn.implicitWidth + Theme.spacingS * 2))
|
|
||||||
height: Math.max(60, windowColumn.implicitHeight + Theme.spacingS * 2)
|
|
||||||
|
|
||||||
x: {
|
|
||||||
var left = 10
|
|
||||||
var right = root.width - width - 10
|
|
||||||
var want = root.anchorPos.x - width / 2
|
|
||||||
return Math.max(left, Math.min(right, want))
|
|
||||||
}
|
|
||||||
y: Math.max(10, root.anchorPos.y - height + 30)
|
|
||||||
color: Theme.popupBackground()
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
|
|
||||||
Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
opacity: showWindowsMenu ? 1 : 0
|
|
||||||
scale: showWindowsMenu ? 1 : 0.85
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.topMargin: 4
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
anchors.rightMargin: -2
|
|
||||||
anchors.bottomMargin: -4
|
|
||||||
radius: parent.radius
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.15)
|
|
||||||
z: parent.z - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: windowColumn
|
|
||||||
width: parent.width - Theme.spacingS * 2
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: Theme.spacingS
|
|
||||||
spacing: 1
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: root.appData
|
|
||||||
&& root.appData.windows ? root.appData.windows : null
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
required property var model
|
|
||||||
width: windowColumn.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: windowArea.containsMouse ? Qt.rgba(
|
|
||||||
Theme.primary.r,
|
|
||||||
Theme.primary.g,
|
|
||||||
Theme.primary.b,
|
|
||||||
0.12) : "transparent"
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: model.title || "Untitled Window"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: model.is_focused ? Theme.primary : Theme.surfaceText
|
|
||||||
font.weight: model.is_focused ? Font.Medium : Font.Normal
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: windowArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
NiriService.focusWindow(model.id)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: -1
|
|
||||||
hoverEnabled: false
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: {
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -12,6 +13,7 @@ Rectangle {
|
|||||||
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
|
readonly property int baseWidth: contentRow.implicitWidth + Theme.spacingS * 2
|
||||||
readonly property int maxNormalWidth: 456
|
readonly property int maxNormalWidth: 456
|
||||||
readonly property int maxCompactWidth: 288
|
readonly property int maxCompactWidth: 288
|
||||||
|
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
|
||||||
|
|
||||||
width: compactMode ? Math.min(baseWidth,
|
width: compactMode ? Math.min(baseWidth,
|
||||||
maxCompactWidth) : Math.min(baseWidth,
|
maxCompactWidth) : Math.min(baseWidth,
|
||||||
@@ -19,7 +21,7 @@ Rectangle {
|
|||||||
height: 30
|
height: 30
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: {
|
color: {
|
||||||
if (!NiriService.focusedWindowTitle)
|
if (!activeWindow || !activeWindow.title)
|
||||||
return "transparent"
|
return "transparent"
|
||||||
|
|
||||||
const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover
|
const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover
|
||||||
@@ -27,7 +29,7 @@ Rectangle {
|
|||||||
baseColor.a * Theme.widgetTransparency)
|
baseColor.a * Theme.widgetTransparency)
|
||||||
}
|
}
|
||||||
clip: true
|
clip: true
|
||||||
visible: NiriService.niriAvailable && NiriService.focusedWindowTitle
|
visible: activeWindow && activeWindow.title
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: contentRow
|
id: contentRow
|
||||||
@@ -39,18 +41,12 @@ Rectangle {
|
|||||||
id: appText
|
id: appText
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
if (!NiriService.focusedWindowId)
|
if (!activeWindow || !activeWindow.appId)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
var window = NiriService.windows.find(w => {
|
var desktopEntry = DesktopEntries.byId(activeWindow.appId)
|
||||||
return w.id == NiriService.focusedWindowId
|
|
||||||
})
|
|
||||||
if (!window || !window.app_id)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
var desktopEntry = DesktopEntries.byId(window.app_id)
|
|
||||||
return desktopEntry
|
return desktopEntry
|
||||||
&& desktopEntry.name ? desktopEntry.name : window.app_id
|
&& desktopEntry.name ? desktopEntry.name : activeWindow.appId
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
@@ -74,7 +70,7 @@ Rectangle {
|
|||||||
id: titleText
|
id: titleText
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
var title = NiriService.focusedWindowTitle || ""
|
var title = activeWindow && activeWindow.title ? activeWindow.title : ""
|
||||||
var appName = appText.text
|
var appName = appText.text
|
||||||
|
|
||||||
if (!title || !appName)
|
if (!title || !appName)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -15,7 +16,8 @@ Rectangle {
|
|||||||
property var topBar: null
|
property var topBar: null
|
||||||
// The visual root for this window
|
// The visual root for this window
|
||||||
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
|
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
|
||||||
readonly property int windowCount: NiriService.windows.length
|
readonly property var sortedToplevels: CompositorService.sortedToplevels
|
||||||
|
readonly property int windowCount: sortedToplevels.length
|
||||||
readonly property int calculatedWidth: {
|
readonly property int calculatedWidth: {
|
||||||
if (windowCount === 0)
|
if (windowCount === 0)
|
||||||
return 0
|
return 0
|
||||||
@@ -50,16 +52,15 @@ Rectangle {
|
|||||||
Repeater {
|
Repeater {
|
||||||
id: windowRepeater
|
id: windowRepeater
|
||||||
|
|
||||||
model: NiriService.windows
|
model: sortedToplevels
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
id: delegateItem
|
id: delegateItem
|
||||||
|
|
||||||
property bool isFocused: String(modelData.id) === String(
|
property bool isFocused: modelData.activated
|
||||||
NiriService.focusedWindowId)
|
property string appId: modelData.appId || ""
|
||||||
property string appId: modelData.app_id || ""
|
|
||||||
property string windowTitle: modelData.title || "(Unnamed)"
|
property string windowTitle: modelData.title || "(Unnamed)"
|
||||||
property int windowId: modelData.id
|
property var toplevelObject: modelData
|
||||||
property string tooltipText: {
|
property string tooltipText: {
|
||||||
var appName = "Unknown"
|
var appName = "Unknown"
|
||||||
if (appId) {
|
if (appId) {
|
||||||
@@ -176,7 +177,9 @@ Rectangle {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
NiriService.focusWindow(windowId)
|
if (toplevelObject) {
|
||||||
|
toplevelObject.activate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onEntered: {
|
onEntered: {
|
||||||
root.hoveredItem = delegateItem
|
root.hoveredItem = delegateItem
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayWorkspaces() {
|
function getDisplayWorkspaces() {
|
||||||
if (!NiriService.niriAvailable
|
if (!CompositorService.isNiri
|
||||||
|| NiriService.allWorkspaces.length === 0)
|
|| NiriService.allWorkspaces.length === 0)
|
||||||
return [1, 2]
|
return [1, 2]
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayActiveWorkspace() {
|
function getDisplayActiveWorkspace() {
|
||||||
if (!NiriService.niriAvailable
|
if (!CompositorService.isNiri
|
||||||
|| NiriService.allWorkspaces.length === 0)
|
|| NiriService.allWorkspaces.length === 0)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ Rectangle {
|
|||||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
||||||
baseColor.a * Theme.widgetTransparency)
|
baseColor.a * Theme.widgetTransparency)
|
||||||
}
|
}
|
||||||
visible: NiriService.niriAvailable
|
visible: CompositorService.isNiri
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onAllWorkspacesChanged() {
|
function onAllWorkspacesChanged() {
|
||||||
@@ -83,16 +83,10 @@ Rectangle {
|
|||||||
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNiriAvailableChanged() {
|
|
||||||
if (NiriService.niriAvailable) {
|
|
||||||
root.workspaceList = SettingsData.showWorkspacePadding ? root.padWorkspaces(root.getDisplayWorkspaces()) : root.getDisplayWorkspaces()
|
|
||||||
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target: NiriService
|
target: NiriService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onShowWorkspacePaddingChanged() {
|
function onShowWorkspacePaddingChanged() {
|
||||||
var baseList = root.getDisplayWorkspaces()
|
var baseList = root.getDisplayWorkspaces()
|
||||||
@@ -118,7 +112,7 @@ Rectangle {
|
|||||||
property bool isHovered: mouseArea.containsMouse
|
property bool isHovered: mouseArea.containsMouse
|
||||||
property int sequentialNumber: index + 1
|
property int sequentialNumber: index + 1
|
||||||
property var workspaceData: {
|
property var workspaceData: {
|
||||||
if (isPlaceholder || !NiriService.niriAvailable)
|
if (isPlaceholder || !CompositorService.isNiri)
|
||||||
return null
|
return null
|
||||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||||
var ws = NiriService.allWorkspaces[i]
|
var ws = NiriService.allWorkspaces[i]
|
||||||
|
|||||||
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 QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
import Quickshell.Wayland
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -22,45 +23,19 @@ Singleton {
|
|||||||
|
|
||||||
// Window management
|
// Window management
|
||||||
property var windows: []
|
property var windows: []
|
||||||
property int focusedWindowIndex: -1
|
|
||||||
property string focusedWindowTitle: "(No active window)"
|
|
||||||
property string focusedWindowId: ""
|
|
||||||
|
|
||||||
// Overview state
|
// Overview state
|
||||||
property bool inOverview: false
|
property bool inOverview: false
|
||||||
|
|
||||||
// Config validation
|
// Internal state (not exposed to external components)
|
||||||
property string configValidationOutput: ""
|
property string configValidationOutput: ""
|
||||||
property bool hasInitialConnection: false
|
property bool hasInitialConnection: false
|
||||||
|
|
||||||
signal windowOpenedOrChanged(var windowData)
|
|
||||||
|
|
||||||
// Feature availability
|
|
||||||
property bool niriAvailable: false
|
|
||||||
|
|
||||||
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
|
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() {
|
function fetchOutputs() {
|
||||||
if (niriAvailable) {
|
if (CompositorService.isNiri) {
|
||||||
outputsProcess.running = true
|
outputsProcess.running = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +73,7 @@ Singleton {
|
|||||||
Socket {
|
Socket {
|
||||||
id: eventStreamSocket
|
id: eventStreamSocket
|
||||||
path: root.socketPath
|
path: root.socketPath
|
||||||
connected: false
|
connected: CompositorService.isNiri
|
||||||
|
|
||||||
onConnectionStateChanged: {
|
onConnectionStateChanged: {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
@@ -121,7 +96,7 @@ Singleton {
|
|||||||
Socket {
|
Socket {
|
||||||
id: requestSocket
|
id: requestSocket
|
||||||
path: root.socketPath
|
path: root.socketPath
|
||||||
connected: root.niriAvailable
|
connected: CompositorService.isNiri
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortWindowsByLayout(windowList) {
|
function sortWindowsByLayout(windowList) {
|
||||||
@@ -203,8 +178,6 @@ Singleton {
|
|||||||
handleWindowsChanged(event.WindowsChanged)
|
handleWindowsChanged(event.WindowsChanged)
|
||||||
} else if (event.WindowClosed) {
|
} else if (event.WindowClosed) {
|
||||||
handleWindowClosed(event.WindowClosed)
|
handleWindowClosed(event.WindowClosed)
|
||||||
} else if (event.WindowFocusChanged) {
|
|
||||||
handleWindowFocusChanged(event.WindowFocusChanged)
|
|
||||||
} else if (event.WindowOpenedOrChanged) {
|
} else if (event.WindowOpenedOrChanged) {
|
||||||
handleWindowOpenedOrChanged(event.WindowOpenedOrChanged)
|
handleWindowOpenedOrChanged(event.WindowOpenedOrChanged)
|
||||||
} else if (event.WindowLayoutsChanged) {
|
} else if (event.WindowLayoutsChanged) {
|
||||||
@@ -279,10 +252,6 @@ Singleton {
|
|||||||
// This is crucial for handling floating window close scenarios
|
// This is crucial for handling floating window close scenarios
|
||||||
if (data.active_window_id !== null
|
if (data.active_window_id !== null
|
||||||
&& data.active_window_id !== undefined) {
|
&& 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
|
// Create new windows array with updated focus states to trigger property change
|
||||||
let updatedWindows = []
|
let updatedWindows = []
|
||||||
for (var i = 0; i < windows.length; i++) {
|
for (var i = 0; i < windows.length; i++) {
|
||||||
@@ -295,13 +264,8 @@ Singleton {
|
|||||||
updatedWindows.push(updatedWindow)
|
updatedWindows.push(updatedWindow)
|
||||||
}
|
}
|
||||||
windows = updatedWindows
|
windows = updatedWindows
|
||||||
|
|
||||||
updateFocusedWindow()
|
|
||||||
} else {
|
} else {
|
||||||
// No active window in this workspace
|
// No active window in this workspace
|
||||||
focusedWindowId = ""
|
|
||||||
focusedWindowIndex = -1
|
|
||||||
|
|
||||||
// Create new windows array with cleared focus states for this workspace
|
// Create new windows array with cleared focus states for this workspace
|
||||||
let updatedWindows = []
|
let updatedWindows = []
|
||||||
for (var i = 0; i < windows.length; i++) {
|
for (var i = 0; i < windows.length; i++) {
|
||||||
@@ -315,42 +279,15 @@ Singleton {
|
|||||||
updatedWindows.push(updatedWindow)
|
updatedWindows.push(updatedWindow)
|
||||||
}
|
}
|
||||||
windows = updatedWindows
|
windows = updatedWindows
|
||||||
|
|
||||||
updateFocusedWindow()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWindowsChanged(data) {
|
function handleWindowsChanged(data) {
|
||||||
windows = sortWindowsByLayout(data.windows)
|
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) {
|
function handleWindowClosed(data) {
|
||||||
windows = windows.filter(w => w.id !== data.id)
|
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) {
|
function handleWindowOpenedOrChanged(data) {
|
||||||
@@ -368,14 +305,6 @@ Singleton {
|
|||||||
windows = sortWindowsByLayout([...windows, window])
|
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) {
|
function handleWindowLayoutsChanged(data) {
|
||||||
@@ -477,17 +406,8 @@ Singleton {
|
|||||||
currentOutputWorkspaces = outputWs
|
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) {
|
function send(request) {
|
||||||
if (!niriAvailable || !requestSocket.connected)
|
if (!CompositorService.isNiri || !requestSocket.connected)
|
||||||
return false
|
return false
|
||||||
requestSocket.write(JSON.stringify(request) + "\n")
|
requestSocket.write(JSON.stringify(request) + "\n")
|
||||||
return true
|
return true
|
||||||
@@ -518,25 +438,7 @@ Singleton {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusWindow(windowId) {
|
|
||||||
return send({
|
|
||||||
"Action": {
|
|
||||||
"FocusWindow": {
|
|
||||||
"id": windowId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeWindow(windowId) {
|
|
||||||
return send({
|
|
||||||
"Action": {
|
|
||||||
"CloseWindow": {
|
|
||||||
"id": windowId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function quit() {
|
function quit() {
|
||||||
return send({
|
return send({
|
||||||
@@ -548,58 +450,66 @@ Singleton {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWindowsByAppId(appId) {
|
|
||||||
if (!appId)
|
|
||||||
return []
|
function sortToplevels(toplevels) {
|
||||||
return windows.filter(w => w.app_id && w.app_id.toLowerCase(
|
if (!toplevels || toplevels.length === 0 || !CompositorService.isNiri || windows.length === 0) {
|
||||||
) === appId.toLowerCase())
|
return [...toplevels]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRunningAppIds() {
|
// Create a map to match toplevels to niri windows
|
||||||
var appIds = new Set()
|
// We'll match by appId and title since toplevels don't have numeric IDs
|
||||||
windows.forEach(w => {
|
var toplevelToNiriMap = {}
|
||||||
if (w.app_id) {
|
|
||||||
appIds.add(w.app_id.toLowerCase())
|
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
|
||||||
})
|
})
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
13
shell.qml
13
shell.qml
@@ -44,11 +44,9 @@ ShellRoot {
|
|||||||
delegate: Dock {
|
delegate: Dock {
|
||||||
modelData: item
|
modelData: item
|
||||||
contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null
|
contextMenu: dockContextMenuLoader.item ? dockContextMenuLoader.item : null
|
||||||
windowsMenu: dockWindowsMenuLoader.item ? dockWindowsMenuLoader.item : null
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
dockContextMenuLoader.active = true
|
dockContextMenuLoader.active = true
|
||||||
dockWindowsMenuLoader.active = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,14 +70,6 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
|
||||||
id: dockWindowsMenuLoader
|
|
||||||
active: false
|
|
||||||
|
|
||||||
DockWindowsMenu {
|
|
||||||
id: dockWindowsMenu
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
id: notificationCenterLoader
|
id: notificationCenterLoader
|
||||||
@@ -258,6 +248,9 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user