JBR-6187 Wayland: implement server-side decoration support

Use -Dsun.awt.wl.WindowDecorationStyle=server to activate
This commit is contained in:
Maxim Kartashev
2025-11-14 13:12:42 +04:00
committed by jbrbot
parent 93db525803
commit dcec46b442
12 changed files with 249 additions and 26 deletions

View File

@@ -37,6 +37,7 @@ WAYLAND_BASIC_PROTOCOL_FILES := \
$(WAYLAND_PROTOCOLS_ROOT)/unstable/xdg-output/xdg-output-unstable-v1.xml \
$(WAYLAND_PROTOCOLS_ROOT)/unstable/relative-pointer/relative-pointer-unstable-v1.xml \
$(WAYLAND_PROTOCOLS_ROOT)/unstable/text-input/text-input-unstable-v3.xml \
$(WAYLAND_PROTOCOLS_ROOT)/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml \
$(GTK_SHELL1_PROTOCOL_PATH) \
#

View File

@@ -26,7 +26,6 @@ package sun.awt.wl;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
@@ -130,5 +129,7 @@ public abstract class FrameDecoration {
return null;
}
public abstract void notifyNativeWindowCreated(long nativePtr);
public abstract void notifyNativeWindowToBeHidden(long nativePtr);
public abstract void dispose();
}

View File

@@ -294,6 +294,14 @@ public abstract class FullFrameDecorationHelper extends FrameDecoration {
return null;
}
@Override
public void notifyNativeWindowCreated(long nativePtr) {
}
@Override
public void notifyNativeWindowToBeHidden(long nativePtr) {
}
@Override
public void dispose() {
WLToolkit.getDefaultToolkit().removePropertyChangeListener("awt.os.theme.isDark", pcl);

View File

@@ -74,6 +74,14 @@ public class MinimalFrameDecoration extends FrameDecoration {
// Nothing to repaint
}
@Override
public void notifyNativeWindowCreated(long nativePtr) {
}
@Override
public void notifyNativeWindowToBeHidden(long nativePtr) {
}
@Override
public void dispose() {
// Nothing to dispose

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2022-2025 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.awt.wl;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
/**
* Decorations based on the xdg-decoration-unstable-v1 protocol.
* Supported iff WLToolkit.isSSDAvailable().
*
* The decoration itself is added by the server on a surface separate from the window itself,
* so the window acts as if it is undecorated. For example, there are no insets, no special
* repainting is done, etc.
*/
public class ServerSideFrameDecoration extends FrameDecoration {
private long nativeDecorPtr;
public ServerSideFrameDecoration(WLDecoratedPeer peer) {
super(peer);
}
@Override
public Insets getContentInsets() {
return new Insets(0, 0, 0, 0);
}
@Override
public Rectangle getTitleBarBounds() {
return new Rectangle(0, 0, 0, 0);
}
@Override
public Dimension getMinimumSize() {
return new Dimension(0, 0);
}
@Override
public void paint(Graphics g) {
// Nothing to paint here, the Wayland server provides all the painting
}
@Override
public void notifyConfigured(boolean active, boolean maximized, boolean fullscreen) {
}
@Override
public boolean isRepaintNeeded() {
return false;
}
@Override
public void markRepaintNeeded(boolean value) {
// Nothing to repaint
}
@Override
public void notifyNativeWindowCreated(long nativePtr) {
if (!peer.targetIsWlPopup()) {
nativeDecorPtr = createToplevelDecorationImpl(nativePtr);
}
}
@Override
public void notifyNativeWindowToBeHidden(long nativePtr) {
if (nativeDecorPtr != 0) {
disposeImpl(nativeDecorPtr);
nativeDecorPtr = 0;
}
}
@Override
public void dispose() {
// Native resources must have been already disposed when the window was hidden
assert nativeDecorPtr == 0;
}
private native long createToplevelDecorationImpl(long nativeFramePtr);
private native void disposeImpl(long nativeDecorPtr);
}

View File

@@ -113,6 +113,8 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
private boolean repositionPopup = false; // protected by stateLock
private boolean resizePending = false; // protected by stateLock
private static final boolean shadowEnabled = Boolean.parseBoolean(System.getProperty("sun.awt.wl.Shadow", "true"));
static {
initIDs();
}
@@ -121,6 +123,10 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
* Standard peer constructor, with corresponding Component
*/
WLComponentPeer(Component target) {
this(target, true);
}
protected WLComponentPeer(Component target, boolean dropShadow) {
this.target = target;
this.background = target.getBackground();
Dimension size = constrainSize(target.getBounds().getSize());
@@ -135,14 +141,11 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
log.fine("WLComponentPeer: target=" + target + " with size=" + wlSize);
}
boolean shadowEnabled = Boolean.parseBoolean(System.getProperty("sun.awt.wl.Shadow", "true"));
if (shadowEnabled) {
if (dropShadow && shadowEnabled) {
shadow = new ShadowImpl(targetIsWlPopup() ? ShadowImage.POPUP_SHADOW_SIZE : ShadowImage.WINDOW_SHADOW_SIZE);
} else {
shadow = new NilShadow();
}
// TODO
// setup parent window for target
}
int getDisplayScale() {
@@ -387,6 +390,8 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
WLRobotPeer.setLocationOfWLSurface(wlSurface, xNative, yNative);
}
notifyNativeWindowCreated(nativePtr);
shadow.createSurface();
// From xdg-shell.xml: "After creating a role-specific object and
@@ -404,6 +409,8 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
} else {
performLocked(() -> {
if (wlSurface != null) { // may get a "hide" request even though we were never shown
notifyNativeWindowToBeHidden(nativePtr);
nativeHideFrame(nativePtr);
shadow.hide();
@@ -414,6 +421,12 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
}
}
protected void notifyNativeWindowCreated(long nativePtr) {
}
protected void notifyNativeWindowToBeHidden(long nativePtr) {
}
/**
* Returns true if our target should be treated as a popup in Wayland's sense,
* i.e. it has to have a parent to position relative to.
@@ -440,6 +453,10 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
shadow.updateSurfaceData();
}
public boolean isResizable() {
return true;
}
@Override
public void updateSurfaceSize() {
assert SunToolkit.isAWTLockHeldByCurrentThread();
@@ -460,9 +477,15 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
wlSurface.updateSurfaceSize(surfaceWidth, surfaceHeight);
nativeSetWindowGeometry(nativePtr, 0, 0, surfaceWidth, surfaceHeight);
nativeSetMinimumSize(nativePtr, surfaceMinSize.width, surfaceMinSize.height);
if (surfaceMaxSize != null) {
nativeSetMaximumSize(nativePtr, surfaceMaxSize.width, surfaceMaxSize.height);
if (isResizable()) {
nativeSetMinimumSize(nativePtr, surfaceMinSize.width, surfaceMinSize.height);
if (surfaceMaxSize != null) {
nativeSetMaximumSize(nativePtr, surfaceMaxSize.width, surfaceMaxSize.height);
}
} else {
// Prevent SSD from resizing windows that are not meant to be resizeable
nativeSetMinimumSize(nativePtr, surfaceWidth, surfaceHeight);
nativeSetMaximumSize(nativePtr, surfaceWidth, surfaceHeight);
}
if (popupNeedsReposition()) {

View File

@@ -40,10 +40,37 @@ public abstract class WLDecoratedPeer extends WLWindowPeer {
private final boolean showMaximize;
private final boolean showMinimize;
private final static String decorationPreference = System.getProperty("sun.awt.wl.WindowDecorationStyle");
private enum DecorationTypePreference {
DEFAULT, // The default decorations painted purely in Java
GTK, // Decorations are painted with the help of GTK (if available)
SERVER; // Wayland server-side decorations (if available)
}
private static final DecorationTypePreference decorationType = determineDecorationPreferenceType();
private static DecorationTypePreference determineDecorationPreferenceType() {
String decorationPreference = System.getProperty("sun.awt.wl.WindowDecorationStyle");
if (decorationPreference != null) {
if ("builtin".equals(decorationPreference)) {
return DecorationTypePreference.DEFAULT;
} else if ("gtk".equals(decorationPreference) && isGTKAvailable()) {
return DecorationTypePreference.GTK;
} else if ("server".equals(decorationPreference) && WLToolkit.isSSDAvailable()) {
return DecorationTypePreference.SERVER;
} else {
return DecorationTypePreference.DEFAULT;
}
} else {
if (!WLToolkit.isKDE() && isGTKAvailable()) {
return DecorationTypePreference.GTK;
} else {
return DecorationTypePreference.DEFAULT;
}
}
}
public WLDecoratedPeer(Window target, boolean isUndecorated, boolean showMinimize, boolean showMaximize) {
super(target);
super(target, decorationType != DecorationTypePreference.SERVER);
this.isUndecorated = isUndecorated;
this.showMinimize = showMinimize;
this.showMaximize = showMaximize;
@@ -54,20 +81,12 @@ public abstract class WLDecoratedPeer extends WLWindowPeer {
FrameDecoration d;
if (isUndecorated) {
d = new MinimalFrameDecoration(this);
} else if (decorationPreference != null) {
if ("builtin".equals(decorationPreference)) {
d = new DefaultFrameDecoration(this, showMinimize, showMaximize);
} else if ("gtk".equals(decorationPreference) && isGTKAvailable()) {
d = new GtkFrameDecoration(this, showMinimize, showMaximize);
} else {
d = new DefaultFrameDecoration(this, showMinimize, showMaximize);
}
} else {
if (!WLToolkit.isKDE() && isGTKAvailable()) {
d = new GtkFrameDecoration(this, showMinimize, showMaximize);
} else {
d = new DefaultFrameDecoration(this, showMinimize, showMaximize);
}
d = switch (decorationType) {
case DecorationTypePreference.DEFAULT -> new DefaultFrameDecoration(this, showMinimize, showMaximize);
case DecorationTypePreference.GTK -> new GtkFrameDecoration(this, showMinimize, showMaximize);
case DecorationTypePreference.SERVER -> new ServerSideFrameDecoration(this);
};
}
return d;
}
@@ -84,9 +103,7 @@ public abstract class WLDecoratedPeer extends WLWindowPeer {
}
}
public abstract boolean isResizable();
public abstract boolean isInteractivelyResizable();
public abstract boolean isFrameStateSupported(int state);
public abstract void setState(int newState);
public abstract int getState();
@@ -230,4 +247,14 @@ public abstract class WLDecoratedPeer extends WLWindowPeer {
getDecoration().dispose();
super.dispose();
}
@Override
protected void notifyNativeWindowCreated(long nativePtr) {
decoration.notifyNativeWindowCreated(nativePtr);
}
@Override
protected void notifyNativeWindowToBeHidden(long nativePtr) {
decoration.notifyNativeWindowToBeHidden(nativePtr);
}
}

View File

@@ -1094,6 +1094,15 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
}
}
/**
* @return true if xdg-decoration-unstable-v1 is supported and false otherwise.
*/
public static boolean isSSDAvailable() {
return isSSDAvailableImpl();
}
private static native boolean isSSDAvailableImpl();
private native int readEvents();
private native void dispatchEventsOnEDT();
private native void flushImpl();

View File

@@ -77,7 +77,11 @@ public class WLWindowPeer extends WLComponentPeer implements WindowPeer, Surface
}
public WLWindowPeer(Window target) {
super(target);
this(target, true);
}
public WLWindowPeer(Window target, boolean dropShadow) {
super(target, dropShadow);
if (!target.isFontSet()) {
target.setFont(getDefaultFont());

View File

@@ -35,6 +35,7 @@
#include "WLRobotPeer.h"
#include "WLGraphicsEnvironment.h"
#include "xdg-decoration-unstable-v1.h"
#ifdef WAKEFIELD_ROBOT
#include "wakefield.h"
#endif
@@ -547,3 +548,26 @@ JNIEXPORT void JNICALL Java_sun_awt_wl_WLComponentPeer_nativeShowWindowMenu
}
}
JNIEXPORT jlong JNICALL Java_sun_awt_wl_ServerSideFrameDecoration_createToplevelDecorationImpl
(JNIEnv *env, jobject obj, jlong ptr)
{
struct WLFrame *frame = jlong_to_ptr(ptr);
if (frame->toplevel) {
struct zxdg_toplevel_decoration_v1 * decor = zxdg_decoration_manager_v1_get_toplevel_decoration(
xdg_decoration_manager, frame->xdg_toplevel);
zxdg_toplevel_decoration_v1_set_mode(decor, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
return ptr_to_jlong(decor);
}
return 0;
}
JNIEXPORT void JNICALL Java_sun_awt_wl_ServerSideFrameDecoration_disposeImpl
(JNIEnv *env, jobject obj, jlong ptr)
{
struct zxdg_toplevel_decoration_v1 * decor = jlong_to_ptr(ptr);
if (decor) {
zxdg_toplevel_decoration_v1_destroy(decor);
}
}

View File

@@ -25,6 +25,7 @@
*/
#include "relative-pointer-unstable-v1.h"
#include "xdg-decoration-unstable-v1.h"
#ifdef HEADLESS
#error This file should not be included in headless library
#endif
@@ -82,6 +83,7 @@ struct gtk_shell1* gtk_shell1 = NULL;
struct wl_keyboard *wl_keyboard; // optional, check for NULL before use
struct wl_pointer *wl_pointer; // optional, check for NULL before use
struct zwp_relative_pointer_manager_v1* relative_pointer_manager; // optional, check for NULL before use
struct zxdg_decoration_manager_v1* xdg_decoration_manager; // optional, check for NULL before use
#define MAX_CURSOR_SCALE 100
struct wl_cursor_theme *cursor_themes[MAX_CURSOR_SCALE] = {NULL};
@@ -632,6 +634,8 @@ registry_global(void *data, struct wl_registry *wl_registry,
if (versionToBind <= version) {
zwp_text_input_manager = wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, versionToBind);
}
} else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
xdg_decoration_manager = wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1);
}
#ifdef WAKEFIELD_ROBOT
@@ -904,6 +908,13 @@ Java_sun_awt_wl_WLToolkit_initIDs(JNIEnv *env, jclass clazz, jlong displayPtr)
checkInterfacesPresent(env);
}
JNIEXPORT jboolean JNICALL
Java_sun_awt_wl_WLToolkit_isSSDAvailableImpl
(JNIEnv *env, jobject cls)
{
return xdg_decoration_manager != NULL;
}
JNIEXPORT void JNICALL
Java_sun_awt_wl_WLToolkit_dispatchEventsOnEDT
(JNIEnv *env, jobject obj)

View File

@@ -32,6 +32,8 @@
#include "viewporter.h"
#include "relative-pointer-unstable-v1.h"
#include "text-input-unstable-v3.h"
#include "xdg-decoration-unstable-v1.h"
#include "jvm_md.h"
#include "jni_util.h"
@@ -70,6 +72,7 @@ extern struct zwp_primary_selection_device_manager_v1 *zwp_selection_dm; // opti
extern struct zxdg_output_manager_v1 *zxdg_output_manager_v1; // optional, check for NULL before use
extern struct zwp_relative_pointer_manager_v1* relative_pointer_manager;
extern struct zwp_text_input_manager_v3 *zwp_text_input_manager; // optional, check for NULL before use
extern struct zxdg_decoration_manager_v1* xdg_decoration_manager; // optional, check for NULL before use
JNIEnv *getEnv();