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
|
|
@ -287,22 +287,20 @@ in
|
||||||
suppress_event = "fullscreen maximize",
|
suppress_event = "fullscreen maximize",
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Battle.net spams window-activation events for as long as it is
|
-- Battle.net requests activation on its own while running, which
|
||||||
-- running. Each one clears quickshell's focus grab, which closes the
|
-- steals focus. Drop those events.
|
||||||
-- launcher / power menu exactly like a click-outside would — so with
|
|
||||||
-- Battle.net open, Super+R appears to do nothing at all: the panel
|
|
||||||
-- opens and is killed within the same frame. Drop those events.
|
|
||||||
--
|
|
||||||
-- Matched on class, not title: a title-scoped rule would have to be
|
|
||||||
-- re-evaluated after the CEF window sets its title, and this needs to
|
|
||||||
-- hold from map onwards. It also catches the launcher's login and
|
|
||||||
-- update windows, which are the same class under other titles.
|
|
||||||
--
|
--
|
||||||
-- Non-Steam shortcuts have been BOTH steam_app_0 and
|
-- Non-Steam shortcuts have been BOTH steam_app_0 and
|
||||||
-- steam_app_default across Steam versions (this rule matched only
|
-- steam_app_default across Steam versions, and this rule matched only
|
||||||
-- steam_app_0, and silently stopped applying when Battle.net turned
|
-- steam_app_0, so it silently stopped applying when Battle.net turned
|
||||||
-- up as steam_app_default — that regression is the bug this alt
|
-- up as steam_app_default. Match either.
|
||||||
-- fixes). Match either so a Steam update can't break it again.
|
--
|
||||||
|
-- Does NOT fix the launcher dying while Battle.net runs. That is
|
||||||
|
-- Hyprland destroying quickshell's seat grab from inside
|
||||||
|
-- mouseMoveUnified() whenever it refocuses with the cursor over a
|
||||||
|
-- surface the grab doesn't own — no activation request involved, so
|
||||||
|
-- there is nothing here to suppress. Unsolved; see the note in
|
||||||
|
-- settings/quickshell.nix.
|
||||||
--
|
--
|
||||||
-- WoW shares steam_app_default and so loses self-activation too.
|
-- WoW shares steam_app_default and so loses self-activation too.
|
||||||
-- Harmless: a game window is focused when it maps, which is not an
|
-- Harmless: a game window is focused when it maps, which is not an
|
||||||
|
|
|
||||||
|
|
@ -801,21 +801,10 @@ in
|
||||||
|
|
||||||
// Bound in hyprland.nix: Super+R → launcher (bottom of the
|
// Bound in hyprland.nix: Super+R → launcher (bottom of the
|
||||||
// bar window), Super+L → session menu (right edge).
|
// 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 {
|
IpcHandler {
|
||||||
target: "launcher"
|
target: "launcher"
|
||||||
function toggle(): void {
|
function toggle(): void { if (root.mainBar) root.mainBar.toggleLauncher(); }
|
||||||
if (!root.mainBar) { console.warn("ipc launcher.toggle: no bar registered"); return; }
|
function powermenu(): void { if (root.mainBar) root.mainBar.toggleSession(); }
|
||||||
root.mainBar.toggleLauncher();
|
|
||||||
}
|
|
||||||
function powermenu(): void {
|
|
||||||
if (!root.mainBar) { console.warn("ipc launcher.powermenu: no bar registered"); return; }
|
|
||||||
root.mainBar.toggleSession();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keybind cheat sheet — Super+C in hyprland.nix.
|
// Keybind cheat sheet — Super+C in hyprland.nix.
|
||||||
|
|
@ -1021,18 +1010,11 @@ in
|
||||||
required property var shellRoot
|
required property var shellRoot
|
||||||
screen: modelData
|
screen: modelData
|
||||||
WlrLayershell.namespace: "quickshell-bar"
|
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
|
// OnDemand + HyprlandFocusGrab is the working combination
|
||||||
// (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: 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
|
// Vertical bar on the left edge. The surface still covers the
|
||||||
// whole screen (the frame band and every panel are drawn into
|
// whole screen (the frame band and every panel are drawn into
|
||||||
|
|
@ -1049,32 +1031,6 @@ in
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: barBgRect
|
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 {
|
Region {
|
||||||
x: activeDropdown ? activeDropdown.x : 0
|
x: activeDropdown ? activeDropdown.x : 0
|
||||||
y: activeDropdown ? activeDropdown.y : 0
|
y: activeDropdown ? activeDropdown.y : 0
|
||||||
|
|
@ -1103,23 +1059,6 @@ in
|
||||||
width: Theme.barWidth
|
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
|
// Register the primary bar so shell.qml's IPC handler can
|
||||||
// reach the session menu.
|
// reach the session menu.
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
|
@ -1581,13 +1520,6 @@ in
|
||||||
// Non-empty when the query parses as arithmetic.
|
// Non-empty when the query parses as arithmetic.
|
||||||
property string calcResult: ""
|
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: [
|
readonly property var catDefs: [
|
||||||
{ key: "All", label: "All apps", icon: "layout-grid" },
|
{ key: "All", label: "All apps", icon: "layout-grid" },
|
||||||
{ key: "Network", label: "Internet", icon: "globe" },
|
{ key: "Network", label: "Internet", icon: "globe" },
|
||||||
|
|
@ -1625,12 +1557,6 @@ in
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
open = !open;
|
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) {
|
if (open) {
|
||||||
searchInput.text = "";
|
searchInput.text = "";
|
||||||
activeCat = "";
|
activeCat = "";
|
||||||
|
|
@ -2993,53 +2919,46 @@ in
|
||||||
// window (or anywhere outside the bar) clears the grab and
|
// window (or anywhere outside the bar) clears the grab and
|
||||||
// closes whatever is open. Suspended while screenshotPinned so
|
// closes whatever is open. Suspended while screenshotPinned so
|
||||||
// slurp can grab input without dismissing the menu.
|
// slurp can grab input without dismissing the menu.
|
||||||
// Kept as the backstop for focus leaving without a click — a
|
// KNOWN UNSOLVED: while an app that churns windows is running
|
||||||
// keybind that focuses another window, a workspace switch. Real
|
// (Battle.net does it continuously), the launcher opens and this
|
||||||
// outside clicks are the MouseArea's job now, because with the
|
// fires ~1ms later, so Super+R looks like it does nothing.
|
||||||
// input region covering the screen the grab never sees them.
|
|
||||||
//
|
//
|
||||||
// Re-arming on a fast clear was tried and removed: when the clear
|
// Cause, from Hyprland 0.55.4 InputManager.cpp mouseMoveUnified():
|
||||||
// comes from refocus() (see mask), the very next refocus() kills
|
//
|
||||||
// the fresh grab too, so it just lost three times in a row before
|
// if (m_seatGrab && !m_seatGrab->accepts(foundSurface) ...) {
|
||||||
// closing. The mask is what actually fixes it.
|
// 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 {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
active: (launcherPanel.open || bar.activeDropdown !== null) && !bar.screenshotPinned
|
||||||
active: bar.panelOpen
|
|
||||||
windows: [bar]
|
windows: [bar]
|
||||||
property double armedAt: 0
|
|
||||||
onActiveChanged: if (active) armedAt = Date.now()
|
|
||||||
|
|
||||||
onCleared: {
|
onCleared: {
|
||||||
if (bar.screenshotPinned) return;
|
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;
|
launcherPanel.open = false;
|
||||||
bar.closeAllDropdowns();
|
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
|
property var activeDropdown: null
|
||||||
|
|
||||||
// Set by the screenshot keybind (via IPC) to hold menus open
|
// Set by the screenshot keybind (via IPC) to hold menus open
|
||||||
|
|
@ -3072,19 +2991,6 @@ in
|
||||||
if (a.startsWith("/")) return "file://" + a;
|
if (a.startsWith("/")) return "file://" + a;
|
||||||
return "";
|
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) {
|
function toggleDropdown(dd, setupFn) {
|
||||||
if (dd.visible && !dd.closing) {
|
if (dd.visible && !dd.closing) {
|
||||||
dd.animateClose();
|
dd.animateClose();
|
||||||
|
|
@ -5913,18 +5819,6 @@ in
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: Math.max(notifPreview.visible ? 48 : 0, ncBody.height)
|
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…)
|
// Image preview (album art, screenshot thumb…)
|
||||||
ClippingRectangle {
|
ClippingRectangle {
|
||||||
id: notifPreview
|
id: notifPreview
|
||||||
|
|
@ -6205,18 +6099,6 @@ in
|
||||||
|
|
||||||
property string previewSource: bar.notifPreviewSource(toastItem.currentNotif)
|
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…)
|
// Image preview (screenshot thumb, album art…)
|
||||||
ClippingRectangle {
|
ClippingRectangle {
|
||||||
id: toastPreview
|
id: toastPreview
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue