mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2026-01-25 18:00:50 +01:00
Compare commits
12 Commits
jbr21.1258
...
jbr21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf48dab0c | ||
|
|
cb2d78320c | ||
|
|
b1f0e67f78 | ||
|
|
4d1f4d9cbd | ||
|
|
8666af5ba2 | ||
|
|
035ca4c97f | ||
|
|
bb6c3af905 | ||
|
|
e188216507 | ||
|
|
076709a0bd | ||
|
|
111c0a49c9 | ||
|
|
1d28022400 | ||
|
|
5df06e8e80 |
@@ -27,8 +27,6 @@ package java.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
@@ -177,25 +175,11 @@ public class FileInputStream extends InputStream
|
||||
|
||||
try (var guard = IoOverNio.RecursionGuard.create(FileInputStream.class)) {
|
||||
IoOverNio.blackhole(guard);
|
||||
java.nio.file.FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
Path nioPath = null;
|
||||
if (nioFs != null && path != null) {
|
||||
try {
|
||||
nioPath = nioFs.getPath(path);
|
||||
isRegularFile = Files.isRegularFile(nioPath);
|
||||
} catch (InvalidPathException|SecurityException ignored) {
|
||||
// Nothing.
|
||||
}
|
||||
}
|
||||
|
||||
// Two significant differences between the legacy java.io and java.nio.files:
|
||||
// * java.nio.file allows to open directories as streams, java.io.FileInputStream doesn't.
|
||||
// * java.nio.file doesn't work well with pseudo devices, i.e., `seek()` fails, while java.io works well.
|
||||
useNio = nioPath != null && isRegularFile == Boolean.TRUE;
|
||||
|
||||
Path nioPath = IoOverNioFileSystem.getNioPath(file, true);
|
||||
useNio = nioPath != null;
|
||||
if (useNio) {
|
||||
var bundle = IoOverNioFileSystem.initializeStreamUsingNio(
|
||||
this, nioFs, file, nioPath, Set.of(StandardOpenOption.READ), channelCleanable);
|
||||
this, nioPath.getFileSystem(), file, nioPath, Set.of(StandardOpenOption.READ), channelCleanable);
|
||||
channel = bundle.channel();
|
||||
fd = bundle.fd();
|
||||
externalChannelHolder = bundle.externalChannelHolder();
|
||||
|
||||
@@ -249,11 +249,10 @@ public class FileOutputStream extends OutputStream
|
||||
this.path = name;
|
||||
try (var guard = IoOverNio.RecursionGuard.create(FileOutputStream.class)) {
|
||||
IoOverNio.blackhole(guard);
|
||||
java.nio.file.FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
useNio = path != null && nioFs != null;
|
||||
Path nioPath = IoOverNioFileSystem.getNioPath(file, false);
|
||||
useNio = nioPath != null;
|
||||
if (useNio) {
|
||||
Path nioPath = nioFs.getPath(path);
|
||||
|
||||
java.nio.file.FileSystem nioFs = nioPath.getFileSystem();
|
||||
// java.io backend doesn't open DOS hidden files for writing, but java.nio.file opens.
|
||||
// This code mimics the old behavior.
|
||||
if (nioFs.getSeparator().equals("\\")) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
@@ -58,6 +59,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -123,6 +125,69 @@ class IoOverNioFileSystem extends FileSystem {
|
||||
return result;
|
||||
}
|
||||
|
||||
static Path getNioPath(File file, boolean mustBeRegularFile) {
|
||||
String path = file.getPath();
|
||||
java.nio.file.FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
if (nioFs == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Path nioPath;
|
||||
try {
|
||||
nioPath = nioFs.getPath(path);
|
||||
} catch (InvalidPathException ignored) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!mustBeRegularFile) {
|
||||
return nioPath;
|
||||
}
|
||||
|
||||
if (isWindowsPipe(nioPath)) {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea see nMaxInstances:
|
||||
//
|
||||
// Getting file attributes in this case is dangerous.
|
||||
// GetFileAttributesW acquires a connection to the pipe internally,
|
||||
// occupying a place on the server side.
|
||||
// The server and the client are very likely two different processes, and it takes time to deliver
|
||||
// the connection closing message to the server.
|
||||
// If the caller invokes CreateFileW fast enough after GetFileAttributesW and nMaxInstances = 1,
|
||||
// CreateFileW is called before the server closes the previous connection created by GetFileAttributesW
|
||||
// and ERROR_PIPE_BUSY is returned.
|
||||
//
|
||||
// Anyway, `readAttributes(nioPath).isRegularFile()` returns true for pipes, so it's safe to return here.
|
||||
return nioPath;
|
||||
}
|
||||
|
||||
// Two significant differences between the legacy java.io and java.nio.files:
|
||||
// * java.nio.file allows to open directories as streams, java.io.FileInputStream doesn't.
|
||||
// * java.nio.file doesn't work well with pseudo devices, i.e., `seek()` fails, while java.io works well.
|
||||
try {
|
||||
if (Files.readAttributes(nioPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).isRegularFile()) {
|
||||
return nioPath;
|
||||
}
|
||||
} catch (NoSuchFileException ignored) {
|
||||
return nioPath;
|
||||
} catch (IOException ignored) {
|
||||
// Ignored.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <a href="https://learn.microsoft.com/en-us/windows/win32/ipc/pipe-names">
|
||||
* The pipe path format: {@code ^\\(\w+|\.)\pipe\.*}
|
||||
* </a>
|
||||
*/
|
||||
private static boolean isWindowsPipe(Path path) {
|
||||
// A small JMH benchmark shows that this code takes less than a microsecond,
|
||||
// and the JIT compiler does its job very well here.
|
||||
return path.isAbsolute() &&
|
||||
path.getRoot().toString().startsWith("\\\\") &&
|
||||
path.getRoot().toString().toLowerCase(Locale.getDefault()).endsWith("\\pipe\\");
|
||||
}
|
||||
|
||||
private static boolean setPermission0(java.nio.file.FileSystem nioFs, File f, int access, boolean enable, boolean owneronly) {
|
||||
if (f.getPath().isEmpty()) {
|
||||
if (nioFs.getSeparator().equals("\\")) {
|
||||
|
||||
@@ -34,23 +34,10 @@ import sun.nio.ch.FileChannelImpl;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Objects;
|
||||
|
||||
import java.nio.channels.NonWritableChannelException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@@ -305,35 +292,11 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
|
||||
try (var guard = IoOverNio.RecursionGuard.create(RandomAccessFile.class)) {
|
||||
IoOverNio.blackhole(guard);
|
||||
FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
Path nioPath = null;
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
nioPath = nioFs.getPath(path);
|
||||
} catch (InvalidPathException ignored) {
|
||||
// Nothing.
|
||||
}
|
||||
}
|
||||
|
||||
// Two significant differences between the legacy java.io and java.nio.files:
|
||||
// * java.nio.file allows to open directories as streams, java.io.FileInputStream doesn't.
|
||||
// * java.nio.file doesn't work well with pseudo devices, i.e., `seek()` fails, while java.io works well.
|
||||
boolean isRegularFile;
|
||||
try {
|
||||
isRegularFile = nioPath != null &&
|
||||
Files.readAttributes(nioPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).isRegularFile();
|
||||
}
|
||||
catch (NoSuchFileException ignored) {
|
||||
isRegularFile = true;
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
isRegularFile = false;
|
||||
}
|
||||
|
||||
useNio = nioPath != null && isRegularFile;
|
||||
Path nioPath = IoOverNioFileSystem.getNioPath(file, true);
|
||||
useNio = nioPath != null;
|
||||
if (useNio) {
|
||||
var bundle = IoOverNioFileSystem.initializeStreamUsingNio(
|
||||
this, nioFs, file, nioPath, optionsForChannel(imode), channelCleanable);
|
||||
this, nioPath.getFileSystem(), file, nioPath, optionsForChannel(imode), channelCleanable);
|
||||
channel = bundle.channel();
|
||||
fd = bundle.fd();
|
||||
externalChannelHolder = bundle.externalChannelHolder();
|
||||
|
||||
@@ -245,7 +245,10 @@ public abstract class FullFrameDecorationHelper extends FrameDecoration {
|
||||
}
|
||||
if (e.getID() == MouseEvent.MOUSE_PRESSED) {
|
||||
pressedLocation = point;
|
||||
} else if (e.getID() == MouseEvent.MOUSE_DRAGGED && pressedInDragStartArea(point) && isSignificantDrag(point)) {
|
||||
} else if (e.getID() == MouseEvent.MOUSE_DRAGGED
|
||||
&& pressedLocation != null
|
||||
&& pressedInDragStartArea(pressedLocation)
|
||||
&& isSignificantDrag(point)) {
|
||||
startDrag();
|
||||
} else if (e.getID() == MouseEvent.MOUSE_CLICKED && e.getClickCount() == 2 && pressedInDragStartArea(point)
|
||||
&& peer.isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class WLClipboard extends SunClipboard {
|
||||
private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.wl.WLClipboard");
|
||||
@@ -61,6 +62,10 @@ public final class WLClipboard extends SunClipboard {
|
||||
// Guarded by dataLock.
|
||||
private WLDataSource ourDataSource;
|
||||
|
||||
// Set when announcing a clipboard data source to a random value
|
||||
// Guarded by dataLock.
|
||||
private String ourDataSourceCookie = null;
|
||||
|
||||
static {
|
||||
flavorTable = DataTransferer.adaptFlavorMap(getDefaultFlavorTable());
|
||||
}
|
||||
@@ -77,6 +82,10 @@ public final class WLClipboard extends SunClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateRandomMimeTypeCookie() {
|
||||
return "JAVA_DATATRANSFER_COOKIE_" + UUID.randomUUID();
|
||||
}
|
||||
|
||||
private int getProtocol() {
|
||||
if (isPrimary) {
|
||||
return WLDataDevice.DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION;
|
||||
@@ -146,14 +155,26 @@ public final class WLClipboard extends SunClipboard {
|
||||
log.fine("Clipboard: Offering new contents (" + contents + ")");
|
||||
}
|
||||
|
||||
WLDataSource newOffer = null;
|
||||
newOffer = new WLDataSource(dataDevice, getProtocol(), contents);
|
||||
WLDataSource newOffer = new WLDataSource(dataDevice, getProtocol(), contents) {
|
||||
@Override
|
||||
protected void handleCancelled() {
|
||||
synchronized (dataLock) {
|
||||
if (ourDataSource == this) {
|
||||
ourDataSource = null;
|
||||
ourDataSourceCookie = null;
|
||||
}
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
synchronized (dataLock) {
|
||||
if (ourDataSource != null) {
|
||||
ourDataSource.destroy();
|
||||
}
|
||||
ourDataSource = newOffer;
|
||||
ourDataSourceCookie = generateRandomMimeTypeCookie();
|
||||
ourDataSource.offerExtraMime(ourDataSourceCookie);
|
||||
dataDevice.setSelection(getProtocol(), newOffer, eventSerial);
|
||||
}
|
||||
}
|
||||
@@ -240,9 +261,11 @@ public final class WLClipboard extends SunClipboard {
|
||||
}
|
||||
|
||||
void handleClipboardOffer(WLDataOffer offer /* nullable */) {
|
||||
lostOwnershipNow(null);
|
||||
|
||||
synchronized (dataLock) {
|
||||
if (offer == null || ourDataSourceCookie == null || !offer.getMimes().contains(ourDataSourceCookie)) {
|
||||
lostOwnershipNow(null);
|
||||
}
|
||||
|
||||
if (clipboardDataOfferedToUs != null) {
|
||||
clipboardDataOfferedToUs.destroy();
|
||||
}
|
||||
|
||||
@@ -120,6 +120,11 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
|
||||
java.security.AccessController.doPrivileged(
|
||||
new sun.security.action.GetPropertyAction("sun.awt.wl.Shadow", "true")));
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static final boolean nativeModalityEnabled = Boolean.parseBoolean(
|
||||
java.security.AccessController.doPrivileged(
|
||||
new sun.security.action.GetPropertyAction("sun.awt.wl.NativeModality", "false")));
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
@@ -460,9 +465,12 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
|
||||
}
|
||||
|
||||
private boolean targetIsModal() {
|
||||
return target instanceof Dialog dialog
|
||||
&& (dialog.getModalityType() == Dialog.ModalityType.APPLICATION_MODAL
|
||||
|| dialog.getModalityType() == Dialog.ModalityType.TOOLKIT_MODAL);
|
||||
if (nativeModalityEnabled) {
|
||||
return target instanceof Dialog dialog
|
||||
&& (dialog.getModalityType() == Dialog.ModalityType.APPLICATION_MODAL
|
||||
|| dialog.getModalityType() == Dialog.ModalityType.TOOLKIT_MODAL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void updateSurfaceData() {
|
||||
@@ -1141,6 +1149,14 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
|
||||
}
|
||||
}
|
||||
|
||||
final void reactivate(long serial, long surface) {
|
||||
performLocked(() -> {
|
||||
if (serial != 0 &&wlSurface != null && surface != 0) {
|
||||
wlSurface.activateByAnotherSurface(serial, surface);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static long getSerialForActivation() {
|
||||
long serial;
|
||||
if (WLToolkit.isKDE()) {
|
||||
@@ -1230,14 +1246,14 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
|
||||
* the freshly updated WLInputState, and the previous WLInputState.
|
||||
*/
|
||||
void dispatchPointerEventInContext(WLPointerEvent e, WLInputState oldInputState, WLInputState newInputState) {
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
|
||||
final int x = newInputState.getPointerX();
|
||||
final int y = newInputState.getPointerY();
|
||||
final Point abs = relativePointToAbsolute(new Point(x, y));
|
||||
int xAbsolute = abs.x;
|
||||
int yAbsolute = abs.y;
|
||||
|
||||
final long timestamp = newInputState.getTimestamp();
|
||||
|
||||
if (e.hasEnterEvent()) {
|
||||
updateCursorImmediately();
|
||||
final MouseEvent mouseEvent = new MouseEvent(getTarget(), MouseEvent.MOUSE_ENTERED,
|
||||
@@ -1273,7 +1289,7 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
|
||||
postMouseEvent(mouseEvent);
|
||||
|
||||
final boolean isButtonReleased = !e.getIsButtonPressed();
|
||||
final boolean wasSameButtonPressed = oldInputState.hasThisPointerButtonPressed(e.getButtonCode());
|
||||
final boolean wasSameButtonPressed = oldInputState.hasThisPointerButtonPressedAt(e.getButtonCode(), x, y);
|
||||
final boolean isButtonClicked = isButtonReleased && wasSameButtonPressed;
|
||||
if (isButtonClicked) {
|
||||
final MouseEvent mouseClickEvent = new MouseEvent(getTarget(),
|
||||
|
||||
@@ -121,6 +121,13 @@ public class WLDataSource {
|
||||
setDnDIconImpl(nativePtr, scale, width, height, offsetX, offsetY, pixels);
|
||||
}
|
||||
|
||||
public void offerExtraMime(String mime) {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("Native pointer is null");
|
||||
}
|
||||
offerMimeImpl(nativePtr, mime);
|
||||
}
|
||||
|
||||
public synchronized void destroy() {
|
||||
if (nativePtr != 0) {
|
||||
destroyImpl(nativePtr);
|
||||
|
||||
@@ -27,12 +27,10 @@ package sun.awt.wl;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
public abstract class WLDecoratedPeer extends WLWindowPeer {
|
||||
private FrameDecoration decoration; // protected by stateLock
|
||||
@@ -97,14 +95,6 @@ public abstract class WLDecoratedPeer extends WLWindowPeer {
|
||||
return ((WLToolkit) WLToolkit.getDefaultToolkit()).checkGtkVersion(3, 20, 0);
|
||||
}
|
||||
|
||||
private static native void initIDs();
|
||||
|
||||
static {
|
||||
if (!GraphicsEnvironment.isHeadless()) {
|
||||
initIDs();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean isInteractivelyResizable();
|
||||
public abstract boolean isFrameStateSupported(int state);
|
||||
public abstract void setState(int newState);
|
||||
@@ -171,11 +161,6 @@ public abstract class WLDecoratedPeer extends WLWindowPeer {
|
||||
super.updateWindow();
|
||||
}
|
||||
|
||||
// called from native code
|
||||
void postWindowClosing() {
|
||||
WLToolkit.postEvent(new WindowEvent((Window) target, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
|
||||
@Override
|
||||
void postMouseEvent(MouseEvent e) {
|
||||
boolean processed = getDecoration().processMouseEvent(e);
|
||||
|
||||
@@ -24,8 +24,13 @@
|
||||
*/
|
||||
package sun.awt.wl;
|
||||
|
||||
import java.awt.*;
|
||||
import sun.awt.AWTAccessor;
|
||||
|
||||
import java.awt.Dialog;
|
||||
import java.awt.Frame;
|
||||
import java.awt.Window;
|
||||
import java.awt.peer.DialogPeer;
|
||||
import java.awt.peer.WindowPeer;
|
||||
import java.util.List;
|
||||
|
||||
public class WLDialogPeer extends WLDecoratedPeer implements DialogPeer {
|
||||
@@ -41,7 +46,12 @@ public class WLDialogPeer extends WLDecoratedPeer implements DialogPeer {
|
||||
|
||||
@Override
|
||||
public void blockWindows(List<Window> windows) {
|
||||
|
||||
for (Window w : windows) {
|
||||
WindowPeer wp = AWTAccessor.getComponentAccessor().getPeer(w);
|
||||
if (wp != null) {
|
||||
wp.setModalBlocked((Dialog)getTarget(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,7 +35,10 @@ import java.awt.GraphicsDevice;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -119,7 +122,7 @@ public class WLGraphicsDevice extends GraphicsDevice {
|
||||
* Top-level window peers that consider this device as their primary one
|
||||
* and get their graphics configuration from it
|
||||
*/
|
||||
private final Set<WLComponentPeer> toplevels = new HashSet<>(); // guarded by 'this'
|
||||
private final Set<WeakReference<WLComponentPeer>> toplevels = new HashSet<>(); // guarded by 'this'
|
||||
|
||||
private WLGraphicsDevice(int id,
|
||||
String name,
|
||||
@@ -205,9 +208,14 @@ public class WLGraphicsDevice extends GraphicsDevice {
|
||||
}
|
||||
|
||||
private void notifyToplevels() {
|
||||
Set<WLComponentPeer> toplevelsCopy = new HashSet<>(toplevels.size());
|
||||
List<WLComponentPeer> toplevelsCopy = new ArrayList<>(toplevels.size());
|
||||
synchronized (this) {
|
||||
toplevelsCopy.addAll(toplevels);
|
||||
for (var toplevel: toplevels) {
|
||||
WLComponentPeer peer = toplevel.get();
|
||||
if (peer != null) {
|
||||
toplevelsCopy.add(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
toplevelsCopy.forEach(WLComponentPeer::checkIfOnNewScreen);
|
||||
}
|
||||
@@ -369,13 +377,16 @@ public class WLGraphicsDevice extends GraphicsDevice {
|
||||
|
||||
public void addWindow(WLComponentPeer peer) {
|
||||
synchronized (this) {
|
||||
toplevels.add(peer);
|
||||
toplevels.add(new WeakReference<>(peer));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeWindow(WLComponentPeer peer) {
|
||||
synchronized (this) {
|
||||
toplevels.remove(peer);
|
||||
toplevels.removeIf(ref -> {
|
||||
WLComponentPeer p = ref.get();
|
||||
return p == null || p == peer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -295,8 +295,9 @@ record WLInputState(WLPointerEvent eventWithSurface,
|
||||
return newModifiers;
|
||||
}
|
||||
|
||||
public boolean hasThisPointerButtonPressed(int linuxCode) {
|
||||
return pointerButtonPressedEvent != null && pointerButtonPressedEvent.linuxCode == linuxCode;
|
||||
public boolean hasThisPointerButtonPressedAt(int linuxCode, long x, long y) {
|
||||
return pointerButtonPressedEvent != null && pointerButtonPressedEvent.linuxCode == linuxCode
|
||||
&& pointerButtonPressedEvent.surfaceX == x && pointerButtonPressedEvent.surfaceY == y;
|
||||
}
|
||||
|
||||
public boolean hasPointerButtonPressed() {
|
||||
@@ -353,6 +354,12 @@ record WLInputState(WLPointerEvent eventWithSurface,
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timestamp of the most recent known {@code wl_pointer::*} event or 0 if no such event is known.
|
||||
*
|
||||
* @apiNote don't use these timestamps for constructing any new {@link java.awt.event.InputEvent}s
|
||||
* because they are not guaranteed to be based on the midnight of Jan 1, 1970 UTC.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return eventWithTimestamp != null ? eventWithTimestamp.getTimestamp() : 0;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ import sun.util.logging.PlatformLogger;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Window;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class WLKeyboardFocusManagerPeer extends KeyboardFocusManagerPeerImpl {
|
||||
private static final PlatformLogger focusLog = PlatformLogger.getLogger("sun.awt.wl.focus.WLKeyboardFocusManagerPeer");
|
||||
|
||||
private Window currentFocusedWindow;
|
||||
private WeakReference<Window> currentFocusedWindow = new WeakReference<>(null);
|
||||
private static final WLKeyboardFocusManagerPeer instance = new WLKeyboardFocusManagerPeer();
|
||||
|
||||
public static WLKeyboardFocusManagerPeer getInstance() {
|
||||
@@ -23,14 +24,14 @@ public class WLKeyboardFocusManagerPeer extends KeyboardFocusManagerPeerImpl {
|
||||
if (focusLog.isLoggable(PlatformLogger.Level.FINER)) {
|
||||
focusLog.finer("Current focused window -> " + win);
|
||||
}
|
||||
currentFocusedWindow = win;
|
||||
currentFocusedWindow = new WeakReference<>(win);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window getCurrentFocusedWindow() {
|
||||
synchronized (this) {
|
||||
return currentFocusedWindow;
|
||||
return currentFocusedWindow.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ public class WLKeyboardFocusManagerPeer extends KeyboardFocusManagerPeerImpl {
|
||||
@Override
|
||||
public Component getCurrentFocusOwner() {
|
||||
synchronized (this) {
|
||||
return currentFocusedWindow;
|
||||
return currentFocusedWindow.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,7 +367,6 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
}
|
||||
|
||||
private static void dispatchKeyboardKeyEvent(long serial,
|
||||
long timestamp,
|
||||
int id,
|
||||
int keyCode,
|
||||
int keyLocation,
|
||||
@@ -378,13 +377,9 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
// Invoked from the native code
|
||||
assert EventQueue.isDispatchThread() : "Method must only be invoked on EDT";
|
||||
|
||||
inputState = inputState.updatedFromKeyEvent(serial);
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
|
||||
if (timestamp == 0) {
|
||||
// Happens when a surface was focused with keys already pressed.
|
||||
// Fake the timestamp by peeking at the last known event.
|
||||
timestamp = inputState.getTimestamp();
|
||||
}
|
||||
inputState = inputState.updatedFromKeyEvent(serial);
|
||||
|
||||
final long surfacePtr = inputState.surfaceForKeyboardInput();
|
||||
final WLComponentPeer peer = peerFromSurface(surfacePtr);
|
||||
@@ -453,22 +448,32 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
+ Long.toHexString(surfacePtr));
|
||||
}
|
||||
|
||||
final WLInputState newInputState = inputState.updatedFromKeyboardEnterEvent(serial, surfacePtr);
|
||||
final WLWindowPeer peer = peerFromSurface(surfacePtr);
|
||||
final WLInputState newInputState = inputState.updatedFromKeyboardEnterEvent(serial, surfacePtr);
|
||||
if (peer != null) {
|
||||
Window window = (Window) peer.getTarget();
|
||||
Window winToFocus = window;
|
||||
|
||||
Component s = peer.getSyntheticFocusOwner();
|
||||
if (s instanceof Window synthWindow) {
|
||||
if (synthWindow.isVisible() && synthWindow.isFocusableWindow()) {
|
||||
winToFocus = synthWindow;
|
||||
Dialog blocker = peer.getBlocker();
|
||||
if (blocker != null) { // Modality support
|
||||
long activationSerial = serial;
|
||||
if (WLToolkit.isKDE()) {
|
||||
activationSerial = inputState.latestInputSerial();
|
||||
}
|
||||
}
|
||||
WLWindowPeer blockerPeer = AWTAccessor.getComponentAccessor().getPeer(blocker);
|
||||
blockerPeer.reactivate(activationSerial, surfacePtr);
|
||||
} else {
|
||||
Window window = (Window) peer.getTarget();
|
||||
Window winToFocus = window;
|
||||
|
||||
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusedWindow(window);
|
||||
WindowEvent windowEnterEvent = new WindowEvent(winToFocus, WindowEvent.WINDOW_GAINED_FOCUS);
|
||||
postPriorityEvent(windowEnterEvent);
|
||||
Component s = peer.getSyntheticFocusOwner();
|
||||
if (s instanceof Window synthWindow) {
|
||||
if (synthWindow.isVisible() && synthWindow.isFocusableWindow()) {
|
||||
winToFocus = synthWindow;
|
||||
}
|
||||
}
|
||||
|
||||
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusedWindow(window);
|
||||
WindowEvent windowEnterEvent = new WindowEvent(winToFocus, WindowEvent.WINDOW_GAINED_FOCUS);
|
||||
postPriorityEvent(windowEnterEvent);
|
||||
}
|
||||
}
|
||||
inputState = newInputState;
|
||||
}
|
||||
@@ -484,12 +489,13 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
|
||||
keyboard.onLostFocus();
|
||||
|
||||
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusedWindow(null);
|
||||
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusOwner(null);
|
||||
|
||||
final WLInputState newInputState = inputState.updatedFromKeyboardLeaveEvent(serial, surfacePtr);
|
||||
final WLWindowPeer peer = peerFromSurface(surfacePtr);
|
||||
if (peer != null && peer.getTarget() instanceof Window window) {
|
||||
final WindowEvent winLostFocusEvent = new WindowEvent(window, WindowEvent.WINDOW_LOST_FOCUS);
|
||||
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusedWindow(null);
|
||||
WLKeyboardFocusManagerPeer.getInstance().setCurrentFocusOwner(null);
|
||||
postPriorityEvent(winLostFocusEvent);
|
||||
}
|
||||
inputState = newInputState;
|
||||
@@ -1140,6 +1146,17 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
|
||||
protected static void targetDisposedPeer(Object target, Object peer) {
|
||||
SunToolkit.targetDisposedPeer(target, peer);
|
||||
if (target instanceof Window window) {
|
||||
// TODO: focusedWindow and activeWindow of class java.awt.KeyboardFocusManager
|
||||
// may still retain references to 'window' because disposed peer may not
|
||||
// get the keyboard_leave event and therefore will not send the WINDOW_LOST_FOCUS
|
||||
// event that would've cleared those references.
|
||||
var gc = window.getGraphicsConfiguration();
|
||||
if (gc != null && peer instanceof WLWindowPeer windowPeer) {
|
||||
WLGraphicsDevice gd = (WLGraphicsDevice) gc.getDevice();
|
||||
gd.removeWindow(windowPeer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void postEvent(AWTEvent event) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.awt.Dialog;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
@@ -57,7 +58,7 @@ import java.util.List;
|
||||
|
||||
public class WLWindowPeer extends WLComponentPeer implements WindowPeer, SurfacePixelGrabber {
|
||||
private static Font defaultFont;
|
||||
private Dialog blocker;
|
||||
private Dialog blocker; // guarded by getStateLock()
|
||||
private static WLWindowPeer grabbingWindow; // fake, kept for UngrabEvent only
|
||||
|
||||
// If this window gets focus from Wayland, we need to transfer focus synthFocusOwner, if any
|
||||
@@ -72,6 +73,12 @@ public class WLWindowPeer extends WLComponentPeer implements WindowPeer, Surface
|
||||
private Path2D.Double bottomRightMask; // guarded by stateLock
|
||||
private SunGraphics2D graphics; // guarded by stateLock
|
||||
|
||||
static {
|
||||
if (!GraphicsEnvironment.isHeadless()) {
|
||||
initIDs();
|
||||
}
|
||||
}
|
||||
|
||||
static synchronized Font getDefaultFont() {
|
||||
if (null == defaultFont) {
|
||||
defaultFont = new Font(Font.DIALOG, Font.PLAIN, 12);
|
||||
@@ -173,7 +180,15 @@ public class WLWindowPeer extends WLComponentPeer implements WindowPeer, Surface
|
||||
|
||||
@Override
|
||||
public void setModalBlocked(Dialog blocker, boolean blocked) {
|
||||
this.blocker = blocked ? blocker : null;
|
||||
synchronized (getStateLock()) {
|
||||
this.blocker = blocked ? blocker : null;
|
||||
}
|
||||
}
|
||||
|
||||
public Dialog getBlocker() {
|
||||
synchronized (getStateLock()) {
|
||||
return blocker;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -311,6 +326,11 @@ public class WLWindowPeer extends WLComponentPeer implements WindowPeer, Surface
|
||||
}
|
||||
}
|
||||
|
||||
// called from native code
|
||||
void postWindowClosing() {
|
||||
WLToolkit.postEvent(new WindowEvent((Window) target, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getClientAreaSnapshot(int x, int y, int width, int height) {
|
||||
// Move the coordinate system to the client area
|
||||
@@ -464,4 +484,6 @@ public class WLWindowPeer extends WLComponentPeer implements WindowPeer, Surface
|
||||
graphics.fill(bottomRightMask);
|
||||
}
|
||||
}
|
||||
|
||||
private static native void initIDs();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
#include "wakefield-client-protocol.h"
|
||||
#endif
|
||||
|
||||
static jmethodID postWindowClosingMID;
|
||||
static jmethodID postWindowClosingMID; // WLWindowPeer.postWindowClosing
|
||||
static jmethodID notifyConfiguredMID;
|
||||
static jmethodID notifyPopupDoneMID;
|
||||
|
||||
@@ -171,7 +171,7 @@ xdg_toplevel_close(void *data,
|
||||
struct WLFrame *frame = (struct WLFrame *) data;
|
||||
JNIEnv *env = getEnv();
|
||||
const jobject nativeFramePeer = (*env)->NewLocalRef(env, frame->nativeFramePeer);
|
||||
if (nativeFramePeer) {
|
||||
if (nativeFramePeer) { // a reference to WLWinowPeer or its descendant
|
||||
(*env)->CallVoidMethod(env, nativeFramePeer, postWindowClosingMID);
|
||||
(*env)->DeleteLocalRef(env, nativeFramePeer);
|
||||
JNU_CHECK_EXCEPTION(env);
|
||||
@@ -223,12 +223,12 @@ Java_sun_awt_wl_WLComponentPeer_initIDs
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_sun_awt_wl_WLDecoratedPeer_initIDs
|
||||
Java_sun_awt_wl_WLWindowPeer_initIDs
|
||||
(JNIEnv *env, jclass clazz)
|
||||
{
|
||||
CHECK_NULL_THROW_IE(env,
|
||||
postWindowClosingMID = (*env)->GetMethodID(env, clazz, "postWindowClosing", "()V"),
|
||||
"Failed to find method WLDecoratedPeer.postWindowClosing");
|
||||
"Failed to find method WLWindowPeer.postWindowClosing");
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
|
||||
@@ -386,6 +386,10 @@ static const struct KeysymToJavaKeycodeMapItem {
|
||||
{0x1005ff72 /* XKB_KEY_SunCopy */, java_awt_event_KeyEvent_VK_COPY, java_awt_event_KeyEvent_KEY_LOCATION_STANDARD},
|
||||
{0x1005ff74 /* XKB_KEY_SunPaste */, java_awt_event_KeyEvent_VK_PASTE, java_awt_event_KeyEvent_KEY_LOCATION_STANDARD},
|
||||
{0x1005ff75 /* XKB_KEY_SunCut */, java_awt_event_KeyEvent_VK_CUT, java_awt_event_KeyEvent_KEY_LOCATION_STANDARD},
|
||||
{0x1008ff55 /* XKB_KEY_XF86Clear */, java_awt_event_KeyEvent_VK_CLEAR, java_awt_event_KeyEvent_KEY_LOCATION_STANDARD},
|
||||
{0x1008ff57 /* XKB_KEY_XF86Copy */, java_awt_event_KeyEvent_VK_COPY, java_awt_event_KeyEvent_KEY_LOCATION_STANDARD},
|
||||
{0x1008ff58 /* XKB_KEY_XF86Cut */, java_awt_event_KeyEvent_VK_CUT, java_awt_event_KeyEvent_KEY_LOCATION_STANDARD},
|
||||
{0x1008ff6d /* XKB_KEY_XF86Paste */, java_awt_event_KeyEvent_VK_PASTE, java_awt_event_KeyEvent_KEY_LOCATION_STANDARD},
|
||||
{0, 0, 0}
|
||||
};
|
||||
|
||||
|
||||
@@ -447,7 +447,6 @@ wlPostKeyEvent(const struct WLKeyEvent* event)
|
||||
tkClass,
|
||||
dispatchKeyboardKeyEventMID,
|
||||
event->serial,
|
||||
event->timestamp,
|
||||
event->id,
|
||||
event->keyCode,
|
||||
event->keyLocation,
|
||||
@@ -795,7 +794,7 @@ initJavaRefs(JNIEnv *env, jclass clazz)
|
||||
JNI_FALSE);
|
||||
CHECK_NULL_RETURN(dispatchKeyboardKeyEventMID = (*env)->GetStaticMethodID(env, tkClass,
|
||||
"dispatchKeyboardKeyEvent",
|
||||
"(JJIIIIICI)V"),
|
||||
"(JIIIIICI)V"),
|
||||
JNI_FALSE);
|
||||
CHECK_NULL_RETURN(dispatchKeyboardModifiersEventMID = (*env)->GetStaticMethodID(env, tkClass,
|
||||
"dispatchKeyboardModifiersEvent",
|
||||
|
||||
120
test/jdk/jb/java/awt/wayland/WLWindowsLeak.java
Normal file
120
test/jdk/jb/java/awt/wayland/WLWindowsLeak.java
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2026 JetBrains s.r.o.
|
||||
* Copyright (c) 2026, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @key headful
|
||||
* @summary Tests that nothing prevents windows from being removed from the global list
|
||||
* after they have been disposed
|
||||
* @library /javax/swing/regtesthelpers
|
||||
* @modules java.desktop/sun.awt
|
||||
* java.desktop/sun.java2d
|
||||
* @build Util
|
||||
* @run main/othervm -Xms32M -Xmx32M WLWindowsLeak
|
||||
*/
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Frame;
|
||||
import java.awt.Robot;
|
||||
import java.awt.Window;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import com.sun.management.HotSpotDiagnosticMXBean;
|
||||
import sun.awt.AppContext;
|
||||
import sun.java2d.Disposer;
|
||||
|
||||
public class WLWindowsLeak {
|
||||
|
||||
public static final int WINDOWS_COUNT = 42;
|
||||
private static volatile boolean disposerPhantomComplete;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
spawnWindows();
|
||||
|
||||
Disposer.addRecord(new Object(), () -> disposerPhantomComplete = true);
|
||||
|
||||
while (!disposerPhantomComplete) {
|
||||
Util.generateOOME();
|
||||
}
|
||||
|
||||
Vector<WeakReference<Window>> windowList =
|
||||
(Vector<WeakReference<Window>>) AppContext.getAppContext().get(Window.class);
|
||||
|
||||
// Note: focusedWindow and activeWindow of class java.awt.KeyboardFocusManager
|
||||
// may still retain references to the last window shown. This makes the test flaky and
|
||||
// therefore less useful. Until this is fixed, the test allows for 1 retained window.
|
||||
if (windowList != null && !windowList.isEmpty() && windowList.size() > 1) {
|
||||
dumpHeap("heap_dump_live_windows.hprof");
|
||||
System.out.println("Live window list:");
|
||||
windowList.forEach(ref -> System.out.println(ref.get()));
|
||||
throw new RuntimeException("Test FAILED: Window list is not empty: " + windowList.size());
|
||||
}
|
||||
|
||||
System.out.println("Test PASSED");
|
||||
}
|
||||
|
||||
private static void dumpHeap(String filePath) {
|
||||
try {
|
||||
HotSpotDiagnosticMXBean mxBean = ManagementFactory
|
||||
.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
|
||||
mxBean.dumpHeap(filePath, true); // true = live objects only
|
||||
System.out.println("Heap dump created at: " + filePath);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to dump heap: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void spawnWindows() throws Exception {
|
||||
List<WeakReference<Frame>> frameList = new ArrayList<>();
|
||||
Robot r = new Robot();
|
||||
|
||||
for (int i = 0; i < WINDOWS_COUNT; i++) {
|
||||
Frame f = new Frame(String.format("Frame %d", i));
|
||||
f.setBackground(Color.WHITE);
|
||||
f.setSize(100, 100);
|
||||
f.setVisible(true);
|
||||
frameList.add(new WeakReference<>(f));
|
||||
Thread.sleep(42);
|
||||
}
|
||||
r.waitForIdle();
|
||||
|
||||
frameList.forEach(ref -> {
|
||||
ref.get().setVisible(false);
|
||||
});
|
||||
r.waitForIdle();
|
||||
|
||||
frameList.forEach(ref -> {
|
||||
var f = ref.get();
|
||||
if (f != null) f.dispose();
|
||||
});
|
||||
frameList.clear();
|
||||
r.waitForIdle(); // to make sure no events hold a reference to frames
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,14 @@
|
||||
/* @test
|
||||
* @summary java.io.RandomAccessFileTest uses java.nio.file inside.
|
||||
* @library testNio
|
||||
* @compile --enable-preview --source 21 RandomAccessFileTest.java
|
||||
* @run junit/othervm
|
||||
* -Djava.nio.file.spi.DefaultFileSystemProvider=testNio.ManglingFileSystemProvider
|
||||
* -Djbr.java.io.use.nio=true
|
||||
* --add-opens jdk.unsupported/com.sun.nio.file=ALL-UNNAMED
|
||||
* --add-opens java.base/java.io=ALL-UNNAMED
|
||||
* --enable-native-access=ALL-UNNAMED
|
||||
* --enable-preview
|
||||
* RandomAccessFileTest
|
||||
*/
|
||||
|
||||
@@ -45,6 +48,9 @@ import testNio.ManglingFileSystemProvider;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.lang.foreign.*;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
@@ -55,6 +61,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -293,4 +300,122 @@ public class RandomAccessFileTest {
|
||||
FileSystems.getDefault().provider().newFileChannel(file.toPath(), Collections.singleton(option)).close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JBR-9779
|
||||
*/
|
||||
@Test
|
||||
public void testWindowsPipe() throws Throwable {
|
||||
Assume.assumeTrue("Windows-only test", System.getProperty("os.name").toLowerCase().startsWith("win"));
|
||||
|
||||
// Creating a pipe.
|
||||
Linker linker = Linker.nativeLinker();
|
||||
SymbolLookup loader = SymbolLookup.libraryLookup("kernel32", Arena.global());
|
||||
|
||||
StructLayout captureLayout = Linker.Option.captureStateLayout();
|
||||
VarHandle GetLastError = captureLayout.varHandle(MemoryLayout.PathElement.groupElement("GetLastError"));
|
||||
|
||||
MethodHandle CreateNamedPipeW = linker.downcallHandle(
|
||||
loader.find("CreateNamedPipeW").get(),
|
||||
FunctionDescriptor.of(ValueLayout.JAVA_LONG,
|
||||
ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT,
|
||||
ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT,
|
||||
ValueLayout.ADDRESS),
|
||||
Linker.Option.captureCallState("GetLastError")
|
||||
);
|
||||
|
||||
MethodHandle ConnectNamedPipe = linker.downcallHandle(
|
||||
loader.find("ConnectNamedPipe").get(),
|
||||
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS),
|
||||
Linker.Option.captureCallState("GetLastError")
|
||||
);
|
||||
|
||||
MethodHandle DisconnectNamedPipe = linker.downcallHandle(
|
||||
loader.find("DisconnectNamedPipe").get(),
|
||||
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG)
|
||||
);
|
||||
|
||||
MethodHandle PeekNamedPipe = linker.downcallHandle(
|
||||
loader.find("PeekNamedPipe").get(),
|
||||
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG,
|
||||
ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS,
|
||||
ValueLayout.ADDRESS, ValueLayout.ADDRESS)
|
||||
);
|
||||
|
||||
MethodHandle CloseHandle = linker.downcallHandle(
|
||||
loader.find("CloseHandle").get(),
|
||||
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG)
|
||||
);
|
||||
|
||||
String pipeName = "\\\\.\\pipe\\jbr-test-pipe-" + System.nanoTime();
|
||||
Arena arena = Arena.ofAuto();
|
||||
char[] nameChars = (pipeName + "\0").toCharArray(); // `char` on Windows is UTF-16, as WinAPI expects.
|
||||
MemorySegment pipeWinPath = arena.allocateArray(ValueLayout.JAVA_CHAR, nameChars);
|
||||
MemorySegment capturedState = arena.allocate(captureLayout);
|
||||
|
||||
final long INVALID_HANDLE_VALUE = -1L;
|
||||
|
||||
long hPipe = (long) CreateNamedPipeW.invokeExact(
|
||||
capturedState,
|
||||
pipeWinPath,
|
||||
0x00000003, // dwOpenMode = PIPE_ACCESS_DUPLEX
|
||||
0x00000000, // dwPipeMode = PIPE_TYPE_BYTE
|
||||
1, // nMaxInstances. Limit to 1 to force ERROR_PIPE_BUSY.
|
||||
1024, // nOutBufferSize
|
||||
1024, // nInBufferSize
|
||||
0, // nDefaultTimeOut
|
||||
MemorySegment.NULL // lpSecurityAttributes
|
||||
);
|
||||
|
||||
if (hPipe == INVALID_HANDLE_VALUE) {
|
||||
int errorCode = (int) GetLastError.get(capturedState);
|
||||
throw new Exception("CreateNamedPipeW failed: " + errorCode);
|
||||
}
|
||||
|
||||
AtomicBoolean keepRunning = new AtomicBoolean(true);
|
||||
Thread serverThread = new Thread(() -> {
|
||||
// This server accepts a connection and does nothing until the client disconnects explicitly.
|
||||
try {
|
||||
int i = 0;
|
||||
while (keepRunning.get()) {
|
||||
int connected = (int) ConnectNamedPipe.invokeExact(capturedState, hPipe, MemorySegment.NULL);
|
||||
if (connected == 0) {
|
||||
int errorCode = (int) GetLastError.get(capturedState);
|
||||
// https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
|
||||
if (errorCode == 6 && !keepRunning.get()) { // ERROR_INVALID_HANDLE
|
||||
break;
|
||||
}
|
||||
throw new Exception("ConnectNamedPipe failed: " + errorCode);
|
||||
}
|
||||
try {
|
||||
int peekResult;
|
||||
do {
|
||||
// Random timeout. The timeout must be big enough to reveal possible consequent
|
||||
// attempts to connect to the pipe.
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Check if the pipe is still connected by peeking at it.
|
||||
peekResult = (int) PeekNamedPipe.invokeExact(hPipe, MemorySegment.NULL, 0,
|
||||
MemorySegment.NULL, MemorySegment.NULL, MemorySegment.NULL);
|
||||
}
|
||||
while (keepRunning.get() && peekResult != 0);
|
||||
} finally {
|
||||
int disconnected = (int) DisconnectNamedPipe.invokeExact(hPipe);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
serverThread.setDaemon(true);
|
||||
serverThread.start();
|
||||
|
||||
try {
|
||||
new RandomAccessFile(pipeName, "rw").close();
|
||||
} finally {
|
||||
keepRunning.set(false);
|
||||
int closed = (int) CloseHandle.invokeExact(hPipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user