Files
LookingGlass/client/src/core.c
Geoffrey McRae e4874ee32d [client] cursor: restore Wayland edge synchronization
Keep guest-to-host pointer synchronization active while an attempted
surface exit is blocked by the edge of a Wayland output. Continue
forwarding relative motion and wait for wl_pointer.leave before marking
the cursor outside the window.

Distinguish whole-surface releases from exits into letterbox space so
the expected asynchronous release callback cannot disable the feedback
loop before the pointer is confined again.

Treat Wayland pointer capability loss as a real leave so an unrelated
inactive callback cannot be mistaken for the intentional release.
2026-08-06 22:26:14 +10:00

1046 lines
27 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 "core.h"
#include "main.h"
#include "app.h"
#include "util.h"
#include "kb.h"
#include "message.h"
#include "common/time.h"
#include "common/debug.h"
#include "common/array.h"
#include "common/proctitle.h"
#include "common/stringutils.h"
#include "common/version.h"
#include <math.h>
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
#define MTRACE(fmt, ...) \
do \
{ \
const uint64_t seq = app_mouseSeq(); \
if (seq) \
app_mouseTrace(__FILE__, __LINE__, __FUNCTION__, seq, \
"core." fmt, ##__VA_ARGS__); \
} \
while (0)
static bool isInView(void)
{
return
g_cursor.pos.x >= g_state.dstRect.x &&
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
g_cursor.pos.y >= g_state.dstRect.y &&
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
}
static int exitPos(double pos, int edge)
{
return pos < edge ? floor(pos) : ceil(pos);
}
static void cancelExit(const char * why)
{
if (!g_cursor.exit)
return;
MTRACE("exit cancel=%s target=%.3f,%.3f", why,
g_cursor.exitPos.x, g_cursor.exitPos.y);
g_cursor.exit = false;
}
static void cancelSurfaceExit(const char * why)
{
if (!g_cursor.surfaceExit)
return;
MTRACE("surface exit cancel=%s", why);
g_cursor.surfaceExit = false;
g_cursor.warpState = WARP_STATE_ON;
}
static void finishExit(void)
{
if (!g_cursor.exit || g_cursor.viewReq)
return;
const int x = exitPos(g_cursor.exitPos.x, g_state.dstRect.x);
const int y = exitPos(g_cursor.exitPos.y, g_state.dstRect.y);
g_cursor.exit = false;
MTRACE("exit warp target=%d,%d projected=%.3f,%.3f inWin=%d",
x, y, g_cursor.exitPos.x, g_cursor.exitPos.y, g_cursor.inWindow);
if (x < 0 || x >= g_state.windowW ||
y < 0 || y >= g_state.windowH)
g_cursor.inWindow = false;
core_warpPointer(x, y, true);
}
static void startExit(double x, double y)
{
g_cursor.exit = true;
g_cursor.exitPos = (struct DoublePoint) { x, y };
core_setCursorInView(false);
}
static bool moveExit(double ex, double ey)
{
if (g_cursor.useScale && g_params.scaleMouseInput)
{
ex *= g_cursor.scale.x;
ey *= g_cursor.scale.y;
}
struct DoublePoint move = {.x = ex, .y = ey};
util_rotatePoint(&move);
g_cursor.exitPos.x += move.x;
g_cursor.exitPos.y += move.y;
const bool inside =
g_cursor.exitPos.x >= g_state.dstRect.x &&
g_cursor.exitPos.x < g_state.dstRect.x + g_state.dstRect.w &&
g_cursor.exitPos.y >= g_state.dstRect.y &&
g_cursor.exitPos.y < g_state.dstRect.y + g_state.dstRect.h;
MTRACE("exit move=%.3f,%.3f target=%.3f,%.3f inside=%d",
move.x, move.y, g_cursor.exitPos.x, g_cursor.exitPos.y, inside);
return inside;
}
bool core_inputEnabled(void)
{
return g_params.useSpiceInput && !g_state.ignoreInput &&
((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly);
}
static void applyView(bool active, bool force)
{
if (active && !g_cursor.viewReq)
{
MTRACE("view active skip=req");
return;
}
if (!force && g_cursor.inView == active)
return;
MTRACE("view active=%d old=%d req=%d force=%d", active,
g_cursor.inView, g_cursor.viewReq, force);
g_cursor.inView = active;
g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
? true : g_cursor.inView;
g_cursor.redraw = true;
g_cursor.warpState = g_cursor.inView ? WARP_STATE_ON : WARP_STATE_OFF;
if (g_cursor.inView)
{
if (g_params.hideMouse)
g_state.ds->setPointer(LG_POINTER_NONE);
if (g_params.grabKeyboardOnFocus)
g_state.ds->grabKeyboard();
}
else
{
if (g_params.hideMouse)
g_state.ds->setPointer(LG_POINTER_SQUARE);
g_state.ds->ungrabKeyboard();
}
g_cursor.warpState = WARP_STATE_ON;
}
void core_invalidatePointer(bool detectInView)
{
MTRACE("invalidate detect=%d inWin=%d req=%d active=%d grab=%d "
"warp=%d", detectInView, g_cursor.inWindow, g_cursor.viewReq,
g_cursor.inView, g_cursor.grab, g_cursor.warpState);
if (!detectInView)
{
applyView(g_cursor.inView, true);
return;
}
const bool inView = isInView();
/* do not allow the view to become active if any mouse buttons are being
* held, this fixes issues with meta window resizing. */
if (inView && g_cursor.buttons)
return;
core_setCursorInView(inView);
}
void core_setCursorInView(bool enable)
{
MTRACE("view req=%d oldReq=%d active=%d focus=%d inWin=%d grab=%d",
enable, g_cursor.viewReq, g_cursor.inView, g_state.focused,
g_cursor.inWindow, g_cursor.grab);
if (!enable)
cancelSurfaceExit("view");
if (g_cursor.exit &&
(enable || !g_state.focused || !g_cursor.inWindow ||
g_state.ignoreInput || !g_state.posInfoValid || app_isOverlayMode()))
cancelExit(enable ? "view" : "state");
if (enable && !g_state.focused)
{
MTRACE("view skip=focus");
return;
}
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
const bool immediate = g_cursor.grab || g_params.captureInputOnly ||
warpSupport == LG_DS_WARP_NONE;
if (g_cursor.viewReq == enable)
{
if (immediate)
applyView(enable, false);
else if (enable && !g_cursor.inView)
{
g_state.ds->grabPointer();
applyView(g_state.ds->isPointerGrabbed(), false);
}
else
MTRACE("view skip=same value=%d", enable);
return;
}
g_cursor.viewReq = enable;
if (immediate)
{
applyView(enable, false);
return;
}
if (enable)
{
g_state.ds->grabPointer();
applyView(g_state.ds->isPointerGrabbed(), false);
}
else
g_state.ds->ungrabPointer();
}
void core_handleGrabEvent(bool active)
{
MTRACE("conf active=%d req=%d old=%d grab=%d", active,
g_cursor.viewReq, g_cursor.inView, g_cursor.grab);
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
if (g_cursor.grab || g_params.captureInputOnly ||
warpSupport == LG_DS_WARP_NONE)
return;
if (g_cursor.surfaceExit)
{
if (!active && g_cursor.viewReq && g_cursor.inWindow)
{
MTRACE("surface exit release pending");
return;
}
cancelSurfaceExit(active ? "confined" : "state");
}
if (!active)
finishExit();
applyView(active, false);
}
void core_setGrab(bool enable)
{
core_setGrabQuiet(enable);
app_alert(
g_cursor.grab ? LG_ALERT_SUCCESS : LG_ALERT_WARNING,
g_cursor.grab ? "Capture Enabled" : "Capture Disabled"
);
}
void core_setGrabQuiet(bool enable)
{
MTRACE("grab req=%d old=%d inWin=%d inView=%d focus=%d",
enable, g_cursor.grab, g_cursor.inWindow, g_cursor.inView,
g_state.focused);
/* we always do this so that at init the cursor is in the right state */
if (g_params.captureInputOnly && g_params.hideMouse)
g_state.ds->setPointer(enable ? LG_POINTER_NONE : LG_POINTER_SQUARE);
if (g_cursor.grab == enable)
return;
cancelSurfaceExit("capture");
g_cursor.grab = enable;
g_cursor.acc.x = 0.0;
g_cursor.acc.y = 0.0;
/* if the display server does not support warp we need to ungrab the pointer
* here instead of in the move handler */
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
if (enable)
{
core_setCursorInView(true);
g_state.ignoreInput = false;
/* ensure the local mouse is inside the window before we capture, this fixes
* odd UI behaviour if the user is using focus follows mouse and the window
* was focused without the cursor being in window already */
struct DoublePoint local;
const bool valid = util_guestCurToLocal(&local);
MTRACE("grab align valid=%d", valid);
core_warpPointer(local.x, local.y, true);
if (g_params.grabKeyboard)
g_state.ds->grabKeyboard();
g_state.ds->capturePointer();
}
else
{
if (g_params.grabKeyboard)
{
if (!g_params.grabKeyboardOnFocus ||
!g_state.focused || g_params.captureInputOnly)
g_state.ds->ungrabKeyboard();
}
g_state.ds->uncapturePointer();
if (!g_params.captureInputOnly && warpSupport != LG_DS_WARP_NONE)
applyView(g_state.ds->isPointerGrabbed(), false);
/* if exiting capture when input on capture only we need to align the local
* cursor to the guest's location before it is shown. */
if (g_params.captureInputOnly || !g_params.hideMouse)
core_alignToGuest();
}
}
bool core_warpPointer(int x, int y, bool exiting)
{
if (!g_cursor.inWindow && !exiting)
{
MTRACE("warp drop=window target=%d,%d exit=%d", x, y, exiting);
return false;
}
if (app_isOverlayMode())
{
MTRACE("warp drop=overlay target=%d,%d exit=%d", x, y, exiting);
return false;
}
if (!exiting && g_cursor.warpState == WARP_STATE_OFF)
{
MTRACE("warp drop=state target=%d,%d exit=%d", x, y, exiting);
return false;
}
if (exiting)
g_cursor.warpState = WARP_STATE_OFF;
if (!exiting && g_cursor.pos.x == x && g_cursor.pos.y == y)
{
MTRACE("warp same target=%d,%d exit=%d", x, y, exiting);
return true;
}
MTRACE("warp send target=%d,%d exit=%d pos=%.3f,%.3f",
x, y, exiting, g_cursor.pos.x, g_cursor.pos.y);
g_state.ds->warpPointer(x, y, exiting);
return true;
}
void core_onWindowSizeChanged(unsigned width, unsigned height)
{
if (!g_state.transport ||
!(g_state.transportFeatures & LG_TRANSPORT_FEATURE_WINDOW_SIZE))
return;
if (g_state.srcSize.x == width && g_state.srcSize.y == height)
return;
const LG_TransportControl control =
{
.type = LG_TRANSPORT_CONTROL_WINDOW_SIZE,
.windowSize = { .width = width, .height = height },
};
LG_TransportControlToken token;
const LG_TransportStatus status = g_state.transportOps->sendControl(
g_state.transport, &control, &token);
if (status != LG_TRANSPORT_OK && status != LG_TRANSPORT_UNAVAILABLE)
DEBUG_WARN("Window-size control failed with status %d", status);
}
void core_updatePositionInfo(void)
{
cancelExit("geometry");
if (g_params.setGuestRes &&
g_state.transportFeatures & LG_TRANSPORT_FEATURE_WINDOW_SIZE)
{
LGMsg msg =
{
.type = LG_MSG_WINDOWSIZE,
.windowSize =
{
.width = round(g_state.windowW * g_state.windowScale),
.height = round(g_state.windowH * g_state.windowScale)
}
};
lgMessage_post(&msg);
}
if (!g_state.haveSrcSize)
goto done;
float srcW;
float srcH;
switch(g_params.winRotate)
{
case LG_ROTATE_0:
case LG_ROTATE_180:
srcW = g_state.srcSize.x;
srcH = g_state.srcSize.y;
break;
case LG_ROTATE_90:
case LG_ROTATE_270:
srcW = g_state.srcSize.y;
srcH = g_state.srcSize.x;
break;
default:
DEBUG_UNREACHABLE();
}
if (g_params.keepAspect)
{
const float srcAspect = srcH / srcW;
const float wndAspect = (float)g_state.windowH / (float)g_state.windowW;
bool force = true;
if (g_params.dontUpscale &&
srcW <= g_state.windowW &&
srcH <= g_state.windowH)
{
force = false;
g_state.dstRect.w = srcW;
g_state.dstRect.h = srcH;
g_state.dstRect.x = g_state.windowCX - srcW / 2;
g_state.dstRect.y = g_state.windowCY - srcH / 2;
}
else
if (g_params.intUpscale &&
srcW <= g_state.windowW &&
srcH <= g_state.windowH)
{
force = false;
const int scale = min(
floor(g_state.windowW / srcW),
floor(g_state.windowH / srcH));
g_state.dstRect.w = srcW * scale;
g_state.dstRect.h = srcH * scale;
g_state.dstRect.x = g_state.windowCX - g_state.dstRect.w / 2;
g_state.dstRect.y = g_state.windowCY - g_state.dstRect.h / 2;
}
else
if ((int)(wndAspect * 1000) == (int)(srcAspect * 1000))
{
force = false;
g_state.dstRect.w = g_state.windowW;
g_state.dstRect.h = g_state.windowH;
g_state.dstRect.x = 0;
g_state.dstRect.y = 0;
}
else
if (wndAspect < srcAspect)
{
g_state.dstRect.w = (float)g_state.windowH / srcAspect;
g_state.dstRect.h = g_state.windowH;
g_state.dstRect.x = (g_state.windowW >> 1) - (g_state.dstRect.w >> 1);
g_state.dstRect.y = 0;
}
else
{
g_state.dstRect.w = g_state.windowW;
g_state.dstRect.h = (float)g_state.windowW * srcAspect;
g_state.dstRect.x = 0;
g_state.dstRect.y = (g_state.windowH >> 1) - (g_state.dstRect.h >> 1);
}
if (g_params.dontUpscale && g_params.shrinkOnUpscale)
{
if (g_state.windowW > srcW)
{
force = true;
g_state.dstRect.w = (int) (srcW + 0.5);
}
if (g_state.windowH > srcH)
{
force = true;
g_state.dstRect.h = (int) (srcH + 0.5);
}
}
if (force && g_params.forceAspect)
{
g_state.resizeTimeout = microtime() + RESIZE_TIMEOUT;
g_state.resizeDone = false;
}
}
else
{
g_state.dstRect.x = 0;
g_state.dstRect.y = 0;
g_state.dstRect.w = g_state.windowW;
g_state.dstRect.h = g_state.windowH;
}
g_state.dstRect.valid = true;
g_cursor.useScale = (
srcH != g_state.dstRect.h ||
srcW != g_state.dstRect.w);
g_cursor.scale.x = (float)srcW / (float)g_state.dstRect.w;
g_cursor.scale.y = (float)srcH / (float)g_state.dstRect.h;
if (!g_state.posInfoValid)
{
g_state.posInfoValid = true;
g_state.ds->realignPointer();
// g_cursor.guest.valid could have become true in the meantime.
if (g_cursor.guest.valid)
{
// Since posInfoValid was false, core_handleGuestMouseUpdate becomes a
// noop when called on the cursor thread, which means we need to call it
// again in order for the cursor to show up.
core_handleGuestMouseUpdate();
// Similarly, the position needs to be valid before the initial mouse
// move, otherwise we wouldn't know if the cursor is in the viewport.
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
}
}
done:
if (g_cursor.surfaceExit)
{
if (g_cursor.viewReq && g_cursor.inWindow && g_state.focused &&
!g_state.ignoreInput && g_state.posInfoValid &&
!app_isOverlayMode())
{
MTRACE("surface exit regrab=geometry");
g_cursor.warpState = WARP_STATE_ON;
g_state.ds->grabPointer();
}
else
core_setCursorInView(false);
}
MTRACE("geometry src=%dx%d dst=%d,%d,%d,%d win=%dx%d "
"scale=%.4f,%.4f uiScale=%.4f rot=%d/%d border=%d,%d,%d,%d "
"valid=%d", g_state.srcSize.x, g_state.srcSize.y,
g_state.dstRect.x, g_state.dstRect.y, g_state.dstRect.w,
g_state.dstRect.h, g_state.windowW, g_state.windowH,
g_cursor.scale.x, g_cursor.scale.y, g_state.windowScale,
g_state.rotate, g_params.winRotate, g_state.border.left,
g_state.border.top, g_state.border.right, g_state.border.bottom,
g_state.posInfoValid);
app_updateMouseState();
atomic_fetch_add(&g_state.lgrResize, 1);
}
void core_alignToGuest(void)
{
if (!g_cursor.guest.valid || !g_state.focused)
return;
struct DoublePoint local;
if (util_guestCurToLocal(&local))
if (core_warpPointer(round(local.x), round(local.y), false))
core_setCursorInView(true);
}
bool core_isValidPointerPos(int x, int y)
{
return g_state.ds->isValidPointerPos(x, y);
}
bool core_startCursorThread(void)
{
if (g_state.cursorThread)
return true;
g_state.stopVideo = false;
if (!lgCreateThread("cursorThread", main_cursorThread, NULL,
&g_state.cursorThread))
{
DEBUG_ERROR("cursor create thread failed");
return false;
}
return true;
}
void core_stopCursorThread(void)
{
g_state.stopVideo = true;
if (g_state.cursorThread)
lgJoinThread(g_state.cursorThread, NULL);
g_state.cursorThread = NULL;
}
bool core_startFrameThread(void)
{
if (g_state.frameThread)
return true;
g_state.stopVideo = false;
if (!lgCreateThread("frameThread", main_frameThread, NULL,
&g_state.frameThread))
{
DEBUG_ERROR("frame create thread failed");
return false;
}
return true;
}
void core_stopFrameThread(void)
{
g_state.stopVideo = true;
if (g_state.frameThread)
lgJoinThread(g_state.frameThread, NULL);
g_state.frameThread = NULL;
}
void core_handleGuestMouseUpdate(void)
{
struct DoublePoint localPos;
if (!util_guestCurToLocal(&localPos))
{
MTRACE("guest drop=geometry guest=%d,%d hot=%d,%d valid=%d",
g_cursor.guest.x, g_cursor.guest.y, g_cursor.guest.hx,
g_cursor.guest.hy, g_cursor.guest.valid);
return;
}
const bool overlay = app_isOverlayMode();
if (overlay || !g_cursor.inView || !g_cursor.viewReq)
{
MTRACE("guest drop=%s guest=%d,%d local=%.3f,%.3f inWin=%d "
"inView=%d req=%d", overlay ? "overlay" : "view",
g_cursor.guest.x,
g_cursor.guest.y, localPos.x, localPos.y, g_cursor.inWindow,
g_cursor.inView, g_cursor.viewReq);
return;
}
MTRACE("guest send guest=%d,%d local=%.3f,%.3f inWin=%d inView=%d",
g_cursor.guest.x, g_cursor.guest.y, localPos.x, localPos.y,
g_cursor.inWindow, g_cursor.inView);
g_state.ds->guestPointerUpdated(
g_cursor.guest.x, g_cursor.guest.y,
util_clamp(localPos.x, g_state.dstRect.x,
g_state.dstRect.x + g_state.dstRect.w),
util_clamp(localPos.y, g_state.dstRect.y,
g_state.dstRect.y + g_state.dstRect.h)
);
}
void core_handleMouseGrabbed(double ex, double ey)
{
const bool active = g_cursor.grab && g_state.ds->isPointerCaptured();
const bool enabled = active && core_inputEnabled();
MTRACE("captured delta=%.3f,%.3f active=%d enabled=%d", ex, ey,
active, enabled);
if (!enabled)
return;
int x, y;
if (g_params.rawMouse && !g_cursor.sens)
{
/* raw unscaled input are always round numbers */
x = floor(ex);
y = floor(ey);
}
else
{
/* apply sensitivity */
ex = (ex / 10.0) * (g_cursor.sens + 10);
ey = (ey / 10.0) * (g_cursor.sens + 10);
util_cursorToInt(ex, ey, &x, &y);
}
if (x == 0 && y == 0)
return;
if (!purespice_mouseMotion(x, y))
DEBUG_ERROR("failed to send mouse motion message");
}
void core_handleMouseNormal(double ex, double ey)
{
MTRACE("normal delta=%.3f,%.3f guest=%d,%d valid=%d inWin=%d "
"inView=%d", ex, ey, g_cursor.guest.x, g_cursor.guest.y,
g_cursor.guest.valid, g_cursor.inWindow, g_cursor.inView);
// prevent cursor handling outside of capture if the position is not known
if (!g_cursor.guest.valid)
{
if (app_guestIsWindows())
{
// wiggle the mouse when the guest has not provided any information, we need
// to do this because windows doesn't enable a cursor at all until it has
// been moved for the first time.
if (!purespice_mouseMotion(1, 1))
DEBUG_ERROR("failed to send mouse motion message");
if (!purespice_mouseMotion(-1, -1))
DEBUG_ERROR("failed to send mouse motion message");
}
return;
}
if (g_cursor.realigning)
return;
if (!core_inputEnabled())
return;
if (g_cursor.viewReq != g_cursor.inView)
{
if (g_cursor.viewReq)
core_setCursorInView(true);
else if (g_cursor.exit && moveExit(ex, ey))
core_setCursorInView(true);
return;
}
/* scale the movement to the guest */
if (g_cursor.useScale && g_params.scaleMouseInput)
{
ex *= g_cursor.scale.x;
ey *= g_cursor.scale.y;
}
bool testExit = true;
bool didExit = false;
const bool inView = isInView();
if (!g_cursor.inView)
{
if (g_cursor.viewReq)
return;
else if (inView)
g_cursor.realign = true;
else /* nothing to do if we are outside the viewport */
return;
}
/*
* do not pass mouse events to the guest if we do not have focus, this must be
* done after the inView test has been performed so that when focus is gained
* we know if we should be drawing the cursor.
*/
if (!g_state.focused)
{
core_setCursorInView(inView);
return;
}
/* if we have been instructed to realign */
if (g_cursor.realign)
{
struct DoublePoint guest;
util_localCurToGuest(&guest);
if (!g_state.stopVideo &&
g_state.transportFeatures & LG_TRANSPORT_FEATURE_SET_CURSOR_POS)
{
const LG_TransportControl control = {
.type = LG_TRANSPORT_CONTROL_SET_CURSOR_POS,
.cursorPos = { .x = round(guest.x), .y = round(guest.y) },
};
LG_TransportControlToken token;
LG_TransportStatus status = g_state.transportOps->sendControl(
g_state.transport, &control, &token);
if (status != LG_TRANSPORT_OK)
{
DEBUG_WARN("Cursor-position control failed with status %d", status);
goto fallback;
}
else
{
/* wait for the move request to be processed */
g_cursor.realigning = true;
unsigned timeout = 200;
do
{
status = g_state.transportOps->controlStatus(g_state.transport,
token);
if (status == LG_TRANSPORT_DISCONNECTED)
{
g_cursor.realigning = false;
break;
}
if (status == LG_TRANSPORT_OK)
break;
if (status != LG_TRANSPORT_UNAVAILABLE)
{
g_cursor.realigning = false;
return;
}
if (--timeout == 0)
{
DEBUG_ERROR("Cursor-position control was not acknowledged");
break;
}
g_state.ds->wait(1);
}
while(app_isRunning());
g_cursor.guest.x = control.cursorPos.x;
g_cursor.guest.y = control.cursorPos.y;
g_cursor.realign = false;
g_cursor.realigning = false;
g_cursor.redraw = true;
if (!g_cursor.inWindow)
return;
core_setCursorInView(true);
return;
}
}
else
{
fallback:
/* add the difference to the offset */
ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx);
ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy);
core_setCursorInView(true);
if (!g_cursor.inView)
return;
}
g_cursor.realign = false;
/* don't test for an exit as we just entered, we can get into a enter/exit
* loop otherwise */
testExit = false;
}
/* if we are in "autoCapture" and the delta was large don't test for exit */
if (g_params.autoCapture &&
(fabs(ex) > 20.0 / g_cursor.scale.x || fabs(ey) > 20.0 / g_cursor.scale.y))
testExit = false;
/* if any buttons are held we should not allow exit to happen */
if (g_cursor.buttons)
testExit = false;
if (testExit)
{
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
/* translate the move to the guests orientation */
struct DoublePoint move = {.x = ex, .y = ey};
util_rotatePoint(&move);
/* translate the guests position to our coordinate space */
struct DoublePoint local;
util_guestCurToLocal(&local);
/* check if the move would push the cursor outside the guest's viewport */
if (
local.x + move.x < g_state.dstRect.x ||
local.y + move.y < g_state.dstRect.y ||
local.x + move.x >= g_state.dstRect.x + g_state.dstRect.w ||
local.y + move.y >= g_state.dstRect.y + g_state.dstRect.h)
{
didExit = true;
local.x += move.x;
local.y += move.y;
const int tx = exitPos(local.x, g_state.dstRect.x);
const int ty = exitPos(local.y, g_state.dstRect.y);
MTRACE("exit local=%.3f,%.3f move=%.3f,%.3f target=%d,%d "
"dst=%d,%d,%d,%d support=%d", local.x - move.x,
local.y - move.y, move.x, move.y, tx, ty, g_state.dstRect.x,
g_state.dstRect.y, g_state.dstRect.w, g_state.dstRect.h,
warpSupport);
switch (warpSupport)
{
case LG_DS_WARP_NONE:
break;
case LG_DS_WARP_SURFACE:
if (tx < 0 || tx >= g_state.windowW ||
ty < 0 || ty >= g_state.windowH)
{
g_cursor.surfaceExit = true;
g_state.ds->ungrabPointer();
core_warpPointer(tx, ty, true);
break;
}
startExit(local.x, local.y);
return;
case LG_DS_WARP_SCREEN:
if (core_isValidPointerPos(
g_state.windowPos.x + g_state.border.left + tx,
g_state.windowPos.y + g_state.border.top + ty))
{
startExit(local.x, local.y);
return;
}
}
}
else if (warpSupport == LG_DS_WARP_SURFACE && isInView())
{
/* regrab the pointer in case the user did not move off the surface */
MTRACE("regrab pos=%.3f,%.3f", g_cursor.pos.x, g_cursor.pos.y);
g_state.ds->grabPointer();
g_cursor.warpState = WARP_STATE_ON;
}
}
int x, y;
util_cursorToInt(ex, ey, &x, &y);
if (x == 0 && y == 0)
return;
if (g_params.autoCapture)
{
g_cursor.delta.x += x;
g_cursor.delta.y += y;
if (fabs(g_cursor.delta.x) > 50.0 || fabs(g_cursor.delta.y) > 50.0)
{
g_cursor.delta.x = 0;
g_cursor.delta.y = 0;
}
}
else
{
/* assume the mouse will move to the location we attempt to move it to so we
* avoid warp out of window issues. The cursorThread will correct this if
* wrong after the movement has ocurred on the guest */
g_cursor.guest.x += x;
g_cursor.guest.y += y;
}
MTRACE("motion delta=%d,%d guest=%d,%d exit=%d test=%d warp=%d",
x, y, g_cursor.guest.x, g_cursor.guest.y, didExit, testExit,
g_cursor.warpState);
if (!purespice_mouseMotion(x, y))
DEBUG_ERROR("failed to send mouse motion message");
}
void core_resetOverlayInputState(void)
{
g_state.io->MouseDown[ImGuiMouseButton_Left ] = false;
g_state.io->MouseDown[ImGuiMouseButton_Right ] = false;
g_state.io->MouseDown[ImGuiMouseButton_Middle] = false;
for(int key = 0; key < KEY_MAX; key++)
if (linux_to_imgui[key])
ImGuiIO_AddKeyEvent(g_state.io, linux_to_imgui[key], false);
}
void core_updateOverlayState(void)
{
g_state.cursorLast = -2;
static bool wasGrabbed = false;
if (app_isOverlayMode())
{
wasGrabbed = g_cursor.grab;
g_state.io->ConfigFlags &= ~ImGuiConfigFlags_NoMouse;
g_state.io->MousePos = (ImVec2) { g_cursor.pos.x, g_cursor.pos.y };
core_setGrabQuiet(false);
core_setCursorInView(false);
}
else
{
g_state.io->ConfigFlags |= ImGuiConfigFlags_NoMouse;
core_resetOverlayInputState();
core_setGrabQuiet(wasGrabbed);
core_invalidatePointer(true);
app_invalidateWindow(false);
if (!g_cursor.grab)
{
g_cursor.realign = true;
core_handleMouseNormal(0, 0);
}
}
}
void core_setTitle(const char * title)
{
if (!lgCanSetProcessTitle())
return;
char * string;
if (alloc_sprintf(&string, "Looking Glass Client %s%s%s", BUILD_VERSION,
title && *title ? ": " : "", title ? title : "") >= 0)
{
lgSetProcessTitle(string);
free(string);
}
}