Skip to content

Commit 319c5cd

Browse files
fedejeanneCopilot
andcommitted
KeyBindingDispatcher: suppress duplicate command execution for same
native key event (Linux/GTK) On GTK (Linux), a single physical key press can, in some situations, be delivered to KeyBindingDispatcher twice for the very same native event: once synchronously via an SWT.Traverse event dispatched from within the native gtk3_key_press_event handling, and once more later via the async message queue. Both deliveries resolve to and execute the exact same bound command, which for page-traversal shortcuts like Ctrl+PageUp / Ctrl+PageDown (Next/Previous Editor) causes an extra tab to be skipped, since the CTabFolder selection is advanced twice per key press. This does not reproduce on Windows or macOS, only on GTK. Fix: track the last executed command together with the native timestamp (Event#time) of its triggering event in executeCommand(...), and skip (without re-executing the handler) a call that matches both the same command and the same event timestamp, since that combination can only happen when the very same native key event is redelivered. Genuinely distinct key presses - including fast auto-repeat - always carry different timestamps, so normal navigation and repeated shortcuts are unaffected. This is reported as the 'bonus' issue in #4135 (Javadoc View tab cycling two tabs per Ctrl+PageUp/PageDown on Linux). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b95f727 commit 319c5cd

1 file changed

Lines changed: 54 additions & 0 deletions

File tree

bundles/org.eclipse.e4.ui.bindings/src/org/eclipse/e4/ui/bindings/keys/KeyBindingDispatcher.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,21 @@ private static boolean isOutOfOrderKey(List<KeyStroke> keyStrokes) {
267267
@Optional
268268
private Logger logger;
269269

270+
/**
271+
* The command executed the last time {@link #executeCommand(ParameterizedCommand, Event)} was
272+
* called, together with the native timestamp of the triggering event. On some platforms (e.g.
273+
* GTK on Linux) a single physical key press can be delivered to the dispatcher more than once
274+
* for the same native event (for example, once as an <code>SWT.Traverse</code> event and once
275+
* as a redundant <code>SWT.KeyDown</code> event), which would otherwise execute the resulting
276+
* command twice, e.g. skipping over an extra tab when navigating with Ctrl+PageUp/PageDown.
277+
* These fields are used to detect and suppress such a duplicate re-delivery of the very same
278+
* native key event, identified by its (platform-provided) timestamp.
279+
*
280+
* @see #isDuplicateKeyEvent(ParameterizedCommand, Event)
281+
*/
282+
private ParameterizedCommand lastExecutedCommand;
283+
private int lastExecutedEventTime;
284+
270285
/**
271286
* Performs the actual execution of the command by looking up the current handler from the
272287
* command manager. If there is a handler and it is enabled, then it tries the actual execution.
@@ -292,6 +307,14 @@ public final boolean executeCommand(final ParameterizedCommand parameterizedComm
292307
throw new NotEnabledException("Command should have been disabled via activity: " + parameterizedCommand); //$NON-NLS-1$
293308
}
294309

310+
if (isDuplicateKeyEvent(parameterizedCommand, trigger)) {
311+
if (isTracingEnabled()) {
312+
logger.trace("Suppressed duplicate execution of " + parameterizedCommand + " for the same native event " //$NON-NLS-1$ //$NON-NLS-2$
313+
+ "(time=" + trigger.time + ") in " + describe(context)); //$NON-NLS-1$ //$NON-NLS-2$
314+
}
315+
return true;
316+
}
317+
295318
final EHandlerService handlerService = getHandlerService();
296319
final Command command = parameterizedCommand.getCommand();
297320

@@ -324,6 +347,37 @@ public final boolean executeCommand(final ParameterizedCommand parameterizedComm
324347
return (commandDefined && commandHandled);
325348
}
326349

350+
/**
351+
* Detects whether the given command execution is a duplicate re-delivery of the same native
352+
* key event that was already used to execute this very command. On some platforms (currently
353+
* known to affect GTK on Linux) a single physical key press can reach the dispatcher twice,
354+
* e.g. once via an <code>SWT.Traverse</code> event and once more via a subsequently generated
355+
* <code>SWT.KeyDown</code> event, both carrying the native event's original timestamp. Without
356+
* this guard, such duplicate delivery would execute the resulting command (e.g. "Next Editor"
357+
* bound to Ctrl+PageDown) twice for a single key press, which is, for example, visible as
358+
* navigation skipping over an extra tab.
359+
* <p>
360+
* As a side effect, this method records the given command/trigger pair as the last executed
361+
* one, so that a subsequent call can be compared against it.
362+
*
363+
* @param parameterizedCommand
364+
* the command about to be executed; must not be <code>null</code>.
365+
* @param trigger
366+
* the triggering event; may be <code>null</code>, in which case no event was
367+
* involved and de-duplication does not apply.
368+
* @return <code>true</code> if this looks like a duplicate delivery of the same native event
369+
* that already triggered the same command; <code>false</code> otherwise.
370+
*/
371+
private boolean isDuplicateKeyEvent(final ParameterizedCommand parameterizedCommand, final Event trigger) {
372+
if (trigger == null) {
373+
return false;
374+
}
375+
boolean duplicate = parameterizedCommand == lastExecutedCommand && trigger.time == lastExecutedEventTime;
376+
lastExecutedCommand = parameterizedCommand;
377+
lastExecutedEventTime = trigger.time;
378+
return duplicate;
379+
}
380+
327381
private boolean handleCommandExecution(final ParameterizedCommand parameterizedCommand,
328382
final EHandlerService handlerService, final Event trigger, Object handler) {
329383
final IEclipseContext staticContext = createContext(trigger);

0 commit comments

Comments
 (0)