8351907: [XWayland] [OL10] Robot.mousePress() is delivered to wrong place

Reviewed-by: honkar, prr
This commit is contained in:
Alexander Zvegintsev
2025-05-21 17:21:05 +00:00
parent f1eead6035
commit 2dfbf41d2a
13 changed files with 887 additions and 137 deletions

View File

@@ -50,7 +50,6 @@ import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import sun.awt.X11.XBaseWindow;
import com.sun.java.swing.plaf.gtk.GTKConstants.TextDirection;
@@ -254,14 +253,12 @@ public abstract class UNIXToolkit extends SunToolkit
return result;
}
private Integer getGnomeShellMajorVersion() {
public Integer getGnomeShellMajorVersion() {
try {
Process process =
new ProcessBuilder("/usr/bin/gnome-shell", "--version")
.start();
try (InputStreamReader isr = new InputStreamReader(process.getInputStream());
BufferedReader reader = new BufferedReader(isr)) {
try (BufferedReader reader = process.inputReader()) {
if (process.waitFor(2, SECONDS) && process.exitValue() == 0) {
String line = reader.readLine();
if (line != null) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2025, 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
@@ -35,13 +35,11 @@ import sun.awt.UNIXToolkit;
import sun.awt.X11GraphicsConfig;
import sun.awt.X11GraphicsDevice;
import sun.awt.screencast.ScreencastHelper;
import sun.awt.screencast.XdgDesktopPortal;
final class XRobotPeer implements RobotPeer {
private static final boolean tryGtk;
private static final String screenshotMethod;
private static final String METHOD_X11 = "x11";
private static final String METHOD_SCREENCAST = "dbusScreencast";
static {
loadNativeLibraries();
@@ -49,19 +47,6 @@ final class XRobotPeer implements RobotPeer {
tryGtk = Boolean.parseBoolean(
System.getProperty("awt.robot.gtk", "true")
);
boolean isOnWayland = false;
if (Toolkit.getDefaultToolkit() instanceof SunToolkit sunToolkit) {
isOnWayland = sunToolkit.isRunningOnWayland();
}
screenshotMethod = System.getProperty(
"awt.robot.screenshotMethod",
isOnWayland
? METHOD_SCREENCAST
: METHOD_X11
);
}
private static volatile boolean useGtk;
@@ -86,39 +71,63 @@ final class XRobotPeer implements RobotPeer {
@Override
public void mouseMove(int x, int y) {
mouseMoveImpl(xgc, xgc.scaleUp(x), xgc.scaleUp(y));
if (XdgDesktopPortal.isRemoteDesktop() && ScreencastHelper.isAvailable()) {
// We still call mouseMoveImpl on purpose to change the mouse position
// within the XWayland server so that we can retrieve it later.
ScreencastHelper.remoteDesktopMouseMove(xgc.scaleUp(x), xgc.scaleUp(y));
}
}
@Override
public void mousePress(int buttons) {
mousePressImpl(buttons);
if (XdgDesktopPortal.isRemoteDesktop() && ScreencastHelper.isAvailable()) {
ScreencastHelper.remoteDesktopMouseButton(true, buttons);
} else {
mousePressImpl(buttons);
}
}
@Override
public void mouseRelease(int buttons) {
mouseReleaseImpl(buttons);
if (XdgDesktopPortal.isRemoteDesktop() && ScreencastHelper.isAvailable()) {
ScreencastHelper.remoteDesktopMouseButton(false, buttons);
} else {
mouseReleaseImpl(buttons);
}
}
@Override
public void mouseWheel(int wheelAmt) {
mouseWheelImpl(wheelAmt);
if (XdgDesktopPortal.isRemoteDesktop() && ScreencastHelper.isAvailable()) {
ScreencastHelper.remoteDesktopMouseWheel(wheelAmt);
} else {
mouseWheelImpl(wheelAmt);
}
}
@Override
public void keyPress(int keycode) {
keyPressImpl(keycode);
if (XdgDesktopPortal.isRemoteDesktop() && ScreencastHelper.isAvailable()) {
ScreencastHelper.remoteDesktopKey(true, keycode);
} else {
keyPressImpl(keycode);
}
}
@Override
public void keyRelease(int keycode) {
keyReleaseImpl(keycode);
if (XdgDesktopPortal.isRemoteDesktop() && ScreencastHelper.isAvailable()) {
ScreencastHelper.remoteDesktopKey(false, keycode);
} else {
keyReleaseImpl(keycode);
}
}
@Override
public int getRGBPixel(int x, int y) {
int[] pixelArray = new int[1];
if (screenshotMethod.equals(METHOD_SCREENCAST)
&& ScreencastHelper.isAvailable()) {
if ((XdgDesktopPortal.isScreencast()
|| XdgDesktopPortal.isRemoteDesktop()) && ScreencastHelper.isAvailable()) {
ScreencastHelper.getRGBPixels(x, y, 1, 1, pixelArray);
} else {
getRGBPixelsImpl(xgc, x, y, 1, 1, pixelArray, useGtk);
@@ -129,8 +138,8 @@ final class XRobotPeer implements RobotPeer {
@Override
public int[] getRGBPixels(Rectangle bounds) {
int[] pixelArray = new int[bounds.width * bounds.height];
if (screenshotMethod.equals(METHOD_SCREENCAST)
&& ScreencastHelper.isAvailable()) {
if ((XdgDesktopPortal.isScreencast()
|| XdgDesktopPortal.isRemoteDesktop()) && ScreencastHelper.isAvailable()) {
ScreencastHelper.getRGBPixels(bounds.x, bounds.y,
bounds.width, bounds.height,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2025, 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
@@ -138,6 +138,8 @@ import sun.awt.X11GraphicsDevice;
import sun.awt.X11GraphicsEnvironment;
import sun.awt.XSettings;
import sun.awt.datatransfer.DataTransferer;
import sun.awt.screencast.ScreencastHelper;
import sun.awt.screencast.XdgDesktopPortal;
import sun.awt.util.PerformanceLogger;
import sun.awt.util.ThreadGroupUtils;
import sun.font.FontConfigManager;
@@ -1521,16 +1523,21 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
awtLock();
try {
if (numberOfButtons == 0) {
numberOfButtons = getNumberOfButtonsImpl();
numberOfButtons = (numberOfButtons > MAX_BUTTONS_SUPPORTED)? MAX_BUTTONS_SUPPORTED : numberOfButtons;
//4th and 5th buttons are for wheel and shouldn't be reported as buttons.
//If we have more than 3 physical buttons and a wheel, we report N-2 buttons.
//If we have 3 physical buttons and a wheel, we report 3 buttons.
//If we have 1,2,3 physical buttons, we report it as is i.e. 1,2 or 3 respectively.
if (numberOfButtons >=5) {
numberOfButtons -= 2;
} else if (numberOfButtons == 4 || numberOfButtons ==5){
if (XdgDesktopPortal.isRemoteDesktop()
&& ScreencastHelper.isAvailable()) {
numberOfButtons = 3;
} else {
numberOfButtons = getNumberOfButtonsImpl();
numberOfButtons = (numberOfButtons > MAX_BUTTONS_SUPPORTED) ? MAX_BUTTONS_SUPPORTED : numberOfButtons;
//4th and 5th buttons are for wheel and shouldn't be reported as buttons.
//If we have more than 3 physical buttons and a wheel, we report N-2 buttons.
//If we have 3 physical buttons and a wheel, we report 3 buttons.
//If we have 1,2,3 physical buttons, we report it as is i.e. 1,2 or 3 respectively.
if (numberOfButtons >= 5) {
numberOfButtons -= 2;
} else if (numberOfButtons == 4 || numberOfButtons == 5) {
numberOfButtons = 3;
}
}
}
//Assume don't have to re-query the number again and again.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, 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
@@ -38,6 +38,7 @@ import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Function;
import java.util.stream.IntStream;
/**
@@ -51,10 +52,13 @@ public final class ScreencastHelper {
static final boolean SCREENCAST_DEBUG;
private static final boolean IS_NATIVE_LOADED;
private static final int ERROR = -1;
private static final int DENIED = -11;
private static final int OUT_OF_BOUNDS = -12;
private static final int NO_STREAMS = -13;
private static final int XDG_METHOD_SCREENCAST = 0;
private static final int XDG_METHOD_REMOTE_DESKTOP = 1;
private static final int DELAY_BEFORE_SESSION_CLOSE = 2000;
@@ -63,17 +67,23 @@ public final class ScreencastHelper {
= new Timer("auto-close screencast session", true);
private ScreencastHelper() {
}
private ScreencastHelper() {}
static {
SCREENCAST_DEBUG = Boolean.getBoolean("awt.robot.screenshotDebug");
boolean loadFailed = false;
boolean shouldLoadNative = XdgDesktopPortal.isRemoteDesktop()
|| XdgDesktopPortal.isScreencast();
int methodId = XdgDesktopPortal.isScreencast()
? XDG_METHOD_SCREENCAST
: XDG_METHOD_REMOTE_DESKTOP;
if (!(Toolkit.getDefaultToolkit() instanceof UNIXToolkit tk
&& tk.loadGTK())
|| !loadPipewire(SCREENCAST_DEBUG)) {
|| !(shouldLoadNative && loadPipewire(methodId, SCREENCAST_DEBUG))) {
System.err.println(
"Could not load native libraries for ScreencastHelper"
@@ -89,7 +99,7 @@ public final class ScreencastHelper {
return IS_NATIVE_LOADED;
}
private static native boolean loadPipewire(boolean screencastDebug);
private static native boolean loadPipewire(int method, boolean isDebug);
private static native int getRGBPixelsImpl(
int x, int y, int width, int height,
@@ -186,7 +196,7 @@ public final class ScreencastHelper {
if (retVal >= 0) { // we have received a screen data
return;
} else if (!checkReturnValue(retVal)) {
} else if (!checkReturnValue(retVal, true)) {
return;
} // else, try other tokens
}
@@ -200,25 +210,72 @@ public final class ScreencastHelper {
null
);
checkReturnValue(retVal);
checkReturnValue(retVal, true);
}
private static boolean checkReturnValue(int retVal) {
private static boolean checkReturnValue(int retVal,
boolean throwException) {
if (retVal == DENIED) {
// user explicitly denied the capture, no more tries.
throw new SecurityException(
"Screen Capture in the selected area was not allowed"
);
if (SCREENCAST_DEBUG) {
System.err.println("robot action: access denied by user.");
}
if (throwException) {
// user explicitly denied the capture, no more tries.
throw new SecurityException(
"Screen Capture in the selected area was not allowed"
);
}
} else if (retVal == ERROR) {
if (SCREENCAST_DEBUG) {
System.err.println("Screen capture failed.");
System.err.println("robot action: failed.");
}
} else if (retVal == OUT_OF_BOUNDS) {
if (SCREENCAST_DEBUG) {
System.err.println(
"Token does not provide access to requested area.");
}
} else if (retVal == NO_STREAMS) {
if (SCREENCAST_DEBUG) {
System.err.println("robot action: no streams available");
}
}
return retVal != ERROR;
}
private static void performWithToken(Function<String, Integer> func) {
if (!XdgDesktopPortal.isRemoteDesktop() || !IS_NATIVE_LOADED) return;
timerCloseSessionRestart();
for (TokenItem tokenItem : TokenStorage.getTokens(getSystemScreensBounds())) {
int retVal = func.apply(tokenItem.token);
if (retVal >= 0 || !checkReturnValue(retVal, false)) {
return;
}
}
checkReturnValue(func.apply(null), false);
}
public static synchronized void remoteDesktopMouseMove(int x, int y) {
performWithToken((token) -> remoteDesktopMouseMoveImpl(x, y, token));
}
public static synchronized void remoteDesktopMouseButton(boolean isPress, int buttons) {
performWithToken((token) -> remoteDesktopMouseButtonImpl(isPress, buttons, token));
}
public static synchronized void remoteDesktopMouseWheel(int wheel) {
performWithToken((token) -> remoteDesktopMouseWheelImpl(wheel, token));
}
public static synchronized void remoteDesktopKey(boolean isPress, int key) {
performWithToken((token) -> remoteDesktopKeyImpl(isPress, key, token));
}
private static synchronized native int remoteDesktopMouseMoveImpl(int x, int y, String token);
private static synchronized native int remoteDesktopMouseButtonImpl(boolean isPress, int buttons, String token);
private static synchronized native int remoteDesktopMouseWheelImpl(int wheelAmt, String token);
private static synchronized native int remoteDesktopKeyImpl(boolean isPress, int key, String token);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, 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
@@ -67,14 +67,15 @@ final class TokenStorage {
private static final String REL_NAME_SECONDARY =
".awt/robot/screencast-tokens.properties";
private static final String REL_RD_NAME =
".java/robot/remote-desktop-tokens.properties";
private static final Properties PROPS = new Properties();
private static final Path PROPS_PATH;
private static final Path PROP_FILENAME;
static {
Path propsPath = setupPath();
PROPS_PATH = propsPath;
PROPS_PATH = setupPath();
if (PROPS_PATH != null) {
PROP_FILENAME = PROPS_PATH.getFileName();
@@ -95,11 +96,18 @@ final class TokenStorage {
return null;
}
Path path = Path.of(userHome, REL_NAME);
Path secondaryPath = Path.of(userHome, REL_NAME_SECONDARY);
Path path;
Path secondaryPath = null;
if (XdgDesktopPortal.isRemoteDesktop()) {
path = Path.of(userHome, REL_RD_NAME);
} else {
path = Path.of(userHome, REL_NAME);
secondaryPath = Path.of(userHome, REL_NAME_SECONDARY);
}
boolean copyFromSecondary = !Files.isWritable(path)
&& Files.isWritable(secondaryPath);
&& secondaryPath != null && Files.isWritable(secondaryPath);
Path workdir = path.getParent();

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2025, 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. 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.screencast;
import sun.awt.SunToolkit;
import sun.awt.UNIXToolkit;
import java.awt.Toolkit;
public class XdgDesktopPortal {
private static final String METHOD_X11 = "x11";
private static final String METHOD_SCREENCAST = "dbusScreencast";
private static final String METHOD_REMOTE_DESKTOP = "dbusRemoteDesktop";
private static final String method;
private static final boolean isRemoteDesktop;
private static final boolean isScreencast;
private XdgDesktopPortal() {}
static {
boolean isOnWayland = false;
if (Toolkit.getDefaultToolkit() instanceof SunToolkit sunToolkit) {
isOnWayland = sunToolkit.isRunningOnWayland();
}
String defaultMethod = METHOD_X11;
if (isOnWayland) {
Integer gnomeShellVersion = null;
UNIXToolkit toolkit = (UNIXToolkit) Toolkit.getDefaultToolkit();
if ("gnome".equals(toolkit.getDesktop())) {
gnomeShellVersion = toolkit.getGnomeShellMajorVersion();
}
defaultMethod = (gnomeShellVersion != null && gnomeShellVersion >= 47)
? METHOD_REMOTE_DESKTOP
: METHOD_SCREENCAST;
}
String m = System.getProperty("awt.robot.screenshotMethod", defaultMethod);
if (!METHOD_REMOTE_DESKTOP.equals(m)
&& !METHOD_SCREENCAST.equals(m)
&& !METHOD_X11.equals(m)) {
m = defaultMethod;
}
isRemoteDesktop = METHOD_REMOTE_DESKTOP.equals(m);
isScreencast = METHOD_SCREENCAST.equals(m);
method = m;
}
public static String getMethod() {
return method;
}
public static boolean isRemoteDesktop() {
return isRemoteDesktop;
}
public static boolean isScreencast() {
return isScreencast;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2025, 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
@@ -617,11 +617,14 @@ GtkApi* gtk3_load(JNIEnv *env, const char* lib_name)
glib_version_2_68 = !fp_glib_check_version(2, 68, 0);
if (glib_version_2_68) {
// those function are called only by Screencast / Remote desktop
fp_g_string_replace = dl_symbol("g_string_replace"); //since: 2.68
fp_g_uuid_string_is_valid = //since: 2.52
dl_symbol("g_uuid_string_is_valid");
fp_g_variant_print = dl_symbol("g_variant_print"); // since 2.24
}
fp_g_string_printf = dl_symbol("g_string_printf");
fp_g_strconcat = dl_symbol("g_strconcat");
fp_g_error_free = dl_symbol("g_error_free");
fp_g_unix_fd_list_get = dl_symbol("g_unix_fd_list_get");
@@ -3102,6 +3105,7 @@ static void gtk3_init(GtkApi* gtk) {
gtk->g_variant_new_string = fp_g_variant_new_string;
gtk->g_variant_new_boolean = fp_g_variant_new_boolean;
gtk->g_variant_new_uint32 = fp_g_variant_new_uint32;
gtk->g_variant_print = fp_g_variant_print;
gtk->g_variant_get = fp_g_variant_get;
gtk->g_variant_get_string = fp_g_variant_get_string;
@@ -3126,6 +3130,7 @@ static void gtk3_init(GtkApi* gtk) {
gtk->g_string_free = fp_g_string_free;
gtk->g_string_replace = fp_g_string_replace;
gtk->g_string_printf = fp_g_string_printf;
gtk->g_strconcat = fp_g_strconcat;
gtk->g_uuid_string_is_valid = fp_g_uuid_string_is_valid;
gtk->g_main_context_iteration = fp_g_main_context_iteration;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2025, 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
@@ -399,6 +399,7 @@ static gboolean (*fp_g_str_has_prefix)(const gchar *str, const gchar *prefix);
static gchar** (*fp_g_strsplit)(const gchar *string, const gchar *delimiter,
gint max_tokens);
static void (*fp_g_strfreev)(gchar **str_array);
static gchar* (*fp_g_strconcat)(const gchar* string1, ...);
static cairo_surface_t* (*fp_cairo_image_surface_create)(cairo_format_t format,
@@ -738,6 +739,8 @@ static GVariant *(*fp_g_variant_new_boolean)(gboolean value);
static GVariant *(*fp_g_variant_new_uint32)(guint32 value);
static gchar *(*fp_g_variant_print) (GVariant* value, gboolean type_annotate);
static void (*fp_g_variant_get)(GVariant *value,
const gchar *format_string,
...);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2025, 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
@@ -678,6 +678,7 @@ typedef struct GtkApi {
GVariant *(*g_variant_new_boolean)(gboolean value);
GVariant *(*g_variant_new_uint32)(guint32 value);
gchar *(*g_variant_print)(GVariant* value, gboolean type_annotate);
void (*g_variant_get)(GVariant *value,
const gchar *format_string,
@@ -734,6 +735,8 @@ typedef struct GtkApi {
const gchar *format,
...);
gchar* (*g_strconcat)(const gchar* string1, ...);
gboolean (*g_uuid_string_is_valid)(const gchar *str);

View File

@@ -94,6 +94,7 @@ struct pw_properties * (*fp_pw_properties_new)(const char *key, ...);
#include "gtk_interface.h"
#include "gtk3_interface.h"
#include "canvas.h"
int DEBUG_SCREENCAST_ENABLED = FALSE;
@@ -108,6 +109,7 @@ static GString *activeSessionToken;
struct ScreenSpace screenSpace = {0};
static struct PwLoopData pw = {0};
volatile bool isGtkMainThread = FALSE;
gboolean isRemoteDesktop = FALSE;
jclass tokenStorageClass = NULL;
jmethodID storeTokenMethodID = NULL;
@@ -198,7 +200,7 @@ static void doCleanup() {
/**
* @return TRUE on success
*/
static gboolean initScreencast(const gchar *token,
static gboolean initPortal(const gchar *token,
GdkRectangle *affectedBounds,
gint affectedBoundsLength) {
gboolean isSameToken = !token
@@ -225,8 +227,8 @@ static gboolean initScreencast(const gchar *token,
if (!initScreenSpace()
|| !initXdgDesktopPortal()
|| (pw.pwFd = getPipewireFd(token,
affectedBounds,
|| !initAndStartSession(token, &pw.pwFd)
|| (pw.pwFd = getPipewireFd(affectedBounds,
affectedBoundsLength)) < 0) {
doCleanup();
return FALSE;
@@ -858,10 +860,19 @@ void storeRestoreToken(const gchar* oldToken, const gchar* newToken) {
* Signature: (IZ)Z
*/
JNIEXPORT jboolean JNICALL Java_sun_awt_screencast_ScreencastHelper_loadPipewire(
JNIEnv *env, jclass cls, jboolean screencastDebug
JNIEnv *env, jclass cls, jint method, jboolean screencastDebug
) {
DEBUG_SCREENCAST_ENABLED = screencastDebug;
if (method != XDG_METHOD_SCREENCAST
&& method != XDG_METHOD_REMOTE_DESKTOP) {
return JNI_FALSE;
}
isRemoteDesktop = method == XDG_METHOD_REMOTE_DESKTOP;
DEBUG_SCREENCAST("method %d\n", method)
if (!loadSymbols()) {
return JNI_FALSE;
}
@@ -934,7 +945,7 @@ static int makeScreencast(
GdkRectangle *affectedScreenBounds,
gint affectedBoundsLength
) {
if (!initScreencast(token, affectedScreenBounds, affectedBoundsLength)) {
if (!initPortal(token, affectedScreenBounds, affectedBoundsLength)) {
return pw.pwFd;
}
@@ -1097,10 +1108,130 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl
releaseToken(env, jtoken, token);
return 0;
}
/*
* Class: sun_awt_screencast_ScreencastHelper
* Method: remoteDesktopMouseMove
* Signature: (IILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMouseMoveImpl
(JNIEnv *env, jclass cls, jint jx, jint jy, jstring jtoken) {
const gchar *token = jtoken
? (*env)->GetStringUTFChars(env, jtoken, NULL)
: NULL;
DEBUG_SCREENCAST("moving mouse to\n\t%d %d\n\twith token |%s|\n", jx, jy, token);
gboolean result = initPortal(token, NULL, 0);
DEBUG_SCREENCAST("init result %b, moving to %d %d\n", result, jx, jy)
if (result) {
if (!remoteDesktopMouseMove(jx, jy)) {
releaseToken(env, jtoken, token);
return RESULT_DENIED;
}
}
releaseToken(env, jtoken, token);
return result ? RESULT_OK : pw.pwFd;
}
/*
* Class: sun_awt_screencast_ScreencastHelper
* Method: remoteDesktopMouseButtonImpl
* Signature: (ZILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMouseButtonImpl
(JNIEnv *env, jclass cls, jboolean isPress, jint buttons, jstring jtoken) {
const gchar *token = jtoken
? (*env)->GetStringUTFChars(env, jtoken, NULL)
: NULL;
gboolean result = initPortal(token, NULL, 0);
DEBUG_SCREENCAST("init result %b, mouse pressing %d\n", result, buttons)
if (result) {
if (!remoteDesktopMouse(isPress, buttons)) {
releaseToken(env, jtoken, token);
return RESULT_DENIED;
}
}
releaseToken(env, jtoken, token);
return result ? RESULT_OK : pw.pwFd;
}
/*
* Class: sun_awt_screencast_ScreencastHelper
* Method: remoteDesktopMouseWheelImpl
* Signature: (ILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMouseWheelImpl
(JNIEnv *env, jclass cls, jint jWheelAmt, jstring jtoken) {
const gchar *token = jtoken
? (*env)->GetStringUTFChars(env, jtoken, NULL)
: NULL;
gboolean result = initPortal(token, NULL, 0);
DEBUG_SCREENCAST("init result %b, mouse wheel %d\n", result, jWheelAmt)
if (result) {
if (!remoteDesktopMouseWheel(jWheelAmt)) {
releaseToken(env, jtoken, token);
return RESULT_DENIED;
}
}
releaseToken(env, jtoken, token);
return result ? RESULT_OK : pw.pwFd;
}
/*
* Class: sun_awt_screencast_ScreencastHelper
* Method: remoteDesktopKeyImpl
* Signature: (ZILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopKeyImpl
(JNIEnv *env, jclass cls, jboolean isPress, jint jkey, jstring jtoken) {
AWT_LOCK();
int key = awt_getX11KeySym(jkey);
AWT_UNLOCK();
if (key == NoSymbol) {
return RESULT_ERROR;
}
const gchar *token = jtoken
? (*env)->GetStringUTFChars(env, jtoken, NULL)
: NULL;
gboolean result = initPortal(token, NULL, 0);
DEBUG_SCREENCAST("init result %b, key %d -> %d isPress %b\n", result, jkey, key, isPress)
if (result) {
if (!remoteDesktopKey(isPress, key)) {
releaseToken(env, jtoken, token);
return RESULT_DENIED;
}
}
releaseToken(env, jtoken, token);
return result ? RESULT_OK : pw.pwFd;
}
#else
JNIEXPORT void JNICALL
Java_sun_awt_screencast_ScreencastHelper_closeSession(JNIEnv *env, jclass cls) {
}
Java_sun_awt_screencast_ScreencastHelper_closeSession(JNIEnv *env, jclass cls) {}
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl(
JNIEnv *env,
@@ -1117,8 +1248,28 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl
}
JNIEXPORT jboolean JNICALL Java_sun_awt_screencast_ScreencastHelper_loadPipewire(
JNIEnv *env, jclass cls, jboolean screencastDebug
JNIEnv *env, jclass cls, jint method, jboolean screencastDebug
) {
return JNI_FALSE;
}
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMouseMoveImpl
(JNIEnv *env, jclass cls, jint jx, jint jy, jstring token) {
return -1;
}
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMouseButtonImpl
(JNIEnv *env, jclass cls, jboolean isPress, jint buttons, jstring jtoken) {
return -1;
}
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMouseWheelImpl
(JNIEnv *env, jclass cls, jint jWheelAmt, jstring jtoken) {
return -1;
}
JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopKeyImpl
(JNIEnv *env, jclass cls, jboolean isPress, jint jkey, jstring jtoken) {
return -1;
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, 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
@@ -41,6 +41,8 @@
void storeRestoreToken(const gchar* oldToken, const gchar* newToken);
void print_gvariant_content(gchar *caption, GVariant *response);
struct ScreenProps {
guint32 id;
GdkRectangle bounds;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, 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
@@ -24,11 +24,9 @@
*/
#include "stdlib.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <unistd.h>
#include "java_awt_event_InputEvent.h"
#ifndef _AIX
#include "screencast_pipewire.h"
@@ -36,10 +34,16 @@
#include "screencast_portal.h"
extern volatile bool isGtkMainThread;
extern gboolean isRemoteDesktop;
extern struct ScreenSpace screenSpace;
struct XdgDesktopPortalApi *portal = NULL;
extern int DEBUG_SCREENCAST_ENABLED;
GDBusProxy *getProxy() {
return isRemoteDesktop ? portal->remoteDesktopProxy : portal->screenCastProxy;
}
void errHandle(
GError *error,
@@ -170,26 +174,38 @@ gboolean rebuildScreenData(GVariantIter *iterStreams, gboolean isTheOnlyMon) {
}
/**
* Checks screencast protocol version
* @return FALSE if version < 4, or could not be determined
* Checks the version of the Screencast/Remote Desktop protocol
* to determine whether it supports the restore_token.
* @return FALSE if version is below required, or could not be determined
*/
gboolean checkVersion() {
static guint32 version = 0;
const gchar *interface = isRemoteDesktop
? PORTAL_IFACE_REMOTE_DESKTOP
: PORTAL_IFACE_SCREENCAST;
if (version == 0) {
GError *error = NULL;
GVariant *retVersion = gtk->g_dbus_proxy_call_sync(
portal->screenCastProxy,
getProxy(),
"org.freedesktop.DBus.Properties.Get",
gtk->g_variant_new("(ss)",
"org.freedesktop.portal.ScreenCast",
interface,
"version"),
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, NULL
);
if (isRemoteDesktop) {
print_gvariant_content("checkVersion Remote Desktop", retVersion);
} else {
print_gvariant_content("checkVersion ScreenCast", retVersion);
}
if (!retVersion) { //no backend on system
DEBUG_SCREENCAST("!!! could not detect the screencast version\n",
NULL);
DEBUG_SCREENCAST("!!! could not detect the %s version\n", interface);
return FALSE;
}
@@ -200,8 +216,7 @@ gboolean checkVersion() {
if (!varVersion){
gtk->g_variant_unref(retVersion);
DEBUG_SCREENCAST("!!! could not get the screencast version\n",
NULL);
DEBUG_SCREENCAST("!!! could not get the %s version\n", interface);
return FALSE;
}
@@ -212,16 +227,22 @@ gboolean checkVersion() {
}
DEBUG_SCREENCAST("ScreenCast protocol version %d\n", version);
if (version < 4) {
DEBUG_SCREENCAST("!!! ScreenCast protocol version %d < 4,"
gboolean isVersionOk = isRemoteDesktop
? version >= PORTAL_MIN_VERSION_REMOTE_DESKTOP
: version >= PORTAL_MIN_VERSION_SCREENCAST;
if (!isVersionOk) {
DEBUG_SCREENCAST("!!! %s protocol version %d < %d,"
" session restore is not available\n",
version);
interface,
version,
isRemoteDesktop
? PORTAL_MIN_VERSION_REMOTE_DESKTOP
: PORTAL_MIN_VERSION_SCREENCAST
);
}
// restore_token was added in version 4, without it,
// user confirmation is required for every screenshot.
return version >= 4;
return isVersionOk;
}
/**
@@ -266,9 +287,9 @@ gboolean initXdgDesktopPortal() {
portal->connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.ScreenCast",
PORTAL_DESKTOP_BUS_NAME,
PORTAL_DESKTOP_OBJECT_PATH,
PORTAL_IFACE_SCREENCAST,
NULL,
&err
);
@@ -277,6 +298,29 @@ gboolean initXdgDesktopPortal() {
DEBUG_SCREENCAST("Failed to get ScreenCast portal: %s", err->message);
ERR_HANDLE(err);
return FALSE;
} else {
DEBUG_SCREENCAST("%s: connection/sender name %s / %s\n",
"ScreenCast", name,
portal->senderName);
}
if (isRemoteDesktop) {
portal->remoteDesktopProxy = gtk->g_dbus_proxy_new_sync(
portal->connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
PORTAL_DESKTOP_BUS_NAME,
PORTAL_DESKTOP_OBJECT_PATH,
PORTAL_IFACE_REMOTE_DESKTOP,
NULL,
&err
);
if (err) {
DEBUG_SCREENCAST("Failed to get Remote Desktop portal: %s", err->message);
ERR_HANDLE(err);
return FALSE;
}
}
return checkVersion();
@@ -337,8 +381,8 @@ static void registerScreenCastCallback(
) {
helper->id = gtk->g_dbus_connection_signal_subscribe(
portal->connection,
"org.freedesktop.portal.Desktop",
"org.freedesktop.portal.Request",
PORTAL_DESKTOP_BUS_NAME,
PORTAL_IFACE_REQUEST,
"Response",
path,
NULL,
@@ -383,7 +427,8 @@ static void callbackScreenCastCreateSession(
if (status != 0) {
DEBUG_SCREENCAST("Failed to create ScreenCast: %u\n", status);
} else {
gtk->g_variant_lookup(result, "session_handle", "s", helper->data);
gboolean returned = gtk->g_variant_lookup(result, "session_handle", "s", helper->data);
DEBUG_SCREENCAST("session_handle returned %b %p\n", returned, helper->data)
}
helper->isDone = TRUE;
@@ -430,6 +475,9 @@ gboolean portalScreenCastCreateSession() {
gtk->g_variant_new_string(requestToken)
);
DEBUG_SCREENCAST("sessionToken %s \n", sessionToken)
gtk->g_variant_builder_add(
&builder,
"{sv}",
@@ -437,8 +485,14 @@ gboolean portalScreenCastCreateSession() {
gtk->g_variant_new_string(sessionToken)
);
DEBUG_SCREENCAST("portalScreenCastCreateSession: proxy %s %p (rd: %p / sc: %p)\n",
isRemoteDesktop ? "remoteDesktop" : "screencast",
getProxy(),
portal->remoteDesktopProxy,
portal->screenCastProxy);
GVariant *response = gtk->g_dbus_proxy_call_sync(
portal->screenCastProxy,
getProxy(),
"CreateSession",
gtk->g_variant_new("(a{sv})", &builder),
G_DBUS_CALL_FLAGS_NONE,
@@ -447,6 +501,8 @@ gboolean portalScreenCastCreateSession() {
&err
);
print_gvariant_content("CreateSession", response);
if (err) {
DEBUG_SCREENCAST("Failed to create ScreenCast session: %s\n",
err->message);
@@ -455,6 +511,8 @@ gboolean portalScreenCastCreateSession() {
waitForCallback(&helper);
}
DEBUG_SCREENCAST("portal->screenCastSessionHandle %s\n", portal->screenCastSessionHandle);
unregisterScreenCastCallback(&helper);
if (response) {
gtk->g_variant_unref(response);
@@ -500,6 +558,39 @@ static void callbackScreenCastSelectSources(
callbackEnd();
}
static void callbackRemoteDesktopSelectDevices(
GDBusConnection *connection,
const char *senderName,
const char *objectPath,
const char *interfaceName,
const char *signalName,
GVariant *parameters,
void *data
) {
struct DBusCallbackHelper *helper = data;
helper->data = (void *) 0;
uint32_t status;
GVariant* result = NULL;
gtk->g_variant_get(parameters, "(u@a{sv})", &status, &result);
if (status != 0) {
DEBUG_SCREENCAST("Failed select devices: %u\n", status);
} else {
helper->data = (void *) 1;
}
helper->isDone = TRUE;
if (result) {
gtk->g_variant_unref(result);
}
callbackEnd();
}
gboolean portalScreenCastSelectSources(const gchar *token) {
GError* err = NULL;
@@ -545,25 +636,33 @@ gboolean portalScreenCastSelectSources(const gchar *token) {
gtk->g_variant_new_uint32(1)
);
// In the case of Remote Desktop,
// we add the restore_token and persist_mode to the SelectDevices call.
// 0: Do not persist (default)
// 1: Permissions persist as long as the application is running
// 2: Permissions persist until explicitly revoked
gtk->g_variant_builder_add(
&builder,
"{sv}",
"persist_mode",
gtk->g_variant_new_uint32(2)
);
if (validateToken(token)) {
if (!isRemoteDesktop) {
gtk->g_variant_builder_add(
&builder,
"{sv}",
"restore_token",
gtk->g_variant_new_string(token)
"persist_mode",
gtk->g_variant_new_uint32(2)
);
}
if (!isRemoteDesktop) {
if (validateToken(token)) {
DEBUG_SCREENCAST(">>> adding token %s\n", token);
gtk->g_variant_builder_add(
&builder,
"{sv}",
"restore_token",
gtk->g_variant_new_string(token)
);
}
}
GVariant *response = gtk->g_dbus_proxy_call_sync(
portal->screenCastProxy,
"SelectSources",
@@ -574,6 +673,8 @@ gboolean portalScreenCastSelectSources(const gchar *token) {
&err
);
print_gvariant_content("SelectSources", response);
if (err) {
DEBUG_SCREENCAST("Failed to call SelectSources: %s\n", err->message);
ERR_HANDLE(err);
@@ -624,6 +725,15 @@ static void callbackScreenCastStart(
G_VARIANT_TYPE_ARRAY
);
print_gvariant_content("Streams", streams);
if (!streams) {
DEBUG_SCREENCAST("No streams available with current token\n", NULL);
startHelper->result = RESULT_NO_STREAMS;
helper->isDone = TRUE;
return;
}
GVariantIter iter;
gtk->g_variant_iter_init(
&iter,
@@ -661,9 +771,7 @@ static void callbackScreenCastStart(
helper->isDone = TRUE;
if (streams) {
gtk->g_variant_unref(streams);
}
gtk->g_variant_unref(streams);
callbackEnd();
}
@@ -706,7 +814,7 @@ ScreenCastResult portalScreenCastStart(const gchar *token) {
);
GVariant *response = gtk->g_dbus_proxy_call_sync(
portal->screenCastProxy,
getProxy(),
"Start",
gtk->g_variant_new("(osa{sv})", portal->screenCastSessionHandle, "", &builder),
G_DBUS_CALL_FLAGS_NONE,
@@ -715,6 +823,8 @@ ScreenCastResult portalScreenCastStart(const gchar *token) {
&err
);
print_gvariant_content("Start", response);
if (err) {
DEBUG_SCREENCAST("Failed to start session: %s\n", err->message);
ERR_HANDLE(err);
@@ -808,9 +918,9 @@ void portalScreenCastCleanup() {
if (portal->screenCastSessionHandle) {
gtk->g_dbus_connection_call_sync(
portal->connection,
"org.freedesktop.portal.Desktop",
PORTAL_DESKTOP_BUS_NAME,
portal->screenCastSessionHandle,
"org.freedesktop.portal.Session",
PORTAL_IFACE_SESSION,
"Close",
NULL,
NULL,
@@ -889,33 +999,140 @@ gboolean checkCanCaptureAllRequiredScreens(GdkRectangle *affectedBounds,
return true;
}
gboolean remoteDesktopSelectDevicesIfNeeded(const gchar* token) {
if (!isRemoteDesktop || !portal->remoteDesktopProxy) {
DEBUG_SCREENCAST("Skipping, remote desktop is not selected \n", NULL);
return TRUE;
}
GError* err = NULL;
gchar *requestPath = NULL;
gchar *requestToken = NULL;
struct DBusCallbackHelper helper = {0};
updateRequestPath(
&requestPath,
&requestToken
);
registerScreenCastCallback(
requestPath,
&helper,
callbackRemoteDesktopSelectDevices
);
GVariantBuilder builder;
gtk->g_variant_builder_init(
&builder,
G_VARIANT_TYPE_VARDICT
);
gtk->g_variant_builder_add(
&builder,
"{sv}", "handle_token",
gtk->g_variant_new_string(requestToken)
);
// 1: KEYBOARD
// 2: POINTER
// 4: TOUCHSCREEN
gtk->g_variant_builder_add(
&builder, "{sv}", "types",
gtk->g_variant_new_uint32(1 | 2)
);
// 0: Do not persist (default)
// 1: Permissions persist as long as the application is running
// 2: Permissions persist until explicitly revoked
gtk->g_variant_builder_add(
&builder,
"{sv}",
"persist_mode",
gtk->g_variant_new_uint32(2)
);
if (validateToken(token)) {
gtk->g_variant_builder_add(
&builder,
"{sv}",
"restore_token",
gtk->g_variant_new_string(token)
);
}
GVariant *response = gtk->g_dbus_proxy_call_sync(
portal->remoteDesktopProxy,
"SelectDevices",
gtk->g_variant_new("(oa{sv})", portal->screenCastSessionHandle, &builder),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&err
);
print_gvariant_content("SelectDevices", response);
if (err) {
DEBUG_SCREENCAST("Failed to call SelectDevices: %s\n", err->message);
ERR_HANDLE(err);
} else {
waitForCallback(&helper);
}
unregisterScreenCastCallback(&helper);
if (response) {
gtk->g_variant_unref(response);
}
free(requestPath);
free(requestToken);
return helper.data != NULL;
}
gboolean initAndStartSession(const gchar *token, int *retVal) {
*retVal = RESULT_ERROR;
int getPipewireFd(const gchar *token,
GdkRectangle *affectedBounds,
gint affectedBoundsLength) {
if (!portalScreenCastCreateSession()) {
DEBUG_SCREENCAST("Failed to create ScreenCast session\n", NULL);
return RESULT_ERROR;
return FALSE;
}
if (!portalScreenCastSelectSources(token)) {
DEBUG_SCREENCAST("Failed to select sources\n", NULL);
return RESULT_ERROR;
return FALSE;
}
if (!remoteDesktopSelectDevicesIfNeeded(token)) {
return FALSE;
}
ScreenCastResult startResult = portalScreenCastStart(token);
DEBUG_SCREENCAST("portalScreenCastStart result |%i|\n", startResult);
if (startResult != RESULT_OK) {
DEBUG_SCREENCAST("Failed to start\n", NULL);
return startResult;
} else {
if (!checkCanCaptureAllRequiredScreens(affectedBounds,
affectedBoundsLength)) {
DEBUG_SCREENCAST("The location of the screens has changed, "
"the capture area is outside the allowed "
"area.\n", NULL)
return RESULT_OUT_OF_BOUNDS;
}
DEBUG_SCREENCAST("Failed to start %d\n", startResult);
*retVal = startResult;
return FALSE;
}
*retVal = RESULT_OK;
return TRUE;
}
int getPipewireFd(GdkRectangle *affectedBounds,
gint affectedBoundsLength) {
if (!checkCanCaptureAllRequiredScreens(affectedBounds,
affectedBoundsLength)) {
DEBUG_SCREENCAST("The location of the screens has changed, "
"the capture area is outside the allowed "
"area.\n", NULL)
return RESULT_OUT_OF_BOUNDS;
}
DEBUG_SCREENCAST("--- portalScreenCastStart\n", NULL);
@@ -928,4 +1145,182 @@ int getPipewireFd(const gchar *token,
DEBUG_SCREENCAST("pwFd %i\n", pipewireFd);
return pipewireFd;
}
void print_gvariant_content(gchar *caption, GVariant *response) {
if (!DEBUG_SCREENCAST_ENABLED) {
return;
}
gchar *str = NULL;
if (response != NULL) {
str = gtk->g_variant_print(response, TRUE);
}
DEBUG_SCREENCAST("%s response:\n\t%s\n",
caption, str);
gtk->g_free(str);
}
static gboolean callRemoteDesktop(const gchar* methodName, GVariant *params) {
GError *err = NULL;
GVariantBuilder builder;
gtk->g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
GVariant *response = gtk->g_dbus_proxy_call_sync(
portal->remoteDesktopProxy,
methodName,
params,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&err
);
gchar * caption = gtk->g_strconcat("callRemoteDesktop ", methodName, NULL);
print_gvariant_content(caption, response);
gtk->g_free(caption);
DEBUG_SCREENCAST("%s: response %p err %p\n", methodName, response, err);
if (err) {
DEBUG_SCREENCAST("Failed to call %s: %s\n", methodName, err->message);
ERR_HANDLE(err);
// e.g. user denied mouse keyboard/interaction
return FALSE;
}
return TRUE;
}
void clampCoordsIfNeeded(int *x, int *y) {
if (screenSpace.screenCount <= 0 || x == NULL || y == NULL) {
return;
}
GdkRectangle s0 = screenSpace.screens[0].bounds;
int minX = s0.x;
int minY = s0.y;
int maxX = s0.x + s0.width;
int maxY = s0.y + s0.height;
for (int i = 1; i < screenSpace.screenCount; ++i) {
GdkRectangle s = screenSpace.screens[i].bounds;
if (s.x < minX) minX = s.x;
if (s.y < minY) minY = s.y;
if (s.x + s.width > maxX) maxX = s.x + s.width;
if (s.y + s.height > maxY) maxY = s.y + s.height;
}
if (*x < minX) {
*x = minX;
} else if (*x > maxX) {
*x = maxX - 1;
}
if (*y < minY) {
*y = minY;
} else if (*y > maxY) {
*y = maxY - 1;
}
}
gboolean remoteDesktopMouseMove(int x, int y) {
guint32 streamId = 0;
int relX = -1;
int relY = -1;
DEBUG_SCREENCAST("mouseMove %d %d\n", x, y);
clampCoordsIfNeeded(&x, &y);
DEBUG_SCREENCAST("after clamping %d %d\n", x, y);
for (int i = 0; i < screenSpace.screenCount; ++i) {
struct ScreenProps *screenProps = &screenSpace.screens[i];
GdkRectangle rect = screenProps->bounds;
if (x >= rect.x &&
y >= rect.y &&
x < rect.x + rect.width &&
y < rect.y + rect.height) {
streamId = screenProps->id;
relX = x - rect.x;
relY = y - rect.y;
DEBUG_SCREENCAST("screenId#%i point %dx%d (rel %i %i) inside of screen (%d, %d, %d, %d)\n",
streamId,
x, y, relX, relY,
rect.x, rect.y, rect.width, rect.height);
break;
}
}
if (streamId == 0) {
DEBUG_SCREENCAST("outside of available screens\n", NULL);
return TRUE;
}
GVariantBuilder builder;
gtk->g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
GVariant *params = gtk->g_variant_new("(oa{sv}udd)", portal->screenCastSessionHandle, &builder,
streamId, (double) relX, (double) relY);
return callRemoteDesktop("NotifyPointerMotionAbsolute", params);
}
gboolean callRemoteDesktopNotifyPointerButton(gboolean isPress, int evdevButton) {
DEBUG_SCREENCAST("isPress %d evdevButton %d\n", isPress, evdevButton);
GVariantBuilder builder;
gtk->g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
GVariant *params = gtk->g_variant_new("(oa{sv}iu)",
portal->screenCastSessionHandle, &builder, evdevButton, isPress);
return callRemoteDesktop("NotifyPointerButton", params);
}
gboolean remoteDesktopMouse(gboolean isPress, int buttons) {
DEBUG_SCREENCAST("isPress %d awt buttons mask %d\n", isPress, buttons);
if (buttons & java_awt_event_InputEvent_BUTTON1_MASK
|| buttons & java_awt_event_InputEvent_BUTTON1_DOWN_MASK) {
if (!callRemoteDesktopNotifyPointerButton(isPress, 0x110)) { // BTN_LEFT
return FALSE;
}
}
if (buttons & java_awt_event_InputEvent_BUTTON2_MASK
|| buttons & java_awt_event_InputEvent_BUTTON2_DOWN_MASK) {
if (!callRemoteDesktopNotifyPointerButton(isPress, 0x112)) { // BTN_MIDDLE
return FALSE;
}
}
if (buttons & java_awt_event_InputEvent_BUTTON3_MASK
|| buttons & java_awt_event_InputEvent_BUTTON3_DOWN_MASK) {
if (!callRemoteDesktopNotifyPointerButton(isPress, 0x111)) { // BTN_RIGHT
return FALSE;
}
}
return TRUE;
}
gboolean remoteDesktopMouseWheel(int wheelAmt) {
DEBUG_SCREENCAST("MouseWheel %d\n", wheelAmt);
GVariantBuilder builder;
gtk->g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
GVariant *params = gtk->g_variant_new("(oa{sv}ui)", portal->screenCastSessionHandle, &builder, 0, wheelAmt);
return callRemoteDesktop("NotifyPointerAxisDiscrete", params);
}
gboolean remoteDesktopKey(gboolean isPress, int key) {
DEBUG_SCREENCAST("Key%s key %d -> \n", isPress ? "Press" : "Release", key);
GVariantBuilder builder;
gtk->g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
GVariant *params = gtk->g_variant_new ("(oa{sv}iu)", portal->screenCastSessionHandle, &builder, key, isPress);
return callRemoteDesktop("NotifyKeyboardKeysym", params);
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, 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
@@ -36,11 +36,21 @@
#define PORTAL_REQUEST_TEMPLATE "/org/freedesktop/portal/desktop/" \
"request/%s/awtPipewire%lu"
#define PORTAL_DESKTOP_BUS_NAME "org.freedesktop.portal.Desktop"
#define PORTAL_DESKTOP_OBJECT_PATH "/org/freedesktop/portal/desktop"
#define PORTAL_IFACE_REQUEST "org.freedesktop.portal.Request"
#define PORTAL_IFACE_SESSION "org.freedesktop.portal.Session"
#define PORTAL_IFACE_SCREENCAST "org.freedesktop.portal.ScreenCast"
#define PORTAL_IFACE_REMOTE_DESKTOP "org.freedesktop.portal.RemoteDesktop"
#define PORTAL_MIN_VERSION_SCREENCAST 4
#define PORTAL_MIN_VERSION_REMOTE_DESKTOP 2
void debug_screencast(const char *__restrict fmt, ...);
int getPipewireFd(const gchar *token,
GdkRectangle *affectedBounds,
gint affectedBoundsLength);
gboolean initAndStartSession(const gchar *token, int *retVal);
int getPipewireFd(GdkRectangle *affectedBounds, gint affectedBoundsLength);
void portalScreenCastCleanup();
@@ -48,8 +58,14 @@ gboolean initXdgDesktopPortal();
void errHandle(GError *error, const gchar *functionName, int lineNum);
gboolean remoteDesktopMouseMove(int x, int y);
gboolean remoteDesktopMouseWheel(int wheelAmt);
gboolean remoteDesktopMouse(gboolean isPress, int buttons);
gboolean remoteDesktopKey(gboolean isPress, int key);
struct XdgDesktopPortalApi {
GDBusConnection *connection;
GDBusProxy *remoteDesktopProxy;
GDBusProxy *screenCastProxy;
gchar *senderName;
char *screenCastSessionHandle;
@@ -66,8 +82,15 @@ typedef enum {
RESULT_ERROR = -1,
RESULT_DENIED = -11,
RESULT_OUT_OF_BOUNDS = -12,
RESULT_NO_STREAMS = -13,
} ScreenCastResult;
typedef enum {
XDG_METHOD_SCREENCAST = 0,
XDG_METHOD_REMOTE_DESKTOP = 1,
} XdgPortalMethod;
struct StartHelper {
const gchar *token;
ScreenCastResult result;