Skip to content

Commit fa8044e

Browse files
committed
[GTK] Use Foreign Function & Memory API for GTK3 OS Support
This converts the GTK-specific "org.eclipse.wb.os.linux" plugin into a multi-release jar, to take advantage of the foreign function and memory API, while still remaining compatible with older Java versions. Rather than calling the native methods via our shared library, the invocations are delegated to the Java linker. If any exception is thrown in native code, this then leads to a `Throwable` being thrown, rather than a crash of the entire IDE.
1 parent 9d78b0c commit fa8044e

File tree

30 files changed

+1970
-4
lines changed

30 files changed

+1970
-4
lines changed

.mvn/maven.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
-Dtycho.version=5.0.0
1+
-Dtycho.version=5.0.1
22
-Djdk.xml.totalEntitySizeLimit=1000000
33
-Djdk.xml.maxGeneralEntitySizeLimit=1000000

Jenkinsfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pipeline {
1111

1212
tools {
1313
maven 'apache-maven-latest'
14-
jdk 'temurin-jdk21-latest'
14+
jdk 'temurin-jdk25-latest'
1515
}
1616

1717
environment {

org.eclipse.wb.doc.user/html-src/whatsnew/v123.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@ endif::[]
44

55
= What's New - v1.23.0
66

7+
== General
8+
9+
- [Linux] FFMA when using Java 24 or higher
10+
11+
The Linux-specific fragment will use the Foreign Function and Memory API when used with Java 24 or higher.
12+
713
What's new - xref:v122.adoc[*v1.22.0*]

org.eclipse.wb.os.linux/.classpath

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<classpath>
3-
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21"/>
3+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-24"/>
44
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
55
<classpathentry kind="src" path="src"/>
6+
<classpathentry kind="src" path="src_java24">
7+
<attributes>
8+
<attribute name="release" value="24"/>
9+
</attributes>
10+
</classpathentry>
611
<classpathentry kind="output" path="bin"/>
712
</classpath>

org.eclipse.wb.os.linux/META-INF/MANIFEST.MF

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Bundle-ActivationPolicy: lazy
1818
Bundle-Localization: plugin
1919
Import-Package: org.apache.commons.io;version="[2.16.1,3.0.0)",
2020
org.apache.commons.lang3;version="[3.14.0,4.0.0)",
21+
org.apache.commons.lang3.function;version="[3.14.0,4.0.0)",
2122
org.eclipse.wb.os
2223
Automatic-Module-Name: org.eclipse.wb.os.linux
24+
Multi-Release: true
2325
Provide-Capability: wbp;type=os
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2011, 2025 Google, Inc. and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* https://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Google, Inc. - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.wb.internal.os.linux;
14+
15+
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
16+
17+
import org.eclipse.core.runtime.IStatus;
18+
import org.eclipse.core.runtime.Plugin;
19+
import org.eclipse.core.runtime.Status;
20+
import org.eclipse.swt.graphics.Image;
21+
import org.eclipse.swt.widgets.Display;
22+
import org.eclipse.ui.plugin.AbstractUIPlugin;
23+
24+
import org.apache.commons.io.IOUtils;
25+
import org.osgi.framework.BundleContext;
26+
27+
import java.io.InputStream;
28+
import java.net.URL;
29+
import java.util.HashMap;
30+
import java.util.Map;
31+
32+
/**
33+
* The activator class controls the plug-in life cycle.
34+
*
35+
* @author mitin_aa
36+
* @coverage os.linux
37+
*/
38+
public class Activator extends AbstractUIPlugin {
39+
public static final String PLUGIN_ID = "org.eclipse.wb.os.linux";
40+
//
41+
private static Activator m_plugin;
42+
43+
////////////////////////////////////////////////////////////////////////////
44+
//
45+
// Life cycle
46+
//
47+
////////////////////////////////////////////////////////////////////////////
48+
@Override
49+
public void start(BundleContext context) throws Exception {
50+
super.start(context);
51+
m_plugin = this;
52+
}
53+
54+
@Override
55+
public void stop(BundleContext context) throws Exception {
56+
m_plugin = null;
57+
super.stop(context);
58+
}
59+
60+
/**
61+
* Returns the shared instance.
62+
*/
63+
public static Activator getDefault() {
64+
return m_plugin;
65+
}
66+
67+
public static void logError(String text, Throwable error) {
68+
getDefault().getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, text, error));
69+
}
70+
71+
////////////////////////////////////////////////////////////////////////////
72+
//
73+
// Files
74+
//
75+
////////////////////////////////////////////////////////////////////////////
76+
/**
77+
* @return the {@link InputStream} for file from plugin directory.
78+
*/
79+
public static InputStream getFile(String path) {
80+
try {
81+
URL url = new URL(getInstallURL(), path);
82+
return url.openStream();
83+
} catch (Throwable e) {
84+
throw ReflectionUtils.propagate(e);
85+
}
86+
}
87+
88+
/**
89+
* @return the install {@link URL} for this {@link Plugin}.
90+
*/
91+
public static URL getInstallURL() {
92+
return getInstallUrl(getDefault());
93+
}
94+
95+
/**
96+
* @return the install {@link URL} for given {@link Plugin}.
97+
*/
98+
private static URL getInstallUrl(Plugin plugin) {
99+
return plugin.getBundle().getEntry("/");
100+
}
101+
102+
////////////////////////////////////////////////////////////////////////////
103+
//
104+
// Images
105+
//
106+
////////////////////////////////////////////////////////////////////////////
107+
private static final Map<String, Image> m_nameToIconMap = new HashMap<>();
108+
109+
/**
110+
* @return the {@link Image} from "icons" directory.
111+
*/
112+
public static Image getImage(String path) {
113+
Image image = m_nameToIconMap.get(path);
114+
if (image == null) {
115+
InputStream is = getFile("icons/" + path);
116+
try {
117+
image = new Image(Display.getCurrent(), is);
118+
m_nameToIconMap.put(path, image);
119+
} finally {
120+
IOUtils.closeQuietly(is);
121+
}
122+
}
123+
return image;
124+
}
125+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Patrick Ziegler and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* https://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Patrick Ziegler - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.wb.internal.os.linux;
14+
15+
import org.eclipse.wb.internal.os.linux.cairo.CairoContext;
16+
import org.eclipse.wb.internal.os.linux.cairo.CairoRegion;
17+
18+
import java.lang.foreign.Arena;
19+
import java.lang.foreign.FunctionDescriptor;
20+
import java.lang.foreign.SymbolLookup;
21+
import java.lang.foreign.ValueLayout;
22+
import java.lang.invoke.MethodHandle;
23+
24+
/**
25+
* The GDK toolkit
26+
*/
27+
public class GDK extends Native {
28+
protected static final SymbolLookup GDK;
29+
30+
static {
31+
if (isGtk4()) {
32+
GDK = SymbolLookup.libraryLookup("libgdk-4.so.0", Arena.ofAuto());
33+
} else {
34+
GDK = SymbolLookup.libraryLookup("libgdk-3.so.0", Arena.ofAuto());
35+
}
36+
}
37+
38+
private static class InstanceHolder {
39+
private static final MethodHandle gdk_cairo_region = createHandle(GDK, "gdk_cairo_region",
40+
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS));
41+
}
42+
43+
/**
44+
* Adds the given region to the current path of {@code cr}.
45+
*
46+
* @param cr A cairo context.
47+
* @param region A {@code cairo_region_t}.
48+
*/
49+
public static void gdk_cairo_region(CairoContext cr, CairoRegion region) {
50+
runSafe(() -> InstanceHolder.gdk_cairo_region.invoke(cr.segment(), region.segment()));
51+
}
52+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Patrick Ziegler and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* https://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Patrick Ziegler - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.wb.internal.os.linux;
14+
15+
import java.lang.foreign.Arena;
16+
import java.lang.foreign.FunctionDescriptor;
17+
import java.lang.foreign.SymbolLookup;
18+
import java.lang.foreign.ValueLayout;
19+
import java.lang.invoke.MethodHandle;
20+
21+
/**
22+
* The GTK toolkit
23+
*/
24+
public abstract class GTK extends Native {
25+
protected static final SymbolLookup GTK;
26+
27+
static {
28+
if (isGtk4()) {
29+
GTK = SymbolLookup.libraryLookup("libgtk-4.so.0", Arena.ofAuto());
30+
} else {
31+
GTK = SymbolLookup.libraryLookup("libgtk-3.so.0", Arena.ofAuto());
32+
}
33+
}
34+
35+
private static class InstanceHolder {
36+
private static final MethodHandle gtk_widget_get_allocated_baseline = createHandle(GTK, "gtk_widget_get_allocated_baseline",
37+
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
38+
39+
private static final MethodHandle gtk_get_major_version = createHandle(GTK, "gtk_get_major_version",
40+
FunctionDescriptor.of(ValueLayout.JAVA_INT));
41+
42+
private static final MethodHandle gtk_get_minor_version = createHandle(GTK, "gtk_get_minor_version",
43+
FunctionDescriptor.of(ValueLayout.JAVA_INT));
44+
45+
private static final MethodHandle gtk_get_micro_version = createHandle(GTK, "gtk_get_micro_version",
46+
FunctionDescriptor.of(ValueLayout.JAVA_INT));
47+
48+
private static final MethodHandle gtk_widget_get_allocation = createHandle(GTK, "gtk_widget_get_allocation",
49+
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS));
50+
51+
private static final MethodHandle gtk_widget_hide = createHandle(GTK, "gtk_widget_hide",
52+
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
53+
}
54+
55+
/**
56+
* @return The major version number of the GTK+ library.
57+
*/
58+
public static int gtk_get_major_version() {
59+
return (int) callSafe(() -> InstanceHolder.gtk_get_major_version.invoke());
60+
}
61+
62+
/**
63+
* @return The minor version number of the GTK+ library.
64+
*/
65+
public static int gtk_get_minor_version() {
66+
return (int) callSafe(() -> InstanceHolder.gtk_get_minor_version.invoke());
67+
}
68+
69+
/**
70+
* @return The micro version number of the GTK+ library.
71+
*/
72+
public static int gtk_get_micro_version() {
73+
return (int) callSafe(() -> InstanceHolder.gtk_get_micro_version.invoke());
74+
}
75+
76+
/**
77+
* Retrieves the widget’s allocation.
78+
*
79+
* Note, when implementing a {@code GtkContainer}: a widget’s allocation will be
80+
* its “adjusted” allocation, that is, the widget’s parent container typically
81+
* calls {@code gtk_widget_size_allocate()} with an allocation, and that
82+
* allocation is then adjusted (to handle margin and alignment for example)
83+
* before assignment to the widget.
84+
*
85+
* {@code gtk_widget_get_allocation()} returns the adjusted allocation that was
86+
* actually assigned to the widget. The adjusted allocation is guaranteed to be
87+
* completely contained within the {@code gtk_widget_size_allocate()}
88+
* allocation, however.
89+
*
90+
* So a {@code GtkContainer} is guaranteed that its children stay inside the
91+
* assigned bounds, but not that they have exactly the bounds the container
92+
* assigned. There is no way to get the original allocation assigned by
93+
* {@code gtk_widget_size_allocate()}, since it isn’t stored; if a container
94+
* implementation needs that information it will have to track it itself.
95+
*/
96+
public static void gtk_widget_get_allocation(GtkWidget widget, GtkAllocation allocation) {
97+
runSafe(() -> InstanceHolder.gtk_widget_get_allocation.invoke(widget.segment(), allocation.segment()));
98+
}
99+
100+
/**
101+
* Reverses the effects of {@code gtk_widget_show()}, causing the {@code widget}
102+
* to be hidden (invisible to the user).
103+
*/
104+
public static void gtk_widget_hide(GtkWidget widget) {
105+
runSafe(() -> InstanceHolder.gtk_widget_hide.invoke(widget.segment()));
106+
}
107+
108+
/**
109+
* Returns the baseline that has currently been allocated to {@code widget} or
110+
* -1, if none.
111+
*/
112+
public static int gtk_widget_get_allocated_baseline(GtkWidget widget) {
113+
return (int) callSafe(() -> InstanceHolder.gtk_widget_get_allocated_baseline.invoke(widget.segment()));
114+
}
115+
}

0 commit comments

Comments
 (0)