diff options
Diffstat (limited to 'window-switcher/HotCorner.qml')
| -rw-r--r-- | window-switcher/HotCorner.qml | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/window-switcher/HotCorner.qml b/window-switcher/HotCorner.qml new file mode 100644 index 0000000..cb7d863 --- /dev/null +++ b/window-switcher/HotCorner.qml @@ -0,0 +1,101 @@ +// Copyright (C) 2026 Danilo M. <danix@danix.xyz> +// +// 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.Io +import Quickshell.Wayland +import QtQuick + +// The top-right corner of DP-1 opens the expose. Touching it grows a glow out +// of the corner over 250ms and the expose opens when the glow is full; +// leaving first shrinks it back and nothing happens. The dwell is what keeps +// a pointer passing through from triggering it, and the glow is the warning. +PanelWindow { + id: corner + + // True while the expose is up. Set by shell.qml. + property bool blocked: false + + signal triggered() + + screen: Quickshell.screens.find(s => s.name === "DP-1") ?? Quickshell.screens[0] + anchors { top: true; right: true } + implicitWidth: 48 + implicitHeight: 48 + color: "transparent" + exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.namespace: "quickshell-hot-corner" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + // Off while a game runs. Read straight from the status registry's file: + // linking shared/Status.qml instead would start a second copy of its game + // poll and its presentation side effects in this process. + visible: !gaming + property bool gaming: false + + FileView { + id: gamingFile + path: (Quickshell.env("XDG_RUNTIME_DIR") || "/tmp") + "/status.gaming" + watchChanges: true + printErrors: false + onFileChanged: reload() + onLoaded: corner.gaming = text().trim() === "1" + // A missing file is the off state. + onLoadFailed: corner.gaming = false + } + + // Input only on the corner's 2x2 pixels. The rest of the 48x48 is the + // glow, drawn but click-through: a larger input area would swallow clicks + // meant for the windows underneath, which is also why the glow cannot + // start before the pointer is in the corner. + mask: Region { item: patch } + + Item { + id: patch + anchors { top: parent.top; right: parent.right } + width: 2 + height: 2 + + HoverHandler { + id: hover + // Re-armed only by leaving while the expose is closed. Opening it + // covers the corner, which reads as leaving; closing it with + // Escape while the pointer still sits here would otherwise reopen + // it 250ms later. + onHoveredChanged: if (!hovered && !corner.blocked) corner.armed = true + } + } + + property bool armed: true + + property real progress: hover.hovered && armed && !blocked ? 1 : 0 + Behavior on progress { NumberAnimation { duration: 250 } } + + onProgressChanged: { + if (progress >= 1) { + armed = false; + triggered(); + } + } + + // A quarter circle centred on the corner, growing with the dwell. + Rectangle { + readonly property real r: corner.width * corner.progress + x: corner.width - r + y: -r + width: 2 * r + height: 2 * r + radius: r + color: Theme.accent + opacity: 0.6 * corner.progress + } +} |
