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 6a19b9fab87aa..78661587554e3 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java @@ -26,11 +26,14 @@ package sun.awt.screencast; import sun.awt.UNIXToolkit; +import sun.java2d.pipe.Region; import sun.security.action.GetPropertyAction; +import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Toolkit; +import java.awt.geom.AffineTransform; import java.security.AccessController; import java.util.Arrays; import java.util.List; @@ -109,9 +112,20 @@ private static List getSystemScreensBounds() { .stream(GraphicsEnvironment .getLocalGraphicsEnvironment() .getScreenDevices()) - .map(graphicsDevice -> - graphicsDevice.getDefaultConfiguration().getBounds() - ).toList(); + .map(graphicsDevice -> { + GraphicsConfiguration gc = + graphicsDevice.getDefaultConfiguration(); + Rectangle screen = gc.getBounds(); + AffineTransform tx = gc.getDefaultTransform(); + + return new Rectangle( + Region.clipRound(screen.x * tx.getScaleX()), + Region.clipRound(screen.y * tx.getScaleY()), + Region.clipRound(screen.width * tx.getScaleX()), + Region.clipRound(screen.height * tx.getScaleY()) + ); + }) + .toList(); } private static synchronized native void closeSession(); 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 d39f7943c29fb..2f31611cf5f57 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java @@ -369,6 +369,17 @@ static Set getTokens(List affectedScreenBounds) { System.out.println("// getTokens same sizes 2. " + result); } + // 3. add tokens with the same or greater number of screens + // This is useful if we once received a token with one screen resolution + // and the same screen was later scaled in the system. + // In that case, the token is still valid. + + allTokenItems + .stream() + .filter(t -> + t.allowedScreensBounds.size() >= affectedScreenBounds.size()) + .forEach(result::add); + return result; } 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 900f2ccd3eedf..7c30b6a9e58a3 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 @@ -319,6 +319,10 @@ GtkApi* gtk3_load(JNIEnv *env, const char* lib_name) /* Pixbuf */ fp_gdk_pixbuf_new = dl_symbol("gdk_pixbuf_new"); + fp_gdk_pixbuf_new_from_data = dl_symbol("gdk_pixbuf_new_from_data"); + fp_gdk_pixbuf_scale_simple = dl_symbol("gdk_pixbuf_scale_simple"); + fp_gdk_pixbuf_copy_area = dl_symbol("gdk_pixbuf_copy_area"); + fp_gdk_pixbuf_new_from_file = dl_symbol("gdk_pixbuf_new_from_file"); fp_gdk_pixbuf_get_from_drawable = @@ -3123,4 +3127,10 @@ static void gtk3_init(GtkApi* gtk) { gtk->g_main_context_iteration = fp_g_main_context_iteration; gtk->g_error_free = fp_g_error_free; gtk->g_unix_fd_list_get = fp_g_unix_fd_list_get; + + gtk->gdk_pixbuf_new = fp_gdk_pixbuf_new; + gtk->gdk_pixbuf_new_from_data = fp_gdk_pixbuf_new_from_data; + gtk->gdk_pixbuf_scale_simple = fp_gdk_pixbuf_scale_simple; + gtk->gdk_pixbuf_get_pixels = fp_gdk_pixbuf_get_pixels; + gtk->gdk_pixbuf_copy_area = fp_gdk_pixbuf_copy_area; } 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 b858d5e680802..ed5997cb0cd13 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 @@ -528,6 +528,30 @@ static void (*fp_gdk_draw_rectangle)(GdkDrawable*, GdkGC*, gboolean, gint, gint, gint, gint); static GdkPixbuf *(*fp_gdk_pixbuf_new)(GdkColorspace colorspace, gboolean has_alpha, int bits_per_sample, int width, int height); + +static GdkPixbuf *(*fp_gdk_pixbuf_new_from_data)( + const guchar *data, + GdkColorspace colorspace, + gboolean has_alpha, + int bits_per_sample, + int width, + int height, + int rowstride, + GdkPixbufDestroyNotify destroy_fn, + gpointer destroy_fn_data +); + +static void (*fp_gdk_pixbuf_copy_area) ( + const GdkPixbuf* src_pixbuf, + int src_x, + int src_y, + int width, + int height, + GdkPixbuf* dest_pixbuf, + int dest_x, + int dest_y +); + static void (*fp_gdk_drawable_get_size)(GdkDrawable *drawable, gint* width, gint* height); static gboolean (*fp_gtk_init_check)(int* argc, char** argv); 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 1ddc239d9b23d..33bd4a6cee831 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 @@ -532,6 +532,8 @@ typedef void (*GClosureNotify)(gpointer data, GClosure *closure); typedef void (*GDestroyNotify)(gpointer data); typedef void (*GCallback)(void); +typedef void GdkPixbuf; +typedef void (* GdkPixbufDestroyNotify) (guchar *pixels, gpointer data); typedef struct GtkApi { int version; @@ -797,6 +799,46 @@ typedef struct GtkApi { gint index_, GError **error); + GdkPixbuf *(*gdk_pixbuf_new)(GdkColorspace colorspace, + gboolean has_alpha, + int bits_per_sample, + int width, + int height); + + + GdkPixbuf *(*gdk_pixbuf_new_from_data)( + const guchar *data, + GdkColorspace colorspace, + gboolean has_alpha, + int bits_per_sample, + int width, + int height, + int rowstride, + GdkPixbufDestroyNotify destroy_fn, + gpointer destroy_fn_data + ); + + + GdkPixbuf *(*gdk_pixbuf_scale_simple)(GdkPixbuf *src, + int dest_width, + int dest_heigh, + GdkInterpType interp_type + ); + + guchar* (*gdk_pixbuf_get_pixels) (const GdkPixbuf* pixbuf); + + + void (*gdk_pixbuf_copy_area) ( + const GdkPixbuf* src_pixbuf, + int src_x, + int src_y, + int width, + int height, + GdkPixbuf* dest_pixbuf, + int dest_x, + int dest_y + ); + /* */ } GtkApi; 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 132ccd0b7c44f..f2c145ff34517 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 @@ -175,49 +175,6 @@ static gboolean initScreencast(const gchar *token, return TRUE; } -static inline void convertRGBxToBGRx(int* in) { - char* o = (char*) in; - char tmp = o[0]; - o[0] = o[2]; - o[2] = tmp; -} - -static gchar * cropTo( - struct spa_data data, - struct spa_video_info_raw raw, - guint32 x, - guint32 y, - guint32 width, - guint32 height -) { - int srcW = raw.size.width; - if (data.chunk->stride / 4 != srcW) { - fprintf(stderr, "%s:%i Unexpected stride / 4: %i srcW: %i\n", - __func__, __LINE__, data.chunk->stride / 4, srcW); - } - - int* d = data.data; - - int *outData = calloc(width * height, sizeof(int)); - if (!outData) { - ERR("failed to allocate memory\n"); - return NULL; - } - - gboolean needConversion = raw.format != SPA_VIDEO_FORMAT_BGRx; - for (guint32 j = y; j < y + height; ++j) { - for (guint32 i = x; i < x + width; ++i) { - int color = *(d + (j * srcW) + i); - if (needConversion) { - convertRGBxToBGRx(&color); - } - *(outData + ((j - y) * width) + (i - x)) = color; - } - } - - return (gchar*) outData; -} - static void onStreamParamChanged( void *userdata, uint32_t id, @@ -301,24 +258,81 @@ static void onStreamProcess(void *userdata) { struct spa_data spaData = spaBuffer->datas[0]; + gint streamWidth = data->rawFormat.size.width; + gint streamHeight = data->rawFormat.size.height; + DEBUG_SCREEN(screen); DEBUG_SCREEN_PREFIX(screen, "got a frame of size %d offset %d stride %d " - "flags %d FD %li captureDataReady %i\n", + "flags %d FD %li captureDataReady %i of stream %dx%d\n", spaBuffer->datas[0].chunk->size, spaData.chunk->offset, spaData.chunk->stride, spaData.chunk->flags, spaData.fd, - screen->captureDataReady + screen->captureDataReady, + streamWidth, + streamHeight ); - data->screenProps->captureData = cropTo( - spaData, - data->rawFormat, - screen->captureArea.x, screen->captureArea.y, - screen->captureArea.width, screen->captureArea.height - ); + GdkRectangle captureArea = screen->captureArea; + GdkRectangle screenBounds = screen->bounds; + + GdkPixbuf *pixbuf = gtk->gdk_pixbuf_new_from_data(spaData.data, + GDK_COLORSPACE_RGB, + TRUE, + 8, + streamWidth, + streamHeight, + spaData.chunk->stride, + NULL, + NULL); + + if (screen->bounds.width != streamWidth + || screen->bounds.height != streamHeight) { + + DEBUG_SCREEN_PREFIX(screen, "scaling stream data %dx%d -> %dx%d\n", + streamWidth, streamHeight, + screen->bounds.width, screen->bounds.height + ); + + GdkPixbuf *scaled = gtk->gdk_pixbuf_scale_simple(pixbuf, + screen->bounds.width, + screen->bounds.height, + GDK_INTERP_BILINEAR); + + gtk->g_object_unref(pixbuf); + pixbuf = scaled; + } + + GdkPixbuf *cropped = NULL; + if (captureArea.width != screenBounds.width + || captureArea.height != screenBounds.height) { + + cropped = gtk->gdk_pixbuf_new(GDK_COLORSPACE_RGB, + TRUE, + 8, + captureArea.width, + captureArea.height); + if (cropped) { + gtk->gdk_pixbuf_copy_area(pixbuf, + captureArea.x, + captureArea.y, + captureArea.width, + captureArea.height, + cropped, + 0, 0); + } else { + ERR("Cannot create a new pixbuf.\n"); + } + + gtk->g_object_unref(pixbuf); + pixbuf = NULL; + + data->screenProps->captureDataPixbuf = cropped; + } else { + data->screenProps->captureDataPixbuf = pixbuf; + } screen->captureDataReady = TRUE; @@ -366,11 +380,7 @@ static bool startStream( SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, - SPA_POD_CHOICE_ENUM_Id( - 2, - SPA_VIDEO_FORMAT_RGBx, - SPA_VIDEO_FORMAT_BGRx - ), + SPA_POD_Id(SPA_VIDEO_FORMAT_BGRx), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), @@ -910,7 +920,7 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl "\t||\tx %5i y %5i w %5i h %5i %s\n" "\t||\tx %5i y %5i w %5i h %5i %s\n" "\t||\tx %5i y %5i w %5i h %5i %s\n\n", - i, screenProps->captureData, + i, screenProps->captureDataPixbuf, requestedArea.x, requestedArea.y, requestedArea.width, requestedArea.height, "requested area", @@ -924,7 +934,7 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl "in-screen coords capture area" ); - if (screenProps->captureData) { + if (screenProps->captureDataPixbuf) { for (int y = 0; y < captureArea.height; y++) { jsize preY = (requestedArea.y > screenProps->bounds.y) ? 0 @@ -939,14 +949,18 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl (*env)->SetIntArrayRegion( env, pixelArray, start, len, - ((jint *) screenProps->captureData) - + (captureArea.width * y) + ((jint *) gtk->gdk_pixbuf_get_pixels( + screenProps->captureDataPixbuf + )) + + (captureArea.width * y) ); } } - free(screenProps->captureData); - screenProps->captureData = NULL; + if (screenProps->captureDataPixbuf) { + gtk->g_object_unref(screenProps->captureDataPixbuf); + screenProps->captureDataPixbuf = NULL; + } screenProps->shouldCapture = FALSE; fp_pw_thread_loop_lock(pw.loop); 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 29b6fa00d1516..07a7e91304c50 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 @@ -48,7 +48,7 @@ struct ScreenProps { GdkRectangle captureArea; struct PwStreamData *data; - gchar *captureData; + GdkPixbuf *captureDataPixbuf; volatile gboolean shouldCapture; volatile gboolean captureDataReady; }; diff --git a/test/jdk/java/awt/Robot/HiDPIScreenCapture/ScreenCaptureGtkTest.java b/test/jdk/java/awt/Robot/HiDPIScreenCapture/ScreenCaptureGtkTest.java index 6ba5ec18e0a90..3c5ddea305554 100644 --- a/test/jdk/java/awt/Robot/HiDPIScreenCapture/ScreenCaptureGtkTest.java +++ b/test/jdk/java/awt/Robot/HiDPIScreenCapture/ScreenCaptureGtkTest.java @@ -32,7 +32,6 @@ import java.awt.Rectangle; import java.awt.Robot; import java.awt.image.BufferedImage; -import javax.swing.UIManager; import javax.imageio.ImageIO; import java.io.File; import java.io.IOException; @@ -54,6 +53,12 @@ public class ScreenCaptureGtkTest { Color.GREEN, Color.BLUE, Color.ORANGE, Color.RED}; public static void main(String[] args) throws Exception { + if ("2".equals(System.getProperty("jdk.gtk.version")) + && System.getenv("WAYLAND_DISPLAY") != null) { + // screen capture is not supported with gtk2 on Wayland + return; + } + final int topOffset = 50; final int leftOffset = 50;