Compare commits

...

8 Commits

Author SHA1 Message Date
Nikita Tsarev
bb83d4ab82 JBR-5900: Remove unused dispatch_nondefault_queues 2023-08-10 12:44:08 +02:00
Nikita Tsarev
d757b1d05e JBR-5676: Wakefield input emulation fixes 2023-08-10 12:39:58 +02:00
Nikita Tsarev
7873f04375 JBR-5896: Don't rely on EventQueue.isDispatchThread at all 2023-08-08 17:36:40 +02:00
Nikita Tsarev
21ccebc484 JBR-5676: Support emulating mouse events in Wakefield 2023-08-08 17:30:34 +02:00
Nikita Tsarev
513d168fa3 JBR-5676: Tests for Wakefield Robot keyboard input 2023-08-03 16:43:31 +02:00
Nikita Tsarev
2eaee2a445 JBR-5676: Support emulating keyboard events in Wakefield 2023-08-03 16:42:01 +02:00
Nikita Tsarev
0b3596aefa JBR-5900: Fix deadlock when enabling the Wakefield extension 2023-08-03 16:37:54 +02:00
Nikita Tsarev
62e6c4c8d8 JBR-5896: Fix WLToolkit being instantiated twice 2023-08-03 16:29:04 +02:00
13 changed files with 860 additions and 74 deletions

6
jb/generate-wakefield.sh Executable file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -131,7 +131,7 @@ public class WLGraphicsEnvironment extends SunGraphicsEnvironment {
}
// Skip notification during the initial configuration events
if (EventQueue.isDispatchThread()) {
if (WLToolkit.isInitialized()) {
displayChanged();
}
}

View File

@@ -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);
}

View File

@@ -133,12 +133,15 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
private static final int MOUSE_BUTTONS_COUNT = 3;
private static final int AWT_MULTICLICK_DEFAULT_TIME_MS = 500;
private static boolean initialized = false;
private static native void initIDs();
static {
if (!GraphicsEnvironment.isHeadless()) {
initIDs();
}
initialized = true;
}
@SuppressWarnings("removal")
@@ -1005,4 +1008,8 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
static void resetPointerInputState() {
inputState = inputState.resetPointerState();
}
static boolean isInitialized() {
return initialized;
}
}

View File

@@ -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,

View File

@@ -927,26 +927,6 @@ wl_display_poll(struct wl_display *display, int events, int poll_timeout)
return rc;
}
static void
dispatch_nondefault_queues(JNIEnv *env)
{
// The handlers of the events on these queues will be called from here, i.e. on
// the 'AWT-Wayland' (toolkit) thread. The handlers must *not* execute any
// arbitrary user code that can block.
int rc = 0;
#ifdef WAKEFIELD_ROBOT
if (robot_queue) {
rc = wl_display_dispatch_queue_pending(wl_display, robot_queue);
}
#endif
if (rc < 0) {
JNU_ThrowByName(env, "java/awt/AWTError", "Wayland error during events processing");
return;
}
}
int
wl_flush_to_server(JNIEnv *env)
{
@@ -985,21 +965,31 @@ JNIEXPORT void JNICALL
Java_sun_awt_wl_WLToolkit_dispatchNonDefaultQueuesImpl
(JNIEnv *env, jobject obj)
{
int rc = 0;
while (rc != -1) {
#ifdef WAKEFIELD_ROBOT
if (robot_queue) {
rc = wl_display_dispatch_queue(wl_display, robot_queue);
} else {
if (!robot_queue) {
return;
}
int rc = 0;
while (rc >= 0) {
// Dispatch pending events on the wakefield queue
while (wl_display_prepare_read_queue(wl_display, robot_queue) != 0 && rc >= 0) {
rc = wl_display_dispatch_queue_pending(wl_display, robot_queue);
}
if (rc < 0) {
wl_display_cancel_read(wl_display);
break;
}
#else
break;
#endif
// Wait for new events, wl_display_read_events is a synchronization point between all threads reading events.
rc = wl_display_read_events(wl_display);
}
// Simply return in case of any error; the actual error reporting (exception)
// and/or shutdown will happen on the "main" toolkit thread AWT-Wayland,
// see readEvents() below.
#endif
}
JNIEXPORT jint JNICALL

View File

@@ -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,
};

View File

@@ -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
@@ -173,6 +177,22 @@ wakefield_add_listener(struct wakefield *wakefield,
* @ingroup iface_wakefield
*/
#define WAKEFIELD_GET_PIXEL_COLOR_SINCE_VERSION 1
/**
* @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
*/
@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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 {

View File

@@ -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;
}
} else {
jvmArgs.addAll(Arrays.asList(args));
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]);
}
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) {