diff options
author | Nikita Tsarev <nikita.tsarev@jetbrains.com> | 2023-08-15 12:37:27 +0200 |
---|---|---|
committer | Nikita Tsarev <nikita.tsarev@jetbrains.com> | 2023-08-15 12:37:27 +0200 |
commit | 77bccaf640d47710e14f52d9aaba010f7b922dc3 (patch) | |
tree | 45f24304f7f8f4ed97f3021f0c92fd8114182fa2 | |
parent | 27bb9662b2d76535c55bea54e0d09fa0d13e718d (diff) | |
download | JetBrainsRuntime-77bccaf640d47710e14f52d9aaba010f7b922dc3.tar.gz |
JBR-5676: Support emulating input events in Wakefield
10 files changed, 835 insertions, 46 deletions
diff --git a/jb/generate-wakefield.sh b/jb/generate-wakefield.sh new file mode 100755 index 00000000000..256871cd487 --- /dev/null +++ b/jb/generate-wakefield.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -ex + +wayland-scanner client-header src/java.desktop/share/native/libwakefield/protocol/wakefield.xml src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.h +wayland-scanner private-code src/java.desktop/share/native/libwakefield/protocol/wakefield.xml src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.c diff --git a/src/java.desktop/share/native/libwakefield/protocol/wakefield.xml b/src/java.desktop/share/native/libwakefield/protocol/wakefield.xml index 560adb42f28..cc9f310028e 100644 --- a/src/java.desktop/share/native/libwakefield/protocol/wakefield.xml +++ b/src/java.desktop/share/native/libwakefield/protocol/wakefield.xml @@ -84,6 +84,37 @@ <arg name="error_code" type="uint" enum="error"/> </event> + <request name="send_key"> + <description summary="facilitates implementation of Robot.keyPress/Robot.keyRelease"> + This requests an emulation of a key press by its Linux event key code. + </description> + <arg name="key" type="uint" /> + <arg name="state" type="uint" /> + </request> + + <request name="send_cursor"> + <description summary="facilitates implementation of Robot.mouseMove"> + This requests an emulation of the mouse cursor being moved to the specified screen coordinates. + </description> + <arg name="x" type="int" /> + <arg name="y" type="int" /> + </request> + + <request name="send_button"> + <description summary="facilitates implementation of Robot.mousePress/Robot.mouseRelease"> + This requests an emulation of a mouse button press by its Linux event code. + </description> + <arg name="button" type="uint" /> + <arg name="state" type="uint" /> + </request> + + <request name="send_wheel"> + <description summary="facilitates implementation of Robot.mouseWheel"> + This requests an emulation of a rotation of a mouse scroll wheel. + </description> + <arg name="amount" type="int" /> + </request> + <enum name="error"> <entry name="no_error" value="0" summary="error code 0 reserved for the absence of error"/> <entry name="invalid_coordinates" value="1" summary="supplied absolute coordinates point diff --git a/src/java.desktop/share/native/libwakefield/src/wakefield.c b/src/java.desktop/share/native/libwakefield/src/wakefield.c index a3da09a5375..1714535dbf4 100644 --- a/src/java.desktop/share/native/libwakefield/src/wakefield.c +++ b/src/java.desktop/share/native/libwakefield/src/wakefield.c @@ -44,6 +44,37 @@ struct wakefield { struct weston_log_scope *log; }; +#define DEFAULT_AXIS_STEP_DISTANCE 10 + +// These functions are part of Weston's private backend API (libweston/backend.h) +void +notify_axis(struct weston_seat *seat, const struct timespec *time, + struct weston_pointer_axis_event *event); + +void +notify_axis_source(struct weston_seat *seat, uint32_t source); + +void +notify_button(struct weston_seat *seat, const struct timespec *time, + int32_t button, enum wl_pointer_button_state state); + +void +notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, + enum wl_keyboard_key_state state, + enum weston_key_state_update update_state); + +void +notify_motion(struct weston_seat *seat, const struct timespec *time, + struct weston_pointer_motion_event *event); + +void +notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, + double x, double y); + +void +notify_pointer_frame(struct weston_seat *seat); + + static struct weston_output* get_output_for_point(struct wakefield* wakefield, int32_t x, int32_t y) { @@ -482,11 +513,96 @@ wakefield_capture_create(struct wl_client *client, wakefield_send_capture_ready(resource, buffer_resource, WAKEFIELD_ERROR_NO_ERROR); } +static void +wakefield_send_key(struct wl_client *client, + struct wl_resource *resource, + uint32_t key, + uint32_t state) +{ + struct wakefield *wakefield = wl_resource_get_user_data(resource); + struct weston_compositor *compositor = wakefield->compositor; + + struct timespec time; + weston_compositor_get_time(&time); + + struct weston_seat *seat; + wl_list_for_each(seat, &compositor->seat_list, link) { + notify_key(seat, &time, key, + state ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_AUTOMATIC); + + } +} + +static void wakefield_send_cursor(struct wl_client* client, + struct wl_resource* resource, + int32_t x, int32_t y) +{ + struct wakefield *wakefield = wl_resource_get_user_data(resource); + struct weston_compositor *compositor = wakefield->compositor; + + struct timespec time; + weston_compositor_get_time(&time); + + struct weston_seat *seat; + wl_list_for_each(seat, &compositor->seat_list, link) { + notify_motion_absolute(seat, &time, (double)x, (double)y); + notify_pointer_frame(seat); + } +} + +static void wakefield_send_button(struct wl_client* client, + struct wl_resource* resource, + uint32_t button, + uint32_t state) +{ + struct wakefield *wakefield = wl_resource_get_user_data(resource); + struct weston_compositor *compositor = wakefield->compositor; + + struct timespec time; + weston_compositor_get_time(&time); + + struct weston_seat *seat; + wl_list_for_each(seat, &compositor->seat_list, link) { + notify_button(seat, &time, (int32_t)button, + state ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED); + notify_pointer_frame(seat); + } +} + +static void wakefield_send_wheel(struct wl_client* client, + struct wl_resource* resource, + int32_t amount) +{ + struct wakefield *wakefield = wl_resource_get_user_data(resource); + struct weston_compositor *compositor = wakefield->compositor; + + struct timespec time; + weston_compositor_get_time(&time); + + struct weston_pointer_axis_event event = { + .axis = WL_POINTER_AXIS_VERTICAL_SCROLL, + .value = DEFAULT_AXIS_STEP_DISTANCE * amount, + .has_discrete = true, + .discrete = amount + }; + + struct weston_seat *seat; + wl_list_for_each(seat, &compositor->seat_list, link) { + notify_axis(seat, &time, &event); + notify_pointer_frame(seat); + } +} + static const struct wakefield_interface wakefield_implementation = { .get_surface_location = wakefield_get_surface_location, .move_surface = wakefield_move_surface, .get_pixel_color = wakefield_get_pixel_color, - .capture_create = wakefield_capture_create + .capture_create = wakefield_capture_create, + .send_key = wakefield_send_key, + .send_cursor = wakefield_send_cursor, + .send_button = wakefield_send_button, + .send_wheel = wakefield_send_wheel, }; static void diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java index 47f0c8ec2c8..9e1f5bd85be 100644 --- a/src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java @@ -42,32 +42,56 @@ public class WLRobotPeer implements RobotPeer { @Override public void mouseMove(int x, int y) { - throw new UnsupportedOperationException("Not implemented: WLRobotPeer.mouseMove()"); + checkExtensionPresent(); + + synchronized (WLRobotPeer.class) { + mouseMoveImpl(x, y); + } } @Override public void mousePress(int buttons) { - throw new UnsupportedOperationException("Not implemented: WLRobotPeer.mousePress()"); + checkExtensionPresent(); + + synchronized (WLRobotPeer.class) { + sendMouseButtonImpl(buttons, true); + } } @Override public void mouseRelease(int buttons) { - throw new UnsupportedOperationException("Not implemented: WLRobotPeer.mouseRelease()"); + checkExtensionPresent(); + + synchronized (WLRobotPeer.class) { + sendMouseButtonImpl(buttons, false); + } } @Override public void mouseWheel(int wheelAmt) { - throw new UnsupportedOperationException("Not implemented: WLRobotPeer.mouseWheel()"); + checkExtensionPresent(); + + synchronized (WLRobotPeer.class) { + mouseWheelImpl(wheelAmt); + } } @Override public void keyPress(int keycode) { - throw new UnsupportedOperationException("Not implemented: WLRobotPeer.keyPress()"); + checkExtensionPresent(); + + synchronized (WLRobotPeer.class) { + sendJavaKeyImpl(keycode, true); + } } @Override public void keyRelease(int keycode) { - throw new UnsupportedOperationException("Not implemented: WLRobotPeer.keyRelease()"); + checkExtensionPresent(); + + synchronized (WLRobotPeer.class) { + sendJavaKeyImpl(keycode, false); + } } @Override @@ -139,4 +163,8 @@ public class WLRobotPeer implements RobotPeer { private static native int[] getRGBPixelsImpl(int x, int y, int width, int height); private static native Point getLocationOfWLSurfaceImpl(long wlSurfacePtr); private static native void setLocationOfWLSurfaceImpl(long wlSurfacePtr, int x, int y); + private static native void sendJavaKeyImpl(int javaKeyCode, boolean pressed); + private static native void mouseMoveImpl(int x, int y); + private static native void sendMouseButtonImpl(int buttons, boolean pressed); + private static native void mouseWheelImpl(int amount); } diff --git a/src/java.desktop/unix/native/libawt_wlawt/WLRobotPeer.c b/src/java.desktop/unix/native/libawt_wlawt/WLRobotPeer.c index 10268d6bda6..817e69cdcf3 100644 --- a/src/java.desktop/unix/native/libawt_wlawt/WLRobotPeer.c +++ b/src/java.desktop/unix/native/libawt_wlawt/WLRobotPeer.c @@ -31,6 +31,8 @@ #include <Trace.h> #include <jni_util.h> +#include <java_awt_event_KeyEvent.h> +#include <java_awt_event_InputEvent.h> #include "sun_awt_wl_WLRobotPeer.h" #include "WLToolkit.h" @@ -116,6 +118,158 @@ init_mutex_and_cond(JNIEnv *env, pthread_mutex_t *mutex, pthread_cond_t *cond) static void handle_wakefield_error(JNIEnv *env, uint32_t error_code); + +struct wayland_keycode_map_item { + int java_key_code; + int wayland_key_code; +}; + +// Key codes correspond to the Linux event codes: +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h +static struct wayland_keycode_map_item wayland_keycode_map[] = { + { java_awt_event_KeyEvent_VK_ESCAPE, 1 }, + { java_awt_event_KeyEvent_VK_1, 2 }, + { java_awt_event_KeyEvent_VK_2, 3 }, + { java_awt_event_KeyEvent_VK_3, 4 }, + { java_awt_event_KeyEvent_VK_4, 5 }, + { java_awt_event_KeyEvent_VK_5, 6 }, + { java_awt_event_KeyEvent_VK_6, 7 }, + { java_awt_event_KeyEvent_VK_7, 8 }, + { java_awt_event_KeyEvent_VK_8, 9 }, + { java_awt_event_KeyEvent_VK_9, 10 }, + { java_awt_event_KeyEvent_VK_0, 11 }, + { java_awt_event_KeyEvent_VK_MINUS, 12 }, + { java_awt_event_KeyEvent_VK_EQUALS, 13 }, + { java_awt_event_KeyEvent_VK_BACK_SPACE, 14 }, + { java_awt_event_KeyEvent_VK_TAB, 15 }, + { java_awt_event_KeyEvent_VK_Q, 16 }, + { java_awt_event_KeyEvent_VK_W, 17 }, + { java_awt_event_KeyEvent_VK_E, 18 }, + { java_awt_event_KeyEvent_VK_R, 19 }, + { java_awt_event_KeyEvent_VK_T, 20 }, + { java_awt_event_KeyEvent_VK_Y, 21 }, + { java_awt_event_KeyEvent_VK_U, 22 }, + { java_awt_event_KeyEvent_VK_I, 23 }, + { java_awt_event_KeyEvent_VK_O, 24 }, + { java_awt_event_KeyEvent_VK_P, 25 }, + { java_awt_event_KeyEvent_VK_OPEN_BRACKET, 26 }, + { java_awt_event_KeyEvent_VK_CLOSE_BRACKET, 27 }, + { java_awt_event_KeyEvent_VK_ENTER, 28 }, + { java_awt_event_KeyEvent_VK_CONTROL, 29 }, + { java_awt_event_KeyEvent_VK_A, 30 }, + { java_awt_event_KeyEvent_VK_S, 31 }, + { java_awt_event_KeyEvent_VK_D, 32 }, + { java_awt_event_KeyEvent_VK_F, 33 }, + { java_awt_event_KeyEvent_VK_G, 34 }, + { java_awt_event_KeyEvent_VK_H, 35 }, + { java_awt_event_KeyEvent_VK_J, 36 }, + { java_awt_event_KeyEvent_VK_K, 37 }, + { java_awt_event_KeyEvent_VK_L, 38 }, + { java_awt_event_KeyEvent_VK_SEMICOLON, 39 }, + { java_awt_event_KeyEvent_VK_QUOTE, 40 }, + { java_awt_event_KeyEvent_VK_BACK_QUOTE, 41 }, + { java_awt_event_KeyEvent_VK_SHIFT, 42 }, + { java_awt_event_KeyEvent_VK_BACK_SLASH, 43 }, + { java_awt_event_KeyEvent_VK_Z, 44 }, + { java_awt_event_KeyEvent_VK_X, 45 }, + { java_awt_event_KeyEvent_VK_C, 46 }, + { java_awt_event_KeyEvent_VK_V, 47 }, + { java_awt_event_KeyEvent_VK_B, 48 }, + { java_awt_event_KeyEvent_VK_N, 49 }, + { java_awt_event_KeyEvent_VK_M, 50 }, + { java_awt_event_KeyEvent_VK_COMMA, 51 }, + { java_awt_event_KeyEvent_VK_PERIOD, 52 }, + { java_awt_event_KeyEvent_VK_SLASH, 53 }, + { java_awt_event_KeyEvent_VK_MULTIPLY, 55 }, + { java_awt_event_KeyEvent_VK_ALT, 56 }, + { java_awt_event_KeyEvent_VK_SPACE, 57 }, + { java_awt_event_KeyEvent_VK_CAPS_LOCK, 58 }, + { java_awt_event_KeyEvent_VK_F1, 59 }, + { java_awt_event_KeyEvent_VK_F2, 60 }, + { java_awt_event_KeyEvent_VK_F3, 61 }, + { java_awt_event_KeyEvent_VK_F4, 62 }, + { java_awt_event_KeyEvent_VK_F5, 63 }, + { java_awt_event_KeyEvent_VK_F6, 64 }, + { java_awt_event_KeyEvent_VK_F7, 65 }, + { java_awt_event_KeyEvent_VK_F8, 66 }, + { java_awt_event_KeyEvent_VK_F9, 67 }, + { java_awt_event_KeyEvent_VK_F10, 68 }, + { java_awt_event_KeyEvent_VK_NUM_LOCK, 69 }, + { java_awt_event_KeyEvent_VK_SCROLL_LOCK, 70 }, + { java_awt_event_KeyEvent_VK_NUMPAD7, 71 }, + { java_awt_event_KeyEvent_VK_KP_UP, 72 }, + { java_awt_event_KeyEvent_VK_NUMPAD8, 72 }, + { java_awt_event_KeyEvent_VK_NUMPAD9, 73 }, + { java_awt_event_KeyEvent_VK_SUBTRACT, 74 }, + { java_awt_event_KeyEvent_VK_KP_LEFT, 75 }, + { java_awt_event_KeyEvent_VK_NUMPAD4, 75 }, + { java_awt_event_KeyEvent_VK_NUMPAD5, 76 }, + { java_awt_event_KeyEvent_VK_KP_RIGHT, 77 }, + { java_awt_event_KeyEvent_VK_NUMPAD6, 77 }, + { java_awt_event_KeyEvent_VK_ADD, 78 }, + { java_awt_event_KeyEvent_VK_NUMPAD1, 79 }, + { java_awt_event_KeyEvent_VK_KP_DOWN, 80 }, + { java_awt_event_KeyEvent_VK_NUMPAD2, 80 }, + { java_awt_event_KeyEvent_VK_NUMPAD3, 81 }, + { java_awt_event_KeyEvent_VK_NUMPAD0, 82 }, + { java_awt_event_KeyEvent_VK_DECIMAL, 83 }, + { java_awt_event_KeyEvent_VK_LESS, 86 }, + { java_awt_event_KeyEvent_VK_F11, 87 }, + { java_awt_event_KeyEvent_VK_F12, 88 }, + { java_awt_event_KeyEvent_VK_KATAKANA, 90 }, + { java_awt_event_KeyEvent_VK_HIRAGANA, 91 }, + { java_awt_event_KeyEvent_VK_INPUT_METHOD_ON_OFF, 92 }, + { java_awt_event_KeyEvent_VK_NONCONVERT, 94 }, + { java_awt_event_KeyEvent_VK_DIVIDE, 98 }, + { java_awt_event_KeyEvent_VK_PRINTSCREEN, 99 }, + { java_awt_event_KeyEvent_VK_ALT_GRAPH, 100 }, + { java_awt_event_KeyEvent_VK_HOME, 102 }, + { java_awt_event_KeyEvent_VK_UP, 103 }, + { java_awt_event_KeyEvent_VK_PAGE_UP, 104 }, + { java_awt_event_KeyEvent_VK_LEFT, 105 }, + { java_awt_event_KeyEvent_VK_RIGHT, 106 }, + { java_awt_event_KeyEvent_VK_END, 107 }, + { java_awt_event_KeyEvent_VK_DOWN, 108 }, + { java_awt_event_KeyEvent_VK_PAGE_DOWN, 109 }, + { java_awt_event_KeyEvent_VK_INSERT, 110 }, + { java_awt_event_KeyEvent_VK_DELETE, 111 }, + { java_awt_event_KeyEvent_VK_PAUSE, 119 }, + { java_awt_event_KeyEvent_VK_DECIMAL, 121 }, + { java_awt_event_KeyEvent_VK_META, 125 }, + { java_awt_event_KeyEvent_VK_WINDOWS, 125 }, + { java_awt_event_KeyEvent_VK_STOP, 128 }, + { java_awt_event_KeyEvent_VK_AGAIN, 129 }, + { java_awt_event_KeyEvent_VK_UNDO, 131 }, + { java_awt_event_KeyEvent_VK_FIND, 136 }, + { java_awt_event_KeyEvent_VK_HELP, 138 }, + { java_awt_event_KeyEvent_VK_LEFT_PARENTHESIS, 179 }, + { java_awt_event_KeyEvent_VK_RIGHT_PARENTHESIS, 180 }, + { java_awt_event_KeyEvent_VK_F13, 183 }, + { java_awt_event_KeyEvent_VK_F14, 184 }, + { java_awt_event_KeyEvent_VK_F15, 185 }, + { java_awt_event_KeyEvent_VK_F16, 186 }, + { java_awt_event_KeyEvent_VK_F17, 187 }, + { java_awt_event_KeyEvent_VK_F18, 188 }, + { java_awt_event_KeyEvent_VK_F19, 189 }, + { java_awt_event_KeyEvent_VK_F20, 190 }, + { java_awt_event_KeyEvent_VK_F21, 191 }, + { java_awt_event_KeyEvent_VK_F22, 192 }, + { java_awt_event_KeyEvent_VK_F23, 193 }, + { java_awt_event_KeyEvent_VK_F24, 194 }, + { java_awt_event_KeyEvent_VK_UNDEFINED, -1 } +}; + +struct wayland_button_map_item { + int java_button_mask; + int wayland_button_code; +}; + +static struct wayland_button_map_item wayland_button_map[] = { + { java_awt_event_InputEvent_BUTTON1_DOWN_MASK | java_awt_event_InputEvent_BUTTON1_MASK, 0x110 }, + { java_awt_event_InputEvent_BUTTON2_DOWN_MASK | java_awt_event_InputEvent_BUTTON2_MASK, 0x112 }, + { java_awt_event_InputEvent_BUTTON3_DOWN_MASK | java_awt_event_InputEvent_BUTTON3_MASK, 0x111 }, + { -1, -1 }, +}; #endif static jclass pointClass; // java.awt.Point @@ -298,6 +452,79 @@ Java_sun_awt_wl_WLRobotPeer_getRGBPixelsImpl #endif } +JNIEXPORT void JNICALL +Java_sun_awt_wl_WLRobotPeer_sendJavaKeyImpl + (JNIEnv *env, jclass clazz, jint javaKeyCode, jboolean pressed) +{ +#ifdef WAKEFIELD_ROBOT + if (!wakefield) { + JNU_ThrowByName(env, "java/awt/AWTError", "no 'wakefield' protocol extension"); + return; + } + + uint32_t key = 0; + + for (struct wayland_keycode_map_item* item = wayland_keycode_map; item->wayland_key_code != -1; ++item) { + if (item->java_key_code == javaKeyCode) { + key = item->wayland_key_code; + break; + } + } + + if (!key) { + return; + } + + wakefield_send_key(wakefield, key, pressed ? 1 : 0); +#endif +} + +JNIEXPORT void JNICALL +Java_sun_awt_wl_WLRobotPeer_mouseMoveImpl + (JNIEnv *env, jclass clazz, jint x, jint y) +{ +#ifdef WAKEFIELD_ROBOT + if (!wakefield) { + JNU_ThrowByName(env, "java/awt/AWTError", "no 'wakefield' protocol extension"); + return; + } + + wakefield_send_cursor(wakefield, x, y); +#endif +} + +JNIEXPORT void JNICALL +Java_sun_awt_wl_WLRobotPeer_sendMouseButtonImpl + (JNIEnv *env, jclass clazz, jint buttons, jboolean pressed) +{ +#ifdef WAKEFIELD_ROBOT + if (!wakefield) { + JNU_ThrowByName(env, "java/awt/AWTError", "no 'wakefield' protocol extension"); + return; + } + + for (struct wayland_button_map_item* item = wayland_button_map; item->wayland_button_code != -1; ++item) { + if (item->java_button_mask & buttons) { + wakefield_send_button(wakefield, item->wayland_button_code, pressed ? 1 : 0); + } + } +#endif +} + +JNIEXPORT void JNICALL +Java_sun_awt_wl_WLRobotPeer_mouseWheelImpl + (JNIEnv *env, jclass clazz, jint amount) +{ +#ifdef WAKEFIELD_ROBOT + if (!wakefield) { + JNU_ThrowByName(env, "java/awt/AWTError", "no 'wakefield' protocol extension"); + return; + } + + wakefield_send_wheel(wakefield, amount); +#endif +} + #ifdef WAKEFIELD_ROBOT static void wakefield_surface_location(void *data, struct wakefield *wakefield, diff --git a/src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.c b/src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.c index a275fbaf790..87dc8808ac6 100644 --- a/src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.c +++ b/src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.c @@ -1,4 +1,4 @@ -/* Generated by wayland-scanner 1.19.0 */ +/* Generated by wayland-scanner 1.21.0 */ /* * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. @@ -28,6 +28,16 @@ #include <stdint.h> #include "wayland-util.h" +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + extern const struct wl_interface wl_buffer_interface; extern const struct wl_interface wl_surface_interface; @@ -56,6 +66,10 @@ static const struct wl_message wakefield_requests[] = { { "move_surface", "oii", wakefield_types + 4 }, { "get_surface_location", "o", wakefield_types + 7 }, { "get_pixel_color", "ii", wakefield_types + 0 }, + { "send_key", "uu", wakefield_types + 0 }, + { "send_cursor", "ii", wakefield_types + 0 }, + { "send_button", "uu", wakefield_types + 0 }, + { "send_wheel", "i", wakefield_types + 0 }, { "capture_create", "oii", wakefield_types + 8 }, }; @@ -65,9 +79,9 @@ static const struct wl_message wakefield_events[] = { { "capture_ready", "ou", wakefield_types + 15 }, }; -WL_EXPORT const struct wl_interface wakefield_interface = { +WL_PRIVATE const struct wl_interface wakefield_interface = { "wakefield", 1, - 5, wakefield_requests, + 9, wakefield_requests, 3, wakefield_events, }; diff --git a/src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.h b/src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.h index 9e45c618594..3dc9714ad82 100644 --- a/src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.h +++ b/src/java.desktop/unix/native/libawt_wlawt/wakefield-client-protocol.h @@ -1,4 +1,4 @@ -/* Generated by wayland-scanner 1.19.0 */ +/* Generated by wayland-scanner 1.21.0 */ #ifndef WAKEFIELD_CLIENT_PROTOCOL_H #define WAKEFIELD_CLIENT_PROTOCOL_H @@ -142,7 +142,11 @@ wakefield_add_listener(struct wakefield *wakefield, #define WAKEFIELD_MOVE_SURFACE 1 #define WAKEFIELD_GET_SURFACE_LOCATION 2 #define WAKEFIELD_GET_PIXEL_COLOR 3 -#define WAKEFIELD_CAPTURE_CREATE 4 +#define WAKEFIELD_SEND_KEY 4 +#define WAKEFIELD_SEND_CURSOR 5 +#define WAKEFIELD_SEND_BUTTON 6 +#define WAKEFIELD_SEND_WHEEL 7 +#define WAKEFIELD_CAPTURE_CREATE 8 /** * @ingroup iface_wakefield @@ -176,6 +180,22 @@ wakefield_add_listener(struct wakefield *wakefield, /** * @ingroup iface_wakefield */ +#define WAKEFIELD_SEND_KEY_SINCE_VERSION 1 +/** + * @ingroup iface_wakefield + */ +#define WAKEFIELD_SEND_CURSOR_SINCE_VERSION 1 +/** + * @ingroup iface_wakefield + */ +#define WAKEFIELD_SEND_BUTTON_SINCE_VERSION 1 +/** + * @ingroup iface_wakefield + */ +#define WAKEFIELD_SEND_WHEEL_SINCE_VERSION 1 +/** + * @ingroup iface_wakefield + */ #define WAKEFIELD_CAPTURE_CREATE_SINCE_VERSION 1 /** @ingroup iface_wakefield */ @@ -204,10 +224,8 @@ wakefield_get_version(struct wakefield *wakefield) static inline void wakefield_destroy(struct wakefield *wakefield) { - wl_proxy_marshal((struct wl_proxy *) wakefield, - WAKEFIELD_DESTROY); - - wl_proxy_destroy((struct wl_proxy *) wakefield); + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), WL_MARSHAL_FLAG_DESTROY); } /** @@ -221,8 +239,8 @@ wakefield_destroy(struct wakefield *wakefield) static inline void wakefield_move_surface(struct wakefield *wakefield, struct wl_surface *surface, int32_t x, int32_t y) { - wl_proxy_marshal((struct wl_proxy *) wakefield, - WAKEFIELD_MOVE_SURFACE, surface, x, y); + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_MOVE_SURFACE, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), 0, surface, x, y); } /** @@ -233,8 +251,8 @@ wakefield_move_surface(struct wakefield *wakefield, struct wl_surface *surface, static inline void wakefield_get_surface_location(struct wakefield *wakefield, struct wl_surface *surface) { - wl_proxy_marshal((struct wl_proxy *) wakefield, - WAKEFIELD_GET_SURFACE_LOCATION, surface); + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_GET_SURFACE_LOCATION, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), 0, surface); } /** @@ -245,8 +263,56 @@ wakefield_get_surface_location(struct wakefield *wakefield, struct wl_surface *s static inline void wakefield_get_pixel_color(struct wakefield *wakefield, int32_t x, int32_t y) { - wl_proxy_marshal((struct wl_proxy *) wakefield, - WAKEFIELD_GET_PIXEL_COLOR, x, y); + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_GET_PIXEL_COLOR, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), 0, x, y); +} + +/** + * @ingroup iface_wakefield + * + * This requests an emulation of a key press by its Linux event key code. + */ +static inline void +wakefield_send_key(struct wakefield *wakefield, uint32_t key, uint32_t state) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_SEND_KEY, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), 0, key, state); +} + +/** + * @ingroup iface_wakefield + * + * This requests an emulation of the mouse cursor being moved to the specified screen coordinates. + */ +static inline void +wakefield_send_cursor(struct wakefield *wakefield, int32_t x, int32_t y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_SEND_CURSOR, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), 0, x, y); +} + +/** + * @ingroup iface_wakefield + * + * This requests an emulation of a mouse button press by its Linux event code. + */ +static inline void +wakefield_send_button(struct wakefield *wakefield, uint32_t button, uint32_t state) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_SEND_BUTTON, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), 0, button, state); +} + +/** + * @ingroup iface_wakefield + * + * This requests an emulation of a rotation of a mouse scroll wheel. + */ +static inline void +wakefield_send_wheel(struct wakefield *wakefield, int32_t amount) +{ + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_SEND_WHEEL, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), 0, amount); } /** @@ -255,8 +321,8 @@ wakefield_get_pixel_color(struct wakefield *wakefield, int32_t x, int32_t y) static inline void wakefield_capture_create(struct wakefield *wakefield, struct wl_buffer *buffer, int32_t x, int32_t y) { - wl_proxy_marshal((struct wl_proxy *) wakefield, - WAKEFIELD_CAPTURE_CREATE, buffer, x, y); + wl_proxy_marshal_flags((struct wl_proxy *) wakefield, + WAKEFIELD_CAPTURE_CREATE, NULL, wl_proxy_get_version((struct wl_proxy *) wakefield), 0, buffer, x, y); } #ifdef __cplusplus diff --git a/test/jdk/java/awt/wakefield/RobotKeyboard.java b/test/jdk/java/awt/wakefield/RobotKeyboard.java new file mode 100644 index 00000000000..3af23e94d37 --- /dev/null +++ b/test/jdk/java/awt/wakefield/RobotKeyboard.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.List; + +/** + * @test + * @key headful + * @summary JBR-5676 Wayland: support generation of input events by AWT Robot in weston plugin + * @requires (os.family == "linux") + * @library /test/lib + * @build RobotKeyboard + * @run driver WakefieldTestDriver -timeout 60 RobotKeyboard + */ + +public class RobotKeyboard { + private static String ordinaryKeyNames[] = { + "VK_0", + "VK_1", + "VK_2", + "VK_3", + "VK_4", + "VK_5", + "VK_6", + "VK_7", + "VK_8", + "VK_9", + "VK_A", + "VK_ADD", + "VK_AGAIN", + "VK_ALT", +// TODO: WLToolkit doesn't differentiate VK_ALT and VK_ALT_GRAPH for now +// "VK_ALT_GRAPH", + "VK_B", + "VK_BACK_QUOTE", + "VK_BACK_SLASH", + "VK_BACK_SPACE", + "VK_C", + "VK_CLOSE_BRACKET", + "VK_COMMA", + "VK_CONTROL", + "VK_D", + "VK_DECIMAL", + "VK_DECIMAL", + "VK_DELETE", + "VK_DIVIDE", + "VK_DOWN", + "VK_E", + "VK_END", + "VK_ENTER", + "VK_EQUALS", + "VK_ESCAPE", + "VK_F", + "VK_F1", + "VK_F2", + "VK_F3", + "VK_F4", + "VK_F5", + "VK_F6", + "VK_F7", + "VK_F8", + "VK_F9", + "VK_F10", + "VK_F11", + "VK_F12", +// TODO: WLToolkit ignores F13..F24 due to the XKB issues presumably +// "VK_F13", +// "VK_F14", +// "VK_F15", +// "VK_F16", +// "VK_F17", +// "VK_F18", +// "VK_F19", +// "VK_F20", +// "VK_F21", +// "VK_F22", +// "VK_F23", +// "VK_F24", + "VK_FIND", + "VK_G", + "VK_H", + "VK_HELP", + "VK_HIRAGANA", + "VK_HOME", + "VK_I", + "VK_INPUT_METHOD_ON_OFF", + "VK_INSERT", + "VK_J", + "VK_K", + "VK_KATAKANA", + "VK_L", + "VK_LEFT", + "VK_LEFT_PARENTHESIS", + "VK_LESS", + "VK_M", +// TODO: WLToolkit reports the Meta key as VK_WINDOWS +// "VK_META", + "VK_MINUS", + "VK_MULTIPLY", + "VK_N", + "VK_NONCONVERT", + "VK_NUMPAD0", + "VK_NUMPAD1", + "VK_NUMPAD2", + "VK_NUMPAD3", + "VK_NUMPAD4", + "VK_NUMPAD5", + "VK_NUMPAD6", + "VK_NUMPAD7", + "VK_NUMPAD8", + "VK_NUMPAD9", + "VK_O", + "VK_OPEN_BRACKET", + "VK_P", + "VK_PAGE_DOWN", + "VK_PAGE_UP", + "VK_PAUSE", + "VK_PERIOD", + "VK_PRINTSCREEN", + "VK_Q", + "VK_QUOTE", + "VK_R", + "VK_RIGHT", + "VK_RIGHT_PARENTHESIS", + "VK_S", + "VK_SEMICOLON", + "VK_SHIFT", + "VK_SLASH", + "VK_SPACE", + "VK_STOP", + "VK_SUBTRACT", + "VK_T", + "VK_TAB", + "VK_U", + "VK_UNDO", + "VK_UP", + "VK_V", + "VK_W", + "VK_WINDOWS", + "VK_X", + "VK_Y", + "VK_Z", + }; + + private static String lockingKeyNames[] = { + "VK_CAPS_LOCK", + "VK_NUM_LOCK", + "VK_SCROLL_LOCK", + }; + + private static Robot robot; + private static JFrame frame; + private static JTextArea textArea; + private static final List<KeyEvent> events = new ArrayList<>(); + + public static void main(String[] args) throws Exception { + SwingUtilities.invokeAndWait(() -> { + frame = new JFrame("test"); + + textArea = new JTextArea(""); + textArea.setEditable(false); + frame.add(new JScrollPane(textArea)); + frame.setSize(500, 500); + frame.setVisible(true); + + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher( + new KeyEventDispatcher() { + @Override + public boolean dispatchKeyEvent(KeyEvent e) { + if (e.getID() == KeyEvent.KEY_PRESSED || e.getID() == KeyEvent.KEY_RELEASED) { + events.add(e); + } + return false; + } + } + ); + }); + + robot = new Robot(); + robot.setAutoDelay(50); + robot.delay(500); + + boolean ok = true; + + for (String key : ordinaryKeyNames) { + ok &= processKey(key); + } + + for (String key : lockingKeyNames) { + ok &= processKey(key); + + // reset the locking state to the previous one + int keyCode = getKeyCodeByName(key); + robot.keyPress(keyCode); + robot.keyRelease(keyCode); + robot.waitForIdle(); + } + + System.err.println("===== TEST RESULT ====="); + System.err.println(ok ? "TEST PASSED" : "TEST FAILED"); + System.err.println("===== FULL LOG ====="); + System.err.println(textArea.getText()); + + frame.dispose(); + + // Due to some reason that probably has something to do with the implementation + // of the test driver, it's necessary to manually call System.exit() here + System.exit(ok ? 0 : 1); + } + + private static int getKeyCodeByName(String name) { + try { + return KeyEvent.class.getDeclaredField(name).getInt(KeyEvent.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void checkKey(String name) { + int keyCode = getKeyCodeByName(name); + events.clear(); + textArea.grabFocus(); + robot.waitForIdle(); + robot.keyPress(keyCode); + robot.keyRelease(keyCode); + robot.waitForIdle(); + + if (events.size() != 2) { + throw new RuntimeException("Expected two events, got: " + events.size()); + } + + if (events.get(0).getID() != KeyEvent.KEY_PRESSED || events.get(1).getID() != KeyEvent.KEY_RELEASED) { + throw new RuntimeException("Expected one KEY_PRESSED and one KEY_RELEASED"); + } + + if (events.get(0).getKeyCode() != keyCode) { + throw new RuntimeException("KEY_PRESSED keyCode is " + events.get(0).getKeyCode() + ", expected " + keyCode); + } + + if (events.get(1).getKeyCode() != keyCode) { + throw new RuntimeException("KEY_RELEASED keyCode is " + events.get(1).getKeyCode() + ", expected " + keyCode); + } + } + + private static void log(String what) { + textArea.append(what); + textArea.setCaretPosition(textArea.getDocument().getLength()); + System.err.print(what); + } + + private static boolean processKey(String name) { + log(name + ": "); + try { + checkKey(name); + log("OK\n"); + return true; + } catch (RuntimeException e) { + log(e.getMessage() + "\n"); + return false; + } + } +}
\ No newline at end of file diff --git a/test/jdk/java/awt/wakefield/ScreenCapture.java b/test/jdk/java/awt/wakefield/ScreenCapture.java index 8194bd7dbe3..ae78dca159c 100644 --- a/test/jdk/java/awt/wakefield/ScreenCapture.java +++ b/test/jdk/java/awt/wakefield/ScreenCapture.java @@ -40,8 +40,8 @@ import java.io.IOException; * @requires (os.family == "linux") * @library /test/lib * @build ScreenCapture - * @run driver WakefieldTestDriver 1x1400x800 ScreenCapture - * @run driver WakefieldTestDriver 2x830x800 ScreenCapture + * @run driver WakefieldTestDriver -resolution 1x1400x800 ScreenCapture + * @run driver WakefieldTestDriver -resolution 2x830x800 ScreenCapture */ public class ScreenCapture { diff --git a/test/jdk/java/awt/wakefield/WakefieldTestDriver.java b/test/jdk/java/awt/wakefield/WakefieldTestDriver.java index aa9e6676937..2d461660691 100644 --- a/test/jdk/java/awt/wakefield/WakefieldTestDriver.java +++ b/test/jdk/java/awt/wakefield/WakefieldTestDriver.java @@ -37,11 +37,11 @@ public class WakefieldTestDriver { final static int DEFAULT_NUMBER_OF_SCREENS = 1; final static int DEFAULT_SCREEN_WIDTH = 1280; final static int DEFAULT_SCREEN_HEIGHT = 800; - final static int TEST_TIMEOUT_SECONDS = 10; + static int testTimeoutSeconds = 10; static void usage() { System.out.println( """ - WakefieldTestDriver [NxWxH] ClassName [args] + WakefieldTestDriver [-resolution NxWxH] [-timeout SECONDS] ClassName [args] where N - number of Weston outputs (screens); defaults to 1 W - width of each screen in pixels; defaults to 1280 @@ -65,24 +65,37 @@ public class WakefieldTestDriver { int nScreens = DEFAULT_NUMBER_OF_SCREENS; int screenWidth = DEFAULT_SCREEN_WIDTH; int screenHeight = DEFAULT_SCREEN_HEIGHT; - final String firstArg = args[0]; - if (Character.isDigit(firstArg.charAt(0))) { - try { - final int firstXIndex = firstArg.indexOf("x", 0); - final int secondXIndex = firstArg.indexOf("x", firstXIndex + 1); - nScreens = Integer.valueOf(firstArg.substring(0, firstXIndex)); - screenWidth = Integer.valueOf(firstArg.substring(firstXIndex + 1, secondXIndex)); - screenHeight = Integer.valueOf(firstArg.substring(secondXIndex + 1, firstArg.length())); - - for (int i = 1; i < args.length; ++i) { - jvmArgs.add(args[i]); + + for (int i = 0; i < args.length; ++i) { + if (args[i].equals("-resolution") && i + 1 < args.length) { + final String arg = args[i + 1]; + if (Character.isDigit(arg.charAt(0))) { + try { + final int firstXIndex = arg.indexOf("x", 0); + final int secondXIndex = arg.indexOf("x", firstXIndex + 1); + nScreens = Integer.valueOf(arg.substring(0, firstXIndex)); + screenWidth = Integer.valueOf(arg.substring(firstXIndex + 1, secondXIndex)); + screenHeight = Integer.valueOf(arg.substring(secondXIndex + 1, arg.length())); + } catch (IndexOutOfBoundsException | NumberFormatException ignored) { + usage(); + throw new RuntimeException("Error parsing the first argument of the test driver"); + } } - } catch (IndexOutOfBoundsException | NumberFormatException ignored) { - usage(); - throw new RuntimeException("Error parsing the first argument of the test driver"); + ++i; + continue; + } + + if (args[i].equals("-timeout") && i + 1 < args.length) { + final String arg = args[i + 1]; + testTimeoutSeconds = Integer.valueOf(arg); + ++i; + continue; + } + + for (int j = i; j < args.length; ++j) { + jvmArgs.add(args[j]); } - } else { - jvmArgs.addAll(Arrays.asList(args)); + break; } final String socketName = SOCKET_NAME_PREFIX + ProcessHandle.current().pid(); @@ -95,13 +108,13 @@ public class WakefieldTestDriver { final Process p = pb.start(); final OutputAnalyzer output = new OutputAnalyzer(p); - final boolean exited = p.waitFor(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + final boolean exited = p.waitFor(testTimeoutSeconds, TimeUnit.SECONDS); if (!exited) p.destroy(); System.out.println("Test finished. Output: [[["); System.out.println(output.getOutput()); System.out.println("]]]"); if (!exited) { - throw new RuntimeException("Test timed out after " + TEST_TIMEOUT_SECONDS + " seconds"); + throw new RuntimeException("Test timed out after " + testTimeoutSeconds + " seconds"); } if (exited && output.getExitValue() != 0) { |