Revert the launcher focus-grab fix attempts
None of them worked. quickshell.nix is back to a8eb767 apart from the
hidden-directory search change, which was a separate request and does
work. Removed: the toggle/grab/compositor-event logging, the grab re-arm,
and the full-screen input mask with its click catcher.
Kept the Battle.net window rule's class match (steam_app_0 was stale, and
the tray-icon rule below it needs the widened match), but its comment no
longer claims to fix the launcher.
Recorded the diagnosis at the focus grab so this doesn't get re-derived:
Hyprland's mouseMoveUnified() destroys the seat grab on any refocus()
while the cursor is over a surface the grab doesn't own, and every window
map/unmap calls refocus(). No user input involved, nothing to suppress.
This commit is contained in:
parent
59c1b10334
commit
dcfc7f9573
2 changed files with 45 additions and 165 deletions
|
|
@ -801,21 +801,10 @@ in
|
|||
|
||||
// Bound in hyprland.nix: Super+R → launcher (bottom of the
|
||||
// bar window), Super+L → session menu (right edge).
|
||||
// mainBar is claimed by the primary screen's Bar in its
|
||||
// Component.onCompleted. If it is somehow unset — an output
|
||||
// change recreating the Variants, a reload racing the keybind —
|
||||
// Super+R has nothing to call, and used to fail dead silently.
|
||||
// Say so instead: journalctl --user -u quickshell.
|
||||
IpcHandler {
|
||||
target: "launcher"
|
||||
function toggle(): void {
|
||||
if (!root.mainBar) { console.warn("ipc launcher.toggle: no bar registered"); return; }
|
||||
root.mainBar.toggleLauncher();
|
||||
}
|
||||
function powermenu(): void {
|
||||
if (!root.mainBar) { console.warn("ipc launcher.powermenu: no bar registered"); return; }
|
||||
root.mainBar.toggleSession();
|
||||
}
|
||||
function toggle(): void { if (root.mainBar) root.mainBar.toggleLauncher(); }
|
||||
function powermenu(): void { if (root.mainBar) root.mainBar.toggleSession(); }
|
||||
}
|
||||
|
||||
// Keybind cheat sheet — Super+C in hyprland.nix.
|
||||
|
|
@ -1021,18 +1010,11 @@ in
|
|||
required property var shellRoot
|
||||
screen: modelData
|
||||
WlrLayershell.namespace: "quickshell-bar"
|
||||
// One source of truth for "a panel is up and wants the seat":
|
||||
// the keyboard focus mode, the input mask and the focus grab all
|
||||
// key off this, and they have to agree or the grab dies (see the
|
||||
// mask below).
|
||||
readonly property bool panelOpen: (launcherPanel.open || bar.activeDropdown !== null)
|
||||
&& !bar.screenshotPinned
|
||||
|
||||
// OnDemand + HyprlandFocusGrab is the working combination
|
||||
// (caelestia's): the grab redirects focus to this window and
|
||||
// OnDemand lets the layer surface accept it. Exclusive fights
|
||||
// the grab — it self-clears and instantly closes the panel.
|
||||
WlrLayershell.keyboardFocus: bar.panelOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
WlrLayershell.keyboardFocus: (launcherPanel.open || bar.activeDropdown !== null) && !bar.screenshotPinned ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
|
||||
// Vertical bar on the left edge. The surface still covers the
|
||||
// whole screen (the frame band and every panel are drawn into
|
||||
|
|
@ -1049,32 +1031,6 @@ in
|
|||
|
||||
mask: Region {
|
||||
item: barBgRect
|
||||
// While a panel is open the input region is the WHOLE screen,
|
||||
// and that is load-bearing rather than cosmetic.
|
||||
//
|
||||
// Hyprland destroys a seat grab whenever it refocuses while
|
||||
// the cursor sits over a surface the grab doesn't own:
|
||||
//
|
||||
// // InputManager.cpp, mouseMoveUnified()
|
||||
// if (m_seatGrab && !m_seatGrab->accepts(foundSurface) ...) {
|
||||
// if (m_hardInput || refocus) {
|
||||
// g_pSeatManager->setGrab(nullptr);
|
||||
//
|
||||
// `refocus` is true for all 20 CInputManager::refocus() call
|
||||
// sites, which include every window map and unmap. So an app
|
||||
// that churns windows (Battle.net does, continuously) fires
|
||||
// refocus() over and over, and with the cursor anywhere but
|
||||
// this window's input region the launcher was being killed
|
||||
// ~1ms after opening, every single time.
|
||||
//
|
||||
// Owning the pointer everywhere makes accepts() always true,
|
||||
// so there is nothing left to tear the grab down. The grab no
|
||||
// longer sees outside clicks either, which is what the
|
||||
// dismiss catcher below is for.
|
||||
Region {
|
||||
width: bar.panelOpen ? bar.width : 0
|
||||
height: bar.panelOpen ? bar.height : 0
|
||||
}
|
||||
Region {
|
||||
x: activeDropdown ? activeDropdown.x : 0
|
||||
y: activeDropdown ? activeDropdown.y : 0
|
||||
|
|
@ -1103,23 +1059,6 @@ in
|
|||
width: Theme.barWidth
|
||||
}
|
||||
|
||||
// Click-outside dismissal. The focus grab used to do this, but it
|
||||
// can only see clicks that land outside our input region — and
|
||||
// the region is now the whole screen (see mask). Declared here,
|
||||
// before the bar widgets and every panel, so it sits UNDERNEATH
|
||||
// them in stacking order and only catches what they don't.
|
||||
//
|
||||
// Transparent, not dimmed: this is the launcher, not a modal.
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: bar.panelOpen
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onPressed: {
|
||||
launcherPanel.open = false;
|
||||
bar.closeAllDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
// Register the primary bar so shell.qml's IPC handler can
|
||||
// reach the session menu.
|
||||
Component.onCompleted: {
|
||||
|
|
@ -1581,13 +1520,6 @@ in
|
|||
// Non-empty when the query parses as arithmetic.
|
||||
property string calcResult: ""
|
||||
|
||||
// Diagnostic clock (ms since the last toggle). Deliberately
|
||||
// NOT `open`-gated: the first version of the compositor trace
|
||||
// keyed off `open`, and the focus-grab clear sets open=false
|
||||
// before Hyprland's IPC event arrives, so it hid the one
|
||||
// event worth seeing.
|
||||
property double traceFrom: 0
|
||||
|
||||
readonly property var catDefs: [
|
||||
{ key: "All", label: "All apps", icon: "layout-grid" },
|
||||
{ key: "Network", label: "Internet", icon: "globe" },
|
||||
|
|
@ -1625,12 +1557,6 @@ in
|
|||
|
||||
function toggle() {
|
||||
open = !open;
|
||||
// Logged so a "Super+R did nothing" can be told apart
|
||||
// from "Super+R never arrived": if the journal shows
|
||||
// `-> open` with no matching focusgrab line after it,
|
||||
// the panel really did open and something drew over it.
|
||||
traceFrom = Date.now();
|
||||
console.log("launcher toggle -> " + (open ? "open" : "closed"));
|
||||
if (open) {
|
||||
searchInput.text = "";
|
||||
activeCat = "";
|
||||
|
|
@ -2993,53 +2919,46 @@ in
|
|||
// 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.
|
||||
// Kept as the backstop for focus leaving without a click — a
|
||||
// keybind that focuses another window, a workspace switch. Real
|
||||
// outside clicks are the MouseArea's job now, because with the
|
||||
// input region covering the screen the grab never sees them.
|
||||
// KNOWN UNSOLVED: while an app that churns windows is running
|
||||
// (Battle.net does it continuously), the launcher opens and this
|
||||
// fires ~1ms later, so Super+R looks like it does nothing.
|
||||
//
|
||||
// Re-arming on a fast clear was tried and removed: when the clear
|
||||
// comes from refocus() (see mask), the very next refocus() kills
|
||||
// the fresh grab too, so it just lost three times in a row before
|
||||
// closing. The mask is what actually fixes it.
|
||||
// Cause, from Hyprland 0.55.4 InputManager.cpp mouseMoveUnified():
|
||||
//
|
||||
// if (m_seatGrab && !m_seatGrab->accepts(foundSurface) ...) {
|
||||
// if (m_hardInput || refocus) {
|
||||
// g_pSeatManager->setGrab(nullptr);
|
||||
//
|
||||
// foundSurface is the surface under the CURSOR, and `refocus` is
|
||||
// true for all 20 CInputManager::refocus() call sites, which
|
||||
// include every window map and unmap. So each window a busy app
|
||||
// churns destroys the grab unless the cursor happens to be over
|
||||
// this window's input region. No user input is involved, which is
|
||||
// why it is invisible from the client side. It also explains the
|
||||
// workaround: click a bar widget first (cursor lands on the bar)
|
||||
// and the launcher stays up.
|
||||
//
|
||||
// Tried and reverted, both failed:
|
||||
// - suppress_event activate/activatefocus on the app (nothing is
|
||||
// requesting activation, so there is nothing to suppress)
|
||||
// - re-arming the grab on a suspiciously fast clear (the next
|
||||
// refocus() kills the fresh grab too — lost 3/3, every time)
|
||||
// - expanding the input mask to the whole screen while open so
|
||||
// accepts() is always true, with a MouseArea for outside
|
||||
// clicks (behaviour changed, still died)
|
||||
//
|
||||
// Probably needs fixing upstream, or the launcher needs to stop
|
||||
// depending on a seat grab at all.
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
active: bar.panelOpen
|
||||
active: (launcherPanel.open || bar.activeDropdown !== null) && !bar.screenshotPinned
|
||||
windows: [bar]
|
||||
property double armedAt: 0
|
||||
onActiveChanged: if (active) armedAt = Date.now()
|
||||
|
||||
onCleared: {
|
||||
if (bar.screenshotPinned) return;
|
||||
if (launcherPanel.open || bar.activeDropdown !== null) {
|
||||
const top = Hyprland.activeToplevel;
|
||||
const cls = top && top.lastIpcObject ? top.lastIpcObject["class"] : "?";
|
||||
console.log("focusgrab cleared +" + (Date.now() - focusGrab.armedAt)
|
||||
+ "ms: launcher=" + launcherPanel.open
|
||||
+ " dropdown=" + (bar.activeDropdown !== null)
|
||||
+ " focus=" + (top ? cls + " / " + top.title : "none"));
|
||||
}
|
||||
launcherPanel.open = false;
|
||||
bar.closeAllDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostic for the "launcher opens and dies in the same frame
|
||||
// while Battle.net runs" bug. Gated on a panel actually being
|
||||
// open, so this is a handful of lines around each Super+R rather
|
||||
// than a firehose — the whole point is to see which compositor
|
||||
// event lands between `launcher toggle -> open` and `focusgrab
|
||||
// cleared`. Read with `journalctl --user -u quickshell -f -o
|
||||
// short-precise`; second resolution is too coarse here.
|
||||
Connections {
|
||||
target: Hyprland
|
||||
function onRawEvent(event) {
|
||||
const dt = Date.now() - launcherPanel.traceFrom;
|
||||
if (dt < 3000)
|
||||
console.log("hypr event +" + dt + "ms: " + event.name + " | " + event.data);
|
||||
}
|
||||
}
|
||||
|
||||
property var activeDropdown: null
|
||||
|
||||
// Set by the screenshot keybind (via IPC) to hold menus open
|
||||
|
|
@ -3072,19 +2991,6 @@ in
|
|||
if (a.startsWith("/")) return "file://" + a;
|
||||
return "";
|
||||
}
|
||||
// Clicking a notification jumps to the sending app: invoke its
|
||||
// default action (apps use it to open the right chat/tab) and
|
||||
// focus the window ourselves — misc:focus_on_activate is off,
|
||||
// so the app's own activation request would be ignored.
|
||||
function activateNotif(notif) {
|
||||
if (!notif) return;
|
||||
for (const a of notif.actions)
|
||||
if (a.identifier === "default") { a.invoke(); break; }
|
||||
const entry = (notif.desktopEntry || "").replace(/\.desktop$/, "");
|
||||
const m = [entry, notif.appName].filter(s => s !== "").join("|");
|
||||
if (m !== "") Hyprland.dispatch("focuswindow class:(?i)^(" + m + ")$");
|
||||
}
|
||||
|
||||
function toggleDropdown(dd, setupFn) {
|
||||
if (dd.visible && !dd.closing) {
|
||||
dd.animateClose();
|
||||
|
|
@ -5913,18 +5819,6 @@ in
|
|||
width: parent.width
|
||||
height: Math.max(notifPreview.visible ? 48 : 0, ncBody.height)
|
||||
|
||||
// First child, so the dismiss button and
|
||||
// action chips still get their clicks.
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
bar.activateNotif(notifItem.modelData);
|
||||
notifItem.modelData.dismiss();
|
||||
bar.closeAllDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
// Image preview (album art, screenshot thumb…)
|
||||
ClippingRectangle {
|
||||
id: notifPreview
|
||||
|
|
@ -6205,18 +6099,6 @@ in
|
|||
|
||||
property string previewSource: bar.notifPreviewSource(toastItem.currentNotif)
|
||||
|
||||
// First child, so the dismiss button and action
|
||||
// chips still get their clicks.
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
bar.activateNotif(toastItem.currentNotif);
|
||||
toastItem.currentNotif.dismiss();
|
||||
toastItem.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
// Image preview (screenshot thumb, album art…)
|
||||
ClippingRectangle {
|
||||
id: toastPreview
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue