// 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.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 glow centred on the corner, growing with the dwell. The disc is // smaller than the window so the fade fits inside it. Glow { readonly property real r: corner.width * 0.5 * corner.progress x: corner.width - r y: -r width: 2 * r height: 2 * r radius: r blur: r on: true opacity: 0.8 * corner.progress } }