// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import Quickshell import Quickshell.Wayland import QtQuick // One tab per consumer with manual steps pending. Centred on the vertical // screen and kept open on purpose: the commands are pasted into a root shell // elsewhere, so the window takes keyboard focus only when clicked and stays // until closed. Done deletes a tab's file; the producer also clears it once // the consumer is up to date. Scope { id: root property string monitor: "DP-3" property bool open: false property string tab: "" readonly property var screenObj: Quickshell.screens.find(s => s.name === root.monitor) ?? Quickshell.screens[0] readonly property var current: Steps.tabs.find(t => t.name === root.tab) ?? Steps.tabs[0] ?? null // A poke from udt-steps: reload, and open when something is pending. // Also what a keybind calls to bring the window back after closing it. function poke() { root.opening = true; Steps.refresh(); } property bool opening: false Connections { target: Steps function onLoaded() { if (Steps.tabs.length === 0) root.open = false; else if (root.opening) root.open = true; root.opening = false; } } LazyLoader { active: root.open PanelWindow { id: win screen: root.screenObj // No anchors: a layer surface anchored nowhere is centred. implicitWidth: 960 implicitHeight: body.implicitHeight + 40 color: "transparent" exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Top WlrLayershell.namespace: "quickshell-steps" WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand Rectangle { anchors.fill: parent radius: 14 color: Qt.alpha(Theme.base, 0.92) border.width: 1 border.color: Qt.alpha(Theme.text, 0.12) Item { anchors.fill: parent focus: true Keys.onEscapePressed: root.open = false } Column { id: body anchors { top: parent.top; left: parent.left; right: parent.right; margins: 20 } spacing: 16 Item { width: parent.width height: tabBar.implicitHeight Row { id: tabBar spacing: 8 Repeater { model: Steps.tabs Tab { required property var modelData text: modelData.label selected: root.current !== null && root.current.name === modelData.name onClicked: root.tab = modelData.name } } } Tab { anchors.right: parent.right text: "Close" onClicked: root.open = false } } Flickable { width: parent.width // Sized from the screen, never from the window: the // window's height comes from this, and a binding back // to it collapsed the whole thing to its margins. height: Math.min(content.implicitHeight, win.screen.height - 240 - tabBar.implicitHeight - actions.implicitHeight - 32) contentHeight: content.implicitHeight clip: true Column { id: content width: parent.width spacing: 12 Repeater { model: root.current ? root.current.blocks : [] Loader { required property var modelData width: content.width sourceComponent: modelData.cmd ? cmdBlock : prose property var block: modelData } } } } Row { id: actions spacing: 8 anchors.right: parent.right Tab { text: "Copy all commands" enabled: root.current !== null && Steps.commands(root.current) !== "" onClicked: Steps.copy(Steps.commands(root.current)) } Tab { text: "Done" enabled: root.current !== null onClicked: Steps.dismiss(root.current.name) } } } } Component { id: prose Text { text: block.text wrapMode: Text.Wrap font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1 } color: Theme.text } } Component { id: cmdBlock Rectangle { implicitHeight: Math.max(code.implicitHeight, copyBtn.implicitHeight) + 24 radius: 8 color: Qt.alpha(Theme.crust, 0.8) border.width: 1 border.color: Qt.alpha(Theme.accent, 0.3) // Selectable as well as copyable: sometimes one line of a // block is what is wanted. TextEdit { id: code anchors { left: parent.left; right: copyBtn.left; verticalCenter: parent.verticalCenter; margins: 12 } text: block.text readOnly: true selectByMouse: true wrapMode: TextEdit.WrapAnywhere font { family: "Inconsolata Nerd Font Mono"; pixelSize: Theme.fontSize - 1 } color: Theme.text selectionColor: Qt.alpha(Theme.accent, 0.4) selectedTextColor: Theme.text } Tab { id: copyBtn anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter } text: "Copy" onClicked: Steps.copy(block.text) } } } } } }