2021-06-06 01:26:18 +00:00
|
|
|
/**
|
|
|
|
* Looking Glass
|
2023-10-20 04:36:34 +00:00
|
|
|
* Copyright © 2017-2023 The Looking Glass Authors
|
2021-06-06 01:26:18 +00:00
|
|
|
* 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
|
|
|
|
*/
|
2019-03-30 01:26:06 +00:00
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
#include "app.h"
|
|
|
|
|
2019-03-30 01:26:06 +00:00
|
|
|
#include "main.h"
|
2021-01-25 08:58:36 +00:00
|
|
|
#include "core.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "clipboard.h"
|
2022-05-23 23:48:54 +00:00
|
|
|
#include "render_queue.h"
|
2021-01-25 08:58:36 +00:00
|
|
|
|
|
|
|
#include "kb.h"
|
|
|
|
|
2019-04-11 01:12:59 +00:00
|
|
|
#include "common/debug.h"
|
2021-02-21 14:39:56 +00:00
|
|
|
#include "common/stringutils.h"
|
2021-07-22 07:27:30 +00:00
|
|
|
#include "interface/overlay.h"
|
|
|
|
#include "overlays.h"
|
2021-01-26 10:46:30 +00:00
|
|
|
|
2021-07-08 01:45:54 +00:00
|
|
|
#include "cimgui.h"
|
|
|
|
|
2019-03-30 01:26:06 +00:00
|
|
|
#include <stdarg.h>
|
2021-01-26 10:46:30 +00:00
|
|
|
#include <math.h>
|
|
|
|
#include <string.h>
|
2022-06-29 08:24:53 +00:00
|
|
|
#include <ctype.h>
|
2019-03-30 01:26:06 +00:00
|
|
|
|
2021-01-26 23:40:39 +00:00
|
|
|
bool app_isRunning(void)
|
2019-03-30 01:26:06 +00:00
|
|
|
{
|
2021-01-26 23:40:39 +00:00
|
|
|
return
|
|
|
|
g_state.state == APP_STATE_RUNNING ||
|
|
|
|
g_state.state == APP_STATE_RESTART;
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 20:35:36 +00:00
|
|
|
bool app_isCaptureMode(void)
|
|
|
|
{
|
|
|
|
return g_cursor.grab;
|
|
|
|
}
|
|
|
|
|
2021-05-04 00:16:51 +00:00
|
|
|
bool app_isCaptureOnlyMode(void)
|
|
|
|
{
|
|
|
|
return g_params.captureInputOnly;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool app_isFormatValid(void)
|
|
|
|
{
|
|
|
|
return g_state.formatValid;
|
|
|
|
}
|
|
|
|
|
2021-08-14 04:18:43 +00:00
|
|
|
bool app_isOverlayMode(void)
|
|
|
|
{
|
2022-01-08 04:37:44 +00:00
|
|
|
if (g_state.overlayInput)
|
|
|
|
return true;
|
|
|
|
|
2022-01-12 01:17:29 +00:00
|
|
|
bool result = false;
|
2022-01-08 04:37:44 +00:00
|
|
|
struct Overlay * overlay;
|
2022-01-12 01:17:29 +00:00
|
|
|
ll_lock(g_state.overlays);
|
|
|
|
ll_forEachNL(g_state.overlays, item, overlay)
|
2022-01-08 04:37:44 +00:00
|
|
|
{
|
|
|
|
if (overlay->ops->needs_overlay && overlay->ops->needs_overlay(overlay))
|
2022-01-12 01:17:29 +00:00
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
2022-01-08 04:37:44 +00:00
|
|
|
}
|
2022-01-12 01:17:29 +00:00
|
|
|
ll_unlock(g_state.overlays);
|
2022-01-08 04:37:44 +00:00
|
|
|
|
2022-01-12 01:17:29 +00:00
|
|
|
return result;
|
2021-08-14 04:18:43 +00:00
|
|
|
}
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
void app_updateCursorPos(double x, double y)
|
|
|
|
{
|
|
|
|
g_cursor.pos.x = x;
|
|
|
|
g_cursor.pos.y = y;
|
|
|
|
g_cursor.valid = true;
|
2021-07-26 21:27:47 +00:00
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-07-30 01:46:50 +00:00
|
|
|
g_state.io->MousePos = (ImVec2) { x, y };
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void app_handleFocusEvent(bool focused)
|
|
|
|
{
|
2023-09-11 11:01:19 +00:00
|
|
|
if (g_state.focused == focused)
|
|
|
|
return;
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
g_state.focused = focused;
|
2021-07-31 06:40:14 +00:00
|
|
|
|
2021-07-31 10:51:08 +00:00
|
|
|
// release any imgui buttons/keys if we lost focus
|
2022-01-08 04:37:44 +00:00
|
|
|
if (!focused && app_isOverlayMode())
|
2021-07-31 10:51:08 +00:00
|
|
|
core_resetOverlayInputState();
|
2021-07-31 06:40:14 +00:00
|
|
|
|
2021-01-28 18:13:26 +00:00
|
|
|
if (!core_inputEnabled())
|
2021-05-06 12:24:42 +00:00
|
|
|
{
|
2021-05-28 09:32:26 +00:00
|
|
|
if (!focused && g_params.minimizeOnFocusLoss && app_getFullscreen())
|
2021-05-06 12:24:42 +00:00
|
|
|
g_state.ds->minimize();
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
2021-05-06 12:24:42 +00:00
|
|
|
}
|
2021-01-25 08:58:36 +00:00
|
|
|
|
|
|
|
if (!focused)
|
|
|
|
{
|
|
|
|
core_setGrabQuiet(false);
|
|
|
|
core_setCursorInView(false);
|
2021-02-13 07:19:01 +00:00
|
|
|
|
|
|
|
if (g_params.releaseKeysOnFocusLoss)
|
|
|
|
for (int key = 0; key < KEY_MAX; key++)
|
|
|
|
if (g_state.keyDown[key])
|
2022-06-29 08:24:53 +00:00
|
|
|
app_handleKeyRelease(key, 0);
|
2021-04-29 02:50:43 +00:00
|
|
|
|
2021-07-31 01:16:07 +00:00
|
|
|
g_state.escapeActive = false;
|
|
|
|
|
2021-04-29 02:50:43 +00:00
|
|
|
if (!g_params.showCursorDot)
|
2021-07-29 20:31:07 +00:00
|
|
|
g_state.ds->setPointer(LG_POINTER_NONE);
|
2021-05-06 12:24:42 +00:00
|
|
|
|
|
|
|
if (g_params.minimizeOnFocusLoss)
|
|
|
|
g_state.ds->minimize();
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
2023-09-11 11:01:19 +00:00
|
|
|
else
|
|
|
|
if (g_params.captureOnFocus)
|
|
|
|
core_setGrab(true);
|
2021-01-25 08:58:36 +00:00
|
|
|
|
2021-08-19 13:19:59 +00:00
|
|
|
g_cursor.realign = true;
|
2021-01-25 08:58:36 +00:00
|
|
|
g_state.ds->realignPointer();
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_handleEnterEvent(bool entered)
|
|
|
|
{
|
|
|
|
if (entered)
|
|
|
|
{
|
|
|
|
g_cursor.inWindow = true;
|
2021-01-28 18:13:26 +00:00
|
|
|
if (!core_inputEnabled())
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
|
2021-08-19 13:19:59 +00:00
|
|
|
g_cursor.realign = true;
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_cursor.inWindow = false;
|
|
|
|
core_setCursorInView(false);
|
|
|
|
|
2021-07-31 06:40:14 +00:00
|
|
|
// stop the user being able to drag windows off the screen and work around
|
|
|
|
// the mouse button release being missed due to not being in capture mode.
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-07-31 06:40:14 +00:00
|
|
|
{
|
|
|
|
g_state.io->MouseDown[ImGuiMouseButton_Left ] = false;
|
|
|
|
g_state.io->MouseDown[ImGuiMouseButton_Right ] = false;
|
|
|
|
g_state.io->MouseDown[ImGuiMouseButton_Middle] = false;
|
|
|
|
}
|
|
|
|
|
2021-01-28 18:13:26 +00:00
|
|
|
if (!core_inputEnabled())
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (!g_params.alwaysShowCursor)
|
|
|
|
g_cursor.draw = false;
|
|
|
|
g_cursor.redraw = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_clipboardRelease(void)
|
|
|
|
{
|
|
|
|
if (!g_params.clipboardToVM)
|
|
|
|
return;
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
purespice_clipboardRelease();
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
2021-05-30 01:37:15 +00:00
|
|
|
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
|
|
|
|
{
|
2021-06-02 12:09:09 +00:00
|
|
|
if (!g_params.clipboardToVM)
|
|
|
|
return;
|
|
|
|
|
2021-05-30 01:37:15 +00:00
|
|
|
if (count == 0)
|
|
|
|
{
|
2021-12-28 11:03:48 +00:00
|
|
|
purespice_clipboardRelease();
|
2021-05-30 01:37:15 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
PSDataType conv[count];
|
2021-05-30 01:37:15 +00:00
|
|
|
for(int i = 0; i < count; ++i)
|
|
|
|
conv[i] = cb_lgTypeToSpiceType(types[i]);
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
purespice_clipboardGrab(conv, count);
|
2021-05-30 01:37:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
|
|
|
if (!g_params.clipboardToVM)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (type == LG_CLIPBOARD_DATA_NONE)
|
|
|
|
{
|
2021-12-28 11:03:48 +00:00
|
|
|
purespice_clipboardRelease();
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_state.cbType = cb_lgTypeToSpiceType(type);
|
|
|
|
g_state.cbChunked = size > 0;
|
|
|
|
g_state.cbXfer = size;
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
purespice_clipboardDataStart(g_state.cbType, size);
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
|
|
|
{
|
|
|
|
if (!g_params.clipboardToVM)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (g_state.cbChunked && size > g_state.cbXfer)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("refusing to send more then cbXfer bytes for chunked xfer");
|
|
|
|
size = g_state.cbXfer;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!g_state.cbChunked)
|
2021-12-28 11:03:48 +00:00
|
|
|
purespice_clipboardDataStart(g_state.cbType, size);
|
2021-01-25 08:58:36 +00:00
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
purespice_clipboardData(g_state.cbType, data, (uint32_t)size);
|
2021-01-25 08:58:36 +00:00
|
|
|
g_state.cbXfer -= size;
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
|
|
|
|
{
|
|
|
|
if (!g_params.clipboardToLocal)
|
|
|
|
return;
|
|
|
|
|
2021-08-15 22:55:19 +00:00
|
|
|
struct CBRequest * cbr = malloc(sizeof(*cbr));
|
2022-03-06 23:13:54 +00:00
|
|
|
if (!cbr)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("out of memory");
|
|
|
|
return;
|
|
|
|
}
|
2021-01-25 08:58:36 +00:00
|
|
|
|
|
|
|
cbr->type = g_state.cbType;
|
|
|
|
cbr->replyFn = replyFn;
|
|
|
|
cbr->opaque = opaque;
|
|
|
|
ll_push(g_state.cbRequestList, cbr);
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
purespice_clipboardRequest(g_state.cbType);
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
2021-07-26 21:27:47 +00:00
|
|
|
static int mapSpiceToImGuiButton(uint32_t button)
|
|
|
|
{
|
|
|
|
switch (button)
|
|
|
|
{
|
|
|
|
case 1: // SPICE_MOUSE_BUTTON_LEFT
|
|
|
|
return ImGuiMouseButton_Left;
|
|
|
|
case 2: // SPICE_MOUSE_BUTTON_MIDDLE
|
|
|
|
return ImGuiMouseButton_Middle;
|
|
|
|
case 3: // SPICE_MOUSE_BUTTON_RIGHT
|
|
|
|
return ImGuiMouseButton_Right;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
void app_handleButtonPress(int button)
|
|
|
|
{
|
2021-02-01 22:36:01 +00:00
|
|
|
g_cursor.buttons |= (1U << button);
|
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-07-26 21:27:47 +00:00
|
|
|
{
|
|
|
|
int igButton = mapSpiceToImGuiButton(button);
|
|
|
|
if (igButton != -1)
|
|
|
|
g_state.io->MouseDown[igButton] = true;
|
2021-07-29 09:29:33 +00:00
|
|
|
return;
|
2021-07-26 21:27:47 +00:00
|
|
|
}
|
|
|
|
|
2021-01-28 18:13:26 +00:00
|
|
|
if (!core_inputEnabled() || !g_cursor.inView)
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
if (!purespice_mousePress(button))
|
2021-01-26 10:46:30 +00:00
|
|
|
DEBUG_ERROR("app_handleButtonPress: failed to send message");
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void app_handleButtonRelease(int button)
|
|
|
|
{
|
2021-02-01 22:36:01 +00:00
|
|
|
g_cursor.buttons &= ~(1U << button);
|
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-07-26 21:27:47 +00:00
|
|
|
{
|
|
|
|
int igButton = mapSpiceToImGuiButton(button);
|
|
|
|
if (igButton != -1)
|
|
|
|
g_state.io->MouseDown[igButton] = false;
|
2021-07-29 09:29:33 +00:00
|
|
|
return;
|
2021-07-26 21:27:47 +00:00
|
|
|
}
|
|
|
|
|
2021-01-28 18:13:26 +00:00
|
|
|
if (!core_inputEnabled())
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
if (!purespice_mouseRelease(button))
|
2021-01-26 10:46:30 +00:00
|
|
|
DEBUG_ERROR("app_handleButtonRelease: failed to send message");
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
2021-07-30 11:01:37 +00:00
|
|
|
void app_handleWheelMotion(double motion)
|
|
|
|
{
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-07-30 11:01:37 +00:00
|
|
|
g_state.io->MouseWheel -= motion;
|
|
|
|
}
|
|
|
|
|
2022-06-29 08:24:53 +00:00
|
|
|
void app_handleKeyPress(int sc, int charcode)
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
2022-01-08 04:37:44 +00:00
|
|
|
if (!app_isOverlayMode() || !g_state.io->WantCaptureKeyboard)
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
2021-07-31 05:55:58 +00:00
|
|
|
if (sc == g_params.escapeKey && !g_state.escapeActive)
|
|
|
|
{
|
|
|
|
g_state.escapeActive = true;
|
|
|
|
g_state.escapeTime = microtime();
|
|
|
|
g_state.escapeAction = -1;
|
|
|
|
return;
|
|
|
|
}
|
2021-01-25 08:58:36 +00:00
|
|
|
|
2021-07-31 05:55:58 +00:00
|
|
|
if (g_state.escapeActive)
|
|
|
|
{
|
|
|
|
g_state.escapeAction = sc;
|
2022-06-29 08:24:53 +00:00
|
|
|
KeybindHandle handle;
|
|
|
|
ll_forEachNL(g_state.bindings, item, handle)
|
|
|
|
{
|
|
|
|
if ((handle->sc && handle->sc == sc ) ||
|
|
|
|
(handle->charcode && handle->charcode == charcode))
|
|
|
|
{
|
|
|
|
handle->callback(sc, handle->opaque);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-07-31 05:55:58 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-07-26 21:36:23 +00:00
|
|
|
{
|
2021-07-31 10:51:38 +00:00
|
|
|
if (sc == KEY_ESC)
|
|
|
|
app_setOverlay(false);
|
|
|
|
else
|
|
|
|
g_state.io->KeysDown[sc] = true;
|
2021-07-29 09:29:33 +00:00
|
|
|
return;
|
2021-07-26 21:36:23 +00:00
|
|
|
}
|
|
|
|
|
2021-01-28 18:13:26 +00:00
|
|
|
if (!core_inputEnabled())
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!g_state.keyDown[sc])
|
|
|
|
{
|
2021-08-17 08:41:21 +00:00
|
|
|
uint32_t ps2 = linux_to_ps2[sc];
|
2021-01-25 08:58:36 +00:00
|
|
|
if (!ps2)
|
|
|
|
return;
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
if (purespice_keyDown(ps2))
|
2021-01-25 08:58:36 +00:00
|
|
|
g_state.keyDown[sc] = true;
|
|
|
|
else
|
|
|
|
{
|
2021-01-26 10:46:30 +00:00
|
|
|
DEBUG_ERROR("app_handleKeyPress: failed to send message");
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-29 08:24:53 +00:00
|
|
|
void app_handleKeyRelease(int sc, int charcode)
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
|
|
|
if (g_state.escapeActive)
|
|
|
|
{
|
|
|
|
if (g_state.escapeAction == -1)
|
|
|
|
{
|
2022-01-08 04:37:44 +00:00
|
|
|
if (!g_state.escapeHelp && g_params.useSpiceInput &&
|
|
|
|
!app_isOverlayMode())
|
2021-01-25 08:58:36 +00:00
|
|
|
core_setGrab(!g_cursor.grab);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sc == g_params.escapeKey)
|
|
|
|
g_state.escapeActive = false;
|
|
|
|
}
|
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-07-26 21:36:23 +00:00
|
|
|
{
|
|
|
|
g_state.io->KeysDown[sc] = false;
|
2021-07-29 09:29:33 +00:00
|
|
|
return;
|
2021-07-26 21:36:23 +00:00
|
|
|
}
|
|
|
|
|
2021-07-31 05:41:21 +00:00
|
|
|
if (!core_inputEnabled())
|
|
|
|
return;
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
// avoid sending key up events when we didn't send a down
|
|
|
|
if (!g_state.keyDown[sc])
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
|
|
|
|
return;
|
|
|
|
|
2021-08-17 08:41:21 +00:00
|
|
|
uint32_t ps2 = linux_to_ps2[sc];
|
2021-01-25 08:58:36 +00:00
|
|
|
if (!ps2)
|
|
|
|
return;
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
if (purespice_keyUp(ps2))
|
2021-01-25 08:58:36 +00:00
|
|
|
g_state.keyDown[sc] = false;
|
|
|
|
else
|
|
|
|
{
|
2021-01-26 10:46:30 +00:00
|
|
|
DEBUG_ERROR("app_handleKeyRelease: failed to send message");
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-14 04:18:43 +00:00
|
|
|
void app_handleKeyboardTyped(const char * typed)
|
|
|
|
{
|
|
|
|
ImGuiIO_AddInputCharactersUTF8(g_state.io, typed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super)
|
|
|
|
{
|
2021-08-19 19:57:20 +00:00
|
|
|
g_state.modCtrl = ctrl;
|
|
|
|
g_state.modShift = shift;
|
|
|
|
g_state.modAlt = alt;
|
|
|
|
g_state.modSuper = super;
|
2021-08-14 04:18:43 +00:00
|
|
|
}
|
|
|
|
|
2021-08-14 10:14:09 +00:00
|
|
|
void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock)
|
|
|
|
{
|
2021-08-15 22:11:36 +00:00
|
|
|
if (!core_inputEnabled())
|
|
|
|
return;
|
|
|
|
|
2021-08-14 10:14:09 +00:00
|
|
|
uint32_t modifiers =
|
|
|
|
(scrollLock ? 1 /* SPICE_SCROLL_LOCK_MODIFIER */ : 0) |
|
|
|
|
(numLock ? 2 /* SPICE_NUM_LOCK_MODIFIER */ : 0) |
|
|
|
|
(capsLock ? 4 /* SPICE_CAPS_LOCK_MODIFIER */ : 0);
|
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
if (!purespice_keyModifiers(modifiers))
|
2021-08-14 10:14:09 +00:00
|
|
|
DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message");
|
|
|
|
}
|
|
|
|
|
2021-01-29 04:02:29 +00:00
|
|
|
void app_handleMouseRelative(double normx, double normy,
|
2021-01-28 18:13:26 +00:00
|
|
|
double rawx, double rawy)
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-07-29 09:29:33 +00:00
|
|
|
return;
|
|
|
|
|
2021-01-28 18:13:26 +00:00
|
|
|
if (g_cursor.grab)
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
2021-01-28 18:13:26 +00:00
|
|
|
if (g_params.rawMouse)
|
|
|
|
core_handleMouseGrabbed(rawx, rawy);
|
|
|
|
else
|
|
|
|
core_handleMouseGrabbed(normx, normy);
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
else
|
2021-01-28 18:13:26 +00:00
|
|
|
if (g_cursor.inWindow)
|
|
|
|
core_handleMouseNormal(normx, normy);
|
2021-01-25 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// On some display servers normal cursor logic does not work due to the lack of
|
|
|
|
// cursor warp support. Instead, we attempt a best-effort emulation which works
|
|
|
|
// with a 1:1 mouse movement patch applied in the guest. For anything fancy, use
|
|
|
|
// capture mode.
|
2022-09-26 05:16:01 +00:00
|
|
|
void app_handleMouseBasic(void)
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
|
|
|
/* do not pass mouse events to the guest if we do not have focus */
|
2022-01-08 04:37:44 +00:00
|
|
|
if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused ||
|
|
|
|
app_isOverlayMode())
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
|
2021-01-28 18:13:26 +00:00
|
|
|
if (!core_inputEnabled())
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
const bool inView =
|
|
|
|
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;
|
|
|
|
|
|
|
|
core_setCursorInView(inView);
|
|
|
|
|
[client] spice/wayland: improve cursor tracking logic
One of the major issues with the old tracking code is a data race
between the cursor thread updating g_cursor.guest and the
app_handleMouseBasic function. Specifically, the latter may have
sent mouse input via spice that has not been processed by the guest
and updated g_cursor.guest, but the guest may overwrite g_cursor.guest
to a previous state before the input is processed. This causes some
movements to be doubled. Eventually, the cursor positions will
synchronize, but this nevertheless causes a lot of jitter.
In this commit, we introduce a new field g_cursor.projected, which
is unambiguously the position of the cursor after taking into account
all the input already sent via spice. This is synced up to the guest
cursor upon entering the window and when the host restarts. Afterwards,
all mouse movements will be based on this position. This eliminates
all cursor jitter as far as I could tell.
Also, the cursor is now synced to the host position when exiting
capture mode.
A downside of this commit is that if the 1:1 movement patch is not
correctly applied, the cursor position would be wildly off instead
of simply jittering, but that is an unsupported configuration and
should not matter.
Also unsupported is when an application in guest moves the cursor
programmatically and bypassing spice. When using those applications,
capture mode must be on. Before this commit, we try to move the guest
cursor back to where it should be, but it's inherently fragile and
may lead to scenarios such as wild movements in first-person shooters.
2021-01-21 02:05:50 +00:00
|
|
|
/* translate the current position to guest coordinate space */
|
|
|
|
struct DoublePoint guest;
|
|
|
|
util_localCurToGuest(&guest);
|
2021-01-25 08:58:36 +00:00
|
|
|
|
2021-05-03 19:14:33 +00:00
|
|
|
int x = (int) round(util_clamp(guest.x, 0, g_state.srcSize.x) -
|
|
|
|
g_cursor.projected.x);
|
|
|
|
int y = (int) round(util_clamp(guest.y, 0, g_state.srcSize.y) -
|
|
|
|
g_cursor.projected.y);
|
2021-01-25 08:58:36 +00:00
|
|
|
|
|
|
|
if (!x && !y)
|
|
|
|
return;
|
|
|
|
|
[client] spice/wayland: improve cursor tracking logic
One of the major issues with the old tracking code is a data race
between the cursor thread updating g_cursor.guest and the
app_handleMouseBasic function. Specifically, the latter may have
sent mouse input via spice that has not been processed by the guest
and updated g_cursor.guest, but the guest may overwrite g_cursor.guest
to a previous state before the input is processed. This causes some
movements to be doubled. Eventually, the cursor positions will
synchronize, but this nevertheless causes a lot of jitter.
In this commit, we introduce a new field g_cursor.projected, which
is unambiguously the position of the cursor after taking into account
all the input already sent via spice. This is synced up to the guest
cursor upon entering the window and when the host restarts. Afterwards,
all mouse movements will be based on this position. This eliminates
all cursor jitter as far as I could tell.
Also, the cursor is now synced to the host position when exiting
capture mode.
A downside of this commit is that if the 1:1 movement patch is not
correctly applied, the cursor position would be wildly off instead
of simply jittering, but that is an unsupported configuration and
should not matter.
Also unsupported is when an application in guest moves the cursor
programmatically and bypassing spice. When using those applications,
capture mode must be on. Before this commit, we try to move the guest
cursor back to where it should be, but it's inherently fragile and
may lead to scenarios such as wild movements in first-person shooters.
2021-01-21 02:05:50 +00:00
|
|
|
g_cursor.projected.x += x;
|
|
|
|
g_cursor.projected.y += y;
|
2021-01-25 08:58:36 +00:00
|
|
|
|
2021-12-28 11:03:48 +00:00
|
|
|
if (!purespice_mouseMotion(x, y))
|
2021-01-25 08:58:36 +00:00
|
|
|
DEBUG_ERROR("failed to send mouse motion message");
|
|
|
|
}
|
|
|
|
|
2022-09-26 05:16:01 +00:00
|
|
|
void app_resyncMouseBasic(void)
|
[client] spice/wayland: improve cursor tracking logic
One of the major issues with the old tracking code is a data race
between the cursor thread updating g_cursor.guest and the
app_handleMouseBasic function. Specifically, the latter may have
sent mouse input via spice that has not been processed by the guest
and updated g_cursor.guest, but the guest may overwrite g_cursor.guest
to a previous state before the input is processed. This causes some
movements to be doubled. Eventually, the cursor positions will
synchronize, but this nevertheless causes a lot of jitter.
In this commit, we introduce a new field g_cursor.projected, which
is unambiguously the position of the cursor after taking into account
all the input already sent via spice. This is synced up to the guest
cursor upon entering the window and when the host restarts. Afterwards,
all mouse movements will be based on this position. This eliminates
all cursor jitter as far as I could tell.
Also, the cursor is now synced to the host position when exiting
capture mode.
A downside of this commit is that if the 1:1 movement patch is not
correctly applied, the cursor position would be wildly off instead
of simply jittering, but that is an unsupported configuration and
should not matter.
Also unsupported is when an application in guest moves the cursor
programmatically and bypassing spice. When using those applications,
capture mode must be on. Before this commit, we try to move the guest
cursor back to where it should be, but it's inherently fragile and
may lead to scenarios such as wild movements in first-person shooters.
2021-01-21 02:05:50 +00:00
|
|
|
{
|
|
|
|
if (!g_cursor.guest.valid)
|
|
|
|
return;
|
|
|
|
g_cursor.projected.x = g_cursor.guest.x + g_cursor.guest.hx;
|
|
|
|
g_cursor.projected.y = g_cursor.guest.y + g_cursor.guest.hy;
|
|
|
|
}
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
void app_updateWindowPos(int x, int y)
|
|
|
|
{
|
|
|
|
g_state.windowPos.x = x;
|
|
|
|
g_state.windowPos.y = y;
|
|
|
|
}
|
|
|
|
|
2021-02-21 05:08:52 +00:00
|
|
|
void app_handleResizeEvent(int w, int h, double scale, const struct Border border)
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
2021-01-25 11:23:53 +00:00
|
|
|
memcpy(&g_state.border, &border, sizeof(border));
|
2021-01-25 08:58:36 +00:00
|
|
|
|
|
|
|
/* don't do anything else if the window dimensions have not changed */
|
2021-02-21 05:08:52 +00:00
|
|
|
if (g_state.windowW == w && g_state.windowH == h && g_state.windowScale == scale)
|
2021-01-25 08:58:36 +00:00
|
|
|
return;
|
|
|
|
|
2021-02-21 05:08:52 +00:00
|
|
|
g_state.windowW = w;
|
|
|
|
g_state.windowH = h;
|
|
|
|
g_state.windowCX = w / 2;
|
|
|
|
g_state.windowCY = h / 2;
|
|
|
|
g_state.windowScale = scale;
|
2021-01-25 08:58:36 +00:00
|
|
|
core_updatePositionInfo();
|
|
|
|
|
2021-01-28 18:13:26 +00:00
|
|
|
if (core_inputEnabled())
|
2021-01-25 08:58:36 +00:00
|
|
|
{
|
|
|
|
/* if the window is moved/resized causing a loss of focus while grabbed, it
|
|
|
|
* makes it impossible to re-focus the window, so we quietly re-enter
|
|
|
|
* capture if we were already in it */
|
|
|
|
if (g_cursor.grab)
|
|
|
|
{
|
|
|
|
core_setGrabQuiet(false);
|
|
|
|
core_setGrabQuiet(true);
|
|
|
|
}
|
|
|
|
core_alignToGuest();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-04 20:47:36 +00:00
|
|
|
void app_invalidateWindow(bool full)
|
2021-07-25 05:29:29 +00:00
|
|
|
{
|
2021-08-04 20:47:36 +00:00
|
|
|
if (full)
|
|
|
|
atomic_store(&g_state.invalidateWindow, true);
|
2022-01-08 03:06:35 +00:00
|
|
|
|
2022-05-27 14:52:03 +00:00
|
|
|
if (g_state.dsInitialized && g_state.jitRender && g_state.ds->stopWaitFrame)
|
2022-01-08 03:06:35 +00:00
|
|
|
g_state.ds->stopWaitFrame();
|
|
|
|
|
2021-07-25 05:29:29 +00:00
|
|
|
lgSignalEvent(g_state.frameEvent);
|
|
|
|
}
|
|
|
|
|
2021-01-25 08:58:36 +00:00
|
|
|
void app_handleCloseEvent(void)
|
|
|
|
{
|
|
|
|
if (!g_params.ignoreQuit || !g_cursor.inView)
|
|
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
|
|
}
|
2021-01-26 10:46:30 +00:00
|
|
|
|
2021-02-25 23:21:56 +00:00
|
|
|
void app_handleRenderEvent(const uint64_t timeUs)
|
|
|
|
{
|
2021-08-01 08:04:43 +00:00
|
|
|
bool invalidate = false;
|
2021-02-25 23:21:56 +00:00
|
|
|
if (!g_state.escapeActive)
|
|
|
|
{
|
|
|
|
if (g_state.escapeHelp)
|
|
|
|
{
|
|
|
|
g_state.escapeHelp = false;
|
2021-08-01 08:04:43 +00:00
|
|
|
invalidate = true;
|
2021-02-25 23:21:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-01 01:44:13 +00:00
|
|
|
if (!g_state.escapeHelp && timeUs - g_state.escapeTime > g_params.helpMenuDelayUs)
|
2021-02-25 23:21:56 +00:00
|
|
|
{
|
|
|
|
g_state.escapeHelp = true;
|
2021-08-01 08:04:43 +00:00
|
|
|
invalidate = true;
|
2021-02-25 23:21:56 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-01 08:04:43 +00:00
|
|
|
|
|
|
|
if (invalidate)
|
2021-08-04 20:47:36 +00:00
|
|
|
app_invalidateWindow(false);
|
2021-02-25 23:21:56 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 10:46:30 +00:00
|
|
|
void app_setFullscreen(bool fs)
|
|
|
|
{
|
|
|
|
g_state.ds->setFullscreen(fs);
|
|
|
|
}
|
|
|
|
|
2021-01-27 00:11:53 +00:00
|
|
|
bool app_getFullscreen(void)
|
|
|
|
{
|
|
|
|
return g_state.ds->getFullscreen();
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:40:39 +00:00
|
|
|
bool app_getProp(LG_DSProperty prop, void * ret)
|
|
|
|
{
|
|
|
|
return g_state.ds->getProp(prop, ret);
|
|
|
|
}
|
|
|
|
|
2021-01-27 08:20:13 +00:00
|
|
|
#ifdef ENABLE_EGL
|
2021-01-26 23:40:39 +00:00
|
|
|
EGLDisplay app_getEGLDisplay(void)
|
|
|
|
{
|
|
|
|
return g_state.ds->getEGLDisplay();
|
|
|
|
}
|
|
|
|
|
|
|
|
EGLNativeWindowType app_getEGLNativeWindow(void)
|
|
|
|
{
|
|
|
|
return g_state.ds->getEGLNativeWindow();
|
|
|
|
}
|
|
|
|
|
2021-05-11 20:49:39 +00:00
|
|
|
void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
|
2021-01-26 23:59:10 +00:00
|
|
|
{
|
2021-05-11 20:49:39 +00:00
|
|
|
g_state.ds->eglSwapBuffers(display, surface, damage, count);
|
2021-01-26 23:59:10 +00:00
|
|
|
}
|
2021-01-27 08:20:13 +00:00
|
|
|
#endif
|
2021-01-26 23:59:10 +00:00
|
|
|
|
2021-01-27 20:13:35 +00:00
|
|
|
#ifdef ENABLE_OPENGL
|
2021-01-27 10:27:26 +00:00
|
|
|
LG_DSGLContext app_glCreateContext(void)
|
|
|
|
{
|
|
|
|
return g_state.ds->glCreateContext();
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_glDeleteContext(LG_DSGLContext context)
|
|
|
|
{
|
|
|
|
g_state.ds->glDeleteContext(context);
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_glMakeCurrent(LG_DSGLContext context)
|
|
|
|
{
|
|
|
|
g_state.ds->glMakeCurrent(context);
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_glSetSwapInterval(int interval)
|
|
|
|
{
|
|
|
|
g_state.ds->glSetSwapInterval(interval);
|
|
|
|
}
|
|
|
|
|
2021-01-26 10:46:30 +00:00
|
|
|
void app_glSwapBuffers(void)
|
|
|
|
{
|
|
|
|
g_state.ds->glSwapBuffers();
|
|
|
|
}
|
2021-01-27 20:13:35 +00:00
|
|
|
#endif
|
2021-01-26 23:40:39 +00:00
|
|
|
|
|
|
|
void app_alert(LG_MsgAlert type, const char * fmt, ...)
|
|
|
|
{
|
|
|
|
if (!g_state.lgr || !g_params.showAlerts)
|
|
|
|
return;
|
|
|
|
|
2021-02-21 14:39:56 +00:00
|
|
|
va_list args;
|
2021-01-26 23:40:39 +00:00
|
|
|
va_start(args, fmt);
|
2022-01-08 03:32:13 +00:00
|
|
|
overlayAlert_show(type, fmt, args);
|
2021-01-26 23:40:39 +00:00
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
2022-01-11 22:35:09 +00:00
|
|
|
MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...)
|
2022-01-08 04:37:44 +00:00
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
2022-03-18 05:35:13 +00:00
|
|
|
MsgBoxHandle handle = overlayMsg_show(caption, NULL, NULL, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
MsgBoxHandle app_confirmMsgBox(const char * caption,
|
|
|
|
MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
|
|
MsgBoxHandle handle = overlayMsg_show(caption, callback, opaque, fmt, args);
|
2022-01-08 04:37:44 +00:00
|
|
|
va_end(args);
|
|
|
|
|
2022-01-11 22:35:09 +00:00
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
2022-03-18 06:13:56 +00:00
|
|
|
void app_msgBoxClose(MsgBoxHandle handle)
|
2022-01-11 22:35:09 +00:00
|
|
|
{
|
|
|
|
if (!handle)
|
|
|
|
return;
|
|
|
|
|
|
|
|
overlayMsg_close(handle);
|
2022-01-08 04:37:44 +00:00
|
|
|
}
|
|
|
|
|
2022-03-19 05:02:04 +00:00
|
|
|
void app_showRecord(bool show)
|
|
|
|
{
|
2022-05-25 18:08:22 +00:00
|
|
|
overlayStatus_set(LG_USER_STATUS_RECORDING, show);
|
2022-03-19 05:02:04 +00:00
|
|
|
}
|
|
|
|
|
2022-06-29 08:24:53 +00:00
|
|
|
KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback,
|
|
|
|
void * opaque, const char * description)
|
2021-01-26 23:40:39 +00:00
|
|
|
{
|
2022-06-29 08:24:53 +00:00
|
|
|
if (charcode != 0 && sc != 0)
|
2021-01-26 23:40:39 +00:00
|
|
|
{
|
2022-06-29 08:24:53 +00:00
|
|
|
DEBUG_ERROR("invalid keybind, one of scancode or charcode must be 0");
|
2021-01-26 23:40:39 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-06-29 08:24:53 +00:00
|
|
|
if (charcode && islower(charcode))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("invalid keybind, charcode must be uppercase");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
KeybindHandle handle;
|
|
|
|
|
|
|
|
// don't allow duplicate binds
|
|
|
|
ll_forEachNL(g_state.bindings, item, handle)
|
|
|
|
{
|
|
|
|
if ((sc && handle->sc == sc ) ||
|
|
|
|
(charcode && handle->charcode == charcode))
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Key already bound");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handle = malloc(sizeof(*handle));
|
2022-03-06 23:13:54 +00:00
|
|
|
if (!handle)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("out of memory");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-06-29 08:24:53 +00:00
|
|
|
handle->sc = sc;
|
|
|
|
handle->charcode = charcode;
|
|
|
|
handle->callback = callback;
|
|
|
|
handle->description = description;
|
|
|
|
handle->opaque = opaque;
|
2021-01-26 23:40:39 +00:00
|
|
|
|
2022-06-29 08:24:53 +00:00
|
|
|
ll_push(g_state.bindings, handle);
|
2021-01-26 23:40:39 +00:00
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_releaseKeybind(KeybindHandle * handle)
|
|
|
|
{
|
|
|
|
if (!*handle)
|
|
|
|
return;
|
|
|
|
|
2022-06-29 08:24:53 +00:00
|
|
|
ll_removeData(g_state.bindings, *handle);
|
2021-01-26 23:40:39 +00:00
|
|
|
free(*handle);
|
|
|
|
*handle = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_releaseAllKeybinds(void)
|
|
|
|
{
|
2022-06-29 08:24:53 +00:00
|
|
|
KeybindHandle * handle;
|
|
|
|
while(ll_shift(g_state.bindings, (void **)&handle))
|
|
|
|
free(handle);
|
2021-01-26 23:40:39 +00:00
|
|
|
}
|
2021-01-30 09:52:52 +00:00
|
|
|
|
2022-01-17 11:08:56 +00:00
|
|
|
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
|
|
|
|
float min, float max, GraphFormatFn formatFn)
|
2021-07-18 02:31:11 +00:00
|
|
|
{
|
2022-01-17 11:08:56 +00:00
|
|
|
return overlayGraph_register(name, buffer, min, max, formatFn);
|
2021-07-18 02:31:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void app_unregisterGraph(GraphHandle handle)
|
|
|
|
{
|
2021-07-22 07:27:30 +00:00
|
|
|
overlayGraph_unregister(handle);
|
2021-07-18 02:31:11 +00:00
|
|
|
}
|
|
|
|
|
2022-01-27 23:59:12 +00:00
|
|
|
void app_invalidateGraph(GraphHandle handle)
|
2022-01-17 11:49:19 +00:00
|
|
|
{
|
2022-01-27 23:59:12 +00:00
|
|
|
overlayGraph_invalidate(handle);
|
2022-01-17 11:49:19 +00:00
|
|
|
}
|
|
|
|
|
2021-08-04 20:40:06 +00:00
|
|
|
void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params)
|
2021-07-18 02:31:11 +00:00
|
|
|
{
|
2021-07-22 07:27:30 +00:00
|
|
|
ASSERT_LG_OVERLAY_VALID(ops);
|
2021-07-18 02:31:11 +00:00
|
|
|
|
2021-08-15 16:18:22 +00:00
|
|
|
struct Overlay * overlay = malloc(sizeof(*overlay));
|
2022-03-06 23:13:54 +00:00
|
|
|
if (!overlay)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("out of ram");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-22 09:43:16 +00:00
|
|
|
overlay->ops = ops;
|
2021-08-04 20:40:06 +00:00
|
|
|
overlay->params = params;
|
|
|
|
overlay->udata = NULL;
|
2021-07-22 09:43:16 +00:00
|
|
|
overlay->lastRectCount = 0;
|
2021-07-22 07:27:30 +00:00
|
|
|
ll_push(g_state.overlays, overlay);
|
2021-08-04 20:40:06 +00:00
|
|
|
|
|
|
|
if (ops->earlyInit)
|
|
|
|
ops->earlyInit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void app_initOverlays(void)
|
|
|
|
{
|
|
|
|
struct Overlay * overlay;
|
2022-01-12 01:17:29 +00:00
|
|
|
ll_lock(g_state.overlays);
|
|
|
|
ll_forEachNL(g_state.overlays, item, overlay)
|
2021-08-04 20:40:06 +00:00
|
|
|
{
|
2022-03-06 23:13:54 +00:00
|
|
|
DEBUG_ASSERT(overlay->ops);
|
2021-08-04 20:40:06 +00:00
|
|
|
if (!overlay->ops->init(&overlay->udata, overlay->params))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Overlay `%s` failed to initialize", overlay->ops->name);
|
|
|
|
overlay->ops = NULL;
|
|
|
|
}
|
|
|
|
}
|
2022-01-12 01:17:29 +00:00
|
|
|
ll_unlock(g_state.overlays);
|
2021-07-10 04:21:18 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 09:43:16 +00:00
|
|
|
static inline void mergeRect(struct Rect * dest, const struct Rect * a, const struct Rect * b)
|
|
|
|
{
|
|
|
|
int x2 = max(a->x + a->w, b->x + b->w);
|
|
|
|
int y2 = max(a->y + a->h, b->y + b->h);
|
|
|
|
|
|
|
|
dest->x = min(a->x, b->x);
|
|
|
|
dest->y = min(a->y, b->y);
|
|
|
|
dest->w = x2 - dest->x;
|
|
|
|
dest->h = y2 - dest->y;
|
|
|
|
}
|
|
|
|
|
2021-07-29 21:36:28 +00:00
|
|
|
static inline LG_DSPointer mapImGuiCursor(ImGuiMouseCursor cursor)
|
|
|
|
{
|
|
|
|
switch (cursor)
|
|
|
|
{
|
|
|
|
case ImGuiMouseCursor_None:
|
|
|
|
return LG_POINTER_NONE;
|
|
|
|
case ImGuiMouseCursor_Arrow:
|
|
|
|
return LG_POINTER_ARROW;
|
|
|
|
case ImGuiMouseCursor_TextInput:
|
|
|
|
return LG_POINTER_INPUT;
|
|
|
|
case ImGuiMouseCursor_ResizeAll:
|
|
|
|
return LG_POINTER_MOVE;
|
|
|
|
case ImGuiMouseCursor_ResizeNS:
|
|
|
|
return LG_POINTER_RESIZE_NS;
|
|
|
|
case ImGuiMouseCursor_ResizeEW:
|
|
|
|
return LG_POINTER_RESIZE_EW;
|
|
|
|
case ImGuiMouseCursor_ResizeNESW:
|
|
|
|
return LG_POINTER_RESIZE_NESW;
|
|
|
|
case ImGuiMouseCursor_ResizeNWSE:
|
|
|
|
return LG_POINTER_RESIZE_NWSE;
|
|
|
|
case ImGuiMouseCursor_Hand:
|
|
|
|
return LG_POINTER_HAND;
|
|
|
|
case ImGuiMouseCursor_NotAllowed:
|
|
|
|
return LG_POINTER_NOT_ALLOWED;
|
|
|
|
default:
|
|
|
|
return LG_POINTER_ARROW;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-01 11:13:59 +00:00
|
|
|
bool app_overlayNeedsRender(void)
|
|
|
|
{
|
2022-01-08 04:37:44 +00:00
|
|
|
if (app_isOverlayMode())
|
2021-08-01 11:13:59 +00:00
|
|
|
return true;
|
|
|
|
|
2022-01-12 01:17:29 +00:00
|
|
|
bool result = false;
|
|
|
|
struct Overlay * overlay;
|
|
|
|
ll_lock(g_state.overlays);
|
|
|
|
ll_forEachNL(g_state.overlays, item, overlay)
|
2021-08-01 11:13:59 +00:00
|
|
|
{
|
|
|
|
if (!overlay->ops->needs_render)
|
|
|
|
continue;
|
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
if (overlay->ops->needs_render(overlay->udata, false))
|
2022-01-12 01:17:29 +00:00
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
2021-08-01 11:13:59 +00:00
|
|
|
}
|
2022-01-12 01:17:29 +00:00
|
|
|
ll_unlock(g_state.overlays);
|
2021-08-01 11:13:59 +00:00
|
|
|
|
2022-01-12 01:17:29 +00:00
|
|
|
return result;
|
2021-08-01 11:13:59 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 07:27:30 +00:00
|
|
|
int app_renderOverlay(struct Rect * rects, int maxRects)
|
2021-07-08 01:45:54 +00:00
|
|
|
{
|
2021-07-22 08:33:50 +00:00
|
|
|
int totalRects = 0;
|
|
|
|
bool totalDamage = false;
|
2021-07-22 07:27:30 +00:00
|
|
|
struct Overlay * overlay;
|
2021-07-22 09:43:16 +00:00
|
|
|
struct Rect buffer[MAX_OVERLAY_RECTS];
|
2021-07-17 23:59:37 +00:00
|
|
|
|
2021-08-19 19:57:20 +00:00
|
|
|
g_state.io->KeyCtrl = g_state.modCtrl;
|
|
|
|
g_state.io->KeyShift = g_state.modShift;
|
|
|
|
g_state.io->KeyAlt = g_state.modAlt;
|
|
|
|
g_state.io->KeySuper = g_state.modSuper;
|
|
|
|
|
2021-08-30 10:48:41 +00:00
|
|
|
uint64_t now = nanotime();
|
|
|
|
g_state.io->DeltaTime = (now - g_state.lastImGuiFrame) * 1e-9f;
|
|
|
|
g_state.lastImGuiFrame = now;
|
|
|
|
|
2022-01-07 13:46:16 +00:00
|
|
|
render_again:
|
|
|
|
|
2021-07-22 08:33:50 +00:00
|
|
|
igNewFrame();
|
2021-07-18 10:43:17 +00:00
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
const bool overlayMode = app_isOverlayMode();
|
2022-05-27 01:36:39 +00:00
|
|
|
if (overlayMode && g_params.overlayDim)
|
2021-07-29 08:44:55 +00:00
|
|
|
{
|
|
|
|
totalDamage = true;
|
2022-01-07 13:26:12 +00:00
|
|
|
ImDrawList_AddRectFilled(igGetBackgroundDrawList_Nil(), (ImVec2) { 0.0f , 0.0f },
|
|
|
|
g_state.io->DisplaySize,
|
|
|
|
igGetColorU32_Col(ImGuiCol_ModalWindowDimBg, 1.0f),
|
|
|
|
0, 0);
|
2021-07-29 08:44:55 +00:00
|
|
|
}
|
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
const bool msgModal = overlayMsg_modal();
|
|
|
|
|
2021-07-22 07:27:30 +00:00
|
|
|
// render the overlays
|
2022-01-12 01:17:29 +00:00
|
|
|
ll_lock(g_state.overlays);
|
|
|
|
ll_forEachNL(g_state.overlays, item, overlay)
|
2021-07-18 10:43:17 +00:00
|
|
|
{
|
2022-01-08 04:37:44 +00:00
|
|
|
if (msgModal && overlay->ops != &LGOverlayMsg)
|
|
|
|
continue;
|
|
|
|
|
2021-07-22 08:33:50 +00:00
|
|
|
const int written =
|
2022-01-08 04:37:44 +00:00
|
|
|
overlay->ops->render(overlay->udata, overlayMode,
|
2021-07-31 09:11:40 +00:00
|
|
|
buffer, MAX_OVERLAY_RECTS);
|
2021-07-22 08:33:50 +00:00
|
|
|
|
2021-07-30 01:46:50 +00:00
|
|
|
for (int i = 0; i < written; ++i)
|
|
|
|
{
|
|
|
|
buffer[i].x *= g_state.windowScale;
|
|
|
|
buffer[i].y *= g_state.windowScale;
|
|
|
|
buffer[i].w *= g_state.windowScale;
|
|
|
|
buffer[i].h *= g_state.windowScale;
|
|
|
|
}
|
|
|
|
|
2021-07-22 09:43:16 +00:00
|
|
|
// It is an error to run out of rectangles, because we will not be able to
|
|
|
|
// correctly calculate the damage of the next frame.
|
2021-08-13 23:51:36 +00:00
|
|
|
DEBUG_ASSERT(written >= 0);
|
2021-07-18 10:43:17 +00:00
|
|
|
|
2021-07-22 09:43:16 +00:00
|
|
|
const int toAdd = max(written, overlay->lastRectCount);
|
|
|
|
totalDamage |= toAdd > maxRects;
|
|
|
|
|
|
|
|
if (!totalDamage && toAdd)
|
2021-07-22 08:33:50 +00:00
|
|
|
{
|
2021-07-22 09:43:16 +00:00
|
|
|
int i = 0;
|
|
|
|
for (; i < overlay->lastRectCount && i < written; ++i)
|
|
|
|
mergeRect(rects + i, buffer + i, overlay->lastRects + i);
|
|
|
|
|
|
|
|
// only one of the following memcpys will copy non-zero bytes.
|
|
|
|
memcpy(rects + i, buffer + i, (written - i) * sizeof(struct Rect));
|
|
|
|
memcpy(rects + i, overlay->lastRects + i, (overlay->lastRectCount - i) * sizeof(struct Rect));
|
|
|
|
|
|
|
|
rects += toAdd;
|
|
|
|
totalRects += toAdd;
|
|
|
|
maxRects -= toAdd;
|
2021-07-22 08:33:50 +00:00
|
|
|
}
|
2021-07-22 09:43:16 +00:00
|
|
|
|
|
|
|
memcpy(overlay->lastRects, buffer, sizeof(struct Rect) * written);
|
|
|
|
overlay->lastRectCount = written;
|
2021-07-22 07:27:30 +00:00
|
|
|
}
|
2022-01-12 01:17:29 +00:00
|
|
|
ll_unlock(g_state.overlays);
|
2021-07-18 10:43:17 +00:00
|
|
|
|
2022-01-08 04:37:44 +00:00
|
|
|
if (overlayMode)
|
2021-08-04 00:45:11 +00:00
|
|
|
{
|
|
|
|
ImGuiMouseCursor cursor = igGetMouseCursor();
|
|
|
|
if (cursor != g_state.cursorLast)
|
|
|
|
{
|
|
|
|
g_state.ds->setPointer(mapImGuiCursor(cursor));
|
|
|
|
g_state.cursorLast = cursor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-22 07:27:30 +00:00
|
|
|
igRender();
|
2021-07-18 10:43:17 +00:00
|
|
|
|
2022-01-07 13:46:16 +00:00
|
|
|
/* imgui requires two passes to calculate the bounding box of auto sized
|
|
|
|
* windows, this is by design
|
|
|
|
* ref: https://github.com/ocornut/imgui/issues/2158#issuecomment-434223618
|
|
|
|
*/
|
|
|
|
if (g_state.renderImGuiTwice)
|
|
|
|
{
|
|
|
|
g_state.renderImGuiTwice = false;
|
|
|
|
goto render_again;
|
|
|
|
}
|
|
|
|
|
2021-07-22 08:33:50 +00:00
|
|
|
return totalDamage ? -1 : totalRects;
|
2021-07-22 07:27:30 +00:00
|
|
|
}
|
2021-07-10 04:21:18 +00:00
|
|
|
|
2021-07-22 07:27:30 +00:00
|
|
|
void app_freeOverlays(void)
|
|
|
|
{
|
|
|
|
struct Overlay * overlay;
|
|
|
|
while(ll_shift(g_state.overlays, (void **)&overlay))
|
|
|
|
{
|
|
|
|
overlay->ops->free(overlay->udata);
|
|
|
|
free(overlay);
|
2021-07-10 04:21:18 +00:00
|
|
|
}
|
2021-07-08 01:45:54 +00:00
|
|
|
}
|
2021-07-31 10:51:38 +00:00
|
|
|
|
|
|
|
void app_setOverlay(bool enable)
|
|
|
|
{
|
|
|
|
if (g_state.overlayInput == enable)
|
|
|
|
return;
|
|
|
|
|
|
|
|
g_state.overlayInput = enable;
|
2022-01-08 04:37:44 +00:00
|
|
|
core_updateOverlayState();
|
2021-07-31 10:51:38 +00:00
|
|
|
}
|
2021-08-04 14:55:51 +00:00
|
|
|
|
|
|
|
void app_overlayConfigRegister(const char * title,
|
2021-08-11 23:04:45 +00:00
|
|
|
void (*callback)(void * udata, int * id), void * udata)
|
2021-08-04 14:55:51 +00:00
|
|
|
{
|
|
|
|
overlayConfig_register(title, callback, udata);
|
|
|
|
}
|
2021-08-11 23:04:45 +00:00
|
|
|
|
|
|
|
void app_overlayConfigRegisterTab(const char * title,
|
|
|
|
void (*callback)(void * udata, int * id), void * udata)
|
|
|
|
{
|
|
|
|
overlayConfig_registerTab(title, callback, udata);
|
|
|
|
}
|
2022-01-08 04:18:40 +00:00
|
|
|
|
|
|
|
void app_invalidateOverlay(bool renderTwice)
|
|
|
|
{
|
2022-05-25 14:40:13 +00:00
|
|
|
if (g_state.state == APP_STATE_SHUTDOWN)
|
|
|
|
return;
|
|
|
|
|
2022-01-08 04:18:40 +00:00
|
|
|
if (renderTwice)
|
|
|
|
g_state.renderImGuiTwice = true;
|
|
|
|
app_invalidateWindow(false);
|
|
|
|
}
|
2022-01-17 09:26:45 +00:00
|
|
|
|
|
|
|
bool app_guestIsLinux(void)
|
|
|
|
{
|
|
|
|
return g_state.guestOS == KVMFR_OS_LINUX;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool app_guestIsWindows(void)
|
|
|
|
{
|
|
|
|
return g_state.guestOS == KVMFR_OS_WINDOWS;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool app_guestIsOSX(void)
|
|
|
|
{
|
|
|
|
return g_state.guestOS == KVMFR_OS_OSX;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool app_guestIsBSD(void)
|
|
|
|
{
|
|
|
|
return g_state.guestOS == KVMFR_OS_BSD;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool app_guestIsOther(void)
|
|
|
|
{
|
|
|
|
return g_state.guestOS == KVMFR_OS_OTHER;
|
|
|
|
}
|
2022-05-22 01:25:34 +00:00
|
|
|
|
2022-05-23 14:05:22 +00:00
|
|
|
void app_stopVideo(bool stop)
|
2022-05-22 01:25:34 +00:00
|
|
|
{
|
2022-05-23 14:05:22 +00:00
|
|
|
if (g_state.stopVideo == stop)
|
|
|
|
return;
|
2022-05-23 12:35:27 +00:00
|
|
|
|
2022-05-23 14:05:22 +00:00
|
|
|
// do not change the state if the host app is not connected
|
|
|
|
if (!g_state.lgHostConnected)
|
|
|
|
return;
|
|
|
|
|
|
|
|
g_state.stopVideo = stop;
|
|
|
|
|
|
|
|
app_alert(
|
|
|
|
LG_ALERT_INFO,
|
|
|
|
stop ? "Video Stream Disabled" : "Video Stream Enabled"
|
|
|
|
);
|
|
|
|
|
|
|
|
if (stop)
|
|
|
|
{
|
|
|
|
core_stopCursorThread();
|
|
|
|
core_stopFrameThread();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
core_startCursorThread();
|
|
|
|
core_startFrameThread();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-26 16:24:01 +00:00
|
|
|
bool app_useSpiceDisplay(bool enable)
|
2022-05-23 14:05:22 +00:00
|
|
|
{
|
|
|
|
static bool lastState = false;
|
|
|
|
if (!g_params.useSpice || lastState == enable)
|
2022-05-26 16:24:01 +00:00
|
|
|
return g_params.useSpice && lastState;
|
2022-05-22 01:25:34 +00:00
|
|
|
|
2022-05-23 23:48:54 +00:00
|
|
|
// if spice is not yet ready, flag the state we want for when it is
|
|
|
|
if (!g_state.spiceReady)
|
|
|
|
{
|
|
|
|
g_state.initialSpiceDisplay = enable;
|
2022-05-26 16:24:01 +00:00
|
|
|
return false;
|
2022-05-23 23:48:54 +00:00
|
|
|
}
|
|
|
|
|
2022-05-22 01:25:34 +00:00
|
|
|
if (!purespice_hasChannel(PS_CHANNEL_DISPLAY))
|
2022-05-26 16:24:01 +00:00
|
|
|
return false;
|
2022-05-22 01:25:34 +00:00
|
|
|
|
2022-05-26 16:24:01 +00:00
|
|
|
// do not allow stopping of the host app if not connected
|
2022-05-23 14:05:22 +00:00
|
|
|
if (!enable && !g_state.lgHostConnected)
|
2022-05-26 16:24:01 +00:00
|
|
|
return false;
|
2022-05-23 14:05:22 +00:00
|
|
|
|
|
|
|
lastState = enable;
|
2022-05-22 01:25:34 +00:00
|
|
|
if (enable)
|
|
|
|
{
|
|
|
|
purespice_connectChannel(PS_CHANNEL_DISPLAY);
|
2022-09-19 00:30:18 +00:00
|
|
|
purespice_connectChannel(PS_CHANNEL_CURSOR);
|
2022-05-23 23:48:54 +00:00
|
|
|
renderQueue_spiceShow(true);
|
2022-05-22 01:25:34 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-05-23 23:48:54 +00:00
|
|
|
renderQueue_spiceShow(false);
|
2022-05-22 01:25:34 +00:00
|
|
|
purespice_disconnectChannel(PS_CHANNEL_DISPLAY);
|
2022-09-19 00:30:18 +00:00
|
|
|
purespice_disconnectChannel(PS_CHANNEL_CURSOR);
|
2022-05-22 01:25:34 +00:00
|
|
|
}
|
2022-05-26 16:24:01 +00:00
|
|
|
|
2022-05-25 18:08:22 +00:00
|
|
|
overlayStatus_set(LG_USER_STATUS_SPICE, enable);
|
2022-05-26 16:24:01 +00:00
|
|
|
return enable;
|
2022-05-22 01:25:34 +00:00
|
|
|
}
|