diff --git a/src/java.desktop/unix/classes/sun/awt/UNIXToolkit.java b/src/java.desktop/unix/classes/sun/awt/UNIXToolkit.java index 053684423de..7e27248ebae 100644 --- a/src/java.desktop/unix/classes/sun/awt/UNIXToolkit.java +++ b/src/java.desktop/unix/classes/sun/awt/UNIXToolkit.java @@ -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) { diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XRobotPeer.java b/src/java.desktop/unix/classes/sun/awt/X11/XRobotPeer.java index fa0bdbed68c..f53e521d81a 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XRobotPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XRobotPeer.java @@ -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, diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java b/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java index 3a7ea1e40ed..eab0817af23 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java @@ -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. diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java index 4065fd04b93..33af39810d5 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java @@ -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 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); } diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java b/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java index 8f712e1d192..9db64725048 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java @@ -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(); diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/XdgDesktopPortal.java b/src/java.desktop/unix/classes/sun/awt/screencast/XdgDesktopPortal.java new file mode 100644 index 00000000000..6170c9222a3 --- /dev/null +++ b/src/java.desktop/unix/classes/sun/awt/screencast/XdgDesktopPortal.java @@ -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; + } +} diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/gtk3_interface.c b/src/java.desktop/unix/native/libawt_xawt/awt/gtk3_interface.c index 3b115780acf..916880873c6 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/gtk3_interface.c +++ b/src/java.desktop/unix/native/libawt_xawt/awt/gtk3_interface.c @@ -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; diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/gtk3_interface.h b/src/java.desktop/unix/native/libawt_xawt/awt/gtk3_interface.h index 6849645784d..d3a83677dd6 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/gtk3_interface.h +++ b/src/java.desktop/unix/native/libawt_xawt/awt/gtk3_interface.h @@ -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, ...); diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/gtk_interface.h b/src/java.desktop/unix/native/libawt_xawt/awt/gtk_interface.h index ac6d2d7fdf6..11ec245ce8b 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/gtk_interface.h +++ b/src/java.desktop/unix/native/libawt_xawt/awt/gtk_interface.h @@ -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); diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c index 62d9e358173..ec12445c87b 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c +++ b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c @@ -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 diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.h b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.h index 07a7e91304c..30ac0f3582a 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.h +++ b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.h @@ -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; diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_portal.c b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_portal.c index 4d99b90ba08..fad2cab1696 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_portal.c +++ b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_portal.c @@ -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 -#include #include -#include -#include + +#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 diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_portal.h b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_portal.h index 9ac210217ff..706337610f1 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_portal.h +++ b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_portal.h @@ -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;