Files
LookingGlass/client/tests/mouse_test.c
2026-08-06 13:40:29 +10:00

444 lines
8.5 KiB
C

/**
* 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;
}