diff --git a/Common/NotificationWrapper.qml b/Common/NotificationWrapper.qml index 91ae704..852f5b8 100644 --- a/Common/NotificationWrapper.qml +++ b/Common/NotificationWrapper.qml @@ -15,7 +15,6 @@ BackgroundRectangle { property var notification: null property bool startTimer: false property int timerDuration: 2000 - property bool firstTime: true property string image: { if (hasImage) { @@ -26,62 +25,66 @@ BackgroundRectangle { } return Quickshell.iconPath(notification?.appIcon); } - property real targetHeight: notifLayout.implicitHeight + 20 + + property bool collapsed: false implicitWidth: 400 - implicitHeight: 0 - // implicitHeight: Math.max(notifLayout.implicitHeight, 100) + readonly property real contentHeight: Math.max(notifLayout.implicitHeight + 20, 100) - Component.onCompleted: { - root.implicitHeight = targetHeight; - } - clip: true + height: collapsed ? 0 : contentHeight - Behavior on implicitHeight { + Behavior on height { SequentialAnimation { NumberAnimation { - duration: 150 + duration: 200 easing.type: Easing.OutCubic } ScriptAction { script: { - if (clicked) + if (root.clicked) root.dismissed(); - if (root.implicitHeight === 0) + if (root.height === 0) root.timedout(); } } - } + } } + clip: true + MouseArea { anchors.fill: parent onClicked: { root.clicked = true; - root.implicitHeight = 0; + root.collapsed = true; + root.dismissed(); } } Timer { id: notifTimer - interval: timerDuration running: root.startTimer + repeat: false onTriggered: { - root.implicitHeight = 0; + root.collapsed = true; } } RowLayout { id: notifLayout - implicitHeight: Math.max(iconImage.implicitHeight, gridRoot.implicitHeight) + Layout.preferredHeight: Math.max(iconImage.implicitHeight, gridRoot.implicitHeight) anchors { - fill: parent + top: parent.top + left: parent.left + right: parent.right + margins: 10 } + IconImage { id: iconImage @@ -89,7 +92,7 @@ BackgroundRectangle { Layout.leftMargin: 10 implicitSize: 80 visible: root.image ? true : false - source: root.image + source: root.image ? root.image : "" } ColumnLayout { @@ -115,9 +118,8 @@ BackgroundRectangle { StyledText { id: summaryText - anchors.fill: parent - + wrapMode: Text.WordWrap text: root.notification ? root.notification.summary : "" } } @@ -137,9 +139,8 @@ BackgroundRectangle { } StyledText { id: bodyText - anchors.fill: parent - + wrapMode: Text.WordWrap text: root.notification ? root.notification.body : "" } } diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index 5833c88..69bbbe5 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -11,119 +11,102 @@ Singleton { id: root readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE") - readonly property var focusedMon: Hyprland.focusedMonitor - property var hasFullscreen: false - property var isScreencasting: false - - property var sortedTopLevels: { - if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) { - return []; - } - - const topLevels = Array.from(Hyprland.toplevels.values); - const sortedHyprland = topLevels.sort((a, b) => { - if (a.monitor && b.monitor) { - const monitorCompare = a.monitor.name.localeCompare(b.monitor.name); - if (monitorCompare !== 0) { - return monitorCompare; - } - } - - 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 sortedHyprland.filter(tl => tl.wayland !== null); - } - - property var topLevelWorkspaces: { - return sortedTopLevels.map(topLevel => topLevel.workspace); - } + property bool hasFullscreen: false + property bool isScreencasting: false property ListModel sortedDesktopApplicationsModel: ListModel {} - onSortedTopLevelsChanged: updateSortedDesktopApplications() + // Derived property of sorted toplevels + property var sortedTopLevels: { + const topLevels = Array.from(Hyprland.toplevels?.values ?? []); + const sorted = topLevels.sort((a, b) => { + if (a.monitor && b.monitor) { + const monitorCompare = a.monitor.name.localeCompare(b.monitor.name); + if (monitorCompare !== 0) return monitorCompare; + } + if (a.workspace && b.workspace) { + const workspaceCompare = a.workspace.id - b.workspace.id; + if (workspaceCompare !== 0) return workspaceCompare; + } + if (a.lastIpcObject?.at && b.lastIpcObject?.at) { + const xCompare = a.lastIpcObject.at[0] - b.lastIpcObject.at[0]; + if (Math.abs(xCompare) > 10) return xCompare; + return a.lastIpcObject.at[1] - b.lastIpcObject.at[1]; + } + if (a.title && b.title) + return a.title.localeCompare(b.title); + return 0; + }); + return sorted.filter(tl => tl.wayland !== null); + } - function updateSortedDesktopApplications() { - sortedDesktopApplicationsModel.clear(); + property var topLevelWorkspaces: { + return sortedTopLevels.map(toplevel => toplevel.workspace); + } - for (const topLevel of sortedTopLevels) { - const entry = DesktopEntries.heuristicLookup(topLevel.wayland.appId); - sortedDesktopApplicationsModel.append({ - topLevel: topLevel, - desktopEntry: entry - }); + onSortedTopLevelsChanged: refreshSortedDesktopApplications() + + Timer { + id: retryTimer + interval: 300 + repeat: false + onTriggered: refreshSortedDesktopApplications() + } + + function refreshSortedDesktopApplications() { + if (!Hyprland.toplevels || Hyprland.toplevels.size === 0) + return; + + try { + sortedDesktopApplicationsModel.clear(); + + for (const topLevel of sortedTopLevels) { + const entry = DesktopEntries.heuristicLookup(topLevel.wayland.appId); + if (!entry) { + retryTimer.restart(); + return; + } + sortedDesktopApplicationsModel.append({ + topLevel: topLevel, + desktopEntry: entry + }); + } + + + } catch (err) { + retryTimer.restart(); } } function workspaceApps(workspaceIndexAlign) { const list = []; - const model = sortedDesktopApplicationsModel; - for (let i = 0; i < model.count; i++) { - const item = model.get(i); + for (let i = 0; i < sortedDesktopApplicationsModel.count; i++) { + const item = sortedDesktopApplicationsModel.get(i); if (item.topLevel.workspace.id === workspaceIndexAlign) list.push(item.desktopEntry); } return list; } + // Hyprland socket listener Socket { - path: `${Quickshell.env("XDG_RUNTIME_DIR")}/hypr/${Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")}/.socket2.sock` + path: `${Quickshell.env("XDG_RUNTIME_DIR")}/hypr/${root.hyprlandSignature}/.socket2.sock` connected: true parser: SplitParser { - property var fullscreenRegex: new RegExp("fullscreen>>.") - property var screencastRegex: new RegExp("screencast>>.*") - + property var fullscreenRegex: /fullscreen>>./ + property var screencastRegex: /screencast>>.*/ onRead: msg => { - let match = fullscreenRegex.exec(msg); - if (match != null) { - if (msg.split(">>")[1] === "1") { - root.hasFullscreen = true; - } else { - root.hasFullscreen = false; - } + if (fullscreenRegex.test(msg)) { + root.hasFullscreen = msg.split(">>")[1] === "1"; } - match = screencastRegex.exec(msg); - if (match != null) { - if (msg.split(">>")[1].split(',')[0] === "1") { - root.isScreencasting = true; - } else { - root.isScreencasting = false; - } + if (screencastRegex.test(msg)) { + root.isScreencasting = msg.split(">>")[1].split(',')[0] === "1"; } } } } } + diff --git a/Widgets/WindowSwitcher.qml b/Widgets/WindowSwitcher.qml index 0846bab..f0f80a4 100644 --- a/Widgets/WindowSwitcher.qml +++ b/Widgets/WindowSwitcher.qml @@ -36,7 +36,7 @@ PanelWindow { Rectangle { id: notifWindow - anchors.centerIn:parent + anchors.centerIn: parent implicitHeight: 300 implicitWidth: listview.contentWidth @@ -51,32 +51,35 @@ PanelWindow { spacing: 5 orientation: ListView.Horizontal - focus:true - highlight: Rectangle { color: "black"; radius: 5 } + focus: true + highlight: Rectangle { + color: "black" + radius: 5 + } highlightFollowsCurrentItem: true keyNavigationEnabled: false - Keys.onPressed: (event) => { + Keys.onPressed: event => { switch (event.key) { - case Qt.Key_L: // move down - case Qt.Key_D: - if (currentIndex < count - 1) - currentIndex++ - event.accepted = true - break - case Qt.Key_H: // move up - case Qt.Key_A: - if (currentIndex > 0) - currentIndex-- - event.accepted = true - break - case Qt.Key_Return: - currentItem.activate() - event.accepted = true - break - case Qt.Key_Escape: - root.clear() - break + case Qt.Key_L: // move down + case Qt.Key_D: + if (currentIndex < count - 1) + currentIndex++; + event.accepted = true; + break; + case Qt.Key_H: // move up + case Qt.Key_A: + if (currentIndex > 0) + currentIndex--; + event.accepted = true; + break; + case Qt.Key_Return: + currentItem.activate(); + event.accepted = true; + break; + case Qt.Key_Escape: + root.clear(); + break; } } @@ -91,30 +94,34 @@ PanelWindow { color: "transparent" function activate() { - modelData.topLevel.wayland.activate() - root.clear() + modelData.topLevel.wayland.activate(); + root.clear(); } MouseArea { anchors.fill: parent - onClicked: activate() + onClicked: parent.activate() } ScreencopyView { anchors.fill: parent live: true - captureSource: modelData.topLevel.wayland + captureSource: parent.modelData.topLevel.wayland } IconImage { - anchors.bottom: parent.bottom - property int workspaceId: modelData.topLevel.workspace.id - property var desktopEntry: modelData.desktopEntry + anchors { + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + + property int workspaceId: parent.modelData.topLevel.workspace.id + property var desktopEntry: parent.modelData.desktopEntry? parent.modelData.desktopEntry:null width: 30 height: 30 - source: (modelData && desktopEntry.icon) ? Quickshell.iconPath(desktopEntry.icon, 1) : "aaa" + source: (parent.modelData && desktopEntry.icon) ? Quickshell.iconPath(desktopEntry.icon, 1) : "aaa" } } }