// Continuum Mission Control: a GNOME-like workspace filmstrip above a large // selected desktop, with cross-workspace window search when the user types. import Quickshell import Quickshell.Hyprland import Quickshell.Io import QtQuick import qs.config import qs.services Item { id: root property int refreshToken: 0 property bool open: false property int selectedWorkspaceId: 0 property string query: "" property int actionRefreshToken: 0 property int selectedResultIndex: 0 readonly property int sideMargin: 52 readonly property int filmCardWidth: 210 readonly property int filmCardHeight: 140 readonly property var workspaceList: { const out = []; const all = Hyprland.workspaces?.values ?? []; for (const workspace of all) { if (workspace && workspace.id > 0) out.push(workspace); } out.sort((a, b) => a.id - b.id); return out; } readonly property var scratchWorkspace: { const all = Hyprland.workspaces?.values ?? []; return all.find(workspace => workspace?.name === "special:scratch") ?? null; } readonly property int nextWorkspaceId: { let max = 0; for (const workspace of root.workspaceList) max = Math.max(max, workspace.id); return max + 1; } readonly property var selectedWorkspace: { for (const workspace of root.workspaceList) { if (workspace.id === root.selectedWorkspaceId) return workspace; } return Hyprland.focusedWorkspace?.id > 0 ? Hyprland.focusedWorkspace : (root.workspaceList[0] ?? null); } readonly property real screenAspect: { const monitor = Hyprland.focusedMonitor; return monitor && monitor.height > 0 ? monitor.width / monitor.height : 3 / 2; } readonly property var windowEntries: { const out = []; for (const workspace of root.workspaceList) { const windows = workspace.toplevels?.values ?? []; for (const toplevel of windows) out.push({ workspace, toplevel }); } return out; } readonly property var filteredWindows: { const needle = root.query.trim().toLowerCase(); if (!needle) return []; return root.windowEntries.filter(entry => { const title = entry.toplevel?.title ?? ""; const appId = entry.toplevel?.wayland?.appId ?? ""; return `${title} ${appId}`.toLowerCase().includes(needle); }); } readonly property int resultColumns: Math.min(3, Math.max(1, root.filteredWindows.length)) readonly property var selectedResult: root.filteredWindows.length > 0 ? root.filteredWindows[Math.min(root.selectedResultIndex, root.filteredWindows.length - 1)] : null onQueryChanged: root.selectedResultIndex = 0 onFilteredWindowsChanged: { if (root.selectedResultIndex >= root.filteredWindows.length) root.selectedResultIndex = Math.max(0, root.filteredWindows.length - 1); } function prepare(): void { root.query = ShellState.overviewQuery; const requested = ShellState.overviewWorkspaceId; const focused = Hyprland.focusedWorkspace?.id ?? 0; root.selectedWorkspaceId = root.hasWorkspace(requested) ? requested : focused; Qt.callLater(() => searchInput.forceActiveFocus()); } function hasWorkspace(id: int): bool { if (id <= 0) return false; return root.workspaceList.some(workspace => workspace.id === id); } function selectWorkspace(workspace: var): void { if (!workspace) return; root.selectedWorkspaceId = workspace.id; workspace.activate(); } function cycleWorkspace(delta: int): void { if (root.workspaceList.length === 0) return; let index = root.workspaceList.findIndex(workspace => workspace.id === root.selectedWorkspaceId); if (index < 0) index = 0; const next = Math.max(0, Math.min(root.workspaceList.length - 1, index + delta)); root.selectWorkspace(root.workspaceList[next]); } function focusToplevel(toplevel: var): void { if (!toplevel) return; const workspace = toplevel.workspace; if (workspace) workspace.activate(); if (toplevel.address) { const address = toplevel.address.startsWith("0x") ? toplevel.address : "0x" + toplevel.address; Hyprland.dispatch(`hl.dsp.focus({ window = "address:${address}" })`); } else if (toplevel.wayland) { toplevel.wayland.activate(); } ShellState.close(); } function addressOf(toplevel: var): string { const raw = String(toplevel?.address ?? ""); if (!raw) return ""; return raw.startsWith("0x") ? raw : "0x" + raw; } function closeToplevel(toplevel: var): void { const address = root.addressOf(toplevel); if (!address) return; Hyprland.dispatch(`hl.dsp.window.close({ window = "address:${address}" })`); root.refreshAfterAction(); } function moveToplevel(toplevel: var, workspaceName: string): void { const address = root.addressOf(toplevel); if (!address || !/^(?:[1-9]\d*|special:scratch)$/.test(workspaceName)) return; Hyprland.dispatch(`hl.dsp.window.move({ workspace = "${workspaceName}", follow = false, window = "address:${address}" })`); root.refreshAfterAction(); } function restoreToplevel(toplevel: var): void { const address = root.addressOf(toplevel); const target = root.selectedWorkspaceId > 0 ? root.selectedWorkspaceId : (Hyprland.focusedWorkspace?.id ?? 1); if (!address || target <= 0) return; Hyprland.dispatch(`hl.dsp.window.move({ workspace = ${target}, follow = true, window = "address:${address}" })`); ShellState.close(); } function refreshAfterAction(): void { actionRefresh.restart(); } Timer { id: actionRefresh interval: 140 onTriggered: { Hyprland.refreshWorkspaces(); Hyprland.refreshToplevels(); root.actionRefreshToken++; } } // Address-only actions make the exact same boundary testable end-to-end // without synthesizing pointer input. They are also useful to launcher // commands later; validation still limits destinations to Panama's model. IpcHandler { target: "overview-actions" function move(address: string, workspaceName: string): void { root.moveToplevel({ address }, workspaceName); } function close(address: string): void { root.closeToplevel({ address }); } function restore(address: string): void { root.restoreToplevel({ address }); } } // ── Header and search ────────────────────────────────────────────────── Item { id: header anchors.top: parent.top anchors.topMargin: 42 anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: root.sideMargin anchors.rightMargin: root.sideMargin height: 42 Text { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter text: "Activities" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeTitle font.weight: Font.DemiBold } Rectangle { id: searchBox anchors.centerIn: parent width: 390 height: 38 radius: Theme.pillRadius border.width: searchInput.activeFocus ? 1 : 0 border.color: Theme.alpha(Theme.accent, 0.72) color: Theme.alpha(Theme.bgPopover, 0.84) Text { anchors.left: parent.left anchors.leftMargin: 14 anchors.verticalCenter: parent.verticalCenter text: "\u{F0349}" // magnify color: searchInput.activeFocus ? Theme.accent : Theme.fgDim font.family: Theme.fontMono font.pixelSize: 16 } TextInput { id: searchInput anchors.left: parent.left anchors.leftMargin: 40 anchors.right: parent.right anchors.rightMargin: 14 anchors.verticalCenter: parent.verticalCenter text: root.query color: Theme.fg selectionColor: Theme.accent selectedTextColor: Theme.bgDark clip: true font.family: Theme.fontFamily font.pixelSize: Theme.fontSize onTextEdited: root.query = text Keys.onReturnPressed: { if (root.query.trim() === "") { if (root.selectedWorkspace) root.selectWorkspace(root.selectedWorkspace); ShellState.close(); } else if (root.selectedResult) { root.focusToplevel(root.selectedResult.toplevel); } } Keys.onLeftPressed: event => { if (root.query.trim() !== "") return; root.cycleWorkspace(-1); event.accepted = true; } Keys.onRightPressed: event => { if (root.query.trim() !== "") return; root.cycleWorkspace(1); event.accepted = true; } Keys.onUpPressed: event => { if (!root.selectedResult) return; root.selectedResultIndex = Math.max(0, root.selectedResultIndex - root.resultColumns); event.accepted = true; } Keys.onDownPressed: event => { if (!root.selectedResult) return; root.selectedResultIndex = Math.min(root.filteredWindows.length - 1, root.selectedResultIndex + root.resultColumns); event.accepted = true; } Keys.onDeletePressed: event => { if (!root.selectedResult) return; root.closeToplevel(root.selectedResult.toplevel); event.accepted = true; } } Text { anchors.left: searchInput.left anchors.verticalCenter: parent.verticalCenter visible: searchInput.text === "" text: "Find a window" color: Theme.fgMuted font.family: Theme.fontFamily font.pixelSize: Theme.fontSize } } Text { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: root.workspaceList.length + (root.workspaceList.length === 1 ? " workspace" : " workspaces") color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSize } } ScratchpadShelf { id: scratchpadShelf anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: root.sideMargin anchors.rightMargin: root.sideMargin anchors.bottom: parent.bottom anchors.bottomMargin: 20 workspace: root.scratchWorkspace refreshToken: root.refreshToken + root.actionRefreshToken onWindowDropped: toplevel => root.moveToplevel(toplevel, "special:scratch") onRestoreRequested: toplevel => root.restoreToplevel(toplevel) onCloseRequested: toplevel => root.closeToplevel(toplevel) } // ── Workspace filmstrip ──────────────────────────────────────────────── Flickable { id: filmstrip anchors.top: header.bottom anchors.topMargin: 18 anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: root.sideMargin anchors.rightMargin: root.sideMargin height: root.filmCardHeight clip: true contentWidth: Math.max(width, filmRow.implicitWidth) contentHeight: height boundsBehavior: Flickable.StopAtBounds MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton onWheel: event => root.cycleWorkspace(event.angleDelta.y > 0 ? -1 : 1) } Row { id: filmRow x: filmstrip.contentWidth > filmstrip.width ? 0 : (filmstrip.width - implicitWidth) / 2 spacing: Theme.itemSpacing Repeater { model: root.workspaceList WorkspaceCard { required property var modelData workspace: modelData refreshToken: root.refreshToken + root.actionRefreshToken emphasized: modelData.id === root.selectedWorkspaceId focusBound: FocusSession.active && modelData.id === FocusSession.workspaceId width: root.filmCardWidth height: root.filmCardHeight onActivated: root.selectWorkspace(modelData) onWindowActivated: toplevel => root.focusToplevel(toplevel) onWindowCloseRequested: toplevel => root.closeToplevel(toplevel) onWindowDropped: (toplevel, workspaceName) => root.moveToplevel(toplevel, workspaceName) } } WorkspaceCard { workspace: null newWorkspaceId: root.nextWorkspaceId emphasized: false width: root.filmCardWidth height: root.filmCardHeight onActivated: { Hyprland.dispatch(`hl.dsp.focus({ workspace = ${root.nextWorkspaceId} })`); root.selectedWorkspaceId = root.nextWorkspaceId; } onWindowCloseRequested: toplevel => root.closeToplevel(toplevel) onWindowDropped: (toplevel, workspaceName) => root.moveToplevel(toplevel, workspaceName) } } } // ── Large selected desktop ───────────────────────────────────────────── Item { id: desktopStage anchors.top: filmstrip.bottom anchors.topMargin: 26 anchors.left: parent.left anchors.right: parent.right anchors.bottom: hint.top anchors.bottomMargin: 18 visible: root.query.trim() === "" WorkspaceCard { id: largeWorkspace anchors.top: parent.top anchors.topMargin: 18 anchors.horizontalCenter: parent.horizontalCenter workspace: root.selectedWorkspace refreshToken: root.refreshToken + root.actionRefreshToken emphasized: true focusBound: FocusSession.active && root.selectedWorkspace?.id === FocusSession.workspaceId width: Math.min(parent.width - root.sideMargin * 2, 1760) height: Math.min(parent.height, Math.round(width / root.screenAspect) + labelHeight) onActivated: { if (workspace) workspace.activate(); ShellState.close(); } onWindowActivated: toplevel => root.focusToplevel(toplevel) onWindowCloseRequested: toplevel => root.closeToplevel(toplevel) onWindowDropped: (toplevel, workspaceName) => root.moveToplevel(toplevel, workspaceName) } } // ── Cross-workspace search results ───────────────────────────────────── Flickable { id: searchResults anchors.top: filmstrip.bottom anchors.topMargin: 28 anchors.left: parent.left anchors.right: parent.right anchors.bottom: hint.top anchors.leftMargin: root.sideMargin anchors.rightMargin: root.sideMargin anchors.bottomMargin: 18 visible: root.query.trim() !== "" clip: true contentWidth: width contentHeight: resultFlow.implicitHeight boundsBehavior: Flickable.StopAtBounds Flow { id: resultFlow readonly property real cardWidth: (searchResults.width - Theme.sectionSpacing * 2) / 3 x: Math.max(0, (searchResults.width - width) / 2) width: root.resultColumns * cardWidth + (root.resultColumns - 1) * Theme.sectionSpacing spacing: Theme.sectionSpacing Repeater { model: root.filteredWindows WindowThumbnail { required property var modelData required property int index toplevel: modelData.toplevel refreshToken: root.refreshToken + root.actionRefreshToken selected: index === root.selectedResultIndex width: resultFlow.cardWidth height: Math.round(width / root.screenAspect) + 18 onActivated: root.focusToplevel(modelData.toplevel) onCloseRequested: root.closeToplevel(modelData.toplevel) } } } Text { anchors.centerIn: parent visible: root.filteredWindows.length === 0 text: "No windows match “" + root.query + "”" color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge } } Text { id: hint anchors.bottom: scratchpadShelf.top anchors.bottomMargin: 8 anchors.horizontalCenter: parent.horizontalCenter text: root.query.trim() === "" ? "Drag windows between workspaces or into Scratchpad · Esc to return" : "Type to search every workspace · Enter opens the first match" color: Theme.fgMuted font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall } }