From 22c6bbf8bac5e547a03a3140c0f44ba0c6a89e21 Mon Sep 17 00:00:00 2001 From: Amaro Lopes Date: Mon, 13 Oct 2025 16:51:26 -0300 Subject: [PATCH] Create Alt+tab window, refactor sortedDesktopApplications so they are inside a ListModel instead of a Map() --- Common/Shortcuts.qml | 5 ++ Services/HyprlandService.qml | 29 +++++++++ Services/PopUpHover.qml | 24 ++++--- Widgets/WindowSwitcher.qml | 122 +++++++++++++++++++++++++++++++++++ shell.qml | 34 ++++++---- 5 files changed, 189 insertions(+), 25 deletions(-) create mode 100644 Common/Shortcuts.qml create mode 100644 Widgets/WindowSwitcher.qml diff --git a/Common/Shortcuts.qml b/Common/Shortcuts.qml new file mode 100644 index 0000000..5755fd1 --- /dev/null +++ b/Common/Shortcuts.qml @@ -0,0 +1,5 @@ +import Quickshell.Hyprland + +GlobalShortcut { + appid: "quickbar" +} diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index 39f2ed0..736b156 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -1,5 +1,6 @@ pragma Singleton +import QtQuick import Quickshell import Quickshell.Hyprland import Quickshell.Wayland @@ -70,6 +71,34 @@ Singleton { return sortedTopLevels.map(topLevel => topLevel.workspace); } + property ListModel sortedDesktopApplicationsModel: ListModel {} + + onSortedTopLevelsChanged: updateSortedDesktopApplications() + + function updateSortedDesktopApplications() { + sortedDesktopApplicationsModel.clear(); + + + for (const topLevel of sortedTopLevels) { + const entry = DesktopEntries.heuristicLookup(topLevel.wayland.appId); + sortedDesktopApplicationsModel.append({ + topLevel: topLevel, + desktopEntry: entry + }); + } + } + + function workspaceApps(workspaceIndexAlign) { + const list = [] + const model = sortedDesktopApplicationsModel + for (let i = 0; i < model.count; i++) { + const item = model.get(i) + if (item.topLevel.workspace.id === workspaceIndexAlign) + list.push(item.desktopEntry) + } + return list + } + property var sortedDesktopApplications: { const sortedWayland = sortedTopLevels.map(topLevel => topLevel.wayland).filter(wayland => wayland !== null); diff --git a/Services/PopUpHover.qml b/Services/PopUpHover.qml index c29e4ff..8a57e05 100644 --- a/Services/PopUpHover.qml +++ b/Services/PopUpHover.qml @@ -62,13 +62,13 @@ Singleton { StyledText { text: { if (!HoverMediator.component?.model) - return ""; + return ""; if (HoverMediator.component.model.tooltipTitle) - return HoverMediator.component.model.tooltipTitle; + return HoverMediator.component.model.tooltipTitle; if (HoverMediator.component.model.title) - return HoverMediator.component.model.title; + return HoverMediator.component.model.title; else - return ""; + return ""; } } } @@ -76,7 +76,7 @@ Singleton { Component { id: time - RowLayout{ + RowLayout { id: rowlayoutCalendar readonly property date now: new Date() @@ -132,9 +132,7 @@ Singleton { Repeater { - property var modelo: HyprlandService.sortedDesktopApplications.get(parent.workspaceIndexAlign) - - model: modelo + model: HyprlandService.workspaceApps(parent.workspaceIndexAlign) delegate: IconImage { required property var modelData @@ -172,15 +170,15 @@ Singleton { id: hoverLoader sourceComponent: { if (!HoverMediator.type) - return stub; + return stub; if (HoverMediator.type === "workspace") - return workspaceComponent; + return workspaceComponent; if (HoverMediator.type === "audio") - return audio; + return audio; if (HoverMediator.type === "time") - return time; + return time; if (HoverMediator.type === "systray") - return systray; + return systray; } } } diff --git a/Widgets/WindowSwitcher.qml b/Widgets/WindowSwitcher.qml new file mode 100644 index 0000000..0846bab --- /dev/null +++ b/Widgets/WindowSwitcher.qml @@ -0,0 +1,122 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import qs.Services +import qs.Common.Styled +import qs.Common +import Quickshell.Wayland + +PanelWindow { + id: root + + anchors { + left: true + bottom: true + right: true + top: true + } + + implicitWidth: screen.width + // implicitWidth: 400 + implicitHeight: screen.height + color: "transparent" + visible: true + signal clear + + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + + MouseArea { + anchors.fill: parent + onClicked: root.clear() + } + + Rectangle { + id: notifWindow + + anchors.centerIn:parent + implicitHeight: 300 + implicitWidth: listview.contentWidth + + color: "transparent" + + ListView { + id: listview + + anchors.fill: parent + + clip: true + spacing: 5 + orientation: ListView.Horizontal + + focus:true + highlight: Rectangle { color: "black"; radius: 5 } + highlightFollowsCurrentItem: true + keyNavigationEnabled: false + + 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 + } + } + + model: HyprlandService.sortedDesktopApplicationsModel + + delegate: Rectangle { + required property var modelData + + implicitHeight: 300 + implicitWidth: 300 + + color: "transparent" + + function activate() { + modelData.topLevel.wayland.activate() + root.clear() + } + + MouseArea { + anchors.fill: parent + onClicked: activate() + } + + ScreencopyView { + anchors.fill: parent + live: true + captureSource: modelData.topLevel.wayland + } + + IconImage { + anchors.bottom: parent.bottom + + property int workspaceId: modelData.topLevel.workspace.id + property var desktopEntry: modelData.desktopEntry + + width: 30 + height: 30 + source: (modelData && desktopEntry.icon) ? Quickshell.iconPath(desktopEntry.icon, 1) : "aaa" + } + } + } + } +} diff --git a/shell.qml b/shell.qml index 39ab8b4..7dc95bb 100644 --- a/shell.qml +++ b/shell.qml @@ -1,20 +1,13 @@ //@ pragma UseQApplication import Quickshell +import qs.Widgets +import qs.Common +import qs.Services ShellRoot { - // Bar { - // modelData: Quickshell.screens.values[0] - // barComponentsLeft: ["NotificationsWidget.qml"] - // barComponentsCenter: ["Workspaces.qml"] - // barComponentsRight: ["AudioWidget.qml", "SysTrayWidget.qml", "ClockWidget.qml"] - // } + id: root - // Bar { - // panelMonitor: "DP-2" - // barComponentsLeft: [] - // barComponentsCenter: ["Workspaces.qml"] - // barComponentsRight: ["AudioWidget.qml", "ClockWidget.qml"] - // } + property bool createWindow: false Variants { model: Quickshell.screens @@ -25,4 +18,21 @@ ShellRoot { barComponentsRight: ["AudioWidget.qml", "SysTrayWidget.qml", "ClockWidget.qml"] } } + + Shortcuts { + name: "showAltTab" + onPressed: { + root.createWindow = !root.createWindow; + } + } + + LazyLoader { + id: windowLoader + + active: root.createWindow + + component: WindowSwitcher { + onClear: root.createWindow = false + } + } }