mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 00:31:31 +00:00
[client] tests: cover mouse exit sequencing
This commit is contained in:
@@ -46,6 +46,43 @@ set_tests_properties(wayland-motion-tests PROPERTIES
|
||||
TIMEOUT 10
|
||||
)
|
||||
|
||||
add_executable(mouse-tests
|
||||
mouse_test.c
|
||||
../src/core.c
|
||||
../src/util.c
|
||||
)
|
||||
target_compile_definitions(mouse-tests PRIVATE
|
||||
CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1
|
||||
)
|
||||
target_include_directories(mouse-tests PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
||||
"${PROJECT_TOP}/repos/PureSpice/include"
|
||||
"${PROJECT_TOP}/repos/gui/cimgui"
|
||||
"${PROJECT_TOP}/repos/gui/cimgui/imgui"
|
||||
)
|
||||
target_link_libraries(mouse-tests
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
)
|
||||
set(MOUSE_CASES
|
||||
inset-exit
|
||||
exit-delay
|
||||
exit-guest
|
||||
confine-pending
|
||||
confine-guest
|
||||
capture-pending
|
||||
capture-revoked
|
||||
capture-release
|
||||
)
|
||||
foreach(name IN LISTS MOUSE_CASES)
|
||||
add_test(NAME mouse-${name}
|
||||
COMMAND mouse-tests ${name}
|
||||
)
|
||||
set_tests_properties(mouse-${name} PROPERTIES
|
||||
TIMEOUT 10
|
||||
)
|
||||
endforeach()
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
find_program(WESTON_EXECUTABLE NAMES weston)
|
||||
if(NOT WESTON_EXECUTABLE)
|
||||
|
||||
443
client/tests/mouse_test.c
Normal file
443
client/tests/mouse_test.c
Normal file
@@ -0,0 +1,443 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program 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 for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "app.h"
|
||||
#include "core.h"
|
||||
#include "main.h"
|
||||
#include "test.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
enum EvType
|
||||
{
|
||||
EV_GRAB,
|
||||
EV_UNGRAB,
|
||||
EV_CAPTURE,
|
||||
EV_UNCAPTURE,
|
||||
EV_WARP,
|
||||
EV_GUEST,
|
||||
EV_MOTION,
|
||||
EV_VALID,
|
||||
};
|
||||
|
||||
struct Ev
|
||||
{
|
||||
enum EvType type;
|
||||
double x;
|
||||
double y;
|
||||
bool exit;
|
||||
bool conf;
|
||||
bool lock;
|
||||
};
|
||||
|
||||
struct Trap
|
||||
{
|
||||
bool req;
|
||||
bool on;
|
||||
bool hold;
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
enum LG_DSWarpSupport support;
|
||||
struct Trap conf;
|
||||
struct Trap lock;
|
||||
bool valid;
|
||||
struct Ev ev[256];
|
||||
unsigned int count;
|
||||
}
|
||||
m;
|
||||
|
||||
struct AppState g_state;
|
||||
struct CursorState g_cursor;
|
||||
struct AppParams g_params;
|
||||
|
||||
static void push(enum EvType type, double x, double y, bool leaving)
|
||||
{
|
||||
CHECK(m.count < sizeof(m.ev) / sizeof(m.ev[0]));
|
||||
m.ev[m.count++] = (struct Ev) {
|
||||
.type = type,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.exit = leaving,
|
||||
.conf = m.conf.on,
|
||||
.lock = m.lock.on,
|
||||
};
|
||||
}
|
||||
|
||||
static unsigned int count(enum EvType type)
|
||||
{
|
||||
unsigned int result = 0;
|
||||
for (unsigned int i = 0; i < m.count; ++i)
|
||||
if (m.ev[i].type == type)
|
||||
++result;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int first(enum EvType type)
|
||||
{
|
||||
for (unsigned int i = 0; i < m.count; ++i)
|
||||
if (m.ev[i].type == type)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void guest(double x, double y, double localX, double localY)
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
push(EV_GUEST, localX, localY, false);
|
||||
}
|
||||
|
||||
static void setPointer(LG_DSPointer pointer)
|
||||
{
|
||||
(void)pointer;
|
||||
}
|
||||
|
||||
static void keyNoop(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void grab(void)
|
||||
{
|
||||
m.conf.req = true;
|
||||
push(EV_GRAB, 0, 0, false);
|
||||
}
|
||||
|
||||
static void ungrab(void)
|
||||
{
|
||||
push(EV_UNGRAB, 0, 0, false);
|
||||
m.conf.req = false;
|
||||
if (!m.conf.hold)
|
||||
m.conf.on = false;
|
||||
}
|
||||
|
||||
static void capture(void)
|
||||
{
|
||||
m.conf.req = false;
|
||||
if (!m.conf.hold)
|
||||
m.conf.on = false;
|
||||
m.lock.req = true;
|
||||
push(EV_CAPTURE, 0, 0, false);
|
||||
}
|
||||
|
||||
static void uncapture(void)
|
||||
{
|
||||
push(EV_UNCAPTURE, 0, 0, false);
|
||||
m.lock.req = false;
|
||||
if (!m.lock.hold)
|
||||
m.lock.on = false;
|
||||
}
|
||||
|
||||
static void warp(int x, int y, bool exiting)
|
||||
{
|
||||
push(EV_WARP, x, y, exiting);
|
||||
}
|
||||
|
||||
static bool valid(int x, int y)
|
||||
{
|
||||
push(EV_VALID, x, y, false);
|
||||
return m.valid;
|
||||
}
|
||||
|
||||
static struct LG_DisplayServerOps ds = {
|
||||
.name = "test",
|
||||
.guestPointerUpdated = guest,
|
||||
.setPointer = setPointer,
|
||||
.grabKeyboard = keyNoop,
|
||||
.ungrabKeyboard = keyNoop,
|
||||
.grabPointer = grab,
|
||||
.ungrabPointer = ungrab,
|
||||
.capturePointer = capture,
|
||||
.uncapturePointer = uncapture,
|
||||
.warpPointer = warp,
|
||||
.isValidPointerPos = valid,
|
||||
};
|
||||
|
||||
bool app_getProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
if (prop != LG_DS_WARP_SUPPORT)
|
||||
return false;
|
||||
|
||||
*(enum LG_DSWarpSupport *)ret = m.support;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool app_guestIsWindows(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool app_isOverlayMode(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool app_isRunning(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t app_mouseSeq(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void app_mouseTrace(const char * file, unsigned int line,
|
||||
const char * function, uint64_t seq, const char * format, ...)
|
||||
{
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)function;
|
||||
(void)seq;
|
||||
(void)format;
|
||||
}
|
||||
|
||||
bool purespice_mouseMotion(int32_t x, int32_t y)
|
||||
{
|
||||
push(EV_MOTION, x, y, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void reset(void)
|
||||
{
|
||||
memset(&m, 0, sizeof(m));
|
||||
memset(&g_state, 0, sizeof(g_state));
|
||||
memset(&g_cursor, 0, sizeof(g_cursor));
|
||||
memset(&g_params, 0, sizeof(g_params));
|
||||
|
||||
m.support = LG_DS_WARP_SURFACE;
|
||||
m.valid = true;
|
||||
|
||||
g_state.ds = &ds;
|
||||
g_state.focused = true;
|
||||
g_state.haveSrcSize = true;
|
||||
g_state.posInfoValid = true;
|
||||
g_state.formatValid = true;
|
||||
g_state.windowW = 300;
|
||||
g_state.windowH = 200;
|
||||
g_state.srcSize = (struct Point) { 100, 80 };
|
||||
g_state.dstRect = (LG_RendererRect) {
|
||||
.valid = true,
|
||||
.x = 10,
|
||||
.y = 20,
|
||||
.w = 100,
|
||||
.h = 80,
|
||||
};
|
||||
|
||||
g_params.useSpiceInput = true;
|
||||
g_params.scaleMouseInput = true;
|
||||
|
||||
g_cursor.inWindow = true;
|
||||
g_cursor.inView = true;
|
||||
g_cursor.warpState = WARP_STATE_ON;
|
||||
g_cursor.guest.valid = true;
|
||||
g_cursor.scale = (struct DoublePoint) { 1.0, 1.0 };
|
||||
}
|
||||
|
||||
static void setLocal(double x, double y)
|
||||
{
|
||||
g_cursor.pos = (struct DoublePoint) { x, y };
|
||||
|
||||
struct DoublePoint guest;
|
||||
util_localCurToGuest(&guest);
|
||||
g_cursor.guest.x = lround(guest.x);
|
||||
g_cursor.guest.y = lround(guest.y);
|
||||
}
|
||||
|
||||
static void testInsetExit(void)
|
||||
{
|
||||
reset();
|
||||
setLocal(10, 20);
|
||||
g_cursor.pos = (struct DoublePoint) { 9.75, 19.75 };
|
||||
|
||||
core_handleMouseNormal(-0.25, -0.25);
|
||||
|
||||
const int drop = first(EV_UNGRAB);
|
||||
const int move = first(EV_WARP);
|
||||
CHECK(drop >= 0);
|
||||
CHECK(count(EV_WARP) == 1);
|
||||
CHECK(move > drop);
|
||||
CHECK(m.ev[move].x == 9);
|
||||
CHECK(m.ev[move].y == 19);
|
||||
CHECK(m.ev[move].exit);
|
||||
CHECK(!g_cursor.inView);
|
||||
|
||||
core_handleGuestMouseUpdate();
|
||||
CHECK(count(EV_GUEST) == 0);
|
||||
}
|
||||
|
||||
static void startExit(void)
|
||||
{
|
||||
reset();
|
||||
setLocal(109, 50);
|
||||
m.conf.req = true;
|
||||
m.conf.on = true;
|
||||
m.conf.hold = true;
|
||||
|
||||
core_handleMouseNormal(2, 0);
|
||||
}
|
||||
|
||||
static void testExitWait(void)
|
||||
{
|
||||
startExit();
|
||||
const int drop = first(EV_UNGRAB);
|
||||
CHECK(drop >= 0);
|
||||
CHECK(count(EV_WARP) == 0);
|
||||
CHECK(g_cursor.inView);
|
||||
}
|
||||
|
||||
static void testExitGuest(void)
|
||||
{
|
||||
startExit();
|
||||
g_cursor.guest.x = 99;
|
||||
core_handleGuestMouseUpdate();
|
||||
CHECK(count(EV_GUEST) == 0);
|
||||
}
|
||||
|
||||
static void startConf(void)
|
||||
{
|
||||
reset();
|
||||
setLocal(50, 50);
|
||||
g_cursor.inView = false;
|
||||
|
||||
core_handleMouseNormal(0, 0);
|
||||
}
|
||||
|
||||
static void testConfWait(void)
|
||||
{
|
||||
startConf();
|
||||
CHECK(m.conf.req);
|
||||
CHECK(!m.conf.on);
|
||||
CHECK(!g_cursor.inView);
|
||||
}
|
||||
|
||||
static void testConfGuest(void)
|
||||
{
|
||||
startConf();
|
||||
core_handleGuestMouseUpdate();
|
||||
CHECK(m.conf.req);
|
||||
CHECK(count(EV_GUEST) == 0);
|
||||
}
|
||||
|
||||
static void testCapWait(void)
|
||||
{
|
||||
reset();
|
||||
setLocal(50, 50);
|
||||
g_params.captureInputOnly = true;
|
||||
|
||||
core_handleMouseGrabbed(1, 0);
|
||||
CHECK(count(EV_MOTION) == 0);
|
||||
|
||||
core_setGrabQuiet(true);
|
||||
CHECK(g_cursor.grab);
|
||||
CHECK(m.lock.req);
|
||||
CHECK(!m.lock.on);
|
||||
|
||||
core_handleMouseGrabbed(1, 0);
|
||||
CHECK(count(EV_MOTION) == 0);
|
||||
}
|
||||
|
||||
static void testCapRevoke(void)
|
||||
{
|
||||
reset();
|
||||
setLocal(50, 50);
|
||||
g_params.captureInputOnly = true;
|
||||
core_setGrabQuiet(true);
|
||||
|
||||
m.lock.on = true;
|
||||
core_handleMouseGrabbed(1, 0);
|
||||
CHECK(count(EV_MOTION) == 1);
|
||||
CHECK(m.ev[first(EV_MOTION)].lock);
|
||||
|
||||
m.lock.on = false;
|
||||
core_handleMouseGrabbed(1, 0);
|
||||
CHECK(g_cursor.grab);
|
||||
CHECK(count(EV_MOTION) == 1);
|
||||
}
|
||||
|
||||
static void testCapRelease(void)
|
||||
{
|
||||
reset();
|
||||
setLocal(50, 50);
|
||||
g_params.captureInputOnly = true;
|
||||
core_setGrabQuiet(true);
|
||||
|
||||
m.lock.on = true;
|
||||
core_handleMouseGrabbed(1, 0);
|
||||
CHECK(count(EV_MOTION) == 1);
|
||||
|
||||
m.lock.hold = true;
|
||||
core_setGrabQuiet(false);
|
||||
CHECK(!g_cursor.grab);
|
||||
CHECK(!m.lock.req);
|
||||
CHECK(m.lock.on);
|
||||
|
||||
const int drop = first(EV_UNCAPTURE);
|
||||
CHECK(drop >= 0);
|
||||
CHECK(m.ev[drop].lock);
|
||||
|
||||
core_handleMouseGrabbed(1, 0);
|
||||
CHECK(count(EV_MOTION) == 1);
|
||||
}
|
||||
|
||||
struct Test
|
||||
{
|
||||
const char * name;
|
||||
void (*run)(void);
|
||||
};
|
||||
|
||||
static const struct Test tests[] = {
|
||||
{ "inset-exit" , testInsetExit },
|
||||
{ "exit-delay" , testExitWait },
|
||||
{ "exit-guest" , testExitGuest },
|
||||
{ "confine-pending", testConfWait },
|
||||
{ "confine-guest" , testConfGuest },
|
||||
{ "capture-pending", testCapWait },
|
||||
{ "capture-revoked", testCapRevoke },
|
||||
{ "capture-release", testCapRelease },
|
||||
};
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
if (argc == 2)
|
||||
{
|
||||
for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i)
|
||||
if (strcmp(argv[1], tests[i].name) == 0)
|
||||
{
|
||||
tests[i].run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "unknown test: %s\n", argv[1]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (argc != 1)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i)
|
||||
tests[i].run();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user