m_controlsRegistry;
+
+ /**
+ * Prepares to screen shot: register controls. See
+ * {@link #registerControl(Control)} for details.
+ */
+ private void prepareScreenshot(Shell shell) {
+ m_controlsRegistry = new HashMap<>();
+ registerControl(shell);
+ registerByHandle(shell);
+ }
+
+ /**
+ * Registers the control to be checked in screen shot callback. Every control
+ * can be registered multiple times. The first image handle received for this
+ * control in callback is "root" for this control and should be bound as
+ * {@link Image}.
+ */
+ private void registerControl(Control control) {
+ // check size
+ Point size = control.getSize();
+ if (size.x == 0 || size.y == 0) {
+ return;
+ }
+ {
+ registerByHandle(control);
+ registerByHandle(control);
+ }
+ control.setData(OSSupport.WBP_IMAGE, null);
+ // traverse children
+ if (control instanceof Composite composite) {
+ for (Control child : composite.getChildren()) {
+ registerControl(child);
+ }
+ }
+ }
+
+ /**
+ * Tries to get the {@code handle} from {@code control}. If the handle exists,
+ * fills {@code m_needsImage}.
+ */
+ private void registerByHandle(Control control) {
+ GtkWidget widget = new GtkWidget(control);
+ if (widget.segment() != MemorySegment.NULL) {
+ m_controlsRegistry.put(widget, control);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Screen shot
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public void makeShots(Control control) {
+ Shell shell = control.getShell();
+ makeShots0(shell);
+ // check for decorations and draw if needed
+ drawDecorations(shell, shell.getDisplay());
+ }
+
+ /**
+ * Screen shot algorithm is the following:
+ *
+ *
+ * 1. Register controls which requires the image. See {@link #registerControl(Control)}.
+ * 2. Create the callback, binding the screenshot to the model. See {@link #makeShot(Shell, BiConsumer)
+ * 3. While traversing the gtk widgets/gdk windows, the callback returns native widget handle and the
+ * image. If the control corresponding to the widget handle has been found in the registry the
+ * received image is bound to the control (see {@link #bindImage(Display, Control, int)}).
+ * Otherwise, the image is disposed later (because it may be used in drawing).
+ *
+ */
+ private void makeShots0(final Shell shell) {
+ prepareScreenshot(shell);
+ final Set disposeImages = new HashSet<>();
+ // apply shot magic
+ makeShot(shell, (handle, image) -> {
+ // get the registered control by handle
+ Control imageForControl = m_controlsRegistry.get(handle);
+ if (imageForControl == null || !bindImage(imageForControl, image)) {
+ // this means given image handle used to draw the gtk widget internally
+ disposeImages.add(image);
+ }
+ });
+ // done, dispose image handles needed to draw internally.
+ for (Image image : disposeImages) {
+ image.dispose();
+ }
+ }
+
+ /**
+ * Causes taking the screen shot.
+ *
+ * @param shell the root {@link Shell} to capture.
+ * @param callback the callback instance for binding the snapshot to the data
+ * model. Can be null.
+ * @return the GdkPixmap* or cairo_surface_t* of {@link Shell}.
+ */
+ protected abstract Image makeShot(Shell shell, BiConsumer callback);
+
+ private boolean bindImage(final Control control, final Image image) {
+ if (control.getData(OSSupport.WBP_NEED_IMAGE) != null && control.getData(OSSupport.WBP_IMAGE) == null) {
+ control.setData(OSSupport.WBP_IMAGE, image);
+ return true;
+ }
+ return false;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Utils
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Draws decorations if available/applicable.
+ */
+ private void drawDecorations(Shell shell, final Display display) {
+ Image shellImage = (Image) shell.getData(OSSupport.WBP_IMAGE);
+ // draw title if any
+ if (shellImage != null && (shell.getStyle() & SWT.TITLE) != 0) {
+ Rectangle shellBounds = shell.getBounds();
+ Rectangle imageBounds = shellImage.getBounds();
+ Point offset = shell.toControl(shell.getLocation());
+ offset.x = -offset.x;
+ offset.y = -offset.y;
+ // adjust by menu bar size
+ if (shell.getMenuBar() != null) {
+ offset.y -= OSSupportLinux.getWidgetBounds(shell.getMenuBar()).height;
+ }
+ // draw
+ Image decoratedShellImage = new Image(display, shellBounds.width, shellBounds.height);
+ GC gc = new GC(decoratedShellImage);
+ // draw background
+ gc.setBackground(ColorConstants.titleBackground);
+ gc.fillRectangle(0, 0, shellBounds.width, shellBounds.height);
+ // title area gradient
+ gc.setForeground(ColorConstants.titleGradient);
+ gc.fillGradientRectangle(0, 0, shellBounds.width, offset.y, true);
+ int buttonGapX = offset.x - 1;
+ int nextPositionX;
+ // buttons and title
+ {
+ // menu button
+ Image buttonImage = Activator.getImage("decorations/button-menu-icon.png");
+ Rectangle buttonImageBounds = buttonImage.getBounds();
+ int buttonOffsetY = offset.y / 2 - buttonImageBounds.height / 2;
+ gc.drawImage(buttonImage, buttonGapX, buttonOffsetY);
+ nextPositionX = buttonGapX + buttonImageBounds.width + buttonGapX;
+ }
+ {
+ // close button
+ Image buttonImage = Activator.getImage("decorations/button-close-icon.png");
+ Rectangle buttonImageBounds = buttonImage.getBounds();
+ nextPositionX = shellBounds.width - buttonImageBounds.width - buttonGapX;
+ int buttonOffsetY = offset.y / 2 - buttonImageBounds.height / 2;
+ gc.drawImage(buttonImage, nextPositionX, buttonOffsetY);
+ nextPositionX -= buttonGapX + buttonImageBounds.width;
+ }
+ {
+ // maximize button
+ Image buttonImage = Activator.getImage("decorations/button-max-icon.png");
+ Rectangle buttonImageBounds = buttonImage.getBounds();
+ int buttonOffsetY = offset.y / 2 - buttonImageBounds.height / 2;
+ gc.drawImage(buttonImage, nextPositionX, buttonOffsetY);
+ nextPositionX -= buttonGapX + buttonImageBounds.width;
+ }
+ {
+ // minimize button
+ Image buttonImage = Activator.getImage("decorations/button-min-icon.png");
+ Rectangle buttonImageBounds = buttonImage.getBounds();
+ int buttonOffsetY = offset.y / 2 - buttonImageBounds.height / 2;
+ gc.drawImage(buttonImage, nextPositionX, buttonOffsetY);
+ }
+ // outline
+ gc.setForeground(TITLE_BORDER_COLOR_DARKEST);
+ gc.drawRectangle(offset.x - 1, offset.y - 1, imageBounds.width + 1, imageBounds.height + 1);
+ gc.setForeground(TITLE_BORDER_COLOR_DARKER);
+ gc.drawRectangle(offset.x - 2, offset.y - 2, imageBounds.width + 3, imageBounds.height + 3);
+ // shell screen shot
+ gc.drawImage(shellImage, offset.x, offset.y);
+ // done
+ gc.dispose();
+ shellImage.dispose();
+ shell.setData(OSSupport.WBP_IMAGE, decoratedShellImage);
+ }
+ }
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/Cairo.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/Cairo.java
new file mode 100644
index 000000000..5ce6f8ad9
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/Cairo.java
@@ -0,0 +1,187 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.cairo;
+
+import org.eclipse.wb.internal.os.linux.Native;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SymbolLookup;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+
+/**
+ * The Cairo toolkit.
+ */
+public class Cairo extends Native {
+ private static final SymbolLookup CAIRO = SymbolLookup.libraryLookup("libcairo.so.2", Arena.ofAuto());
+
+ private static final MethodHandle cairo_clip = createHandle(CAIRO, "cairo_clip",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
+
+ private static final MethodHandle cairo_create = createHandle(CAIRO, "cairo_create",
+ FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS));
+
+ private static final MethodHandle cairo_destroy = createHandle(CAIRO, "cairo_destroy",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
+
+ private static final MethodHandle cairo_image_surface_create = createHandle(CAIRO, "cairo_image_surface_create",
+ FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
+
+ private static final MethodHandle cairo_paint = createHandle(CAIRO, "cairo_paint",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
+
+ private static final MethodHandle cairo_region_destroy = createHandle(CAIRO, "cairo_region_destroy",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
+
+ private static final MethodHandle cairo_set_operator = createHandle(CAIRO, "cairo_set_operator",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
+
+ private static final MethodHandle cairo_surface_flush = createHandle(CAIRO, "cairo_surface_flush",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
+
+ /**
+ * Establishes a new clip region by intersecting the current clip region with
+ * the current path as it would be filled by {@code cairo_fill()} and according
+ * to the current fill rule (see {@code cairo_set_fill_rule()}).
+ *
+ * After {@code cairo_clip()}, the current path will be cleared from the cairo
+ * context.
+ *
+ * The current clip region affects all drawing operations by effectively masking
+ * out any changes to the surface that are outside the current clip region.
+ *
+ * Calling {@code cairo_clip()} can only make the clip region smaller, never
+ * larger. But the current clip is part of the graphics state, so a temporary
+ * restriction of the clip region can be achieved by calling
+ * {@code cairo_clip()} within a {@code cairo_save()}/{@code cairo_restore()}
+ * pair. The only other means of increasing the size of the clip region is
+ * {@code cairo_reset_clip()}.
+ *
+ * @param cr a cairo context
+ */
+ public static void cairo_clip(CairoContext cr) {
+ runSafe(() -> cairo_clip.invoke(cr.segment()));
+ }
+
+ /**
+ * Creates a new {@code cairo_t} with all graphics state parameters set to
+ * default values and with target as a target surface. The target surface should
+ * be constructed with a backend-specific function such as
+ * {@code cairo_image_surface_create()} (or any other
+ * {@code cairo_backend_surface_create()} variant).
+ *
+ * This function references {@code target}, so you can immediately call
+ * {@code cairo_surface_destroy()} on it if you don't need to maintain a
+ * separate reference to it.
+ *
+ * @param target target surface for the context
+ * @return a newly allocated {@code cairo_t} with a reference count of 1. The
+ * initial reference count should be released with
+ * {@code cairo_destroy()} when you are done using the cairo_t. This
+ * function never returns NULL. If memory cannot be allocated, a special
+ * {@code cairo_t} object will be returned on which
+ * {@code cairo_status()} returns {@code CAIRO_STATUS_NO_MEMORY}. If you
+ * attempt to target a surface which does not support writing (such as
+ * {@code cairo_mime_surface_t}) then a {@code CAIRO_STATUS_WRITE_ERROR}
+ * will be raised. You can use this object normally, but no drawing will
+ * be done.
+ */
+ public static CairoContext cairo_create(CairoSurface target) {
+ MemorySegment handle = (MemorySegment) callSafe(() -> cairo_create.invoke(target.segment()));
+ return new CairoContext(handle);
+ }
+
+ /**
+ * Decreases the reference count on {@code cr} by one. If the result is zero,
+ * then {@code cr} and all associated resources are freed. See
+ * cairo_reference().
+ *
+ * @param cr a {@code cairo_t}
+ */
+ public static void cairo_destroy(CairoContext cr) {
+ runSafe(() -> cairo_destroy.invoke(cr.segment()));
+ }
+
+ /**
+ * Creates an image surface of the specified format and dimensions. Initially
+ * the surface contents are set to 0. (Specifically, within each pixel, each
+ * color or alpha channel belonging to format will be 0. The contents of bits
+ * within a pixel, but not belonging to the given format are undefined).
+ *
+ * @param format format of pixels in the surface to create
+ * @param width width of the surface, in pixels
+ * @param height height of the surface, in pixels
+ * @return a pointer to the newly created surface. The caller owns the surface
+ * and should call {@code cairo_surface_destroy()} when done with it.
+ *
+ * This function always returns a valid pointer, but it will return a
+ * pointer to a "nil" surface if an error such as out of memory occurs.
+ * You can use {@code cairo_surface_status()} to check for this.
+ *
+ */
+ public static CairoSurface cairo_image_surface_create(CairoFormat format, int width, int height) {
+ MemorySegment handle = (MemorySegment) callSafe(() -> cairo_image_surface_create.invoke(format.getValue(), width, height));
+ return new CairoSurface(handle);
+ }
+
+ /**
+ * A drawing operator that paints the current source everywhere within the
+ * current clip region.
+ *
+ * @param cr a cairo context
+ */
+ public static void cairo_paint(CairoContext cr) {
+ runSafe(() -> cairo_paint.invoke(cr.segment()));
+ }
+
+ /**
+ * Destroys a {@code cairo_region_t} object created with
+ * {@code cairo_region_create()}, {@code cairo_region_copy()}, or or
+ * {@code cairo_region_create_rectangle()}.
+ *
+ * @param region a {@code cairo_region_t}
+ */
+ public static void cairo_region_destroy(CairoRegion region) {
+ runSafe(() -> cairo_region_destroy.invoke(region.segment()));
+ }
+
+ /**
+ * Sets the compositing operator to be used for all drawing operations. See
+ * {@code cairo_operator_t} for details on the semantics of each available
+ * compositing operator.
+ *
+ * The default operator is {@code CAIRO_OPERATOR_OVER}.
+ *
+ *
+ * @param cr a {@code cairo_t}
+ * @param op a compositing operator, specified as a {@code cairo_operator_t}
+ */
+ public static void cairo_set_operator(CairoContext cr, CairoOperator op) {
+ runSafe(() -> cairo_set_operator.invoke(cr.segment(), op.getValue()));
+ }
+
+ /**
+ * Do any pending drawing for the surface and also restore any temporary
+ * modifications cairo has made to the surface's state. This function must be
+ * called before switching from drawing on the surface with cairo to drawing on
+ * it directly with native APIs, or accessing its memory outside of Cairo. If
+ * the surface doesn't support direct access, then this function does nothing.
+ *
+ * @param surface a {@code cairo_surface_t}
+ */
+ public static void cairo_surface_flush(CairoSurface surface) {
+ runSafe(() -> cairo_surface_flush.invoke(surface.segment()));
+ }
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoContext.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoContext.java
new file mode 100644
index 000000000..e0035d4bf
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoContext.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.cairo;
+
+import java.lang.foreign.MemorySegment;
+
+/**
+ * A {@code cairo_t} contains the current state of the rendering device,
+ * including coordinates of yet to be drawn shapes.
+ *
+ * Cairo contexts, as {@code cairo_t} objects are named, are central to cairo
+ * and all drawing with cairo is always done to a cairo_t object.
+ */
+public record CairoContext(MemorySegment segment) {
+
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoFormat.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoFormat.java
new file mode 100644
index 000000000..261495e5f
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoFormat.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.cairo;
+
+/**
+ * {@code cairo_format_t} is used to identify the memory format of image data.
+ *
+ * New entries may be added in future versions.
+ */
+public enum CairoFormat {
+ /**
+ * no such format exists or is supported.
+ */
+ CAIRO_FORMAT_INVALID(-1),
+ /**
+ * each pixel is a 32-bit quantity, with alpha in the upper 8 bits, then red,
+ * then green, then blue. The 32-bit quantities are stored native-endian.
+ * Pre-multiplied alpha is used. (That is, 50% transparent red is 0x80800000,
+ * not 0x80ff0000.) (Since 1.0)
+ */
+ CAIRO_FORMAT_ARGB32(0),
+ /**
+ * each pixel is a 32-bit quantity, with the upper 8 bits unused. Red, Green,
+ * and Blue are stored in the remaining 24 bits in that order. (Since 1.0)
+ */
+ CAIRO_FORMAT_RGB24(1),
+ /**
+ * each pixel is a 8-bit quantity holding an alpha value. (Since 1.0)
+ */
+ CAIRO_FORMAT_A8(2),
+ /**
+ * each pixel is a 1-bit quantity holding an alpha value. Pixels are packed
+ * together into 32-bit quantities. The ordering of the bits matches the
+ * endianness of the platform. On a big-endian machine, the first pixel is in
+ * the uppermost bit, on a little-endian machine the first pixel is in the
+ * least-significant bit. (Since 1.0)
+ */
+ CAIRO_FORMAT_A1(3),
+ /**
+ * each pixel is a 16-bit quantity with red in the upper 5 bits, then green in
+ * the middle 6 bits, and blue in the lower 5 bits. (Since 1.2)
+ */
+ CAIRO_FORMAT_RGB16_565(4),
+ /**
+ * like RGB24 but with 10bpc. (Since 1.12)
+ */
+ CAIRO_FORMAT_RGB30(5),
+ /**
+ * 3 floats, R, G, B. (Since 1.17.2)
+ */
+ CAIRO_FORMAT_RGB96F(6),
+ /**
+ * 4 floats, R, G, B, A. (Since 1.17.2)
+ */
+ CAIRO_FORMAT_RGBA128F(7);
+
+ private final int value;
+
+ private CairoFormat(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoOperator.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoOperator.java
new file mode 100644
index 000000000..d1a1a5410
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoOperator.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.cairo;
+
+/**
+ * {@code CairoOperator} is used to set the compositing operator for all cairo
+ * drawing operations.
+ *
+ * The default operator is {@link #CAIRO_OPERATOR_OVER}.
+ *
+ * The operators marked as unbounded modify their destination even outside of
+ * the mask layer (that is, their effect is not bound by the mask layer).
+ * However, their effect can still be limited by way of clipping.
+ *
+ * To keep things simple, the operator descriptions here document the behavior
+ * for when both source and destination are either fully transparent or fully
+ * opaque. The actual implementation works for translucent layers too. For a
+ * more detailed explanation of the effects of each operator, including the
+ * mathematical definitions, see https://cairographics.org/operators/.
+ */
+public enum CairoOperator {
+ /**
+ * clear destination layer (bounded) (Since 1.0)
+ */
+ CAIRO_OPERATOR_CLEAR(0),
+ /**
+ * replace destination layer (bounded) (Since 1.0)
+ */
+ CAIRO_OPERATOR_SOURCE(1),
+ /**
+ * draw source layer on top of destination layer (bounded) (Since 1.0)
+ */
+ CAIRO_OPERATOR_OVER(2),
+ /**
+ * draw source where there was destination content (unbounded) (Since 1.0)
+ */
+ CAIRO_OPERATOR_IN(3),
+ /**
+ * draw source where there was no destination content (unbounded) (Since 1.0)
+ */
+ CAIRO_OPERATOR_OUT(4),
+ /**
+ * draw source on top of destination content and only there (Since 1.0)
+ */
+ CAIRO_OPERATOR_ATOP(5),
+ /**
+ * ignore the source (Since 1.0)
+ */
+ CAIRO_OPERATOR_DEST(6),
+ /**
+ * draw destination on top of source (Since 1.0)
+ */
+ CAIRO_OPERATOR_DEST_OVER(7),
+ /**
+ * leave destination only where there was source content (unbounded) (Since 1.0)
+ */
+ CAIRO_OPERATOR_DEST_IN(8),
+ /**
+ * leave destination only where there was no source content (Since 1.0)
+ */
+ CAIRO_OPERATOR_DEST_OUT(9),
+ /**
+ * leave destination on top of source content and only there (unbounded) (Since
+ * 1.0)
+ */
+ CAIRO_OPERATOR_DEST_ATOP(10),
+ /**
+ * source and destination are shown where there is only one of them (Since 1.0)
+ */
+ CAIRO_OPERATOR_XOR(11),
+ /**
+ * source and destination layers are accumulated (Since 1.0)
+ */
+ CAIRO_OPERATOR_ADD(12),
+ /**
+ * like over, but assuming source and dest are disjoint geometries (Since 1.0)
+ */
+ CAIRO_OPERATOR_SATURATE(13),
+ /**
+ * source and destination layers are multiplied. This causes the result to be at
+ * least as dark as the darker inputs. (Since 1.10)
+ */
+ CAIRO_OPERATOR_MULTIPLY(14),
+ /**
+ * source and destination are complemented and multiplied. This causes the
+ * result to be at least as light as the lighter inputs. (Since 1.10)
+ */
+ CAIRO_OPERATOR_SCREEN(15),
+ /**
+ * multiplies or screens, depending on the lightness of the destination color.
+ * (Since 1.10)
+ */
+ CAIRO_OPERATOR_OVERLAY(16),
+ /**
+ * replaces the destination with the source if it is darker, otherwise keeps the
+ * source. (Since 1.10)
+ */
+ CAIRO_OPERATOR_DARKEN(17),
+ /**
+ * replaces the destination with the source if it is lighter, otherwise keeps
+ * the source. (Since 1.10)
+ */
+ CAIRO_OPERATOR_LIGHTEN(18),
+ /**
+ * brightens the destination color to reflect the source color. (Since 1.10)
+ */
+ CAIRO_OPERATOR_COLOR_DODGE(19),
+ /**
+ * darkens the destination color to reflect the source color. (Since 1.10)
+ */
+ CAIRO_OPERATOR_COLOR_BURN(20),
+ /**
+ * Multiplies or screens, dependent on source color. (Since 1.10)
+ */
+ CAIRO_OPERATOR_HARD_LIGHT(21),
+ /**
+ * Darkens or lightens, dependent on source color. (Since 1.10)
+ */
+ CAIRO_OPERATOR_SOFT_LIGHT(22),
+ /**
+ * Takes the difference of the source and destination color. (Since 1.10)
+ */
+ CAIRO_OPERATOR_DIFFERENCE(23),
+ /**
+ * Produces an effect similar to difference, but with lower contrast. (Since
+ * 1.10)
+ */
+ CAIRO_OPERATOR_EXCLUSION(24),
+ /**
+ * Creates a color with the hue of the source and the saturation and luminosity
+ * of the target. (Since 1.10)
+ */
+ CAIRO_OPERATOR_HSL_HUE(25),
+ /**
+ * Creates a color with the saturation of the source and the hue and luminosity
+ * of the target. Painting with this mode onto a gray area produces no change.
+ * (Since 1.10)
+ */
+ CAIRO_OPERATOR_HSL_SATURATION(26),
+ /**
+ * Creates a color with the hue and saturation of the source and the luminosity
+ * of the target. This preserves the gray levels of the target and is useful for
+ * coloring monochrome images or tinting color images. (Since 1.10)
+ */
+ CAIRO_OPERATOR_HSL_COLOR(27),
+ /**
+ * Creates a color with the luminosity of the source and the hue and saturation
+ * of the target. This produces an inverse effect to
+ * {@link CAIRO_OPERATOR_HSL_COLOR} . (Since 1.10)
+ */
+ CAIRO_OPERATOR_HSL_LUMINOSITY(28);
+
+ private final int value;
+
+ private CairoOperator(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoRegion.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoRegion.java
new file mode 100644
index 000000000..b0cbcc512
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoRegion.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.cairo;
+
+import java.lang.foreign.MemorySegment;
+
+/**
+ * A {@code cairo_region_t}.
+ */
+public record CairoRegion(MemorySegment segment) {
+
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoSurface.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoSurface.java
new file mode 100644
index 000000000..b66632d2d
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoSurface.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.cairo;
+
+import java.lang.foreign.MemorySegment;
+
+/**
+ * Base class for surfaces.
+ */
+public record CairoSurface(MemorySegment segment) {
+
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GDK3.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GDK3.java
new file mode 100644
index 000000000..d1045f5c5
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GDK3.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.gtk3;
+
+import org.eclipse.wb.internal.os.linux.GDK;
+import org.eclipse.wb.internal.os.linux.cairo.CairoContext;
+import org.eclipse.wb.internal.os.linux.cairo.CairoRegion;
+
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+
+/**
+ * The GDK toolkit compatible with GDK 3.x
+ */
+public class GDK3 extends GDK {
+
+ private static class InstanceHolder {
+ private static final MethodHandle gdk_cairo_set_source_window = createHandle(GDK, "gdk_cairo_set_source_window",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE));
+
+ private static final MethodHandle gdk_window_get_height = createHandle(GDK, "gdk_window_get_height",
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
+
+ private static final MethodHandle gdk_window_get_visible_region = createHandle(GDK, "gdk_window_get_visible_region",
+ FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS));
+
+ private static final MethodHandle gdk_window_get_width = createHandle(GDK, "gdk_window_get_width",
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
+
+ private static final MethodHandle gdk_window_is_visible = createHandle(GDK, "gdk_window_is_visible",
+ FunctionDescriptor.of(ValueLayout.JAVA_BOOLEAN, ValueLayout.ADDRESS));
+
+ private static final MethodHandle gdk_window_process_updates = createHandle(GDK, "gdk_window_process_updates",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_BOOLEAN));
+ }
+
+ /**
+ * Sets the given window as the source pattern for {@code cr}.
+ *
+ * The pattern has an extend mode of {@code CAIRO_EXTEND_NONE} and is aligned so
+ * that the origin of window is {@code x}, {@code y}. The window contains all
+ * its subwindows when rendering.
+ *
+ * Note that the contents of window are undefined outside of the visible part of
+ * {@code window}, so use this function with care.
+ *
+ * @param cr A cairo context.
+ * @param window A {@link GdkWindow}.
+ * @param x X coordinate of location to place upper left corner of
+ * {@code window}.
+ * @param y Y coordinate of location to place upper left corner of
+ * {@code window}.
+ */
+ public static void gdk_cairo_set_source_window(CairoContext cr, GdkWindow window, double x, double y) {
+ runSafe(() -> InstanceHolder.gdk_cairo_set_source_window.invoke(cr.segment(), window.segment(), x, y));
+ }
+
+ /**
+ * Returns the height of the given window.
+ *
+ * On the X11 platform the returned size is the size reported in the
+ * most-recently-processed configure event, rather than the current size on the
+ * X server.
+ *
+ * @return The height of {@code window}.
+ */
+ public static int gdk_window_get_height(GdkWindow window) {
+ return (int) callSafe(() -> InstanceHolder.gdk_window_get_height.invoke(window.segment()));
+ }
+
+ /**
+ * Computes the region of the {@code window} that is potentially visible. This
+ * does not necessarily take into account if the window is obscured by other
+ * windows, but no area outside of this region is visible.
+ *
+ * @return A {@link CairoRegion}. This must be freed with cairo_region_destroy()
+ * when you are done.
+ */
+ public static CairoRegion gdk_window_get_visible_region(GdkWindow window) {
+ MemorySegment handle = (MemorySegment) callSafe(() -> InstanceHolder.gdk_window_get_visible_region.invoke(window.segment()));
+ return new CairoRegion(handle);
+ }
+
+ /**
+ * Returns the width of the given window.
+ *
+ * On the X11 platform the returned size is the size reported in the
+ * most-recently-processed configure event, rather than the current size on the
+ * X server.
+ *
+ * @return The width of {@code window}.
+ */
+ public static int gdk_window_get_width(GdkWindow window) {
+ return (int) callSafe(() -> InstanceHolder.gdk_window_get_width.invoke(window.segment()));
+ }
+
+ /**
+ * Checks whether the window has been mapped (with {@code gdk_window_show()} or
+ * {@code gdk_window_show_unraised())}.
+ *
+ * @return {@code true} if the window is mapped.
+ */
+ public static boolean gdk_window_is_visible(GdkWindow window) {
+ return (boolean) callSafe(() -> InstanceHolder.gdk_window_is_visible.invoke(window.segment()));
+ }
+
+ /**
+ * Sends one or more expose events to {@code window}. The areas in each expose
+ * event will cover the entire update area for the window (see
+ * {@code gdk_window_invalidate_region()} for details). Normally GDK calls
+ * {@code gdk_window_process_all_updates()} on your behalf, so there’s no need
+ * to call this function unless you want to force expose events to be delivered
+ * immediately and synchronously (vs. the usual case, where GDK delivers them in
+ * an idle handler). Occasionally this is useful to produce nicer scrolling
+ * behavior, for example.
+ *
+ * @param update_children Whether to also process updates for child windows.
+ */
+ public static void gdk_window_process_updates(GdkWindow window, boolean update_children) {
+ runSafe(() -> InstanceHolder.gdk_window_process_updates.invoke(window.segment(), update_children));
+ }
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GTK3.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GTK3.java
new file mode 100644
index 000000000..903e400a8
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GTK3.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.gtk3;
+
+import org.eclipse.wb.internal.os.linux.GTK;
+import org.eclipse.wb.internal.os.linux.GtkWidget;
+
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+
+/**
+ * The GTK toolkit compatible with GTK 3.x
+ */
+public final class GTK3 extends GTK {
+
+ private static class InstanceHolder {
+ private static final MethodHandle gtk_widget_get_window = createHandle(GTK, "gtk_widget_get_window",
+ FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS));
+
+ private static final MethodHandle gtk_window_set_keep_above = createHandle(GTK, "gtk_window_set_keep_above",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_BOOLEAN));
+
+ private static final MethodHandle gtk_widget_show_now = createHandle(GTK, "gtk_widget_show_now",
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
+ }
+
+ /**
+ * Returns the widget’s window if it is realized, {@code NULL} otherwise.
+ *
+ * @return {@code widget}’s window.
+ */
+ public static GdkWindow gtk_widget_get_window(GtkWidget widget) {
+ MemorySegment handle = (MemorySegment) callSafe(() -> InstanceHolder.gtk_widget_get_window.invoke(widget.segment()));
+ return new GdkWindow(handle);
+ }
+
+ /**
+ * Shows a widget. If the widget is an unmapped toplevel widget (i.e. a
+ * {@code GtkWindow} that has not yet been shown), enter the main loop and wait
+ * for the window to actually be mapped. Be careful; because the main loop is
+ * running, anything can happen during this function.
+ */
+ public static void gtk_widget_show_now(GtkWidget widget) {
+ runSafe(() -> InstanceHolder.gtk_widget_show_now.invoke(widget.segment()));
+ }
+
+ /**
+ * Asks to keep {@code window} above, so that it stays on top. Note that you
+ * shouldn’t assume the window is definitely above afterward, because other
+ * entities (e.g. the user or [window manager][gtk-X11-arch]) could not keep it
+ * above, and not all window managers support keeping windows above. But
+ * normally the window will end kept above. Just don’t write code that crashes
+ * if not.
+ *
+ * It’s permitted to call this function before showing a window, in which case
+ * the window will be kept above when it appears onscreen initially.
+ *
+ * You can track the above state via the “window-state-event” signal on
+ * {@code GtkWidget}.
+ *
+ * Note that, according to the Extended Window Manager Hints Specification, the
+ * above state is mainly meant for user preferences and should not be used by
+ * applications e.g. for drawing attention to their dialogs.
+ *
+ * @param setting Whether to keep {@code window} above other windows.
+ */
+ public static void gtk_window_set_keep_above(GtkWindow window, boolean setting) {
+ runSafe(() -> InstanceHolder.gtk_window_set_keep_above.invoke(window.segment(), setting));
+ }
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GTK3ScreenshotMaker.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GTK3ScreenshotMaker.java
new file mode 100644
index 000000000..e6ff0436e
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GTK3ScreenshotMaker.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2011, 2025 Google, Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.gtk3;
+
+import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
+import org.eclipse.wb.internal.os.linux.GDK;
+import org.eclipse.wb.internal.os.linux.GtkRuntimeException;
+import org.eclipse.wb.internal.os.linux.GtkWidget;
+import org.eclipse.wb.internal.os.linux.ScreenshotMaker;
+import org.eclipse.wb.internal.os.linux.cairo.Cairo;
+import org.eclipse.wb.internal.os.linux.cairo.CairoContext;
+import org.eclipse.wb.internal.os.linux.cairo.CairoFormat;
+import org.eclipse.wb.internal.os.linux.cairo.CairoOperator;
+import org.eclipse.wb.internal.os.linux.cairo.CairoRegion;
+import org.eclipse.wb.internal.os.linux.cairo.CairoSurface;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Widget;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Creates a screenshot of a given widget using the GTK3 API.
+ */
+public class GTK3ScreenshotMaker extends ScreenshotMaker {
+
+ @Override
+ protected Image makeShot(Shell shell, BiConsumer callback) {
+ return traverse(shell, callback);
+ }
+
+ private Image traverse(Widget widget, BiConsumer callback) {
+ Image image = getImageSurface(new GtkWidget(widget), callback);
+ if (image == null) {
+ return null;
+ }
+ if (widget instanceof Composite composite) {
+ for (Control childWidget : composite.getChildren()) {
+ Image childImage = traverse(childWidget, callback);
+ if (childImage == null) {
+ continue;
+ }
+ if (callback == null) {
+ childImage.dispose();
+ }
+ }
+ }
+ return image;
+ }
+
+ protected Image getImageSurface(GtkWidget widget, BiConsumer callback) {
+ GdkWindow window = GTK3.gtk_widget_get_window(widget);
+ if (!GDK3.gdk_window_is_visible(window)) {
+ // don't deal with unmapped windows
+ return null;
+ }
+
+ int width = GDK3.gdk_window_get_width(window);
+ int height = GDK3.gdk_window_get_height(window);
+ // force paint. Note, not all widgets do this completely, known so far is
+ // GtkTreeViewer.
+ GDK3.gdk_window_process_updates(window, true);
+ // take screenshot
+ Image image = createImage(window, width, height);
+ // get Java code notified
+ if (callback != null) {
+ callback.accept(widget, image);
+ }
+ // done
+ return image;
+ }
+
+ private Image createImage(GdkWindow sourceWindow, int width, int height) {
+ // Create the Cairo surface on which the snapshot is drawn on
+ CairoSurface targetSurface = Cairo.cairo_image_surface_create(CairoFormat.CAIRO_FORMAT_ARGB32, width, height);
+ CairoContext cr = Cairo.cairo_create(targetSurface);
+ // Get the visible region of the window
+ // Wayland: Trying to take a screenshot of a partially unmapped widget
+ // results in a SIGFAULT.
+ CairoRegion visibleRegion = GDK3.gdk_window_get_visible_region(sourceWindow);
+ // Set the visible region as the clip for the Cairo context
+ GDK.gdk_cairo_region(cr, visibleRegion);
+ Cairo.cairo_clip(cr);
+ // Paint the surface
+ GDK3.gdk_cairo_set_source_window(cr, sourceWindow, 0, 0);
+ Cairo.cairo_set_operator(cr, CairoOperator.CAIRO_OPERATOR_SOURCE);
+ Cairo.cairo_paint(cr);
+ // Cleanup
+ Cairo.cairo_destroy(cr);
+ Cairo.cairo_surface_flush(targetSurface);
+ Cairo.cairo_region_destroy(visibleRegion);
+ return createImage(targetSurface.segment().address());
+ }
+
+ private Image createImage(long imageHandle) {
+ Image image = createImage0(imageHandle);
+ // BUG in SWT: Image instance is not fully initialized
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=382175
+ Image newImage = new Image(null, image.getImageData());
+ image.dispose();
+ return newImage;
+ }
+
+ private Image createImage0(long imageHandle) {
+ try {
+ return (Image) ReflectionUtils.invokeMethod2( //
+ Image.class, //
+ "gtk_new", //
+ Device.class, //
+ int.class, //
+ long.class, //
+ long.class, //
+ null, //
+ SWT.BITMAP, //
+ imageHandle, //
+ 0);
+ } catch (Exception e) {
+ throw new GtkRuntimeException(e);
+ }
+ }
+
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GdkWindow.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GdkWindow.java
new file mode 100644
index 000000000..350687754
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GdkWindow.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.gtk3;
+
+import java.lang.foreign.MemorySegment;
+
+/**
+ * A GDK window.
+ */
+public record GdkWindow(MemorySegment segment) {
+
+}
diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GtkWindow.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GtkWindow.java
new file mode 100644
index 000000000..ac0af1210
--- /dev/null
+++ b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GtkWindow.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.os.linux.gtk3;
+
+import org.eclipse.wb.internal.os.linux.GtkWidget;
+
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A GtkWindow is a toplevel window which can contain other widgets. Windows
+ * normally have decorations that are under the control of the windowing system
+ * and allow the user to manipulate the window (resize it, move it, close it,…).
+ */
+public class GtkWindow extends GtkWidget {
+ public GtkWindow(Shell shell) {
+ super(shell);
+ }
+}
diff --git a/org.eclipse.wb.os/META-INF/MANIFEST.MF b/org.eclipse.wb.os/META-INF/MANIFEST.MF
index 8f880ea30..41aca42d0 100644
--- a/org.eclipse.wb.os/META-INF/MANIFEST.MF
+++ b/org.eclipse.wb.os/META-INF/MANIFEST.MF
@@ -8,7 +8,8 @@ Bundle-Vendor: %providerName
Require-Bundle: org.eclipse.ui;bundle-version="[3.206.0,4.0.0)",
org.eclipse.core.runtime;bundle-version="[3.31.100,4.0.0)"
Bundle-RequiredExecutionEnvironment: JavaSE-21
-Export-Package: org.eclipse.wb.os
+Export-Package: org.eclipse.wb.os,
+ org.eclipse.wb.os.internal;x-friends:="org.eclipse.wb.os.linux"
Bundle-ActivationPolicy: lazy
Bundle-Localization: plugin
Automatic-Module-Name: org.eclipse.wb.os
diff --git a/org.eclipse.wb.os/src/org/eclipse/wb/os/internal/OSRuntimeException.java b/org.eclipse.wb.os/src/org/eclipse/wb/os/internal/OSRuntimeException.java
new file mode 100644
index 000000000..8fd892e55
--- /dev/null
+++ b/org.eclipse.wb.os/src/org/eclipse/wb/os/internal/OSRuntimeException.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Patrick Ziegler and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.os.internal;
+
+/**
+ * Wrapper for all exceptions thrown while accessing native code.
+ */
+public class OSRuntimeException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public OSRuntimeException(Throwable t) {
+ super(t);
+ }
+}
diff --git a/target-platform/mvn/wb-mvn.target b/target-platform/mvn/wb-mvn.target
index 6268f7609..d74fa5124 100644
--- a/target-platform/mvn/wb-mvn.target
+++ b/target-platform/mvn/wb-mvn.target
@@ -52,6 +52,18 @@
4.0.4
jar
+
+ net.bytebuddy
+ byte-buddy-agent
+ 1.18.1
+ jar
+
+
+ net.bytebuddy
+ byte-buddy
+ 1.18.1
+ jar
+
org.apache.commons
commons-text
diff --git a/target-platform/wb-2024-06.target b/target-platform/wb-2024-06.target
index 404b61fd5..b09a289ac 100644
--- a/target-platform/wb-2024-06.target
+++ b/target-platform/wb-2024-06.target
@@ -21,6 +21,7 @@
+