From 793dd0d1d2f53b4e510267d5aac01f42a6964b75 Mon Sep 17 00:00:00 2001 From: Siddharth Kannan Date: Fri, 9 May 2025 23:04:17 +0900 Subject: [PATCH] Fail gracefully instead of crashing if grab fails I have been using PaperWM on a daily basis for the past few months and it is great. One of the problems that I kept running into repeatedly was https://github.com/paperwm/PaperWM/issues/991. This issue was reported on Gnome 47. I ran into this problem on Gnome 44. I attempted a naive fix which simply tries to clean-up all the state instead of crashing. This works well; I have been using a version of this fix, applied on top of the gnome-44 branch, for the past month and I have not suffered any `Could not grab modal` crashes. It would be great if I could get some guidance about whether this fix looks logical, and whether there might be a way to reproduce this code path somehow. We need to somehow get into `preview_navigate` (which happens when one uses a keyboard shortcut to switch windows, I believe?) but get there in a state where the keyboard is already grabbed by something else, thus causing PaperWM to fail to grab the keyboard. I would really like to fix this issue within PaperWM because I have not run into any other bug at all and this is a great project! Signed-off-by: Siddharth Kannan --- navigator.js | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/navigator.js b/navigator.js index fa79b1ca0..814c863e2 100644 --- a/navigator.js +++ b/navigator.js @@ -82,10 +82,21 @@ class ActionDispatcher { // grab = stage.grab(this.actor) grab = Main.pushModal(this.actor); + // Assume that grab succeeds and store that state in the object + this.success = true; // We expect at least a keyboard grab here if ((grab.get_seat_state() & Clutter.GrabState.KEYBOARD) === 0) { console.error("Failed to grab modal"); - throw new Error('Could not grab modal'); + // Release current grab and let the user try again + try { + this.success = false; + if (grab) { + Main.popModal(grab); + grab = null; + } + } catch (e) { + console.error("Failed to release grab"); + } } this.signals.connect(this.actor, 'key-press-event', this._keyPressEvent.bind(this)); @@ -117,6 +128,9 @@ class ActionDispatcher { } show(_backward, binding, mask) { + // If required grab was not successful, then do not show anything + if (!this.success) return; + this._modifierMask = primaryModifier(mask); this.navigator = getNavigator(); Topbar.fixTopBar(); @@ -519,11 +533,11 @@ export function getActionDispatcher(mode) { return dispatcher; } dispatcher = new ActionDispatcher(); - return getActionDispatcher(mode); + return getActionDispatcher(mode); } /** - * Fishes current dispatcher (if any). + * Finishes current dispatcher (if any). */ export function finishDispatching() { dispatcher?._finish(global.get_current_time()); @@ -546,5 +560,14 @@ export function dismissDispatcher(mode) { export function preview_navigate(meta_window, space, { _display, _screen, binding }) { let tabPopup = getActionDispatcher(Clutter.GrabState.KEYBOARD); - tabPopup.show(binding.is_reversed(), binding.get_name(), binding.get_mask()); + + // Getting a dispatcher does not always succeed. Sometimes, it can fail because we are unable to + // grab the keyboard. + // In the case of a failure, fail gracefully by destroying the pop-up. + if (!tabPopup.success) { + tabPopup.destroy(); + return; + } + + tabPopup.show(binding.is_reversed(), binding.get_name(), binding.get_mask()); }