From 25a808cf45dc2400f282eff356348cfc529bab16 Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Thu, 18 Jun 2026 19:42:47 +0200 Subject: [PATCH] Rewrite GTK3 screenshot logic to work with Wayland #633 For the longest time, screenshots on Linux were done by capturing parts of the window where a given widget was located. This approach is inherently incompatible with constraints imposed by Wayland, resulting in a blank preview. Instead of working on the window, we directly work on the widget (similar to the SWT implementation). This approach requires the widget to be fully rendered. Note that this new implementation is only available with Java 24 or higher. Closes https://github.com/eclipse-windowbuilder/windowbuilder/issues/633 --- .../html-src/whatsnew/v125.asciidoc | 14 ++ .../{cairo/CairoContext.java => Cairo.java} | 21 +- .../org/eclipse/wb/internal/os/linux/GDK.java | 52 ----- .../wb/internal/os/linux/cairo/Cairo.java | 187 ------------------ .../internal/os/linux/cairo/CairoFormat.java | 76 ------- .../os/linux/cairo/CairoOperator.java | 173 ---------------- .../internal/os/linux/cairo/CairoRegion.java | 22 --- .../internal/os/linux/cairo/CairoSurface.java | 22 --- .../wb/internal/os/linux/gtk3/GDK3.java | 134 ------------- .../wb/internal/os/linux/gtk3/GTK3.java | 96 +++++++-- .../os/linux/gtk3/GTK3ScreenshotMaker.java | 95 ++------- .../{GdkWindow.java => GtkRequisition.java} | 19 +- 12 files changed, 153 insertions(+), 758 deletions(-) create mode 100644 org.eclipse.wb.doc.user/html-src/whatsnew/v125.asciidoc rename org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/{cairo/CairoContext.java => Cairo.java} (57%) delete mode 100644 org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/GDK.java delete mode 100644 org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/Cairo.java delete mode 100644 org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoFormat.java delete mode 100644 org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoOperator.java delete mode 100644 org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoRegion.java delete mode 100644 org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoSurface.java delete mode 100644 org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GDK3.java rename org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/{GdkWindow.java => GtkRequisition.java} (55%) diff --git a/org.eclipse.wb.doc.user/html-src/whatsnew/v125.asciidoc b/org.eclipse.wb.doc.user/html-src/whatsnew/v125.asciidoc new file mode 100644 index 0000000000..d6d568add4 --- /dev/null +++ b/org.eclipse.wb.doc.user/html-src/whatsnew/v125.asciidoc @@ -0,0 +1,14 @@ +ifdef::env-github[] +:imagesdir: ../../html/whatsnew +endif::[] + += What's New - v1.25.0 + +== General + +- [Linux] Wayland Support for SWT Designer + +The screenshot mechanism for GTK3 has been adapted to support both X11 and Wayland. _Note_ This support is only +available with Java 24 or higher. + +What's new - xref:v124.adoc[*v1.24.0*] \ No newline at end of file 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.java similarity index 57% rename from org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoContext.java rename to org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/Cairo.java index 6d35703dc3..531dc2dece 100644 --- 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.java @@ -10,7 +10,11 @@ * Contributors: * Patrick Ziegler - initial API and implementation *******************************************************************************/ -package org.eclipse.wb.internal.os.linux.cairo; +package org.eclipse.wb.internal.os.linux; + +import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils; + +import org.eclipse.swt.graphics.GC; import java.lang.foreign.MemorySegment; @@ -21,6 +25,19 @@ * 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) { +public record Cairo(MemorySegment segment) { + /** + * Creates a new CairoContext instance associated with the GC object. + * + * @param gc The context to paint on. + * @return The Cairo context backed by the given GC. + */ + public static Cairo from(GC gc) { + long handle = getHandleValue(gc, "handle"); + return new Cairo(MemorySegment.ofAddress(handle)); + } + private static long getHandleValue(GC gc, String fieldName) { + return ReflectionUtils.getFieldLong(gc, fieldName); + } } diff --git a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/GDK.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/GDK.java deleted file mode 100644 index f0eba986f8..0000000000 --- a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/GDK.java +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2026 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; - -import org.eclipse.wb.internal.os.linux.cairo.CairoContext; -import org.eclipse.wb.internal.os.linux.cairo.CairoRegion; - -import java.lang.foreign.Arena; -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.SymbolLookup; -import java.lang.foreign.ValueLayout; -import java.lang.invoke.MethodHandle; - -/** - * The GDK toolkit - */ -public class GDK extends Native { - protected static final SymbolLookup GDK; - - static { - if (isGtk4()) { - GDK = SymbolLookup.libraryLookup("libgdk-4.so.0", Arena.ofAuto()); - } else { - GDK = SymbolLookup.libraryLookup("libgdk-3.so.0", Arena.ofAuto()); - } - } - - private static class InstanceHolder { - private static final MethodHandle gdk_cairo_region = createHandle(GDK, "gdk_cairo_region", - FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS)); - } - - /** - * Adds the given region to the current path of {@code cr}. - * - * @param cr A cairo context. - * @param region A {@code cairo_region_t}. - */ - public static void gdk_cairo_region(CairoContext cr, CairoRegion region) { - runSafe(() -> InstanceHolder.gdk_cairo_region.invoke(cr.segment(), region.segment())); - } -} 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 deleted file mode 100644 index 4f29f08d1a..0000000000 --- a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/Cairo.java +++ /dev/null @@ -1,187 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2026 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/CairoFormat.java b/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoFormat.java deleted file mode 100644 index 498202d0fb..0000000000 --- a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoFormat.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2026 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 deleted file mode 100644 index d39418002a..0000000000 --- a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoOperator.java +++ /dev/null @@ -1,173 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2026 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 deleted file mode 100644 index 6a17b09127..0000000000 --- a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoRegion.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2026 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 deleted file mode 100644 index dc79ea4132..0000000000 --- a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/cairo/CairoSurface.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2026 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 deleted file mode 100644 index e0405f65ec..0000000000 --- a/org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GDK3.java +++ /dev/null @@ -1,134 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2026 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 index 71b7af7d36..57b403cc0c 100644 --- 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 @@ -12,11 +12,12 @@ *******************************************************************************/ package org.eclipse.wb.internal.os.linux.gtk3; +import org.eclipse.wb.internal.os.linux.Cairo; import org.eclipse.wb.internal.os.linux.GTK; +import org.eclipse.wb.internal.os.linux.GtkAllocation; 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; @@ -26,26 +27,22 @@ 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_get_preferred_size = createHandle(GTK, "gtk_widget_get_preferred_size", + FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)); + + private static final MethodHandle gtk_widget_size_allocate = createHandle(GTK, "gtk_widget_size_allocate", + FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS)); + + private static final MethodHandle gtk_widget_draw = createHandle(GTK, "gtk_widget_draw", + FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS)); + 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 @@ -79,4 +76,75 @@ public static void gtk_widget_show_now(GtkWidget widget) { public static void gtk_window_set_keep_above(GtkWindow window, boolean setting) { runSafe(() -> InstanceHolder.gtk_window_set_keep_above.invoke(window.segment(), setting)); } + + /** + * Retrieves the minimum and natural size of a widget, taking into account the + * widget’s preference for height-for-width management. + * + * This is used to retrieve a suitable size by container widgets which do not + * impose any restrictions on the child placement. It can be used to deduce + * toplevel window and menu sizes as well as child widgets in free-form + * containers such as {@code GtkFixed}. + * + * Handle with care. Note that the natural height of a height-for-width widget + * will generally be a smaller size than the minimum height, since the required + * height for the natural width is generally smaller than the required height + * for the minimum width. + * + * Use gtk_widget_measure() + * if you want to support baseline alignment. + * + * @param minimum_size Location for storing the minimum size. Can be + * {@code NULL}. + * @param natural_size Location for storing the natural size. Can be + * {@code NULL}. + */ + public static void gtk_widget_get_preferred_size(GtkWidget widget, GtkRequisition minimum_size, GtkRequisition natural_size) { + runSafe(() -> InstanceHolder.gtk_widget_get_preferred_size.invoke(widget.segment(), minimum_size.segment(), natural_size.segment())); + } + + /** + * This function is only used by {@code GtkContainer} subclasses, to assign a + * size and position to their child widgets. + * + * In this function, the allocation may be adjusted. It will be forced to a 1x1 + * minimum size, and the adjust_size_allocation virtual method on the child will + * be used to adjust the allocation. Standard adjustments include removing the + * widget’s margins, and applying the widget’s {@code GtkWidget:halign} and + * {@code GtkWidget:valign} properties. + * + * For baseline support in containers you need to use + * {@code gtk_widget_size_allocate_with_baseline()} instead. + * + * @param allocation Position and size to be allocated to {@code widget}. + */ + public static void gtk_widget_size_allocate(GtkWidget widget, GtkAllocation allocation) { + runSafe(() -> InstanceHolder.gtk_widget_size_allocate.invoke(widget.segment(), allocation.segment())); + } + + /** + * Draws {@code widget} to {@code cr}. The top left corner of the widget will be + * drawn to the currently set origin point of {@code cr}. + * + * You should pass a cairo context as {@code cr} argument that is in an original + * state. Otherwise the resulting drawing is undefined. For example changing the + * operator using {@code cairo_set_operator()} or the line width using + * {@code cairo_set_line_width()} might have unwanted side effects. You may + * however change the context’s transform matrix - like with + * {@code cairo_scale(),}, {@code cairo_translate()} or + * {@code cairo_set_matrix()} and clip region with {@code cairo_clip()} prior to + * calling this function. Also, it is fine to modify the context with + * {@code cairo_save()} and {@code cairo_push_group()} prior to calling this + * function. + * + * Note that special-purpose widgets may contain special code for rendering to + * the screen and might appear differently on screen and when rendered using + * gtk_widget_draw(). + * + * @param cr A cairo context to draw to. + */ + public static void gtk_widget_draw(GtkWidget widget, Cairo cr) { + runSafe(() -> InstanceHolder.gtk_widget_draw.invoke(widget.segment(), cr.segment())); + } } 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 index 46715fa6b6..5498573364 100644 --- 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 @@ -12,22 +12,17 @@ *******************************************************************************/ 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.Cairo; +import org.eclipse.wb.internal.os.linux.GTK; +import org.eclipse.wb.internal.os.linux.GtkAllocation; 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.GC; import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import java.lang.foreign.Arena; /** * Creates a screenshot of a given widget using the GTK3 API. */ @@ -35,69 +30,21 @@ public class GTK3ScreenshotMaker extends ScreenshotMaker { @Override protected Image getImageSurface(GtkWidget widget) { - 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); - // 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); + try (Arena arena = Arena.ofConfined()) { + GtkAllocation allocation = new GtkAllocation(arena); + GTK.gtk_widget_get_allocation(widget, allocation); + // Prevent allocation warnings + GTK3.gtk_widget_get_preferred_size(widget, GtkRequisition.NULL, GtkRequisition.NULL); + GTK3.gtk_widget_size_allocate(widget, allocation); + + int width = Math.max(1, allocation.width()); + int height = Math.max(1, allocation.height()); + + Image image = new Image(Display.getCurrent(), width, height); + GC gc = new GC(image); + GTK3.gtk_widget_draw(widget, Cairo.from(gc)); + gc.dispose(); + return image; } } 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/GtkRequisition.java similarity index 55% rename from org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GdkWindow.java rename to org.eclipse.wb.os.linux/src_java24/org/eclipse/wb/internal/os/linux/gtk3/GtkRequisition.java index 38392d28da..fbc98d3d5c 100644 --- 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/GtkRequisition.java @@ -13,10 +13,25 @@ package org.eclipse.wb.internal.os.linux.gtk3; import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; /** - * A GDK window. + * Represents the desired size of a widget. */ -public record GdkWindow(MemorySegment segment) { +public record GtkRequisition(MemorySegment segment) { + public static final GtkRequisition NULL = new GtkRequisition(MemorySegment.NULL); + /** + * @return The widget’s desired width. + */ + public int width() { + return segment.getAtIndex(ValueLayout.JAVA_INT, 0); + } + + /** + * @return The widget’s desired height. + */ + public int height() { + return segment.getAtIndex(ValueLayout.JAVA_INT, 0); + } }