quickshell: click-outside dropdown dismissal, drop auto-close timers
Extend the launcher/session HyprlandFocusGrab to the bar dropdowns and remove the per-dropdown inactivity timers. Shift+Super+S brackets hyprshot with a screenshot pin so open menus survive slurp's input grab. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
215239e7aa
commit
83b4c5ef09
2 changed files with 36 additions and 40 deletions
|
|
@ -281,7 +281,9 @@ in
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Screenshots — Shift+Super+S matches GNOME binding
|
-- Screenshots — Shift+Super+S matches GNOME binding
|
||||||
hl.bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd("hyprshot -m region --clipboard-only"))
|
-- Pin/unpin quickshell's focus grab around the region select so an
|
||||||
|
-- open menu survives slurp's input grab (no-ops if qs isn't up).
|
||||||
|
hl.bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd("sh -c 'qs ipc call screenshot pin; hyprshot -m region --clipboard-only; qs ipc call screenshot unpin'"))
|
||||||
hl.bind("Print", hl.dsp.exec_cmd("hyprshot -m output --clipboard-only"))
|
hl.bind("Print", hl.dsp.exec_cmd("hyprshot -m output --clipboard-only"))
|
||||||
|
|
||||||
-- Settings shortcut — Super+I matches GNOME binding
|
-- Settings shortcut — Super+I matches GNOME binding
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,16 @@ in
|
||||||
function reload(): void { Quickshell.reload(false); }
|
function reload(): void { Quickshell.reload(false); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Screenshot pin: the Shift+Super+S keybind brackets hyprshot
|
||||||
|
// with pin/unpin so the focus grab is suspended while slurp
|
||||||
|
// grabs input — otherwise the open menu would close like any
|
||||||
|
// click-outside. Self-heals via a watchdog if unpin is missed.
|
||||||
|
IpcHandler {
|
||||||
|
target: "screenshot"
|
||||||
|
function pin(): void { if (root.mainBar) root.mainBar.setScreenshotPin(true); }
|
||||||
|
function unpin(): void { if (root.mainBar) root.mainBar.setScreenshotPin(false); }
|
||||||
|
}
|
||||||
|
|
||||||
NotificationServer {
|
NotificationServer {
|
||||||
id: _notifServer
|
id: _notifServer
|
||||||
bodySupported: true
|
bodySupported: true
|
||||||
|
|
@ -291,7 +301,7 @@ in
|
||||||
// (caelestia's): the grab redirects focus to this window and
|
// (caelestia's): the grab redirects focus to this window and
|
||||||
// OnDemand lets the layer surface accept it. Exclusive fights
|
// OnDemand lets the layer surface accept it. Exclusive fights
|
||||||
// the grab — it self-clears and instantly closes the panel.
|
// the grab — it self-clears and instantly closes the panel.
|
||||||
WlrLayershell.keyboardFocus: sessionMenu.open || launcherPanel.open ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: (sessionMenu.open || launcherPanel.open || bar.activeDropdown !== null) && !bar.screenshotPinned ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
|
|
@ -910,18 +920,38 @@ in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click-outside dismissal for the keyboard-grabbing panels
|
// Click-outside dismissal for every panel — the launcher,
|
||||||
|
// session menu AND the bar dropdowns. Clicking into another
|
||||||
|
// window (or anywhere outside the bar) clears the grab and
|
||||||
|
// closes whatever is open. Suspended while screenshotPinned so
|
||||||
|
// slurp can grab input without dismissing the menu.
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
active: sessionMenu.open || launcherPanel.open
|
active: (sessionMenu.open || launcherPanel.open || bar.activeDropdown !== null) && !bar.screenshotPinned
|
||||||
windows: [bar]
|
windows: [bar]
|
||||||
onCleared: {
|
onCleared: {
|
||||||
|
if (bar.screenshotPinned) return;
|
||||||
sessionMenu.open = false;
|
sessionMenu.open = false;
|
||||||
launcherPanel.open = false;
|
launcherPanel.open = false;
|
||||||
|
bar.closeAllDropdowns();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property var activeDropdown: null
|
property var activeDropdown: null
|
||||||
|
|
||||||
|
// Set by the screenshot keybind (via IPC) to hold menus open
|
||||||
|
// while a region screenshot runs. Watchdog unpins if the
|
||||||
|
// bracketing unpin call is ever missed.
|
||||||
|
property bool screenshotPinned: false
|
||||||
|
Timer {
|
||||||
|
id: _pinWatchdog
|
||||||
|
interval: 30000
|
||||||
|
onTriggered: bar.screenshotPinned = false
|
||||||
|
}
|
||||||
|
function setScreenshotPin(v) {
|
||||||
|
screenshotPinned = v;
|
||||||
|
if (v) _pinWatchdog.restart(); else _pinWatchdog.stop();
|
||||||
|
}
|
||||||
|
|
||||||
function closeAllDropdowns() {
|
function closeAllDropdowns() {
|
||||||
if (activeDropdown && activeDropdown.visible) {
|
if (activeDropdown && activeDropdown.visible) {
|
||||||
activeDropdown.animateClose();
|
activeDropdown.animateClose();
|
||||||
|
|
@ -1024,7 +1054,6 @@ in
|
||||||
onEntered: {
|
onEntered: {
|
||||||
if (bar.activeDropdown) {
|
if (bar.activeDropdown) {
|
||||||
if (bar.activeDropdown !== calPopup) bar.toggleDropdown(calPopup, function() { calPopup.resetView(); });
|
if (bar.activeDropdown !== calPopup) bar.toggleDropdown(calPopup, function() { calPopup.resetView(); });
|
||||||
else bar.activeDropdown.resetAutoClose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1104,7 +1133,6 @@ in
|
||||||
onEntered: {
|
onEntered: {
|
||||||
if (bar.activeDropdown) {
|
if (bar.activeDropdown) {
|
||||||
if (bar.activeDropdown !== volDropdown) volWidget.openVolDropdown();
|
if (bar.activeDropdown !== volDropdown) volWidget.openVolDropdown();
|
||||||
else bar.activeDropdown.resetAutoClose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1271,7 +1299,6 @@ in
|
||||||
onEntered: {
|
onEntered: {
|
||||||
if (bar.activeDropdown) {
|
if (bar.activeDropdown) {
|
||||||
if (bar.activeDropdown !== netDropdown) netWidget.openNetDropdown();
|
if (bar.activeDropdown !== netDropdown) netWidget.openNetDropdown();
|
||||||
else bar.activeDropdown.resetAutoClose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1352,7 +1379,6 @@ in
|
||||||
onEntered: {
|
onEntered: {
|
||||||
if (bar.activeDropdown) {
|
if (bar.activeDropdown) {
|
||||||
if (bar.activeDropdown !== batteryDropdown) batteryWidget.openBatteryDropdown();
|
if (bar.activeDropdown !== batteryDropdown) batteryWidget.openBatteryDropdown();
|
||||||
else bar.activeDropdown.resetAutoClose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1366,12 +1392,6 @@ in
|
||||||
height: Theme.barHeight
|
height: Theme.barHeight
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
onHoveredChanged: {
|
|
||||||
if (hovered && bar.activeDropdown) bar.activeDropdown.resetAutoClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: SystemTray.items
|
model: SystemTray.items
|
||||||
|
|
||||||
|
|
@ -1405,7 +1425,6 @@ in
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
onEntered: {
|
onEntered: {
|
||||||
if (bar.activeDropdown) {
|
if (bar.activeDropdown) {
|
||||||
bar.activeDropdown.resetAutoClose();
|
|
||||||
if (modelData.hasMenu && !(bar.activeDropdown === contextMenu && contextMenu.trayItem === modelData)) {
|
if (modelData.hasMenu && !(bar.activeDropdown === contextMenu && contextMenu.trayItem === modelData)) {
|
||||||
if (bar.activeDropdown === contextMenu) {
|
if (bar.activeDropdown === contextMenu) {
|
||||||
// Same dropdown, just switch content
|
// Same dropdown, just switch content
|
||||||
|
|
@ -1413,7 +1432,6 @@ in
|
||||||
contextMenu.dropdownX = pos.x;
|
contextMenu.dropdownX = pos.x;
|
||||||
contextMenu.trayItem = modelData;
|
contextMenu.trayItem = modelData;
|
||||||
menuOpener.menu = modelData.menu;
|
menuOpener.menu = modelData.menu;
|
||||||
contextMenu.resetAutoClose();
|
|
||||||
} else {
|
} else {
|
||||||
bar.toggleDropdown(contextMenu, function() {
|
bar.toggleDropdown(contextMenu, function() {
|
||||||
let pos = parent.mapToItem(bar.contentItem, parent.width / 2, 0);
|
let pos = parent.mapToItem(bar.contentItem, parent.width / 2, 0);
|
||||||
|
|
@ -1457,7 +1475,6 @@ in
|
||||||
property real dropdownX: 0
|
property real dropdownX: 0
|
||||||
property real fullWidth: 200
|
property real fullWidth: 200
|
||||||
property real fullHeight: 200
|
property real fullHeight: 200
|
||||||
property int autoCloseMs: 1500
|
|
||||||
// Flush-right dropdowns merge into the screen frame's
|
// Flush-right dropdowns merge into the screen frame's
|
||||||
// right column instead of centering on their widget.
|
// right column instead of centering on their widget.
|
||||||
property bool alignRight: false
|
property bool alignRight: false
|
||||||
|
|
@ -1477,14 +1494,9 @@ in
|
||||||
bar.activeDropdown = null;
|
bar.activeDropdown = null;
|
||||||
chrome.shrinkToButton(dropdown);
|
chrome.shrinkToButton(dropdown);
|
||||||
}
|
}
|
||||||
_autoClose.stop();
|
|
||||||
_closeDelay.start();
|
_closeDelay.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetAutoClose() {
|
|
||||||
if (visible && !closing) _autoClose.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reopen a dropdown that's mid-close: the pending hide
|
// Reopen a dropdown that's mid-close: the pending hide
|
||||||
// timer must be cancelled, otherwise it fires later and
|
// timer must be cancelled, otherwise it fires later and
|
||||||
// closes the revived dropdown (and the whole chrome).
|
// closes the revived dropdown (and the whole chrome).
|
||||||
|
|
@ -1492,7 +1504,6 @@ in
|
||||||
_closeDelay.stop();
|
_closeDelay.stop();
|
||||||
closing = false;
|
closing = false;
|
||||||
open = true;
|
open = true;
|
||||||
_autoClose.restart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
x: alignRight
|
x: alignRight
|
||||||
|
|
@ -1507,33 +1518,18 @@ in
|
||||||
if (visible) {
|
if (visible) {
|
||||||
closing = false;
|
closing = false;
|
||||||
open = true;
|
open = true;
|
||||||
_autoClose.restart();
|
|
||||||
} else {
|
} else {
|
||||||
open = false;
|
open = false;
|
||||||
closing = false;
|
closing = false;
|
||||||
_autoClose.stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: _autoClose
|
|
||||||
interval: dropdown.autoCloseMs
|
|
||||||
onTriggered: bar.closeAllDropdowns()
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: _closeDelay
|
id: _closeDelay
|
||||||
interval: 300
|
interval: 300
|
||||||
onTriggered: { dropdown.visible = false; dropdown.closing = false; if (bar.activeDropdown === dropdown) bar.activeDropdown = null; }
|
onTriggered: { dropdown.visible = false; dropdown.closing = false; if (bar.activeDropdown === dropdown) bar.activeDropdown = null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
onHoveredChanged: {
|
|
||||||
if (hovered) _autoClose.stop();
|
|
||||||
else _autoClose.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content is clipped to the chrome's ANIMATED geometry —
|
// Content is clipped to the chrome's ANIMATED geometry —
|
||||||
// revealed as the panel slides/grows over it and wiped as
|
// revealed as the panel slides/grows over it and wiped as
|
||||||
// the panel leaves, instead of popping in place. The inner
|
// the panel leaves, instead of popping in place. The inner
|
||||||
|
|
@ -1731,7 +1727,6 @@ in
|
||||||
alignRight: true
|
alignRight: true
|
||||||
fullWidth: volDropdownCol.width + 28
|
fullWidth: volDropdownCol.width + 28
|
||||||
fullHeight: volDropdownCol.height + 20
|
fullHeight: volDropdownCol.height + 20
|
||||||
autoCloseMs: 3000
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: volDropdownCol
|
id: volDropdownCol
|
||||||
|
|
@ -2165,7 +2160,6 @@ in
|
||||||
// edges render as soft 2px lines
|
// edges render as soft 2px lines
|
||||||
fullWidth: Math.ceil(calRow.width) + 24
|
fullWidth: Math.ceil(calRow.width) + 24
|
||||||
fullHeight: Math.ceil(calRow.height) + 24
|
fullHeight: Math.ceil(calRow.height) + 24
|
||||||
autoCloseMs: 3000
|
|
||||||
|
|
||||||
// Month being viewed; reset to today when the popup opens
|
// Month being viewed; reset to today when the popup opens
|
||||||
// (via the setup function passed to bar.toggleDropdown).
|
// (via the setup function passed to bar.toggleDropdown).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue