[PATCH v5] user32: Implement GetMouseMovePointsEx().

Arkadiusz Hiler ahiler at codeweavers.com
Fri Oct 2 18:44:16 CDT 2020


With this patch we start storing history of last 64 mouse positions on the
server side which can be retrieved by the newly introduced get_cursor_history
request.

GetMouseMovePointsEx() is implemented on top of that.

The cursor position history is shared between all the desktops within the same
window station. Non-default window stations are non-interactive, i.e. they
don't support input - SetCursorPos(), GetMouseMovePointsEx(), etc. are failing
there with ERROR_ACCESS_DENIED. This aspect remains unimplemented and the
related tests are marked with todo_wine.

The only way to have multiple interactive window stations for a single user in
Windows seems to be a remote desktop session.

Testing with multiple desktops / winstations is done in a separate process as
SetThreadDesktop() is very brittle on Windows, e.g. it tends to fail if the
process has ever created a window.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=36873
Signed-off-by: Arkadiusz Hiler <ahiler at codeweavers.com>
---
 dlls/user32/input.c       |  58 +++++++++--
 dlls/user32/tests/input.c | 198 +++++++++++++++++++++++++++++++++++++-
 server/protocol.def       |  15 +++
 server/queue.c            |  39 ++++++++
 server/trace.c            |  25 +++++
 5 files changed, 325 insertions(+), 10 deletions(-)

diff --git a/dlls/user32/input.c b/dlls/user32/input.c
index 3425a2ea10f..e06f8b4413e 100644
--- a/dlls/user32/input.c
+++ b/dlls/user32/input.c
@@ -1267,22 +1267,64 @@ TrackMouseEvent (TRACKMOUSEEVENT *ptme)
  *     Success: count of point set in the buffer
  *     Failure: -1
  */
-int WINAPI GetMouseMovePointsEx(UINT size, LPMOUSEMOVEPOINT ptin, LPMOUSEMOVEPOINT ptout, int count, DWORD res) {
+int WINAPI GetMouseMovePointsEx( UINT size, LPMOUSEMOVEPOINT ptin, LPMOUSEMOVEPOINT ptout, int count, DWORD resolution )
+{
+    cursor_pos_t *pos, positions[64];
+    int copied;
+    unsigned int i;
 
-    if((size != sizeof(MOUSEMOVEPOINT)) || (count < 0) || (count > 64)) {
-        SetLastError(ERROR_INVALID_PARAMETER);
+
+    TRACE( "%d, %p, %p, %d, %d\n", size, ptin, ptout, count, resolution );
+
+    if ((size != sizeof(MOUSEMOVEPOINT)) || (count < 0) || (count > ARRAY_SIZE( positions )))
+    {
+        SetLastError( ERROR_INVALID_PARAMETER );
         return -1;
     }
 
-    if(!ptin || (!ptout && count)) {
-        SetLastError(ERROR_NOACCESS);
+    if (!ptin || (!ptout && count))
+    {
+        SetLastError( ERROR_NOACCESS );
         return -1;
     }
 
-    FIXME("(%d %p %p %d %d) stub\n", size, ptin, ptout, count, res);
+    if (resolution != GMMP_USE_DISPLAY_POINTS)
+    {
+        FIXME( "only GMMP_USE_DISPLAY_POINTS is supported for now\n" );
+        SetLastError( ERROR_POINT_NOT_FOUND );
+        return -1;
+    }
+
+    SERVER_START_REQ( get_cursor_history )
+    {
+        wine_server_set_reply( req, &positions, sizeof(positions) );
+        if (wine_server_call_err( req )) return -1;
+    }
+    SERVER_END_REQ;
+
+    for (i = 0; i < ARRAY_SIZE( positions ); i++)
+    {
+        pos = &positions[i];
+        if (ptin->x == pos->x && ptin->y == pos->y && (!ptin->time || ptin->time == pos->time))
+            break;
+    }
+
+    if (i == ARRAY_SIZE( positions ))
+    {
+        SetLastError( ERROR_POINT_NOT_FOUND );
+        return -1;
+    }
+
+    for (copied = 0; copied < count && i < ARRAY_SIZE( positions ); copied++, i++)
+    {
+        pos = &positions[i];
+        ptout[copied].x = pos->x;
+        ptout[copied].y = pos->y;
+        ptout[copied].time = pos->time;
+        ptout[copied].dwExtraInfo = pos->info;
+    }
 
-    SetLastError(ERROR_POINT_NOT_FOUND);
-    return -1;
+    return copied;
 }
 
 /***********************************************************************
diff --git a/dlls/user32/tests/input.c b/dlls/user32/tests/input.c
index 1809c147cbd..cf83ad81bb8 100644
--- a/dlls/user32/tests/input.c
+++ b/dlls/user32/tests/input.c
@@ -1473,14 +1473,18 @@ done:
     SetCursorPos(pt_org.x, pt_org.y);
 }
 
-static void test_GetMouseMovePointsEx(void)
+static void test_GetMouseMovePointsEx(const char *argv0)
 {
 #define BUFLIM  64
 #define MYERROR 0xdeadbeef
+    PROCESS_INFORMATION process_info;
+    STARTUPINFOA startup_info;
+    char path[MAX_PATH];
     int count, retval;
     MOUSEMOVEPOINT in;
     MOUSEMOVEPOINT out[200];
     POINT point;
+    TEST_INPUT input;
 
     /* Get a valid content for the input struct */
     if(!GetCursorPos(&point)) {
@@ -1605,10 +1609,194 @@ static void test_GetMouseMovePointsEx(void)
     ok(GetLastError() == ERROR_INVALID_PARAMETER || GetLastError() == MYERROR,
        "expected error ERROR_INVALID_PARAMETER, got %u\n", GetLastError());
 
+    /* more than 64 to be sure we wrap around */
+    for (int i = 0; i < 67; i++)
+    {
+        in.x = i;
+        in.y = i*2;
+        SetCursorPos( in.x, in.y );
+    }
+
+    SetLastError( MYERROR );
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+    ok( GetLastError() == MYERROR, "expected error to stay %x, got %x\n", MYERROR, GetLastError() );
+
+    for (int i = 0; i < retval; i++)
+    {
+        ok( out[i].x == in.x && out[i].y == in.y, "wrong position %d, expected %dx%d got %dx%d\n", i, in.x, in.y, out[i].x, out[i].y );
+        in.x--;
+        in.y -= 2;
+    }
+
+    in.x = 1500;
+    in.y = 1500;
+    SetLastError( MYERROR );
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_DISPLAY_POINTS );
+    ok( retval == -1, "expected to get -1 but got %d\n", retval );
+    ok( GetLastError() == ERROR_POINT_NOT_FOUND, "expected error to be set to %x, got %x\n", ERROR_POINT_NOT_FOUND, GetLastError() );
+
+    /* make sure there's no deduplication */
+    in.x = 6;
+    in.y = 6;
+    SetCursorPos( in.x, in.y );
+    SetCursorPos( in.x, in.y );
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+    ok( out[0].x == 6 && out[0].y == 6, "expected cursor position to be 6x6 but got %d %d\n", out[0].x, out[0].y );
+    ok( out[1].x == 6 && out[1].y == 6, "expected cursor position to be 6x6 but got %d %d\n", out[1].x, out[1].y );
+
+    /* make sure 2 events are distinguishable by their timestamps */
+    in.x = 150;
+    in.y = 75;
+    SetCursorPos( 30, 30 );
+    SetCursorPos( in.x, in.y );
+    SetCursorPos( 150, 150 );
+    Sleep( 3 );
+    SetCursorPos( in.x, in.y );
+
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+    ok( out[0].x == 150 && out[0].y == 75, "expected cursor position to be 150x75 but got %d %d\n", out[0].x, out[0].y );
+    ok( out[1].x == 150 && out[1].y == 150, "expected cursor position to be 150x150 but got %d %d\n", out[1].x, out[1].y );
+    ok( out[2].x == 150 && out[2].y == 75, "expected cursor position to be 150x75 but got %d %d\n", out[2].x, out[2].y );
+
+    in.time = out[2].time;
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 62, "expected to get 62 mouse move points but got %d\n", retval );
+    ok( out[0].x == 150 && out[0].y == 75, "expected cursor position to be 150x75 but got %d %d\n", out[0].x, out[0].y );
+    ok( out[1].x == 30 && out[1].y == 30, "expected cursor position to be 30x30 but got %d %d\n", out[1].x, out[1].y );
+
+    /* events created through other means should also be on the list with correct extra info */
+    mouse_event( MOUSEEVENTF_MOVE, -13, 17, 0, 0xcafecafe );
+    ok( GetCursorPos( &point ), "failed to get cursor position\n" );
+    ok( in.x != point.x && in.y != point.y, "cursor didn't change position after mouse_event()\n" );
+    in.time = 0;
+    in.x = point.x;
+    in.y = point.y;
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+    ok( out[0].dwExtraInfo == 0xcafecafe, "wrong extra info, got 0x%lx expected 0xcafecafe\n", out[0].dwExtraInfo );
+
+    input.type = INPUT_MOUSE;
+    memset( &input, 0, sizeof(input) );
+    input.u.mi.dwFlags = MOUSEEVENTF_MOVE;
+    input.u.mi.dwExtraInfo = 0xdeadbeef;
+    input.u.mi.dx = -17;
+    input.u.mi.dy = 13;
+    SendInput( 1, (INPUT *)&input, sizeof(INPUT) );
+    ok( GetCursorPos( &point ), "failed to get cursor position\n" );
+    ok( in.x != point.x && in.y != point.y, "cursor didn't change position after mouse_event()\n" );
+    in.time = 0;
+    in.x = point.x;
+    in.y = point.y;
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+    ok( out[0].dwExtraInfo == 0xdeadbeef, "wrong extra info, got 0x%lx expected 0xdeadbeef\n", out[0].dwExtraInfo );
+
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, BUFLIM, GMMP_USE_HIGH_RESOLUTION_POINTS );
+    todo_wine ok( retval == 64, "expected to get 64 high resolution mouse move points but got %d\n", retval );
+
+    sprintf(path, "%s input get_mouse_move_points_test", argv0);
+    memset(&startup_info, 0, sizeof(startup_info));
+    startup_info.cb = sizeof(startup_info);
+    startup_info.dwFlags = STARTF_USESHOWWINDOW;
+    startup_info.wShowWindow = SW_SHOWNORMAL;
+    retval = CreateProcessA(NULL, path, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info );
+    ok(retval, "CreateProcess \"%s\" failed err %u.\n", path, GetLastError());
+    winetest_wait_child_process(process_info.hProcess);
+    CloseHandle(process_info.hProcess);
+    CloseHandle(process_info.hThread);
 #undef BUFLIM
 #undef MYERROR
 }
 
+static void test_GetMouseMovePointsEx_process(void)
+{
+    int retval;
+    MOUSEMOVEPOINT in;
+    MOUSEMOVEPOINT out[64], out2[64];
+    POINT point;
+    HDESK desk0, desk1;
+    HWINSTA winstation0, winstation1;
+
+    memset( out, 0, sizeof(out) );
+    memset( out2, 0, sizeof(out2) );
+
+    /* move point history is shared between desktops within the same windowstation */
+    ok( GetCursorPos( &point ), "failed to get cursor position\n" );
+    in.time = 0;
+    in.x = point.x;
+    in.y = point.y;
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, ARRAY_SIZE(out), GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+
+    desk0 = OpenInputDesktop( 0, FALSE, DESKTOP_ALL_ACCESS );
+    ok( desk0 != NULL, "OpenInputDesktop has failed with %d\n", GetLastError() );
+    desk1 = CreateDesktopA( "getmousemovepointsex_test_desktop", NULL, NULL, 0, DESKTOP_ALL_ACCESS, NULL );
+    ok( desk1 != NULL, "CreateDesktopA failed with %d\n", GetLastError() );
+
+    ok( SetThreadDesktop( desk1 ), "SetThreadDesktop failed!\n" );
+    ok( SwitchDesktop( desk1 ), "SwitchDesktop failed\n" );
+
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out2, ARRAY_SIZE(out2), GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+
+    ok( memcmp( out, out2, sizeof(out2) ) == 0, "expected to get exact same history on the new desktop\n" );
+
+    in.time = 0;
+    in.x = 38;
+    in.y = 27;
+    SetCursorPos( in.x, in.y );
+
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out2, ARRAY_SIZE(out2), GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+
+    ok( SetThreadDesktop( desk0 ), "SetThreadDesktop failed!\n" );
+    ok( SwitchDesktop( desk0 ), "SwitchDesktop failed\n" );
+
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, ARRAY_SIZE(out), GMMP_USE_DISPLAY_POINTS );
+    ok( retval == 64, "expected to get 64 mouse move points but got %d\n", retval );
+    ok( memcmp( out, out2, sizeof( out2 ) ) == 0, "expected to get exact same history on the old desktop\n" );
+
+    CloseDesktop( desk1 );
+    CloseDesktop( desk0 );
+
+    /* non-default windowstations are non-interactive */
+    winstation0 = GetProcessWindowStation();
+    ok( winstation0 != NULL, "GetProcessWindowStation has failed with %d\n", GetLastError() );
+    desk0 = OpenInputDesktop( 0, FALSE, DESKTOP_ALL_ACCESS );
+    ok( desk0 != NULL, "OpenInputDesktop has failed with %d\n", GetLastError() );
+    winstation1 = CreateWindowStationA( "test_winstation", 0, WINSTA_ALL_ACCESS, NULL );
+
+    if (winstation1 == NULL && GetLastError() == ERROR_ACCESS_DENIED)
+    {
+        win_skip("not enough priviledges for CreateWindowStation\n");
+        CloseDesktop( desk0 );
+        CloseWindowStation( winstation0 );
+        return;
+    }
+
+    ok( winstation1 != NULL, "CreateWindowStationA has failed with %d\n", GetLastError() );
+    ok( SetProcessWindowStation( winstation1 ), "SetProcessWindowStation has failed\n" );
+
+    desk1 = CreateDesktopA( "getmousemovepointsex_test_desktop", NULL, NULL, 0, DESKTOP_ALL_ACCESS, NULL );
+    ok( desk1 != NULL, "CreateDesktopA failed with %d\n", GetLastError() );
+    ok( SetThreadDesktop( desk1 ), "SetThreadDesktop failed!\n" );
+
+    SetLastError( 0xDEADBEEF );
+    retval = pGetMouseMovePointsEx( sizeof(MOUSEMOVEPOINT), &in, out, ARRAY_SIZE(out), GMMP_USE_DISPLAY_POINTS );
+    todo_wine ok( retval == -1, "expected to get -1 mouse move points but got %d\n", retval );
+    todo_wine ok( GetLastError() == ERROR_ACCESS_DENIED, "expected ERROR_ACCESS_DENIED got %d\n", GetLastError() );
+
+    ok( SetProcessWindowStation( winstation0 ), "SetProcessWindowStation has failed\n" );
+    ok( SetThreadDesktop( desk0 ), "SetThreadDesktop failed!\n" );
+    CloseDesktop( desk1 );
+    CloseWindowStation( winstation1 );
+    CloseDesktop( desk0 );
+    CloseWindowStation( winstation0 );
+}
+
 static void test_GetRawInputDeviceList(void)
 {
     RAWINPUTDEVICELIST devices[32];
@@ -3807,6 +3995,12 @@ START_TEST(input)
         return;
     }
 
+    if (argc >= 3 && strcmp(argv[2], "get_mouse_move_points_test") == 0)
+    {
+        test_GetMouseMovePointsEx_process();
+        return;
+    }
+
     test_Input_blackbox();
     test_Input_whitebox();
     test_Input_unicode();
@@ -3828,7 +4022,7 @@ START_TEST(input)
     test_rawinput(argv[0]);
 
     if(pGetMouseMovePointsEx)
-        test_GetMouseMovePointsEx();
+        test_GetMouseMovePointsEx(argv[0]);
     else
         win_skip("GetMouseMovePointsEx is not available\n");
 
diff --git a/server/protocol.def b/server/protocol.def
index f538c6dcf51..a2834898cce 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -782,6 +782,15 @@ struct rawinput_device
     user_handle_t  target;
 };
 
+typedef struct
+{
+    int x;
+    int y;
+    unsigned int time;
+    int __pad;
+    lparam_t info;
+} cursor_pos_t;
+
 /****************************************************************/
 /* Request declarations */
 
@@ -3605,6 +3614,12 @@ struct handle_info
 #define SET_CURSOR_CLIP   0x08
 #define SET_CURSOR_NOCLIP 0x10
 
+/* Get the history of the 64 last cursor positions */
+ at REQ(get_cursor_history)
+ at REPLY
+    VARARG(history,cursor_positions);
+ at END
+
 
 /* Batch read rawinput message data */
 @REQ(get_rawinput_buffer)
diff --git a/server/queue.c b/server/queue.c
index 2c780a94e2c..84057d6584f 100644
--- a/server/queue.c
+++ b/server/queue.c
@@ -227,6 +227,9 @@ static const struct object_ops thread_input_ops =
 /* pointer to input structure of foreground thread */
 static unsigned int last_input_time;
 
+static cursor_pos_t cursor_history[64];
+static unsigned int cursor_history_latest;
+
 static void queue_hardware_message( struct desktop *desktop, struct message *msg, int always_queue );
 static void free_message( struct message *msg );
 
@@ -1520,14 +1523,33 @@ static void update_rawinput_device(const struct rawinput_device *device)
     e->device.target = get_user_full_handle( e->device.target );
 }
 
+static void prepend_cursor_history( int x, int y, unsigned int time, lparam_t info )
+{
+    const size_t positions_len = ARRAY_SIZE( cursor_history );
+    cursor_pos_t *pos;
+
+    cursor_history_latest = (cursor_history_latest + positions_len - 1) % positions_len;
+
+    pos = &cursor_history[cursor_history_latest];
+    pos->x = x;
+    pos->y = y;
+    pos->time = time;
+    pos->info = info;
+}
+
 /* queue a hardware message into a given thread input */
 static void queue_hardware_message( struct desktop *desktop, struct message *msg, int always_queue )
 {
     user_handle_t win;
     struct thread *thread;
     struct thread_input *input;
+    struct hardware_msg_data *msg_data;
     unsigned int msg_code;
 
+    msg_data = msg->data;
+    if (msg->msg == WM_MOUSEMOVE)
+        prepend_cursor_history( msg->x, msg->y, msg->time, msg_data->info );
+
     update_input_key_state( desktop, desktop->keystate, msg->msg, msg->wparam );
     last_input_time = get_tick_count();
     if (msg->msg != WM_MOUSEMOVE) always_queue = 1;
@@ -3222,6 +3244,23 @@ DECL_HANDLER(set_cursor)
     reply->last_change = input->desktop->cursor.last_change;
 }
 
+/* Get the history of the 64 last cursor positions */
+DECL_HANDLER(get_cursor_history)
+{
+    cursor_pos_t positions[64];
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE( positions ); i++)
+    {
+        positions[i] = cursor_history[(i + cursor_history_latest) % ARRAY_SIZE( cursor_history )];
+    }
+
+    if (sizeof(positions) == get_reply_max_size())
+        set_reply_data( positions, sizeof(positions) );
+    else
+        set_error( STATUS_INVALID_BUFFER_SIZE );
+}
+
 DECL_HANDLER(get_rawinput_buffer)
 {
     struct thread_input *input = current->queue->input;
diff --git a/server/trace.c b/server/trace.c
index ffe9d6e19c6..17a28921ebd 100644
--- a/server/trace.c
+++ b/server/trace.c
@@ -887,6 +887,31 @@ static void dump_varargs_rectangles( const char *prefix, data_size_t size )
     remove_data( size );
 }
 
+static void dump_varargs_cursor_positions( const char *prefix, data_size_t size )
+{
+    const cursor_pos_t *pos = cur_data;
+    data_size_t len;
+
+    if (!size)
+    {
+        fprintf( stderr, "%s{}", prefix );
+        return;
+    }
+
+    fprintf( stderr, "%s{", prefix );
+    len = size / sizeof(*pos);
+    while (len > 0)
+    {
+        fprintf( stderr, "{x=%d,y=%d,time=%u", pos->x, pos->y, pos->time );
+        dump_uint64( ",info=", &pos->info );
+        fputc( '}', stderr );
+        pos++;
+        if (--len) fputc( ',', stderr );
+    }
+    fputc( '}', stderr );
+    remove_data( size );
+}
+
 static void dump_varargs_message_data( const char *prefix, data_size_t size )
 {
     /* FIXME: dump the structured data */
-- 
2.28.0




More information about the wine-devel mailing list