eclipse.platform.swt
eclipse.platform.swt copied to clipboard
[GTK] Double click behaviour changed between 2025-03 and 2025-09
Describe the bug
As reported by @haubi in https://github.com/eclipse-egit/egit/issues/110 a double click into the EGit Staging View may fail to select the element under the cursor, and will instead operate on the previously selected tree element.
Since EGit doesn't seem to have any recent related changes fingers are pointing to SWT for changed behavior.
To Reproduce
- Have a git staging view with several staged files
- Select the first file
- Select any editor or other view
- Perform a double click on the second file in the staging view
Expected behavior A compare editor should open for the second file.
Actual behavior Compare editor will open for the first file.
Environment:
- Select the platform(s) on which the behavior is seen:
-
- [ ] All OS
-
- [ ] Windows
-
- [x] Linux
-
- [ ] macOS
EGit committer tried to reproduce on macOS and failed.
- Additional OS info (e.g. OS version, Linux Desktop, etc)
I'm seeing it on Ubuntu 24.04.3 with
org.eclipse.swt.internal.gtk.theme=Breeze:dark
org.eclipse.swt.internal.gtk.version=3.24.41
org.eclipse.swt.internal.webkitgtk.version=2.48.7
osgi.ws=gtk
and
GTK2_RC_FILES=/etc/gtk-2.0/gtkrc:/home/stephan/.gtkrc-2.0:/home/stephan/.config/gtkrc-2.0
GTK_OVERLAY_SCROLLING=0
GTK_RC_FILES=/etc/gtk/gtkrc:/home/stephan/.gtkrc:/home/stephan/.config/gtkrc
SWT_GTK3=1
SWT_GTK4=0
- JRE/JDK version
java.version=24.0.1
Version since Reported in https://github.com/eclipse-egit/egit/issues/110:
- working: SWT 3.129.0.v20250221-1734
- broken: SWT 3.131.0.v20250820-1556
Workaround (or) Additional context
One additional click is required to first set focus to the staging view.
See https://github.com/eclipse-egit/egit/issues/110
Whatever is causing this has to be very likely be on the EGit side, not the SWT side. Specifically something introduced with 7.4.
This is the 2025-06 with EGit 7.3, where the staging view behaves as expected:
https://github.com/user-attachments/assets/2ad4eac0-1512-4d3c-bad4-982f05c1514c
This is the same 2025-06, this time with EGit 7.4, where the described behavior occurs:
https://github.com/user-attachments/assets/05e72de2-a0d6-4b88-b1fa-36e95805325e
Not in the video, but using the 2025-12 M2 with EGit 7.3 also behaves as it should. If you look closely at the recording, you'll notices that the staging view "lights up" when clicking on it, which doesn't happen with the 7.3. Some change there must cause the view to gain focus.
Caused by https://github.com/eclipse-egit/egit/commit/ec960817dc4b0b6e303df506e0d37858a5bbe25d
Caused by eclipse-egit/egit@ec96081
Theme / css changes??? How that? The only other one https://github.com/eclipse-egit/egit/blob/ec960817dc4b0b6e303df506e0d37858a5bbe25d/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/history/CommitAndDiffComponent.java#L100 is probably not relevant.
I can only report what I'm seeing. But the issue is definitely the result of this CSS class:
.MPartStack.active .MPart Form Composite Section Tree.org-eclipse-egit-ui-StagingView {
background-color: #FFFFFF;
}
I can only report what I'm seeing. But the issue is definitely the result of this CSS class:
.MPartStack.active .MPart Form Composite Section Tree.org-eclipse-egit-ui-StagingView { background-color: #FFFFFF; }
I can even go one step further. It happens with .MPartStack.active but not with .MPartStack
I would then say it's platform (UI/SWT) bug. Css styling shouldn't affect focus in ideal world. But I assume the problem is somewhere between setting background on widgets where "touching" GTK widgets in different order (or for the first time) changes key focus or triggers extra events that steal focus or something like that.
I noticed the same. Even worse in the History view (that should be pure platform?) it happens that one is unable to select multiple lines and therefore can not squash items sometimes!
The strange thing is that one can "fix" it by restart Eclipse but then it happens again (maybe after some time or when switching Views/Perspectives). This is very annoying.
Theme / css changes??? How that?
I have let Copilot doing some guess here and it says:
Root Cause
When setBackground() is called on a Tree widget (triggered by CSS styling from Eclipse's active part stack), GTK may grab focus to the widget during the CSS application process:
- setBackgroundGdkRGBA() is called with the widget handle
- gtk_style_context_add_provider() is invoked to apply CSS styling
- GTK realizes the widget if not already realized and may grab focus
- First click of double-click changes focus, second click operates on wrong item
Solution
Temporarily disable widget's focus capability while applying CSS styles:
- Query current focus state using new gtk_widget_get_can_focus() function
- Set can_focus to false before applying CSS
- Apply CSS via gtk_css_provider_load_from_css()
- Restore original can_focus state
This prevents GTK from grabbing focus during CSS application while preserving the widget's normal focus behavior.
@akurtakov @jonahgraham I don't know if it makes sense here but maybe gives a first direction of how CSS styling can influence widget focus.
GTK realizes the widget if not already realized and may grab focus
I don't know much about SWT, even less about GTK, but how can a widget remain unrealized when the operation is repeated several times and always exhibits the same problem?
When setBackground() is called on a Tree widget (triggered by CSS styling from Eclipse's active part stack), GTK may grab focus to the widget during the CSS application process:
- setBackgroundGdkRGBA() is called with the widget handle
- gtk_style_context_add_provider() is invoked to apply CSS styling
- GTK realizes the widget if not already realized and may grab focus
- First click of double-click changes focus, second click operates on wrong item
I've commented out the call to gtk_css_provider_load_from_css and this this does not solve the issue. If I instead replace
https://github.com/eclipse-platform/eclipse.platform.swt/blob/8c5fcdd0213b53c8cbbda7f35771587df6c0e320/bundles/org.eclipse.swt/Eclipse%20SWT/gtk/org/eclipse/swt/widgets/Tree.java#L3574-L3578
with background = defaultBackground(); or
https://github.com/eclipse-platform/eclipse.platform.swt/blob/8c5fcdd0213b53c8cbbda7f35771587df6c0e320/bundles/org.eclipse.swt/Eclipse%20SWT/gtk/org/eclipse/swt/widgets/Tree.java#L1558-L1567
with
@Override
GdkRGBA getContextBackgroundGdkRGBA () {
// For Tables and Trees, the default background is
// COLOR_LIST_BACKGROUND instead of COLOR_WIDGET_BACKGROUND.
return defaultBackground();
}
the problem disappears as well. So it has definitely something to do with the background field. I just don't understand how and why.
@ptziegler just a wild guess, maybe just always call defaultBackground()
@Override
GdkRGBA getContextBackgroundGdkRGBA () {
if (background != null) {
defaultBackground();
return background;
} else {
// For Tables and Trees, the default background is
// COLOR_LIST_BACKGROUND instead of COLOR_WIDGET_BACKGROUND.
return defaultBackground();
}
}
to check if it is the method call or if it is the value that makes a difference?
@ptziegler just a wild guess, maybe just always call
defaultBackground()@Override GdkRGBA getContextBackgroundGdkRGBA () { if (background != null) { defaultBackground(); return background; } else { // For Tables and Trees, the default background is // COLOR_LIST_BACKGROUND instead of COLOR_WIDGET_BACKGROUND. return defaultBackground(); } }to check if it is the method call or if it is the value that makes a difference?
I've already tried that and it works.
Additionally, I've commented out several parts of where this method is called so that it's only called from the CSS engine. The interaction between the CSSPropertyBackgroundSWTHandler and the call to Tree.getBackground()/Tree.setBackground() is somehow responsible for this.
@ptziegler what about
@Override
GdkRGBA getContextBackgroundGdkRGBA () {
// When a custom background is set, delegate to Control's implementation
// which parses from the CSS provider instead of returning the stored
// background field value directly.
if (background != null && background != defaultBackground()) {
return super.getContextBackgroundGdkRGBA();
}
// For Tables and Trees, the default background is
// COLOR_LIST_BACKGROUND instead of COLOR_WIDGET_BACKGROUND.
return defaultBackground();
}
if (background != null && background != defaultBackground()) {
The colors are are genuinely different: (255, 255, 255) and (248, 248, 248), to be precise. And even then, I rather would've adapted the logic for the set variable in _setBackground to check for equality, rather than it to always be true.
https://github.com/eclipse-platform/eclipse.platform.swt/blob/0b21ff2b2bfdb2bcc278d508454a619f56bd0cbe/bundles/org.eclipse.swt/Eclipse%20SWT/gtk/org/eclipse/swt/widgets/Control.java#L5207-L5209
I think I've pieced together what exactly is going wrong here. The CSS styling is a little bit of a red herring. It is the symptom, rather than the cause. Because of how the CSS class is made, the background color of the tree is updated when it becomes active.
Specifically, it causes a call to setBackground(), because the "old" color (248, 248, 248) no longer matches the "new" color (255, 255, 255). Which why it doesn't happen when using EGit 7.3 or when theming is disabled.
The actual culprit is the call to gtk_style_context_invalidate. I suspect that calling it during activation leaves the widget in an inconsistent state, where the context has not yet been revalidated. Or it perhaps simply discards the selection event.
https://github.com/eclipse-platform/eclipse.platform.swt/blob/e2eb09a73c1d1d5cf723dbe19dee3c18b8b244e0/bundles/org.eclipse.swt/Eclipse%20SWT/gtk/org/eclipse/swt/widgets/Control.java#L5259-L5261
Based on the GTK3 docs, this method is deprecated, because the style is invalidated automatically, and was actually removed with https://github.com/eclipse-platform/eclipse.platform.swt/pull/915, but later restored due to test failures in the Eclipse Platform.
I think it needs to be investigated what exactly was responsible for those failures and whether they can be reproduce in pure SWT.
Here's a small reproducer:
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
public class Issue2702 {
private static Color ACTIVE = new Color(255, 255, 255);
private static Color INACTIVE = new Color(248, 248, 248);
public static void main(String[] args) {
Shell shell = new Shell();
shell.setLayout(new GridLayout(2, true));
shell.setSize(400, 200);
Tree tree = new Tree(shell, SWT.FULL_SELECTION);
tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
TreeItem treeItem1 = new TreeItem(tree, SWT.NONE);
treeItem1.setText("Tree Item 1");
TreeItem treeItem2 = new TreeItem(tree, SWT.NONE);
treeItem2.setText("Tree Item 2");
tree.addListener(SWT.Activate, new Listener() {
@Override
public void handleEvent(Event event) {
tree.setBackground(ACTIVE);
}
});
tree.addListener(SWT.Deactivate, new Listener() {
@Override
public void handleEvent(Event event) {
tree.setBackground(INACTIVE);
}
});
Table table = new Table(shell, SWT.FULL_SELECTION);
table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
TableItem tableItem1 = new TableItem(table, SWT.NONE);
tableItem1.setText("Table Item 1");
TableItem tableItem2 = new TableItem(table, SWT.NONE);
tableItem2.setText("Table Item 2");
table.addListener(SWT.Activate, new Listener() {
@Override
public void handleEvent(Event event) {
table.setBackground(ACTIVE);
}
});
table.addListener(SWT.Deactivate, new Listener() {
@Override
public void handleEvent(Event event) {
table.setBackground(INACTIVE);
}
});
shell.open();
Display display = shell.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}
https://github.com/user-attachments/assets/10328c7d-2d33-40c8-887a-5cc75e810fd0
Maybe this is too simplistic, but what about putting the GTK3.gtk_style_context_invalidate(context) in a Display.asyncExec()? This way all pending events are processed before the context is invalidated and the issue disappears.
Putting invalidate in async should be equivalent to not have it at all as it will be automatically invalidated by running the event loop (even before the async call is executed). In order to be sure here a reproducer for the issue that led to restoring the call should be resurrected/created so actual effect can be easily seen. Alternatively, a PR dropping invalidate can be merged early for the next cycle and see whether someone complains.
Those were the tests that were originally failing when the call to gtk_style_context_invalidate was removed:
[Test Result](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/) (19 failures )
[org.eclipse.ui.tests.dialogs.UIDialogsAuto.testCopyMoveProject](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIDialogsAuto/testCopyMoveProject/)
[org.eclipse.ui.tests.dialogs.UIDialogsAuto.testEditorSelection](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIDialogsAuto/testEditorSelection/)
[org.eclipse.ui.tests.dialogs.UIWizardsAuto.testNewProjectPage1](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIWizardsAuto/testNewProjectPage1/)
[org.eclipse.ui.tests.dialogs.UIWizardsAuto.testNewProjectPage2](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIWizardsAuto/testNewProjectPage2/)
[org.eclipse.ui.tests.dialogs.DeprecatedUIWizardsAuto.testNewProjectPage1](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/DeprecatedUIWizardsAuto/testNewProjectPage1/)
[org.eclipse.ui.tests.dialogs.DeprecatedUIWizardsAuto.testNewProjectPage2](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/DeprecatedUIWizardsAuto/testNewProjectPage2/)
[org.eclipse.ui.tests.dialogs.UIPreferencesAuto.testDefaultTextEditorPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIPreferencesAuto/testDefaultTextEditorPref/)
[org.eclipse.ui.tests.dialogs.UIPreferencesAuto.testPerspectivesPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIPreferencesAuto/testPerspectivesPref/)
[org.eclipse.ui.tests.dialogs.UIPreferencesAuto.testAppearancePref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIPreferencesAuto/testAppearancePref/)
[org.eclipse.ui.tests.dialogs.UIPreferencesAuto.testWorkbenchPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIPreferencesAuto/testWorkbenchPref/)
[org.eclipse.ui.tests.dialogs.UIPreferencesAuto.testFileEditorsPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIPreferencesAuto/testFileEditorsPref/)
[org.eclipse.ui.tests.dialogs.UIPreferencesAuto.testLocalHistoryPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/UIPreferencesAuto/testLocalHistoryPref/)
[org.eclipse.ui.tests.compare.UIComparePreferencesAuto.testCompareViewersPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.compare/UIComparePreferencesAuto/testCompareViewersPref/)
[org.eclipse.ui.tests.dialogs.DeprecatedUIPreferencesAuto.testDefaultTextEditorPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/DeprecatedUIPreferencesAuto/testDefaultTextEditorPref/)
[org.eclipse.ui.tests.dialogs.DeprecatedUIPreferencesAuto.testPerspectivesPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/DeprecatedUIPreferencesAuto/testPerspectivesPref/)
[org.eclipse.ui.tests.dialogs.DeprecatedUIPreferencesAuto.testAppearancePref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/DeprecatedUIPreferencesAuto/testAppearancePref/)
[org.eclipse.ui.tests.dialogs.DeprecatedUIPreferencesAuto.testWorkbenchPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/DeprecatedUIPreferencesAuto/testWorkbenchPref/)
[org.eclipse.ui.tests.dialogs.DeprecatedUIPreferencesAuto.testFileEditorsPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/DeprecatedUIPreferencesAuto/testFileEditorsPref/)
[org.eclipse.ui.tests.dialogs.DeprecatedUIPreferencesAuto.testLocalHistoryPref](https://ci.eclipse.org/platform/job/eclipse.platform.ui/job/PR-1299/3/testReport/junit/org.eclipse.ui.tests.dialogs/DeprecatedUIPreferencesAuto/testLocalHistoryPref/)
In my local workspace however, where those calls have also been removed, those failures don't occur.
I agree that this is best tried out again at the start of next release. Perhaps at a smaller scope where it is first removed for setBackground(), rather than everywhere at once.
DeprecatedUIPreferencesAuto (whatever it does) sounds like smth to not even check. I fully agree on going smaller scope first.