mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-06-17 12:14:28 +00:00
Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39a09ca565 | ||
|
|
5649d1ad95 | ||
|
|
1ba1108099 | ||
|
|
9b688909b0 | ||
|
|
270631f1b9 | ||
|
|
d86014e5ff | ||
|
|
1a407a67b1 | ||
|
|
98a327e99e | ||
|
|
db16efe68b | ||
|
|
800f063a1d | ||
|
|
e01666b6ad | ||
|
|
30a888711b | ||
|
|
62b27760ea | ||
|
|
328f9078ee | ||
|
|
5774e21965 | ||
|
|
2c909f0af7 | ||
|
|
f65aa6e089 | ||
|
|
b447b78b17 | ||
|
|
a450e0f8f5 | ||
|
|
0c9ecdfcb5 | ||
|
|
8e98f863b6 | ||
|
|
cc2104c699 | ||
|
|
253b0e2a7a | ||
|
|
c6af5be1dc | ||
|
|
06f6a96b56 | ||
|
|
89b73512ad | ||
|
|
b45f7a6733 | ||
|
|
1d99c821eb | ||
|
|
2993f7ae7d | ||
|
|
5454053d96 | ||
|
|
17d423db06 | ||
|
|
5ac53362a3 | ||
|
|
17b0e2cb22 | ||
|
|
96dc8c602c | ||
|
|
ead8069dae | ||
|
|
4e765b063a | ||
|
|
5dce97264b | ||
|
|
a00a6429d3 | ||
|
|
aafdec02df | ||
|
|
4e1c0cc0d0 | ||
|
|
33fed48277 | ||
|
|
b0f9d2f713 | ||
|
|
543d660ccc | ||
|
|
ecebcc4c35 | ||
|
|
af2dafbdac | ||
|
|
a56e363e39 | ||
|
|
06af101bf9 | ||
|
|
973806dd9c | ||
|
|
740dad943b | ||
|
|
4dfe4b8e2b | ||
|
|
cc521eab90 | ||
|
|
7d2c9ec447 | ||
|
|
8919d2718f | ||
|
|
cf3e816603 | ||
|
|
b8bf980a29 | ||
|
|
5dad69675b | ||
|
|
d0d1b31c10 | ||
|
|
6206d5dec4 | ||
|
|
265370b0f5 | ||
|
|
081d76268a | ||
|
|
8559b354ae | ||
|
|
9f0b99dac0 | ||
|
|
f4c1927f56 | ||
|
|
cfa9171465 | ||
|
|
2eac3dcb56 | ||
|
|
2d1e3c8022 | ||
|
|
f8ac860fde | ||
|
|
6f4a116942 | ||
|
|
ca5c3938e4 | ||
|
|
7ff5da4d62 | ||
|
|
37b3a26b9c | ||
|
|
e18f7d3365 | ||
|
|
3d03699cc8 | ||
|
|
9674421ce4 | ||
|
|
dbb18a6ecb | ||
|
|
6b1e310343 | ||
|
|
bf583290a4 | ||
|
|
6f1c19b3b0 | ||
|
|
2973319bff | ||
|
|
ec921d7f39 | ||
|
|
637a7625d2 | ||
|
|
32d8a47cd9 | ||
|
|
b4787fcfd1 | ||
|
|
e6ebcec689 | ||
|
|
ff0a859ceb | ||
|
|
1b48ac842a | ||
|
|
a702c912ae | ||
|
|
acc3298344 | ||
|
|
25e74301be | ||
|
|
327d472d64 | ||
|
|
e951aaad2d | ||
|
|
bbfe5aea37 | ||
|
|
0d28ea160e | ||
|
|
4fbaf18c89 | ||
|
|
c91b7f647d | ||
|
|
1761ea2b9b | ||
|
|
fb916cbac1 | ||
|
|
b97130cf20 | ||
|
|
05f2305fa0 | ||
|
|
b76fedeb67 | ||
|
|
6b5842d2ff | ||
|
|
7e15ec5e66 | ||
|
|
1808adc2de | ||
|
|
e2e49bce13 | ||
|
|
0d7be70b56 | ||
|
|
6b0699e664 | ||
|
|
9e96156912 | ||
|
|
837858c214 | ||
|
|
3783a25211 | ||
|
|
941c651fad | ||
|
|
f9ec32b255 | ||
|
|
8caf951c41 | ||
|
|
ef54e1be7f | ||
|
|
4c1893fe20 | ||
|
|
086f73721d | ||
|
|
202739c5be | ||
|
|
88b15cb3fe | ||
|
|
6990d7f7e3 | ||
|
|
9941a4bb83 | ||
|
|
d610aaf2cf | ||
|
|
908aa84599 |
13
.github/issue_template.md
vendored
13
.github/issue_template.md
vendored
@@ -5,8 +5,8 @@ If you are looking for help or support please use one of the following methods
|
||||
Create a New Topic on the Level1Tech's forum under the Looking Glass category:
|
||||
* https://forum.level1techs.com/c/software/lookingGlass/142
|
||||
|
||||
Ask for help in #looking-glass in the VFIO discord server
|
||||
* https://discord.gg/4ahCn4c
|
||||
Ask for help in the Looking Glass discord server
|
||||
* https://discord.gg/52SMupxkvt
|
||||
|
||||
*Issues that are not bug reports or feature requests will be closed & ignored*
|
||||
|
||||
@@ -40,12 +40,9 @@ PASTE CLIENT OUTPUT HERE
|
||||
```
|
||||
|
||||
The entire (not truncated) log file from the host application (if applicable).
|
||||
To obtain this locate the log file on your system, it will be in one of the
|
||||
following two locations depending on how you are launching the Looking Glass Host
|
||||
application:
|
||||
Normally, this is found on the guest system at:
|
||||
|
||||
* C:\Windows\Temp\looking-glass.txt
|
||||
* C:\Users\YOUR_USER\AppData\Local\Temp\looking-glass.txt
|
||||
%ProgramData%\Looking Glass (host)\looking-glass-host.txt
|
||||
|
||||
This log may be quite long, please delete the file first and then proceed to
|
||||
launch the host and reproduce the issue so that the log only contains the
|
||||
@@ -56,7 +53,7 @@ pertinent information.
|
||||
PASTE HOST LOG FILE CONTENTS HERE
|
||||
```
|
||||
|
||||
If the client is unexpetedly exiting without a backtrace, please provide one via
|
||||
If the client is unexpectedly exiting without a backtrace, please provide one via
|
||||
gdb with the command `thread apply all bt`. If you are unsure how to do this
|
||||
please watch the video below on how to perform a Debug build and generate this
|
||||
backtrace.
|
||||
|
||||
@@ -51,11 +51,12 @@ https://looking-glass.io/downloads
|
||||
|
||||
## Web
|
||||
|
||||
https://forum.level1techs.com/t/looking-glass-triage/130952
|
||||
https://forum.level1techs.com/c/software/lookingglass/142
|
||||
|
||||
## Discord
|
||||
|
||||
https://discord.gg/4ahCn4c
|
||||
* Looking Glass: https://discord.gg/52SMupxkvt
|
||||
* VFIO: https://discord.gg/4ahCn4c
|
||||
|
||||
## IRC
|
||||
|
||||
|
||||
@@ -53,6 +53,14 @@ add_compile_options(
|
||||
set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack")
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
if (ENABLE_OPENGL)
|
||||
add_definitions(-D ENABLE_OPENGL)
|
||||
endif()
|
||||
|
||||
if (ENABLE_EGL)
|
||||
add_definitions(-D ENABLE_EGL)
|
||||
endif()
|
||||
|
||||
if(ENABLE_ASAN)
|
||||
add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address")
|
||||
set(EXE_FLAGS "${EXE_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
||||
@@ -99,11 +107,16 @@ link_libraries(
|
||||
set(SOURCES
|
||||
${CMAKE_BINARY_DIR}/version.c
|
||||
src/main.c
|
||||
src/core.c
|
||||
src/app.c
|
||||
src/config.c
|
||||
src/keybind.c
|
||||
src/lg-renderer.c
|
||||
src/ll.c
|
||||
src/utils.c
|
||||
src/util.c
|
||||
src/clipboard.c
|
||||
src/kb.c
|
||||
src/egl_dynprocs.c
|
||||
)
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" )
|
||||
|
||||
@@ -145,21 +145,22 @@ Command line arguments will override any options loaded from the config files.
|
||||
| win:rotate | | 0 | Rotate the displayed image (0, 90, 180, 270) |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| input:grabKeyboard | -G | yes | Grab the keyboard in capture mode |
|
||||
| input:grabKeyboardOnFocus | | yes | Grab the keyboard when focused |
|
||||
| input:escapeKey | -m | 71 = ScrollLock | Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values |
|
||||
| input:ignoreWindowsKeys | | no | Do not pass events for the windows keys to the guest |
|
||||
| input:hideCursor | -M | yes | Hide the local mouse cursor |
|
||||
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
|
||||
| input:mouseSmoothing | | yes | Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing) |
|
||||
| input:rawMouse | | no | Use RAW mouse input when in capture mode (good for gaming) |
|
||||
| input:mouseRedraw | | yes | Mouse movements trigger redraws (ignores FPS minimum) |
|
||||
| input:autoCapture | | no | Try to keep the mouse captured when needed |
|
||||
| input:captureOnly | | no | Only enable input via SPICE if in capture mode |
|
||||
|----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| input:grabKeyboard | -G | no | Grab the keyboard in capture mode |
|
||||
| input:grabKeyboardOnFocus | | no | Grab the keyboard when focused |
|
||||
| input:releaseKeysOnFocusLoss | | yes | On focus loss, send key up events to guest for all held keys |
|
||||
| input:escapeKey | -m | 70 = KEY_SCROLLLOCK | Specify the escape key, see <linux/input-event-codes.h> for valid values |
|
||||
| input:ignoreWindowsKeys | | no | Do not pass events for the windows keys to the guest |
|
||||
| input:hideCursor | -M | yes | Hide the local mouse cursor |
|
||||
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
|
||||
| input:mouseSmoothing | | yes | Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing) |
|
||||
| input:rawMouse | | no | Use RAW mouse input when in capture mode (good for gaming) |
|
||||
| input:mouseRedraw | | yes | Mouse movements trigger redraws (ignores FPS minimum) |
|
||||
| input:autoCapture | | no | Try to keep the mouse captured when needed |
|
||||
| input:captureOnly | | no | Only enable input via SPICE if in capture mode |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
@@ -195,4 +196,10 @@ Command line arguments will override any options loaded from the config files.
|
||||
| opengl:preventBuffer | | yes | Prevent the driver from buffering frames |
|
||||
| opengl:amdPinnedMem | | yes | Use GL_AMD_pinned_memory if it is available |
|
||||
|------------------------------------------------------------------------------------|
|
||||
|
||||
|-------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------|
|
||||
| wayland:warpSupport | | yes | Enable cursor warping |
|
||||
|-------------------------------------------------------------|
|
||||
```
|
||||
|
||||
@@ -18,17 +18,17 @@ function(add_displayserver name)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# SDL must be first as it's the default implementation!
|
||||
add_displayserver(SDL)
|
||||
|
||||
# Add/remove displayservers here!
|
||||
if (ENABLE_WAYLAND)
|
||||
add_displayserver(Wayland)
|
||||
endif()
|
||||
|
||||
if (ENABLE_X11)
|
||||
add_displayserver(X11)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WAYLAND)
|
||||
add_displayserver(Wayland)
|
||||
endif()
|
||||
# SDL must be last as it's the fallback implemntation
|
||||
add_displayserver(SDL)
|
||||
|
||||
list(REMOVE_AT DISPLAYSERVERS 0)
|
||||
list(REMOVE_AT DISPLAYSERVERS_LINK 0)
|
||||
|
||||
@@ -20,13 +20,29 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
#include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
#include <wayland-egl.h>
|
||||
#endif
|
||||
|
||||
#include "app.h"
|
||||
#include "kb.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "common/types.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
struct SDLDSState
|
||||
{
|
||||
SDL_Window * window;
|
||||
SDL_Cursor * cursor;
|
||||
|
||||
EGLNativeWindowType wlDisplay;
|
||||
|
||||
bool keyboardGrabbed;
|
||||
bool pointerGrabbed;
|
||||
bool exiting;
|
||||
@@ -34,15 +50,94 @@ struct SDLDSState
|
||||
|
||||
static struct SDLDSState sdl;
|
||||
|
||||
/* forwards */
|
||||
static int sdlEventFilter(void * userdata, SDL_Event * event);
|
||||
|
||||
static void sdlSetup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool sdlProbe(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdlEarlyInit(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdlInit(SDL_SysWMinfo * info)
|
||||
static bool sdlInit(const LG_DSInitParams params)
|
||||
{
|
||||
memset(&sdl, 0, sizeof(sdl));
|
||||
|
||||
// Allow screensavers for now: we will enable and disable as needed.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
DEBUG_ERROR("SDL_Init Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (params.opengl)
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
sdl.window = SDL_CreateWindow(
|
||||
params.title,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.x,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.y,
|
||||
params.w,
|
||||
params.h,
|
||||
(
|
||||
SDL_WINDOW_HIDDEN |
|
||||
(params.resizable ? SDL_WINDOW_RESIZABLE : 0) |
|
||||
(params.borderless ? SDL_WINDOW_BORDERLESS : 0) |
|
||||
(params.maximize ? SDL_WINDOW_MAXIMIZED : 0) |
|
||||
(params.opengl ? SDL_WINDOW_OPENGL : 0)
|
||||
)
|
||||
);
|
||||
|
||||
if (sdl.window == NULL)
|
||||
{
|
||||
DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError());
|
||||
goto fail_init;
|
||||
}
|
||||
|
||||
const uint8_t data[4] = {0xf, 0x9, 0x9, 0xf};
|
||||
const uint8_t mask[4] = {0xf, 0xf, 0xf, 0xf};
|
||||
sdl.cursor = SDL_CreateCursor(data, mask, 8, 4, 4, 0);
|
||||
SDL_SetCursor(sdl.cursor);
|
||||
|
||||
SDL_ShowWindow(sdl.window);
|
||||
|
||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
|
||||
params.minimizeOnFocusLoss ? "1" : "0");
|
||||
|
||||
if (params.fullscreen)
|
||||
SDL_SetWindowFullscreen(sdl.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
|
||||
if (!params.center)
|
||||
SDL_SetWindowPosition(sdl.window, params.x, params.y);
|
||||
|
||||
// ensure mouse acceleration is identical in server mode
|
||||
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
||||
|
||||
SDL_SetEventFilter(sdlEventFilter, NULL);
|
||||
return true;
|
||||
|
||||
fail_init:
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sdlStartup(void)
|
||||
@@ -55,6 +150,14 @@ static void sdlShutdown(void)
|
||||
|
||||
static void sdlFree(void)
|
||||
{
|
||||
SDL_DestroyWindow(sdl.window);
|
||||
|
||||
if (sdl.cursor)
|
||||
SDL_FreeCursor(sdl.cursor);
|
||||
|
||||
if (sdl.window)
|
||||
SDL_DestroyWindow(sdl.window);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
static bool sdlGetProp(LG_DSProperty prop, void * ret)
|
||||
@@ -62,25 +165,145 @@ static bool sdlGetProp(LG_DSProperty prop, void * ret)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sdlEventFilter(SDL_Event * event)
|
||||
#ifdef ENABLE_EGL
|
||||
static EGLDisplay sdlGetEGLDisplay(void)
|
||||
{
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(sdl.window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
EGLNativeDisplayType native;
|
||||
EGLenum platform;
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
native = (EGLNativeDisplayType)wminfo.info.x11.display;
|
||||
platform = EGL_PLATFORM_X11_KHR;
|
||||
break;
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
native = (EGLNativeDisplayType)wminfo.info.wl.display;
|
||||
platform = EGL_PLATFORM_WAYLAND_KHR;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
|
||||
if (strstr(early_exts, "EGL_KHR_platform_base") != NULL &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplay)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplay");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplay(platform, native, NULL);
|
||||
}
|
||||
|
||||
if (strstr(early_exts, "EGL_EXT_platform_base") != NULL &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplayEXT(platform, native, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO("Using eglGetDisplay");
|
||||
return eglGetDisplay(native);
|
||||
}
|
||||
|
||||
static EGLNativeWindowType sdlGetEGLNativeWindow(void)
|
||||
{
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(sdl.window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
return (EGLNativeWindowType)wminfo.info.x11.window;
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
{
|
||||
if (sdl.wlDisplay)
|
||||
return sdl.wlDisplay;
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSize(sdl.window, &width, &height);
|
||||
sdl.wlDisplay = (EGLNativeWindowType)wl_egl_window_create(
|
||||
wminfo.info.wl.surface, width, height);
|
||||
|
||||
return sdl.wlDisplay;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void sdlEGLSwapBuffers(EGLDisplay display, EGLSurface surface)
|
||||
{
|
||||
eglSwapBuffers(display, surface);
|
||||
}
|
||||
#endif //ENABLE_EGL
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
static LG_DSGLContext sdlGLCreateContext(void)
|
||||
{
|
||||
return (LG_DSGLContext)SDL_GL_CreateContext(sdl.window);
|
||||
}
|
||||
|
||||
static void sdlGLDeleteContext(LG_DSGLContext context)
|
||||
{
|
||||
SDL_GL_DeleteContext((SDL_GLContext)context);
|
||||
}
|
||||
|
||||
static void sdlGLMakeCurrent(LG_DSGLContext context)
|
||||
{
|
||||
SDL_GL_MakeCurrent(sdl.window, (SDL_GLContext)context);
|
||||
}
|
||||
|
||||
static void sdlGLSetSwapInterval(int interval)
|
||||
{
|
||||
SDL_GL_SetSwapInterval(interval);
|
||||
}
|
||||
|
||||
static void sdlGLSwapBuffers(void)
|
||||
{
|
||||
SDL_GL_SwapWindow(sdl.window);
|
||||
}
|
||||
#endif //ENABLE_OPENGL
|
||||
|
||||
static int sdlEventFilter(void * userdata, SDL_Event * event)
|
||||
{
|
||||
switch(event->type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
app_handleCloseEvent();
|
||||
return true;
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
// stop motion events during the warp out of the window
|
||||
if (sdl.exiting)
|
||||
return true;
|
||||
break;
|
||||
|
||||
app_updateCursorPos(event->motion.x, event->motion.y);
|
||||
if (app_cursorIsGrabbed())
|
||||
app_handleMouseGrabbed(event->motion.xrel, event->motion.yrel);
|
||||
else
|
||||
app_handleMouseNormal(event->motion.xrel, event->motion.yrel);
|
||||
return true;
|
||||
app_handleMouseRelative(event->motion.xrel, event->motion.yrel,
|
||||
event->motion.xrel, event->motion.yrel);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
{
|
||||
@@ -89,7 +312,7 @@ static bool sdlEventFilter(SDL_Event * event)
|
||||
button += 2;
|
||||
|
||||
app_handleButtonPress(button);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
@@ -99,7 +322,7 @@ static bool sdlEventFilter(SDL_Event * event)
|
||||
button += 2;
|
||||
|
||||
app_handleButtonRelease(button);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
@@ -107,7 +330,7 @@ static bool sdlEventFilter(SDL_Event * event)
|
||||
int button = event->wheel.y > 0 ? 4 : 5;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
@@ -128,51 +351,69 @@ static bool sdlEventFilter(SDL_Event * event)
|
||||
switch(event->window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
app_handleWindowEnter();
|
||||
return true;
|
||||
app_handleEnterEvent(true);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
sdl.exiting = false;
|
||||
app_handleWindowLeave();
|
||||
return true;
|
||||
app_handleEnterEvent(false);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
app_handleFocusEvent(true);
|
||||
return true;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
app_handleFocusEvent(false);
|
||||
return true;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
app_handleResizeEvent(event->window.data1, event->window.data2);
|
||||
return true;
|
||||
{
|
||||
struct Border border;
|
||||
SDL_GetWindowBordersSize(
|
||||
sdl.window,
|
||||
&border.top,
|
||||
&border.left,
|
||||
&border.bottom,
|
||||
&border.right
|
||||
);
|
||||
app_handleResizeEvent(
|
||||
event->window.data1,
|
||||
event->window.data2,
|
||||
border);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
app_updateWindowPos(event->window.data1, event->window.data2);
|
||||
return true;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
app_handleCloseEvent();
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sdlShowPointer(bool show)
|
||||
{
|
||||
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
|
||||
}
|
||||
|
||||
static void sdlGrabPointer(void)
|
||||
{
|
||||
SDL_SetWindowGrab(app_getWindow(), SDL_TRUE);
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
sdl.pointerGrabbed = true;
|
||||
}
|
||||
|
||||
static void sdlUngrabPointer(void)
|
||||
{
|
||||
SDL_SetWindowGrab(app_getWindow(), SDL_FALSE);
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
sdl.pointerGrabbed = false;
|
||||
}
|
||||
@@ -180,7 +421,7 @@ static void sdlUngrabPointer(void)
|
||||
static void sdlGrabKeyboard(void)
|
||||
{
|
||||
if (sdl.pointerGrabbed)
|
||||
SDL_SetWindowGrab(app_getWindow(), SDL_FALSE);
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
else
|
||||
{
|
||||
DEBUG_WARN("SDL does not support grabbing only the keyboard, grabbing all");
|
||||
@@ -188,16 +429,16 @@ static void sdlGrabKeyboard(void)
|
||||
}
|
||||
|
||||
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
|
||||
SDL_SetWindowGrab(app_getWindow(), SDL_TRUE);
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
sdl.keyboardGrabbed = true;
|
||||
}
|
||||
|
||||
static void sdlUngrabKeyboard(void)
|
||||
{
|
||||
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0");
|
||||
SDL_SetWindowGrab(app_getWindow(), SDL_FALSE);
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
if (sdl.pointerGrabbed)
|
||||
SDL_SetWindowGrab(app_getWindow(), SDL_TRUE);
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
sdl.keyboardGrabbed = false;
|
||||
}
|
||||
|
||||
@@ -213,13 +454,26 @@ static void sdlWarpPointer(int x, int y, bool exiting)
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
|
||||
// issue the warp
|
||||
SDL_WarpMouseInWindow(app_getWindow(), x, y);
|
||||
SDL_WarpMouseInWindow(sdl.window, x, y);
|
||||
}
|
||||
|
||||
static void sdlRealignPointer(void)
|
||||
{
|
||||
// no need to care about grab, realign only happens in normal mode
|
||||
app_handleMouseNormal(0, 0);
|
||||
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
static bool sdlIsValidPointerPos(int x, int y)
|
||||
{
|
||||
const int displays = SDL_GetNumVideoDisplays();
|
||||
for(int i = 0; i < displays; ++i)
|
||||
{
|
||||
SDL_Rect r;
|
||||
SDL_GetDisplayBounds(i, &r);
|
||||
if ((x >= r.x && x < r.x + r.w) &&
|
||||
(y >= r.y && y < r.y + r.h))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sdlInhibitIdle(void)
|
||||
@@ -232,24 +486,65 @@ static void sdlUninhibitIdle(void)
|
||||
SDL_EnableScreenSaver();
|
||||
}
|
||||
|
||||
static void sdlWait(unsigned int time)
|
||||
{
|
||||
SDL_WaitEventTimeout(NULL, time);
|
||||
}
|
||||
|
||||
static void sdlSetWindowSize(int x, int y)
|
||||
{
|
||||
SDL_SetWindowSize(sdl.window, x, y);
|
||||
}
|
||||
|
||||
static void sdlSetFullscreen(bool fs)
|
||||
{
|
||||
SDL_SetWindowFullscreen(sdl.window, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
}
|
||||
|
||||
static bool sdlGetFullscreen(void)
|
||||
{
|
||||
return (SDL_GetWindowFlags(sdl.window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
|
||||
}
|
||||
|
||||
struct LG_DisplayServerOps LGDS_SDL =
|
||||
{
|
||||
.subsystem = SDL_SYSWM_UNKNOWN,
|
||||
.earlyInit = sdlEarlyInit,
|
||||
.init = sdlInit,
|
||||
.startup = sdlStartup,
|
||||
.shutdown = sdlShutdown,
|
||||
.free = sdlFree,
|
||||
.getProp = sdlGetProp,
|
||||
.eventFilter = sdlEventFilter,
|
||||
.grabPointer = sdlGrabPointer,
|
||||
.ungrabPointer = sdlUngrabPointer,
|
||||
.grabKeyboard = sdlGrabKeyboard,
|
||||
.ungrabKeyboard = sdlUngrabKeyboard,
|
||||
.warpPointer = sdlWarpPointer,
|
||||
.realignPointer = sdlRealignPointer,
|
||||
.inhibitIdle = sdlInhibitIdle,
|
||||
.uninhibitIdle = sdlUninhibitIdle,
|
||||
.setup = sdlSetup,
|
||||
.probe = sdlProbe,
|
||||
.earlyInit = sdlEarlyInit,
|
||||
.init = sdlInit,
|
||||
.startup = sdlStartup,
|
||||
.shutdown = sdlShutdown,
|
||||
.free = sdlFree,
|
||||
.getProp = sdlGetProp,
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
.getEGLDisplay = sdlGetEGLDisplay,
|
||||
.getEGLNativeWindow = sdlGetEGLNativeWindow,
|
||||
.eglSwapBuffers = sdlEGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
.glCreateContext = sdlGLCreateContext,
|
||||
.glDeleteContext = sdlGLDeleteContext,
|
||||
.glMakeCurrent = sdlGLMakeCurrent,
|
||||
.glSetSwapInterval = sdlGLSetSwapInterval,
|
||||
.glSwapBuffers = sdlGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
.showPointer = sdlShowPointer,
|
||||
.grabPointer = sdlGrabPointer,
|
||||
.ungrabPointer = sdlUngrabPointer,
|
||||
.grabKeyboard = sdlGrabKeyboard,
|
||||
.ungrabKeyboard = sdlUngrabKeyboard,
|
||||
.warpPointer = sdlWarpPointer,
|
||||
.realignPointer = sdlRealignPointer,
|
||||
.isValidPointerPos = sdlIsValidPointerPos,
|
||||
.inhibitIdle = sdlInhibitIdle,
|
||||
.uninhibitIdle = sdlUninhibitIdle,
|
||||
.wait = sdlWait,
|
||||
.setWindowSize = sdlSetWindowSize,
|
||||
.setFullscreen = sdlSetFullscreen,
|
||||
.getFullscreen = sdlGetFullscreen,
|
||||
|
||||
/* SDL does not have clipboard support */
|
||||
.cbInit = NULL,
|
||||
|
||||
@@ -10,7 +10,16 @@ pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED
|
||||
#)
|
||||
|
||||
add_library(displayserver_Wayland STATIC
|
||||
clipboard.c
|
||||
cursor.c
|
||||
gl.c
|
||||
idle.c
|
||||
input.c
|
||||
poll.c
|
||||
state.c
|
||||
registry.c
|
||||
wayland.c
|
||||
window.c
|
||||
)
|
||||
|
||||
target_link_libraries(displayserver_Wayland
|
||||
@@ -46,6 +55,12 @@ endmacro()
|
||||
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland")
|
||||
include_directories("${CMAKE_BINARY_DIR}/wayland")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol")
|
||||
|
||||
477
client/displayservers/Wayland/clipboard.c
Normal file
477
client/displayservers/Wayland/clipboard.c
Normal file
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 "wayland.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
static const char * textMimetypes[] =
|
||||
{
|
||||
"text/plain",
|
||||
"text/plain;charset=utf-8",
|
||||
"TEXT",
|
||||
"STRING",
|
||||
"UTF8_STRING",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * pngMimetypes[] =
|
||||
{
|
||||
"image/png",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * bmpMimetypes[] =
|
||||
{
|
||||
"image/bmp",
|
||||
"image/x-bmp",
|
||||
"image/x-MS-bmp",
|
||||
"image/x-win-bitmap",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * tiffMimetypes[] =
|
||||
{
|
||||
"image/tiff",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * jpegMimetypes[] =
|
||||
{
|
||||
"image/jpeg",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char ** cbTypeToMimetypes(enum LG_ClipboardData type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT:
|
||||
return textMimetypes;
|
||||
case LG_CLIPBOARD_DATA_PNG:
|
||||
return pngMimetypes;
|
||||
case LG_CLIPBOARD_DATA_BMP:
|
||||
return bmpMimetypes;
|
||||
case LG_CLIPBOARD_DATA_TIFF:
|
||||
return tiffMimetypes;
|
||||
case LG_CLIPBOARD_DATA_JPEG:
|
||||
return jpegMimetypes;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard type");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static bool containsMimetype(const char ** mimetypes,
|
||||
const char * needle)
|
||||
{
|
||||
for (const char ** mimetype = mimetypes; *mimetype; mimetype++)
|
||||
if (!strcmp(needle, *mimetype))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mimetypeEndswith(const char * mimetype, const char * what)
|
||||
{
|
||||
size_t mimetypeLen = strlen(mimetype);
|
||||
size_t whatLen = strlen(what);
|
||||
|
||||
if (mimetypeLen < whatLen)
|
||||
return false;
|
||||
|
||||
return !strcmp(mimetype + mimetypeLen - whatLen, what);
|
||||
}
|
||||
|
||||
static bool isTextMimetype(const char * mimetype)
|
||||
{
|
||||
if (containsMimetype(textMimetypes, mimetype))
|
||||
return true;
|
||||
|
||||
char * text = "text/";
|
||||
if (!strncmp(mimetype, text, strlen(text)))
|
||||
return true;
|
||||
|
||||
if (mimetypeEndswith(mimetype, "script") ||
|
||||
mimetypeEndswith(mimetype, "xml") ||
|
||||
mimetypeEndswith(mimetype, "yaml"))
|
||||
return true;
|
||||
|
||||
if (strstr(mimetype, "json"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static enum LG_ClipboardData mimetypeToCbType(const char * mimetype)
|
||||
{
|
||||
if (isTextMimetype(mimetype))
|
||||
return LG_CLIPBOARD_DATA_TEXT;
|
||||
|
||||
if (containsMimetype(pngMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_PNG;
|
||||
|
||||
if (containsMimetype(bmpMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_BMP;
|
||||
|
||||
if (containsMimetype(tiffMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_TIFF;
|
||||
|
||||
if (containsMimetype(jpegMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_JPEG;
|
||||
|
||||
return LG_CLIPBOARD_DATA_NONE;
|
||||
}
|
||||
|
||||
// Destination client handlers.
|
||||
|
||||
static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer,
|
||||
const char * mimetype)
|
||||
{
|
||||
enum LG_ClipboardData type = mimetypeToCbType(mimetype);
|
||||
// We almost never prefer text/html, as that's used to represent rich text.
|
||||
// Since we can't copy or paste rich text, we should instead prefer actual
|
||||
// images or plain text.
|
||||
if (type != LG_CLIPBOARD_DATA_NONE &&
|
||||
(wlCb.pendingType == LG_CLIPBOARD_DATA_NONE ||
|
||||
strstr(wlCb.pendingMimetype, "html")))
|
||||
{
|
||||
wlCb.pendingType = type;
|
||||
if (wlCb.pendingMimetype)
|
||||
free(wlCb.pendingMimetype);
|
||||
wlCb.pendingMimetype = strdup(mimetype);
|
||||
}
|
||||
|
||||
if (!strcmp(mimetype, wlCb.lgMimetype))
|
||||
wlCb.isSelfCopy = true;
|
||||
}
|
||||
|
||||
static void dataOfferHandleSourceActions(void * data,
|
||||
struct wl_data_offer * offer, uint32_t sourceActions)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void dataOfferHandleAction(void * data, struct wl_data_offer * offer,
|
||||
uint32_t dndAction)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_data_offer_listener dataOfferListener = {
|
||||
.offer = dataOfferHandleOffer,
|
||||
.source_actions = dataOfferHandleSourceActions,
|
||||
.action = dataOfferHandleAction,
|
||||
};
|
||||
|
||||
static void dataDeviceHandleDataOffer(void * data,
|
||||
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
|
||||
{
|
||||
wlCb.pendingType = LG_CLIPBOARD_DATA_NONE;
|
||||
wlCb.isSelfCopy = false;
|
||||
wl_data_offer_add_listener(offer, &dataOfferListener, NULL);
|
||||
}
|
||||
|
||||
static void clipboardReadCancel(struct ClipboardRead * data, bool freeBuf)
|
||||
{
|
||||
waylandEpollUnregister(data->fd);
|
||||
close(data->fd);
|
||||
wl_data_offer_destroy(data->offer);
|
||||
if (freeBuf)
|
||||
free(data->buf);
|
||||
free(data);
|
||||
wlCb.currentRead = NULL;
|
||||
}
|
||||
|
||||
static void clipboardReadCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardRead * data = opaque;
|
||||
if (events & EPOLLERR)
|
||||
{
|
||||
clipboardReadCancel(data, true);
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead);
|
||||
if (result < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno));
|
||||
clipboardReadCancel(data, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
data->buf[data->numRead] = 0;
|
||||
wlCb.stashedType = data->type;
|
||||
wlCb.stashedSize = data->numRead;
|
||||
wlCb.stashedContents = data->buf;
|
||||
|
||||
clipboardReadCancel(data, false);
|
||||
app_clipboardNotify(wlCb.stashedType, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
data->numRead += result;
|
||||
if (data->numRead >= data->size)
|
||||
{
|
||||
data->size *= 2;
|
||||
void * nbuf = realloc(data->buf, data->size);
|
||||
if (!nbuf) {
|
||||
DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno));
|
||||
clipboardReadCancel(data, true);
|
||||
return;
|
||||
}
|
||||
|
||||
data->buf = nbuf;
|
||||
}
|
||||
}
|
||||
|
||||
static void dataDeviceHandleSelection(void * opaque,
|
||||
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
|
||||
{
|
||||
if (wlCb.pendingType == LG_CLIPBOARD_DATA_NONE || wlCb.isSelfCopy || !offer)
|
||||
return;
|
||||
|
||||
if (wlCb.currentRead)
|
||||
clipboardReadCancel(wlCb.currentRead, true);
|
||||
|
||||
int fds[2];
|
||||
if (pipe(fds) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno));
|
||||
abort();
|
||||
}
|
||||
|
||||
wl_data_offer_receive(offer, wlCb.pendingMimetype, fds[1]);
|
||||
close(fds[1]);
|
||||
free(wlCb.pendingMimetype);
|
||||
wlCb.pendingMimetype = NULL;
|
||||
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
|
||||
if (wlCb.stashedContents)
|
||||
{
|
||||
free(wlCb.stashedContents);
|
||||
wlCb.stashedContents = NULL;
|
||||
}
|
||||
|
||||
struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to read clipboard");
|
||||
close(fds[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
data->fd = fds[0];
|
||||
data->size = 4096;
|
||||
data->numRead = 0;
|
||||
data->buf = malloc(data->size);
|
||||
data->offer = offer;
|
||||
data->type = wlCb.pendingType;
|
||||
|
||||
if (!data->buf)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to receive clipboard data");
|
||||
close(data->fd);
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!waylandEpollRegister(data->fd, clipboardReadCallback, data, EPOLLIN))
|
||||
{
|
||||
DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno));
|
||||
close(data->fd);
|
||||
free(data->buf);
|
||||
free(data);
|
||||
}
|
||||
|
||||
wlCb.currentRead = data;
|
||||
}
|
||||
|
||||
static const struct wl_data_device_listener dataDeviceListener = {
|
||||
.data_offer = dataDeviceHandleDataOffer,
|
||||
.selection = dataDeviceHandleSelection,
|
||||
};
|
||||
|
||||
bool waylandCBInit(void)
|
||||
{
|
||||
memset(&wlCb, 0, sizeof(wlCb));
|
||||
|
||||
if (!wlWm.dataDeviceManager)
|
||||
{
|
||||
DEBUG_ERROR("Missing wl_data_device_manager interface");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlCb.dataDevice = wl_data_device_manager_get_data_device(
|
||||
wlWm.dataDeviceManager, wlWm.seat);
|
||||
if (!wlCb.dataDevice)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get data device");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlCb.stashedType = LG_CLIPBOARD_DATA_NONE;
|
||||
wl_data_device_add_listener(wlCb.dataDevice, &dataDeviceListener, NULL);
|
||||
|
||||
snprintf(wlCb.lgMimetype, sizeof(wlCb.lgMimetype),
|
||||
"application/x-looking-glass-copy;pid=%d", getpid());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandCBRequest(LG_ClipboardData type)
|
||||
{
|
||||
// We only notified once, so it must be this.
|
||||
assert(type == wlCb.stashedType);
|
||||
app_clipboardData(wlCb.stashedType, wlCb.stashedContents, wlCb.stashedSize);
|
||||
}
|
||||
|
||||
struct ClipboardWrite
|
||||
{
|
||||
int fd;
|
||||
size_t pos;
|
||||
struct CountedBuffer * buffer;
|
||||
};
|
||||
|
||||
static void clipboardWriteCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
if (events & EPOLLERR)
|
||||
goto error;
|
||||
|
||||
ssize_t written = write(data->fd, data->buffer->data + data->pos, data->buffer->size - data->pos);
|
||||
if (written < 0)
|
||||
{
|
||||
if (errno != EPIPE)
|
||||
DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->pos += written;
|
||||
if (data->pos < data->buffer->size)
|
||||
return;
|
||||
|
||||
error:
|
||||
waylandEpollUnregister(data->fd);
|
||||
close(data->fd);
|
||||
countedBufferRelease(&data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void dataSourceHandleSend(void * data, struct wl_data_source * source,
|
||||
const char * mimetype, int fd)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
if (containsMimetype(transfer->mimetypes, mimetype))
|
||||
{
|
||||
// Consider making this do non-blocking sends to not stall the Wayland
|
||||
// event loop if it becomes a problem. This is "fine" in the sense that
|
||||
// wl-copy also stalls like this, but it's not necessary.
|
||||
fcntl(fd, F_SETFL, 0);
|
||||
|
||||
struct ClipboardWrite * data = malloc(sizeof(struct ClipboardWrite));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite");
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->fd = fd;
|
||||
data->pos = 0;
|
||||
data->buffer = transfer->data;
|
||||
countedBufferAddRef(transfer->data);
|
||||
waylandEpollRegister(fd, clipboardWriteCallback, data, EPOLLOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void dataSourceHandleCancelled(void * data,
|
||||
struct wl_data_source * source)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
countedBufferRelease(&transfer->data);
|
||||
free(transfer);
|
||||
wl_data_source_destroy(source);
|
||||
}
|
||||
|
||||
static const struct wl_data_source_listener dataSourceListener = {
|
||||
.send = dataSourceHandleSend,
|
||||
.cancelled = dataSourceHandleCancelled,
|
||||
};
|
||||
|
||||
static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size)
|
||||
{
|
||||
struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer));
|
||||
if (!transfer)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating WCBTransfer");
|
||||
return;
|
||||
}
|
||||
|
||||
transfer->mimetypes = cbTypeToMimetypes(type);
|
||||
transfer->data = countedBufferNew(size);
|
||||
if (!transfer->data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating clipboard buffer");
|
||||
free(transfer);
|
||||
return;
|
||||
}
|
||||
memcpy(transfer->data->data, data, size);
|
||||
|
||||
struct wl_data_source * source =
|
||||
wl_data_device_manager_create_data_source(wlWm.dataDeviceManager);
|
||||
wl_data_source_add_listener(source, &dataSourceListener, transfer);
|
||||
for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++)
|
||||
wl_data_source_offer(source, *mimetype);
|
||||
wl_data_source_offer(source, wlCb.lgMimetype);
|
||||
|
||||
wl_data_device_set_selection(wlCb.dataDevice, source,
|
||||
wlWm.keyboardEnterSerial);
|
||||
}
|
||||
|
||||
void waylandCBNotice(LG_ClipboardData type)
|
||||
{
|
||||
wlCb.haveRequest = true;
|
||||
wlCb.type = type;
|
||||
app_clipboardRequest(waylandCBReplyFn, NULL);
|
||||
}
|
||||
|
||||
void waylandCBRelease(void)
|
||||
{
|
||||
wlCb.haveRequest = false;
|
||||
}
|
||||
100
client/displayservers/Wayland/cursor.c
Normal file
100
client/displayservers/Wayland/cursor.c
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
static const uint32_t cursorBitmap[] = {
|
||||
0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000,
|
||||
0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000,
|
||||
};
|
||||
|
||||
static struct wl_buffer * createCursorBuffer(void)
|
||||
{
|
||||
int fd = memfd_create("lg-cursor", 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create cursor shared memory: %d", errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct wl_buffer * result = NULL;
|
||||
|
||||
if (ftruncate(fd, sizeof cursorBitmap) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to ftruncate cursor shared memory: %d", errno);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
void * shm_data = mmap(NULL, sizeof cursorBitmap, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (shm_data == MAP_FAILED)
|
||||
{
|
||||
DEBUG_ERROR("Failed to map memory for cursor: %d", errno);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
struct wl_shm_pool * pool = wl_shm_create_pool(wlWm.shm, fd, sizeof cursorBitmap);
|
||||
result = wl_shm_pool_create_buffer(pool, 0, 4, 4, 16, WL_SHM_FORMAT_XRGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
||||
memcpy(shm_data, cursorBitmap, sizeof cursorBitmap);
|
||||
munmap(shm_data, sizeof cursorBitmap);
|
||||
|
||||
fail:
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool waylandCursorInit(void)
|
||||
{
|
||||
if (!wlWm.compositor)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_compositor, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct wl_buffer * cursorBuffer = createCursorBuffer();
|
||||
if (cursorBuffer)
|
||||
{
|
||||
wlWm.cursor = wl_compositor_create_surface(wlWm.compositor);
|
||||
wl_surface_attach(wlWm.cursor, cursorBuffer, 0, 0);
|
||||
wl_surface_commit(wlWm.cursor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandShowPointer(bool show)
|
||||
{
|
||||
wlWm.showPointer = show;
|
||||
wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, show ? wlWm.cursor : NULL, 0, 0);
|
||||
}
|
||||
171
client/displayservers/Wayland/gl.c
Normal file
171
client/displayservers/Wayland/gl.c
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
#include "egl_dynprocs.h"
|
||||
|
||||
bool waylandEGLInit(int w, int h)
|
||||
{
|
||||
wlWm.eglWindow = wl_egl_window_create(wlWm.surface, w, h);
|
||||
if (!wlWm.eglWindow)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL window");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLDisplay waylandGetEGLDisplay(void)
|
||||
{
|
||||
EGLNativeDisplayType native = (EGLNativeDisplayType) wlWm.display;
|
||||
|
||||
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
|
||||
if (strstr(early_exts, "EGL_KHR_platform_wayland") != NULL &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplay)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplay");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native, NULL);
|
||||
}
|
||||
|
||||
if (strstr(early_exts, "EGL_EXT_platform_wayland") != NULL &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, native, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO("Using eglGetDisplay");
|
||||
return eglGetDisplay(native);
|
||||
}
|
||||
|
||||
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface)
|
||||
{
|
||||
eglSwapBuffers(display, surface);
|
||||
|
||||
if (wlWm.resizeSerial)
|
||||
{
|
||||
wl_egl_window_resize(wlWm.eglWindow, wlWm.width, wlWm.height, 0, 0);
|
||||
|
||||
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
|
||||
wl_region_add(region, 0, 0, wlWm.width, wlWm.height);
|
||||
wl_surface_set_opaque_region(wlWm.surface, region);
|
||||
wl_region_destroy(region);
|
||||
|
||||
app_handleResizeEvent(wlWm.width, wlWm.height, (struct Border) {0, 0, 0, 0});
|
||||
xdg_surface_ack_configure(wlWm.xdgSurface, wlWm.resizeSerial);
|
||||
wlWm.resizeSerial = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLNativeWindowType waylandGetEGLNativeWindow(void)
|
||||
{
|
||||
return (EGLNativeWindowType) wlWm.eglWindow;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
bool waylandOpenGLInit(void)
|
||||
{
|
||||
EGLint attr[] =
|
||||
{
|
||||
EGL_BUFFER_SIZE , 24,
|
||||
EGL_CONFORMANT , EGL_OPENGL_BIT,
|
||||
EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT,
|
||||
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
|
||||
EGL_RED_SIZE , 8,
|
||||
EGL_GREEN_SIZE , 8,
|
||||
EGL_BLUE_SIZE , 8,
|
||||
EGL_SAMPLE_BUFFERS , 0,
|
||||
EGL_SAMPLES , 0,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
wlWm.glDisplay = waylandGetEGLDisplay();
|
||||
|
||||
int maj, min;
|
||||
if (!eglInitialize(wlWm.glDisplay, &maj, &min))
|
||||
{
|
||||
DEBUG_ERROR("Unable to initialize EGL");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wlWm.glDisplay == EGL_NO_DISPLAY)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get EGL display (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint num_config;
|
||||
if (!eglChooseConfig(wlWm.glDisplay, attr, &wlWm.glConfig, 1, &num_config))
|
||||
{
|
||||
DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
wlWm.glSurface = eglCreateWindowSurface(wlWm.glDisplay, wlWm.glConfig, wlWm.eglWindow, NULL);
|
||||
if (wlWm.glSurface == EGL_NO_SURFACE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LG_DSGLContext waylandGLCreateContext(void)
|
||||
{
|
||||
eglBindAPI(EGL_OPENGL_API);
|
||||
return eglCreateContext(wlWm.glDisplay, wlWm.glConfig, EGL_NO_CONTEXT, NULL);
|
||||
}
|
||||
|
||||
void waylandGLDeleteContext(LG_DSGLContext context)
|
||||
{
|
||||
eglDestroyContext(wlWm.glDisplay, context);
|
||||
}
|
||||
|
||||
void waylandGLMakeCurrent(LG_DSGLContext context)
|
||||
{
|
||||
eglMakeCurrent(wlWm.glDisplay, wlWm.glSurface, wlWm.glSurface, context);
|
||||
}
|
||||
|
||||
void waylandGLSetSwapInterval(int interval)
|
||||
{
|
||||
eglSwapInterval(wlWm.glDisplay, interval);
|
||||
}
|
||||
|
||||
void waylandGLSwapBuffers(void)
|
||||
{
|
||||
waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface);
|
||||
}
|
||||
#endif
|
||||
59
client/displayservers/Wayland/idle.c
Normal file
59
client/displayservers/Wayland/idle.c
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
bool waylandIdleInit(void)
|
||||
{
|
||||
if (!wlWm.idleInhibitManager)
|
||||
DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will "
|
||||
"not be able to suppress idle states");
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandIdleFree(void)
|
||||
{
|
||||
if (wlWm.idleInhibitManager)
|
||||
{
|
||||
waylandUninhibitIdle();
|
||||
zwp_idle_inhibit_manager_v1_destroy(wlWm.idleInhibitManager);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandInhibitIdle(void)
|
||||
{
|
||||
if (wlWm.idleInhibitManager && !wlWm.idleInhibitor)
|
||||
wlWm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
|
||||
wlWm.idleInhibitManager, wlWm.surface);
|
||||
}
|
||||
|
||||
void waylandUninhibitIdle(void)
|
||||
{
|
||||
if (wlWm.idleInhibitor)
|
||||
{
|
||||
zwp_idle_inhibitor_v1_destroy(wlWm.idleInhibitor);
|
||||
wlWm.idleInhibitor = NULL;
|
||||
}
|
||||
}
|
||||
393
client/displayservers/Wayland/input.c
Normal file
393
client/displayservers/Wayland/input.c
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
// Mouse-handling listeners.
|
||||
|
||||
static void pointerMotionHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW)
|
||||
{
|
||||
wlWm.cursorX = wl_fixed_to_double(sxW);
|
||||
wlWm.cursorY = wl_fixed_to_double(syW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
if (!wlWm.warpSupport && !wlWm.relativePointer)
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void pointerEnterHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW,
|
||||
wl_fixed_t syW)
|
||||
{
|
||||
app_handleEnterEvent(true);
|
||||
|
||||
wl_pointer_set_cursor(pointer, serial, wlWm.showPointer ? wlWm.cursor : NULL, 0, 0);
|
||||
wlWm.pointerEnterSerial = serial;
|
||||
|
||||
wlWm.cursorX = wl_fixed_to_double(sxW);
|
||||
wlWm.cursorY = wl_fixed_to_double(syW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
if (wlWm.warpSupport)
|
||||
{
|
||||
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wlWm.relativePointer)
|
||||
return;
|
||||
|
||||
app_resyncMouseBasic();
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void pointerLeaveHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, struct wl_surface * surface)
|
||||
{
|
||||
app_handleEnterEvent(false);
|
||||
}
|
||||
|
||||
static void pointerAxisHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, uint32_t axis, wl_fixed_t value)
|
||||
{
|
||||
int button = value > 0 ?
|
||||
5 /* SPICE_MOUSE_BUTTON_DOWN */ :
|
||||
4 /* SPICE_MOUSE_BUTTON_UP */;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
}
|
||||
|
||||
static int mapWaylandToSpiceButton(uint32_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case BTN_LEFT:
|
||||
return 1; // SPICE_MOUSE_BUTTON_LEFT
|
||||
case BTN_MIDDLE:
|
||||
return 2; // SPICE_MOUSE_BUTTON_MIDDLE
|
||||
case BTN_RIGHT:
|
||||
return 3; // SPICE_MOUSE_BUTTON_RIGHT
|
||||
case BTN_SIDE:
|
||||
return 6; // SPICE_MOUSE_BUTTON_SIDE
|
||||
case BTN_EXTRA:
|
||||
return 7; // SPICE_MOUSE_BUTTON_EXTRA
|
||||
}
|
||||
|
||||
return 0; // SPICE_MOUSE_BUTTON_INVALID
|
||||
}
|
||||
|
||||
static void pointerButtonHandler(void *data, struct wl_pointer *pointer,
|
||||
uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW)
|
||||
{
|
||||
button = mapWaylandToSpiceButton(button);
|
||||
|
||||
if (stateW == WL_POINTER_BUTTON_STATE_PRESSED)
|
||||
app_handleButtonPress(button);
|
||||
else
|
||||
app_handleButtonRelease(button);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
.enter = pointerEnterHandler,
|
||||
.leave = pointerLeaveHandler,
|
||||
.motion = pointerMotionHandler,
|
||||
.button = pointerButtonHandler,
|
||||
.axis = pointerAxisHandler,
|
||||
};
|
||||
|
||||
static void relativePointerMotionHandler(void * data,
|
||||
struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo,
|
||||
wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW,
|
||||
wl_fixed_t dyUnaccelW)
|
||||
{
|
||||
wlWm.cursorX += wl_fixed_to_double(dxW);
|
||||
wlWm.cursorY += wl_fixed_to_double(dyW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
app_handleMouseRelative(
|
||||
wl_fixed_to_double(dxW),
|
||||
wl_fixed_to_double(dyW),
|
||||
wl_fixed_to_double(dxUnaccelW),
|
||||
wl_fixed_to_double(dyUnaccelW));
|
||||
}
|
||||
|
||||
static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
|
||||
.relative_motion = relativePointerMotionHandler,
|
||||
};
|
||||
|
||||
// Keyboard-handling listeners.
|
||||
|
||||
static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t format, int fd, uint32_t size)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
|
||||
{
|
||||
app_handleFocusEvent(true);
|
||||
wlWm.keyboardEnterSerial = serial;
|
||||
|
||||
uint32_t * key;
|
||||
wl_array_for_each(key, keys)
|
||||
app_handleKeyPress(*key);
|
||||
}
|
||||
|
||||
static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface)
|
||||
{
|
||||
app_handleFocusEvent(false);
|
||||
}
|
||||
|
||||
static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
|
||||
{
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
app_handleKeyPress(key);
|
||||
else
|
||||
app_handleKeyRelease(key);
|
||||
}
|
||||
|
||||
static void keyboardModifiersHandler(void * data,
|
||||
struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed,
|
||||
uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_keyboard_listener keyboardListener = {
|
||||
.keymap = keyboardKeymapHandler,
|
||||
.enter = keyboardEnterHandler,
|
||||
.leave = keyboardLeaveHandler,
|
||||
.key = keyboardKeyHandler,
|
||||
.modifiers = keyboardModifiersHandler,
|
||||
};
|
||||
|
||||
// Seat-handling listeners.
|
||||
|
||||
static void handlePointerCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
|
||||
if (!hasPointer && wlWm.pointer)
|
||||
{
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wlWm.pointer = NULL;
|
||||
}
|
||||
else if (hasPointer && !wlWm.pointer)
|
||||
{
|
||||
wlWm.pointer = wl_seat_get_pointer(wlWm.seat);
|
||||
wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleKeyboardCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
|
||||
if (!hasKeyboard && wlWm.keyboard)
|
||||
{
|
||||
wl_keyboard_destroy(wlWm.keyboard);
|
||||
wlWm.keyboard = NULL;
|
||||
}
|
||||
else if (hasKeyboard && !wlWm.keyboard)
|
||||
{
|
||||
wlWm.keyboard = wl_seat_get_keyboard(wlWm.seat);
|
||||
wl_keyboard_add_listener(wlWm.keyboard, &keyboardListener, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void seatCapabilitiesHandler(void * data, struct wl_seat * seat,
|
||||
uint32_t capabilities)
|
||||
{
|
||||
wlWm.capabilities = capabilities;
|
||||
handlePointerCapability(capabilities);
|
||||
handleKeyboardCapability(capabilities);
|
||||
}
|
||||
|
||||
static void seatNameHandler(void * data, struct wl_seat * seat,
|
||||
const char * name)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_seat_listener seatListener = {
|
||||
.capabilities = seatCapabilitiesHandler,
|
||||
.name = seatNameHandler,
|
||||
};
|
||||
|
||||
bool waylandInputInit(void)
|
||||
{
|
||||
if (!wlWm.seat)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_seat, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wlWm.warpSupport && (!wlWm.relativePointerManager || !wlWm.pointerConstraints))
|
||||
{
|
||||
DEBUG_WARN("Cursor warp is requested, but cannot be honoured due to lack "
|
||||
"of zwp_relative_pointer_manager_v1 or zwp_pointer_constraints_v1");
|
||||
wlWm.warpSupport = false;
|
||||
}
|
||||
|
||||
if (!wlWm.relativePointerManager)
|
||||
DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, "
|
||||
"mouse will not be captured");
|
||||
|
||||
if (!wlWm.pointerConstraints)
|
||||
DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse "
|
||||
"will not be captured");
|
||||
|
||||
if (!wlWm.keyboardInhibitManager)
|
||||
DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by "
|
||||
"compositor, keyboard will not be grabbed");
|
||||
|
||||
wl_seat_add_listener(wlWm.seat, &seatListener, NULL);
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
|
||||
if (wlWm.warpSupport)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandInputFree(void)
|
||||
{
|
||||
waylandUngrabPointer();
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wl_keyboard_destroy(wlWm.keyboard);
|
||||
wl_seat_destroy(wlWm.seat);
|
||||
}
|
||||
|
||||
void waylandGrabPointer(void)
|
||||
{
|
||||
if (!wlWm.relativePointerManager || !wlWm.pointerConstraints)
|
||||
return;
|
||||
|
||||
if (!wlWm.warpSupport && !wlWm.relativePointer)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
if (!wlWm.confinedPointer)
|
||||
{
|
||||
wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandUngrabPointer(void)
|
||||
{
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_destroy(wlWm.confinedPointer);
|
||||
wlWm.confinedPointer = NULL;
|
||||
}
|
||||
|
||||
if (!wlWm.warpSupport)
|
||||
{
|
||||
if (!wlWm.relativePointer)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
app_resyncMouseBasic();
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
}
|
||||
|
||||
void waylandGrabKeyboard(void)
|
||||
{
|
||||
if (wlWm.keyboardInhibitManager && !wlWm.keyboardInhibitor)
|
||||
{
|
||||
wlWm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
|
||||
wlWm.keyboardInhibitManager, wlWm.surface, wlWm.seat);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandUngrabKeyboard(void)
|
||||
{
|
||||
if (wlWm.keyboardInhibitor)
|
||||
{
|
||||
zwp_keyboard_shortcuts_inhibitor_v1_destroy(wlWm.keyboardInhibitor);
|
||||
wlWm.keyboardInhibitor = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void waylandWarpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if (x < 0) x = 0;
|
||||
else if (x >= wlWm.width) x = wlWm.width - 1;
|
||||
if (y < 0) y = 0;
|
||||
else if (y >= wlWm.height) y = wlWm.height - 1;
|
||||
|
||||
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
|
||||
wl_region_add(region, x, y, 1, 1);
|
||||
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region);
|
||||
wl_surface_commit(wlWm.surface);
|
||||
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct zwp_confined_pointer_v1 * confine;
|
||||
confine = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
wl_surface_commit(wlWm.surface);
|
||||
zwp_confined_pointer_v1_destroy(confine);
|
||||
}
|
||||
|
||||
wl_surface_commit(wlWm.surface);
|
||||
wl_region_destroy(region);
|
||||
}
|
||||
|
||||
void waylandRealignPointer(void)
|
||||
{
|
||||
if (!wlWm.warpSupport)
|
||||
app_resyncMouseBasic();
|
||||
}
|
||||
177
client/displayservers/Wayland/poll.c
Normal file
177
client/displayservers/Wayland/poll.c
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait
|
||||
|
||||
static void waylandDisplayCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
if (events & EPOLLERR)
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
else
|
||||
wl_display_read_events(wlWm.display);
|
||||
wl_display_dispatch_pending(wlWm.display);
|
||||
}
|
||||
|
||||
bool waylandPollInit(void)
|
||||
{
|
||||
wlWm.epollFd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (wlWm.epollFd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_list_init(&wlWm.poll);
|
||||
wl_list_init(&wlWm.pollFree);
|
||||
LG_LOCK_INIT(wlWm.pollLock);
|
||||
LG_LOCK_INIT(wlWm.pollFreeLock);
|
||||
|
||||
wlWm.displayFd = wl_display_get_fd(wlWm.display);
|
||||
if (!waylandEpollRegister(wlWm.displayFd, waylandDisplayCallback, NULL, EPOLLIN))
|
||||
{
|
||||
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandWait(unsigned int time)
|
||||
{
|
||||
while (wl_display_prepare_read(wlWm.display))
|
||||
wl_display_dispatch_pending(wlWm.display);
|
||||
wl_display_flush(wlWm.display);
|
||||
|
||||
struct epoll_event events[EPOLL_EVENTS];
|
||||
int count;
|
||||
if ((count = epoll_wait(wlWm.epollFd, events, EPOLL_EVENTS, time)) < 0)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
DEBUG_INFO("epoll failed: %s", strerror(errno));
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
return;
|
||||
}
|
||||
|
||||
bool sawDisplay = false;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
struct WaylandPoll * poll = events[i].data.ptr;
|
||||
if (!poll->removed)
|
||||
poll->callback(events[i].events, poll->opaque);
|
||||
if (poll->fd == wlWm.displayFd)
|
||||
sawDisplay = true;
|
||||
}
|
||||
|
||||
if (!sawDisplay)
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
struct WaylandPoll * node;
|
||||
struct WaylandPoll * temp;
|
||||
wl_list_for_each_safe(node, temp, &wlWm.pollFree, link)
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void waylandEpollRemoveNode(struct WaylandPoll * node)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
});
|
||||
}
|
||||
|
||||
bool waylandEpollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events)
|
||||
{
|
||||
struct WaylandPoll * node = malloc(sizeof(struct WaylandPoll));
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
node->fd = fd;
|
||||
node->removed = false;
|
||||
node->callback = callback;
|
||||
node->opaque = opaque;
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_insert(&wlWm.poll, &node->link);
|
||||
});
|
||||
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) {
|
||||
.events = events,
|
||||
.data = (epoll_data_t) { .ptr = node },
|
||||
}) < 0)
|
||||
{
|
||||
waylandEpollRemoveNode(node);
|
||||
free(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool waylandEpollUnregister(int fd)
|
||||
{
|
||||
struct WaylandPoll * node = NULL;
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_for_each(node, &wlWm.poll, link)
|
||||
{
|
||||
if (node->fd == fd)
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (!node)
|
||||
{
|
||||
DEBUG_ERROR("Attempt to unregister a fd that was not registered: %d", fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
node->removed = true;
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to unregistered from epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
waylandEpollRemoveNode(node);
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
wl_list_insert(&wlWm.pollFree, &node->link);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
89
client/displayservers/Wayland/registry.c
Normal file
89
client/displayservers/Wayland/registry.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
static void registryGlobalHandler(void * data, struct wl_registry * registry,
|
||||
uint32_t name, const char * interface, uint32_t version)
|
||||
{
|
||||
if (!strcmp(interface, wl_seat_interface.name) && !wlWm.seat)
|
||||
wlWm.seat = wl_registry_bind(wlWm.registry, name, &wl_seat_interface, 1);
|
||||
else if (!strcmp(interface, wl_shm_interface.name))
|
||||
wlWm.shm = wl_registry_bind(wlWm.registry, name, &wl_shm_interface, 1);
|
||||
else if (!strcmp(interface, wl_compositor_interface.name))
|
||||
wlWm.compositor = wl_registry_bind(wlWm.registry, name, &wl_compositor_interface, 4);
|
||||
else if (!strcmp(interface, xdg_wm_base_interface.name))
|
||||
wlWm.xdgWmBase = wl_registry_bind(wlWm.registry, name, &xdg_wm_base_interface, 1);
|
||||
else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name))
|
||||
wlWm.xdgDecorationManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zxdg_decoration_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name))
|
||||
wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_relative_pointer_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name))
|
||||
wlWm.pointerConstraints = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_pointer_constraints_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name))
|
||||
wlWm.keyboardInhibitManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, wl_data_device_manager_interface.name))
|
||||
wlWm.dataDeviceManager = wl_registry_bind(wlWm.registry, name,
|
||||
&wl_data_device_manager_interface, 3);
|
||||
else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name))
|
||||
wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_idle_inhibit_manager_v1_interface, 1);
|
||||
}
|
||||
|
||||
static void registryGlobalRemoveHandler(void * data,
|
||||
struct wl_registry * registry, uint32_t name)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registryListener = {
|
||||
.global = registryGlobalHandler,
|
||||
.global_remove = registryGlobalRemoveHandler,
|
||||
};
|
||||
|
||||
bool waylandRegistryInit(void)
|
||||
{
|
||||
wlWm.registry = wl_display_get_registry(wlWm.display);
|
||||
if (!wlWm.registry)
|
||||
{
|
||||
DEBUG_ERROR("Unable to find wl_registry");
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_registry_add_listener(wlWm.registry, ®istryListener, NULL);
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandRegistryFree(void)
|
||||
{
|
||||
wl_registry_destroy(wlWm.registry);
|
||||
}
|
||||
24
client/displayservers/Wayland/state.c
Normal file
24
client/displayservers/Wayland/state.c
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 "wayland.h"
|
||||
|
||||
struct WaylandDSState wlWm;
|
||||
struct WCBState wlCb;
|
||||
@@ -17,6 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "wayland.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -25,315 +28,45 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/input.h>
|
||||
#include <poll.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
# include <wayland-egl.h>
|
||||
# include "egl_dynprocs.h"
|
||||
# include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/countedbuffer.h"
|
||||
#include "common/option.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
|
||||
struct WaylandDSState
|
||||
static struct Option waylandOptions[] =
|
||||
{
|
||||
bool pointerGrabbed;
|
||||
bool keyboardGrabbed;
|
||||
|
||||
struct wl_display * display;
|
||||
struct wl_surface * surface;
|
||||
struct wl_registry * registry;
|
||||
struct wl_seat * seat;
|
||||
|
||||
struct wl_data_device_manager * dataDeviceManager;
|
||||
struct wl_data_device * dataDevice;
|
||||
|
||||
uint32_t capabilities;
|
||||
|
||||
struct wl_keyboard * keyboard;
|
||||
struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager;
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor;
|
||||
uint32_t keyboardEnterSerial;
|
||||
|
||||
struct wl_pointer * pointer;
|
||||
struct zwp_relative_pointer_manager_v1 * relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1 * pointerConstraints;
|
||||
struct zwp_relative_pointer_v1 * relativePointer;
|
||||
struct zwp_confined_pointer_v1 * confinedPointer;
|
||||
|
||||
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
|
||||
struct zwp_idle_inhibitor_v1 * idleInhibitor;
|
||||
};
|
||||
|
||||
struct WCBTransfer
|
||||
{
|
||||
void * data;
|
||||
size_t size;
|
||||
const char ** mimetypes;
|
||||
};
|
||||
|
||||
struct WCBState
|
||||
{
|
||||
enum LG_ClipboardData stashedType;
|
||||
char * stashedMimetype;
|
||||
uint8_t * stashedContents;
|
||||
ssize_t stashedSize;
|
||||
bool isReceiving;
|
||||
bool isSelfCopy;
|
||||
|
||||
bool haveRequest;
|
||||
LG_ClipboardData type;
|
||||
};
|
||||
|
||||
static struct WaylandDSState wm;
|
||||
static struct WCBState wcb;
|
||||
|
||||
// Registry-handling listeners.
|
||||
|
||||
static void registryGlobalHandler(void * data, struct wl_registry * registry,
|
||||
uint32_t name, const char * interface, uint32_t version)
|
||||
{
|
||||
if (!strcmp(interface, wl_seat_interface.name) && !wm.seat)
|
||||
wm.seat = wl_registry_bind(wm.registry, name, &wl_seat_interface, 1);
|
||||
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name))
|
||||
wm.relativePointerManager = wl_registry_bind(wm.registry, name,
|
||||
&zwp_relative_pointer_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name))
|
||||
wm.pointerConstraints = wl_registry_bind(wm.registry, name,
|
||||
&zwp_pointer_constraints_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name))
|
||||
wm.keyboardInhibitManager = wl_registry_bind(wm.registry, name,
|
||||
&zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, wl_data_device_manager_interface.name))
|
||||
wm.dataDeviceManager = wl_registry_bind(wm.registry, name,
|
||||
&wl_data_device_manager_interface, 3);
|
||||
else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name))
|
||||
wm.idleInhibitManager = wl_registry_bind(wm.registry, name,
|
||||
&zwp_idle_inhibit_manager_v1_interface, 1);
|
||||
}
|
||||
|
||||
static void registryGlobalRemoveHandler(void * data,
|
||||
struct wl_registry * registry, uint32_t name)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registryListener = {
|
||||
.global = registryGlobalHandler,
|
||||
.global_remove = registryGlobalRemoveHandler,
|
||||
};
|
||||
|
||||
// Mouse-handling listeners.
|
||||
|
||||
static void pointerMotionHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW)
|
||||
{
|
||||
if (wm.relativePointer)
|
||||
return;
|
||||
|
||||
int sx = wl_fixed_to_int(sxW);
|
||||
int sy = wl_fixed_to_int(syW);
|
||||
app_updateCursorPos(sx, sy);
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void pointerEnterHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW,
|
||||
wl_fixed_t syW)
|
||||
{
|
||||
if (wm.relativePointer)
|
||||
return;
|
||||
|
||||
int sx = wl_fixed_to_int(sxW);
|
||||
int sy = wl_fixed_to_int(syW);
|
||||
app_updateCursorPos(sx, sy);
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void pointerLeaveHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, struct wl_surface * surface)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void pointerAxisHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, uint32_t axis, wl_fixed_t value)
|
||||
{
|
||||
int button = value > 0 ?
|
||||
5 /* SPICE_MOUSE_BUTTON_DOWN */ :
|
||||
4 /* SPICE_MOUSE_BUTTON_UP */;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
}
|
||||
|
||||
static int mapWaylandToSpiceButton(uint32_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case BTN_LEFT:
|
||||
return 1; // SPICE_MOUSE_BUTTON_LEFT
|
||||
case BTN_MIDDLE:
|
||||
return 2; // SPICE_MOUSE_BUTTON_MIDDLE
|
||||
case BTN_RIGHT:
|
||||
return 3; // SPICE_MOUSE_BUTTON_RIGHT
|
||||
case BTN_SIDE:
|
||||
return 6; // SPICE_MOUSE_BUTTON_SIDE
|
||||
case BTN_EXTRA:
|
||||
return 7; // SPICE_MOUSE_BUTTON_EXTRA
|
||||
}
|
||||
|
||||
return 0; // SPICE_MOUSE_BUTTON_INVALID
|
||||
}
|
||||
|
||||
static void pointerButtonHandler(void *data, struct wl_pointer *pointer,
|
||||
uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW)
|
||||
{
|
||||
button = mapWaylandToSpiceButton(button);
|
||||
|
||||
if (stateW == WL_POINTER_BUTTON_STATE_PRESSED)
|
||||
app_handleButtonPress(button);
|
||||
else
|
||||
app_handleButtonRelease(button);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
.enter = pointerEnterHandler,
|
||||
.leave = pointerLeaveHandler,
|
||||
.motion = pointerMotionHandler,
|
||||
.button = pointerButtonHandler,
|
||||
.axis = pointerAxisHandler,
|
||||
};
|
||||
|
||||
static void waylandInhibitIdle(void)
|
||||
{
|
||||
if (wm.idleInhibitManager && !wm.idleInhibitor)
|
||||
wm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
|
||||
wm.idleInhibitManager, wm.surface);
|
||||
}
|
||||
|
||||
static void waylandUninhibitIdle(void)
|
||||
{
|
||||
if (wm.idleInhibitor)
|
||||
{
|
||||
zwp_idle_inhibitor_v1_destroy(wm.idleInhibitor);
|
||||
wm.idleInhibitor = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard-handling listeners.
|
||||
|
||||
static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t format, int fd, uint32_t size)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
|
||||
{
|
||||
wm.keyboardEnterSerial = serial;
|
||||
|
||||
uint32_t * key;
|
||||
wl_array_for_each(key, keys)
|
||||
app_handleKeyPress(*key);
|
||||
}
|
||||
|
||||
static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
|
||||
{
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
app_handleKeyPress(key);
|
||||
else
|
||||
app_handleKeyRelease(key);
|
||||
}
|
||||
|
||||
static void keyboardModifiersHandler(void * data,
|
||||
struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed,
|
||||
uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_keyboard_listener keyboardListener = {
|
||||
.keymap = keyboardKeymapHandler,
|
||||
.enter = keyboardEnterHandler,
|
||||
.leave = keyboardLeaveHandler,
|
||||
.key = keyboardKeyHandler,
|
||||
.modifiers = keyboardModifiersHandler,
|
||||
};
|
||||
|
||||
// Seat-handling listeners.
|
||||
|
||||
static void handlePointerCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
|
||||
if (!hasPointer && wm.pointer)
|
||||
{
|
||||
wl_pointer_destroy(wm.pointer);
|
||||
wm.pointer = NULL;
|
||||
}
|
||||
else if (hasPointer && !wm.pointer)
|
||||
{
|
||||
wm.pointer = wl_seat_get_pointer(wm.seat);
|
||||
wl_pointer_add_listener(wm.pointer, &pointerListener, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleKeyboardCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
|
||||
if (!hasKeyboard && wm.keyboard)
|
||||
{
|
||||
wl_keyboard_destroy(wm.keyboard);
|
||||
wm.keyboard = NULL;
|
||||
}
|
||||
else if (hasKeyboard && !wm.keyboard)
|
||||
{
|
||||
wm.keyboard = wl_seat_get_keyboard(wm.seat);
|
||||
wl_keyboard_add_listener(wm.keyboard, &keyboardListener, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void seatCapabilitiesHandler(void * data, struct wl_seat * seat,
|
||||
uint32_t capabilities)
|
||||
{
|
||||
wm.capabilities = capabilities;
|
||||
handlePointerCapability(capabilities);
|
||||
handleKeyboardCapability(capabilities);
|
||||
}
|
||||
|
||||
static void seatNameHandler(void * data, struct wl_seat * seat,
|
||||
const char * name)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_seat_listener seatListener = {
|
||||
.capabilities = seatCapabilitiesHandler,
|
||||
.name = seatNameHandler,
|
||||
.module = "wayland",
|
||||
.name = "warpSupport",
|
||||
.description = "Enable cursor warping",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
static bool waylandEarlyInit(void)
|
||||
{
|
||||
if (!getenv("SDL_VIDEODRIVER"))
|
||||
{
|
||||
int err = setenv("SDL_VIDEODRIVER", "wayland", 1);
|
||||
if (err < 0)
|
||||
{
|
||||
DEBUG_ERROR("Unable to set the env variable SDL_VIDEODRIVER: %d", err);
|
||||
return false;
|
||||
}
|
||||
DEBUG_INFO("SDL_VIDEODRIVER has been set to wayland");
|
||||
}
|
||||
|
||||
// Request to receive EPIPE instead of SIGPIPE when one end of a pipe
|
||||
// disconnects while a write is pending. This is useful to the Wayland
|
||||
// clipboard backend, where an arbitrary application is on the other end of
|
||||
@@ -343,41 +76,52 @@ static bool waylandEarlyInit(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool waylandInit(SDL_SysWMinfo * info)
|
||||
static void waylandSetup(void)
|
||||
{
|
||||
memset(&wm, 0, sizeof(wm));
|
||||
option_register(waylandOptions);
|
||||
}
|
||||
|
||||
wm.display = info->info.wl.display;
|
||||
wm.surface = info->info.wl.surface;
|
||||
wm.registry = wl_display_get_registry(wm.display);
|
||||
static bool waylandProbe(void)
|
||||
{
|
||||
return getenv("WAYLAND_DISPLAY") != NULL;
|
||||
}
|
||||
|
||||
wl_registry_add_listener(wm.registry, ®istryListener, NULL);
|
||||
wl_display_roundtrip(wm.display);
|
||||
static bool waylandInit(const LG_DSInitParams params)
|
||||
{
|
||||
memset(&wlWm, 0, sizeof(wlWm));
|
||||
|
||||
if (!wm.seat || !wm.dataDeviceManager)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing a required interface, will not proceed");
|
||||
wlWm.warpSupport = option_get_bool("wayland", "warpSupport");
|
||||
|
||||
wlWm.display = wl_display_connect(NULL);
|
||||
|
||||
if (!waylandPollInit())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!wm.relativePointerManager)
|
||||
DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, "
|
||||
"mouse will not be captured");
|
||||
if (!wm.pointerConstraints)
|
||||
DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse "
|
||||
"will not be captured");
|
||||
if (!wm.keyboardInhibitManager)
|
||||
DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by "
|
||||
"compositor, keyboard will not be grabbed");
|
||||
if (!wm.idleInhibitManager)
|
||||
DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will "
|
||||
"not be able to suppress idle states");
|
||||
if (!waylandRegistryInit())
|
||||
return false;
|
||||
|
||||
wl_seat_add_listener(wm.seat, &seatListener, NULL);
|
||||
wl_display_roundtrip(wm.display);
|
||||
if (!waylandIdleInit())
|
||||
return false;
|
||||
|
||||
wm.dataDevice = wl_data_device_manager_get_data_device(
|
||||
wm.dataDeviceManager, wm.seat);
|
||||
if (!waylandInputInit())
|
||||
return false;
|
||||
|
||||
if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless))
|
||||
return false;
|
||||
|
||||
if (!waylandEGLInit(params.w, params.h))
|
||||
return false;
|
||||
|
||||
if (!waylandCursorInit())
|
||||
return false;
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (params.opengl && !waylandOpenGLInit())
|
||||
return false;
|
||||
#endif
|
||||
|
||||
wlWm.width = params.w;
|
||||
wlWm.height = params.h;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -386,482 +130,69 @@ static void waylandStartup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void relativePointerMotionHandler(void * data,
|
||||
struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo,
|
||||
wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW,
|
||||
wl_fixed_t dyUnaccelW)
|
||||
static void waylandShutdown(void)
|
||||
{
|
||||
double dxUnaccel = wl_fixed_to_double(dxUnaccelW);
|
||||
double dyUnaccel = wl_fixed_to_double(dyUnaccelW);
|
||||
app_handleMouseGrabbed(dxUnaccel, dyUnaccel);
|
||||
}
|
||||
|
||||
static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
|
||||
.relative_motion = relativePointerMotionHandler,
|
||||
};
|
||||
|
||||
static void waylandGrabPointer(void)
|
||||
{
|
||||
if (!wm.relativePointerManager || !wm.pointerConstraints)
|
||||
return;
|
||||
|
||||
if (!wm.relativePointer)
|
||||
{
|
||||
wm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wm.relativePointerManager, wm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
if (!wm.confinedPointer)
|
||||
{
|
||||
wm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wm.pointerConstraints, wm.surface, wm.pointer, NULL,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
}
|
||||
}
|
||||
|
||||
static void waylandUngrabPointer(void)
|
||||
{
|
||||
if (wm.relativePointer)
|
||||
{
|
||||
zwp_relative_pointer_v1_destroy(wm.relativePointer);
|
||||
wm.relativePointer = NULL;
|
||||
}
|
||||
|
||||
if (wm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_destroy(wm.confinedPointer);
|
||||
wm.confinedPointer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void waylandGrabKeyboard(void)
|
||||
{
|
||||
if (wm.keyboardInhibitManager && !wm.keyboardInhibitor)
|
||||
{
|
||||
wm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
|
||||
wm.keyboardInhibitManager, wm.surface, wm.seat);
|
||||
}
|
||||
}
|
||||
|
||||
static void waylandUngrabKeyboard(void)
|
||||
{
|
||||
if (wm.keyboardInhibitor)
|
||||
{
|
||||
zwp_keyboard_shortcuts_inhibitor_v1_destroy(wm.keyboardInhibitor);
|
||||
wm.keyboardInhibitor = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void waylandWarpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
// This is an unsupported operation on Wayland.
|
||||
}
|
||||
|
||||
static void waylandRealignPointer(void)
|
||||
{
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void waylandFree(void)
|
||||
{
|
||||
waylandUngrabPointer();
|
||||
|
||||
if (wm.idleInhibitManager)
|
||||
{
|
||||
waylandUninhibitIdle();
|
||||
zwp_idle_inhibit_manager_v1_destroy(wm.idleInhibitManager);
|
||||
}
|
||||
|
||||
// TODO: these also need to be freed, but are currently owned by SDL.
|
||||
// wl_display_destroy(wm.display);
|
||||
// wl_surface_destroy(wm.surface);
|
||||
wl_pointer_destroy(wm.pointer);
|
||||
wl_seat_destroy(wm.seat);
|
||||
wl_registry_destroy(wm.registry);
|
||||
waylandIdleFree();
|
||||
waylandWindowFree();
|
||||
waylandInputFree();
|
||||
waylandRegistryFree();
|
||||
wl_display_disconnect(wlWm.display);
|
||||
}
|
||||
|
||||
static bool waylandGetProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
if (prop == LG_DS_WARP_SUPPORT)
|
||||
{
|
||||
*(bool*)ret = false;
|
||||
*(enum LG_DSWarpSupport*)ret = wlWm.warpSupport ? LG_DS_WARP_SURFACE : LG_DS_WARP_NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool waylandEventFilter(SDL_Event * event)
|
||||
{
|
||||
/* prevent the default processing for the following events */
|
||||
switch(event->type)
|
||||
{
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char * textMimetypes[] =
|
||||
{
|
||||
"text/plain",
|
||||
"text/plain;charset=utf-8",
|
||||
"TEXT",
|
||||
"STRING",
|
||||
"UTF8_STRING",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * pngMimetypes[] =
|
||||
{
|
||||
"image/png",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * bmpMimetypes[] =
|
||||
{
|
||||
"image/bmp",
|
||||
"image/x-bmp",
|
||||
"image/x-MS-bmp",
|
||||
"image/x-win-bitmap",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * tiffMimetypes[] =
|
||||
{
|
||||
"image/tiff",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * jpegMimetypes[] =
|
||||
{
|
||||
"image/jpeg",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char ** cbTypeToMimetypes(enum LG_ClipboardData type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT:
|
||||
return textMimetypes;
|
||||
case LG_CLIPBOARD_DATA_PNG:
|
||||
return pngMimetypes;
|
||||
case LG_CLIPBOARD_DATA_BMP:
|
||||
return bmpMimetypes;
|
||||
case LG_CLIPBOARD_DATA_TIFF:
|
||||
return tiffMimetypes;
|
||||
case LG_CLIPBOARD_DATA_JPEG:
|
||||
return jpegMimetypes;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard type");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static bool containsMimetype(const char ** mimetypes,
|
||||
const char * needle)
|
||||
{
|
||||
for (const char ** mimetype = mimetypes; *mimetype; mimetype++)
|
||||
if (!strcmp(needle, *mimetype))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mimetypeEndswith(const char * mimetype, const char * what)
|
||||
{
|
||||
size_t mimetypeLen = strlen(mimetype);
|
||||
size_t whatLen = strlen(what);
|
||||
|
||||
if (mimetypeLen < whatLen)
|
||||
return false;
|
||||
|
||||
return !strcmp(mimetype + mimetypeLen - whatLen, what);
|
||||
}
|
||||
|
||||
static bool isTextMimetype(const char * mimetype)
|
||||
{
|
||||
if (containsMimetype(textMimetypes, mimetype))
|
||||
return true;
|
||||
|
||||
char * text = "text/";
|
||||
if (!strncmp(mimetype, text, strlen(text)))
|
||||
return true;
|
||||
|
||||
if (mimetypeEndswith(mimetype, "script") ||
|
||||
mimetypeEndswith(mimetype, "xml") ||
|
||||
mimetypeEndswith(mimetype, "yaml"))
|
||||
return true;
|
||||
|
||||
if (strstr(mimetype, "json"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static enum LG_ClipboardData mimetypeToCbType(const char * mimetype)
|
||||
{
|
||||
if (isTextMimetype(mimetype))
|
||||
return LG_CLIPBOARD_DATA_TEXT;
|
||||
|
||||
if (containsMimetype(pngMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_PNG;
|
||||
|
||||
if (containsMimetype(bmpMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_BMP;
|
||||
|
||||
if (containsMimetype(tiffMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_TIFF;
|
||||
|
||||
if (containsMimetype(jpegMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_JPEG;
|
||||
|
||||
return LG_CLIPBOARD_DATA_NONE;
|
||||
}
|
||||
|
||||
// Destination client handlers.
|
||||
|
||||
static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer,
|
||||
const char * mimetype)
|
||||
{
|
||||
enum LG_ClipboardData type = mimetypeToCbType(mimetype);
|
||||
// Oftentimes we'll get text/html alongside text/png, but would prefer to send
|
||||
// image/png. In general, prefer images over text content.
|
||||
if (type != LG_CLIPBOARD_DATA_NONE &&
|
||||
(wcb.stashedType == LG_CLIPBOARD_DATA_NONE ||
|
||||
wcb.stashedType == LG_CLIPBOARD_DATA_TEXT))
|
||||
{
|
||||
wcb.stashedType = type;
|
||||
if (wcb.stashedMimetype)
|
||||
free(wcb.stashedMimetype);
|
||||
wcb.stashedMimetype = strdup(mimetype);
|
||||
}
|
||||
}
|
||||
|
||||
static void dataOfferHandleSourceActions(void * data,
|
||||
struct wl_data_offer * offer, uint32_t sourceActions)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void dataOfferHandleAction(void * data, struct wl_data_offer * offer,
|
||||
uint32_t dndAction)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_data_offer_listener dataOfferListener = {
|
||||
.offer = dataOfferHandleOffer,
|
||||
.source_actions = dataOfferHandleSourceActions,
|
||||
.action = dataOfferHandleAction,
|
||||
};
|
||||
|
||||
static void dataDeviceHandleDataOffer(void * data,
|
||||
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
|
||||
{
|
||||
wcb.stashedType = LG_CLIPBOARD_DATA_NONE;
|
||||
wl_data_offer_add_listener(offer, &dataOfferListener, NULL);
|
||||
}
|
||||
|
||||
static void dataDeviceHandleSelection(void * data,
|
||||
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
|
||||
{
|
||||
if (wcb.stashedType == LG_CLIPBOARD_DATA_NONE || !offer)
|
||||
return;
|
||||
|
||||
int fds[2];
|
||||
if (pipe(fds) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno));
|
||||
abort();
|
||||
}
|
||||
|
||||
wcb.isReceiving = true;
|
||||
wcb.isSelfCopy = false;
|
||||
wl_data_offer_receive(offer, wcb.stashedMimetype, fds[1]);
|
||||
close(fds[1]);
|
||||
free(wcb.stashedMimetype);
|
||||
wcb.stashedMimetype = NULL;
|
||||
|
||||
wl_display_roundtrip(wm.display);
|
||||
|
||||
if (wcb.stashedContents)
|
||||
{
|
||||
free(wcb.stashedContents);
|
||||
wcb.stashedContents = NULL;
|
||||
}
|
||||
|
||||
size_t size = 4096, numRead = 0;
|
||||
uint8_t * buf = (uint8_t *) malloc(size);
|
||||
while (true)
|
||||
{
|
||||
ssize_t result = read(fds[0], buf + numRead, size - numRead);
|
||||
if (result < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno));
|
||||
abort();
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
buf[numRead] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
numRead += result;
|
||||
if (numRead >= size)
|
||||
{
|
||||
size *= 2;
|
||||
void * nbuf = realloc(buf, size);
|
||||
if (!nbuf) {
|
||||
DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno));
|
||||
abort();
|
||||
}
|
||||
|
||||
buf = nbuf;
|
||||
}
|
||||
}
|
||||
|
||||
wcb.stashedSize = numRead;
|
||||
wcb.stashedContents = buf;
|
||||
wcb.isReceiving = false;
|
||||
|
||||
close(fds[0]);
|
||||
wl_data_offer_destroy(offer);
|
||||
|
||||
if (!wcb.isSelfCopy)
|
||||
app_clipboardNotify(wcb.stashedType, 0);
|
||||
}
|
||||
|
||||
static const struct wl_data_device_listener dataDeviceListener = {
|
||||
.data_offer = dataDeviceHandleDataOffer,
|
||||
.selection = dataDeviceHandleSelection,
|
||||
};
|
||||
|
||||
static void waylandCBRequest(LG_ClipboardData type)
|
||||
{
|
||||
// We only notified once, so it must be this.
|
||||
assert(type == wcb.stashedType);
|
||||
app_clipboardData(wcb.stashedType, wcb.stashedContents, wcb.stashedSize);
|
||||
}
|
||||
|
||||
static bool waylandCBInit(void)
|
||||
{
|
||||
memset(&wcb, 0, sizeof(wcb));
|
||||
|
||||
wcb.stashedType = LG_CLIPBOARD_DATA_NONE;
|
||||
wl_data_device_add_listener(wm.dataDevice, &dataDeviceListener, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dataSourceHandleSend(void * data, struct wl_data_source * source,
|
||||
const char * mimetype, int fd)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
if (wcb.isReceiving)
|
||||
wcb.isSelfCopy = true;
|
||||
else if (containsMimetype(transfer->mimetypes, mimetype))
|
||||
{
|
||||
// Consider making this do non-blocking sends to not stall the Wayland
|
||||
// event loop if it becomes a problem. This is "fine" in the sense that
|
||||
// wl-copy also stalls like this, but it's not necessary.
|
||||
fcntl(fd, F_SETFL, 0);
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < transfer->size)
|
||||
{
|
||||
ssize_t written = write(fd, transfer->data + pos, transfer->size - pos);
|
||||
if (written < 0)
|
||||
{
|
||||
if (errno != EPIPE)
|
||||
DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
pos += written;
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void dataSourceHandleCancelled(void * data,
|
||||
struct wl_data_source * source)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
free(transfer->data);
|
||||
free(transfer);
|
||||
wl_data_source_destroy(source);
|
||||
}
|
||||
|
||||
static const struct wl_data_source_listener dataSourceListener = {
|
||||
.send = dataSourceHandleSend,
|
||||
.cancelled = dataSourceHandleCancelled,
|
||||
};
|
||||
|
||||
static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size)
|
||||
{
|
||||
struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer));
|
||||
void * dataCopy = malloc(size);
|
||||
memcpy(dataCopy, data, size);
|
||||
*transfer = (struct WCBTransfer) {
|
||||
.data = dataCopy,
|
||||
.size = size,
|
||||
.mimetypes = cbTypeToMimetypes(type),
|
||||
};
|
||||
|
||||
struct wl_data_source * source =
|
||||
wl_data_device_manager_create_data_source(wm.dataDeviceManager);
|
||||
wl_data_source_add_listener(source, &dataSourceListener, transfer);
|
||||
for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++)
|
||||
wl_data_source_offer(source, *mimetype);
|
||||
|
||||
wl_data_device_set_selection(wm.dataDevice, source,
|
||||
wm.keyboardEnterSerial);
|
||||
}
|
||||
|
||||
static void waylandCBNotice(LG_ClipboardData type)
|
||||
{
|
||||
wcb.haveRequest = true;
|
||||
wcb.type = type;
|
||||
app_clipboardRequest(waylandCBReplyFn, NULL);
|
||||
}
|
||||
|
||||
static void waylandCBRelease(void)
|
||||
{
|
||||
wcb.haveRequest = false;
|
||||
}
|
||||
|
||||
struct LG_DisplayServerOps LGDS_Wayland =
|
||||
{
|
||||
.subsystem = SDL_SYSWM_WAYLAND,
|
||||
.earlyInit = waylandEarlyInit,
|
||||
.init = waylandInit,
|
||||
.startup = waylandStartup,
|
||||
.free = waylandFree,
|
||||
.getProp = waylandGetProp,
|
||||
.eventFilter = waylandEventFilter,
|
||||
.grabPointer = waylandGrabPointer,
|
||||
.ungrabPointer = waylandUngrabPointer,
|
||||
.grabKeyboard = waylandGrabKeyboard,
|
||||
.ungrabKeyboard = waylandUngrabKeyboard,
|
||||
.warpPointer = waylandWarpPointer,
|
||||
.realignPointer = waylandRealignPointer,
|
||||
.inhibitIdle = waylandInhibitIdle,
|
||||
.uninhibitIdle = waylandUninhibitIdle,
|
||||
.setup = waylandSetup,
|
||||
.probe = waylandProbe,
|
||||
.earlyInit = waylandEarlyInit,
|
||||
.init = waylandInit,
|
||||
.startup = waylandStartup,
|
||||
.shutdown = waylandShutdown,
|
||||
.free = waylandFree,
|
||||
.getProp = waylandGetProp,
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
.getEGLDisplay = waylandGetEGLDisplay,
|
||||
.getEGLNativeWindow = waylandGetEGLNativeWindow,
|
||||
.eglSwapBuffers = waylandEGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
.glCreateContext = waylandGLCreateContext,
|
||||
.glDeleteContext = waylandGLDeleteContext,
|
||||
.glMakeCurrent = waylandGLMakeCurrent,
|
||||
.glSetSwapInterval = waylandGLSetSwapInterval,
|
||||
.glSwapBuffers = waylandGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
.showPointer = waylandShowPointer,
|
||||
.grabPointer = waylandGrabPointer,
|
||||
.ungrabPointer = waylandUngrabPointer,
|
||||
.grabKeyboard = waylandGrabKeyboard,
|
||||
.ungrabKeyboard = waylandUngrabKeyboard,
|
||||
.warpPointer = waylandWarpPointer,
|
||||
.realignPointer = waylandRealignPointer,
|
||||
.isValidPointerPos = waylandIsValidPointerPos,
|
||||
.inhibitIdle = waylandInhibitIdle,
|
||||
.uninhibitIdle = waylandUninhibitIdle,
|
||||
.wait = waylandWait,
|
||||
.setWindowSize = waylandSetWindowSize,
|
||||
.setFullscreen = waylandSetFullscreen,
|
||||
.getFullscreen = waylandGetFullscreen,
|
||||
|
||||
.cbInit = waylandCBInit,
|
||||
.cbNotice = waylandCBNotice,
|
||||
|
||||
219
client/displayservers/Wayland/wayland.h
Normal file
219
client/displayservers/Wayland/wayland.h
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
# include <wayland-egl.h>
|
||||
# include <EGL/egl.h>
|
||||
# include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#include "common/locking.h"
|
||||
#include "common/countedbuffer.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
|
||||
typedef void (*WaylandPollCallback)(uint32_t events, void * opaque);
|
||||
|
||||
struct WaylandPoll
|
||||
{
|
||||
int fd;
|
||||
bool removed;
|
||||
WaylandPollCallback callback;
|
||||
void * opaque;
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
struct WaylandDSState
|
||||
{
|
||||
bool pointerGrabbed;
|
||||
bool keyboardGrabbed;
|
||||
|
||||
struct wl_display * display;
|
||||
struct wl_surface * surface;
|
||||
struct wl_registry * registry;
|
||||
struct wl_seat * seat;
|
||||
struct wl_shm * shm;
|
||||
struct wl_compositor * compositor;
|
||||
|
||||
int32_t width, height;
|
||||
bool fullscreen;
|
||||
uint32_t resizeSerial;
|
||||
bool configured;
|
||||
bool warpSupport;
|
||||
double cursorX, cursorY;
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
struct wl_egl_window * eglWindow;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
EGLDisplay glDisplay;
|
||||
EGLConfig glConfig;
|
||||
EGLSurface glSurface;
|
||||
#endif
|
||||
|
||||
struct xdg_wm_base * xdgWmBase;
|
||||
struct xdg_surface * xdgSurface;
|
||||
struct xdg_toplevel * xdgToplevel;
|
||||
struct zxdg_decoration_manager_v1 * xdgDecorationManager;
|
||||
struct zxdg_toplevel_decoration_v1 * xdgToplevelDecoration;
|
||||
|
||||
struct wl_surface * cursor;
|
||||
|
||||
struct wl_data_device_manager * dataDeviceManager;
|
||||
|
||||
uint32_t capabilities;
|
||||
|
||||
struct wl_keyboard * keyboard;
|
||||
struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager;
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor;
|
||||
uint32_t keyboardEnterSerial;
|
||||
|
||||
struct wl_pointer * pointer;
|
||||
struct zwp_relative_pointer_manager_v1 * relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1 * pointerConstraints;
|
||||
struct zwp_relative_pointer_v1 * relativePointer;
|
||||
struct zwp_confined_pointer_v1 * confinedPointer;
|
||||
bool showPointer;
|
||||
uint32_t pointerEnterSerial;
|
||||
|
||||
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
|
||||
struct zwp_idle_inhibitor_v1 * idleInhibitor;
|
||||
|
||||
struct wl_list poll; // WaylandPoll::link
|
||||
struct wl_list pollFree; // WaylandPoll::link
|
||||
LG_Lock pollLock;
|
||||
LG_Lock pollFreeLock;
|
||||
int epollFd;
|
||||
int displayFd;
|
||||
};
|
||||
|
||||
struct WCBTransfer
|
||||
{
|
||||
struct CountedBuffer * data;
|
||||
const char ** mimetypes;
|
||||
};
|
||||
|
||||
struct ClipboardRead
|
||||
{
|
||||
int fd;
|
||||
size_t size;
|
||||
size_t numRead;
|
||||
uint8_t * buf;
|
||||
enum LG_ClipboardData type;
|
||||
struct wl_data_offer * offer;
|
||||
};
|
||||
|
||||
struct WCBState
|
||||
{
|
||||
struct wl_data_device * dataDevice;
|
||||
char lgMimetype[64];
|
||||
|
||||
enum LG_ClipboardData pendingType;
|
||||
char * pendingMimetype;
|
||||
bool isSelfCopy;
|
||||
|
||||
enum LG_ClipboardData stashedType;
|
||||
uint8_t * stashedContents;
|
||||
ssize_t stashedSize;
|
||||
|
||||
bool haveRequest;
|
||||
LG_ClipboardData type;
|
||||
|
||||
struct ClipboardRead * currentRead;
|
||||
};
|
||||
|
||||
extern struct WaylandDSState wlWm;
|
||||
extern struct WCBState wlCb;
|
||||
|
||||
// clipboard module
|
||||
bool waylandCBInit(void);
|
||||
void waylandCBRequest(LG_ClipboardData type);
|
||||
void waylandCBNotice(LG_ClipboardData type);
|
||||
void waylandCBRelease(void);
|
||||
|
||||
// cursor module
|
||||
bool waylandCursorInit(void);
|
||||
void waylandShowPointer(bool show);
|
||||
|
||||
// gl module
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
bool waylandEGLInit(int w, int h);
|
||||
EGLDisplay waylandGetEGLDisplay(void);
|
||||
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLNativeWindowType waylandGetEGLNativeWindow(void);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
bool waylandOpenGLInit(void);
|
||||
LG_DSGLContext waylandGLCreateContext(void);
|
||||
void waylandGLDeleteContext(LG_DSGLContext context);
|
||||
void waylandGLMakeCurrent(LG_DSGLContext context);
|
||||
void waylandGLSetSwapInterval(int interval);
|
||||
void waylandGLSwapBuffers(void);
|
||||
#endif
|
||||
|
||||
// idle module
|
||||
bool waylandIdleInit(void);
|
||||
void waylandIdleFree(void);
|
||||
void waylandInhibitIdle(void);
|
||||
void waylandUninhibitIdle(void);
|
||||
|
||||
// input module
|
||||
bool waylandInputInit(void);
|
||||
void waylandInputFree(void);
|
||||
void waylandGrabKeyboard(void);
|
||||
void waylandGrabPointer(void);
|
||||
void waylandUngrabKeyboard(void);
|
||||
void waylandUngrabPointer(void);
|
||||
void waylandRealignPointer(void);
|
||||
void waylandWarpPointer(int x, int y, bool exiting);
|
||||
|
||||
// poll module
|
||||
bool waylandPollInit(void);
|
||||
void waylandWait(unsigned int time);
|
||||
bool waylandEpollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events);
|
||||
bool waylandEpollUnregister(int fd);
|
||||
|
||||
// registry module
|
||||
bool waylandRegistryInit(void);
|
||||
void waylandRegistryFree(void);
|
||||
|
||||
// window module
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless);
|
||||
void waylandWindowFree(void);
|
||||
void waylandSetWindowSize(int x, int y);
|
||||
void waylandSetFullscreen(bool fs);
|
||||
bool waylandGetFullscreen(void);
|
||||
bool waylandIsValidPointerPos(int x, int y);
|
||||
171
client/displayservers/Wayland/window.c
Normal file
171
client/displayservers/Wayland/window.c
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
// XDG WM base listeners.
|
||||
|
||||
static void xdgWmBasePing(void * data, struct xdg_wm_base * xdgWmBase, uint32_t serial)
|
||||
{
|
||||
xdg_wm_base_pong(xdgWmBase, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_wm_base_listener xdgWmBaseListener = {
|
||||
.ping = xdgWmBasePing,
|
||||
};
|
||||
|
||||
// Surface-handling listeners.
|
||||
|
||||
static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface,
|
||||
uint32_t serial)
|
||||
{
|
||||
if (wlWm.configured)
|
||||
wlWm.resizeSerial = serial;
|
||||
else
|
||||
{
|
||||
xdg_surface_ack_configure(xdgSurface, serial);
|
||||
wlWm.configured = true;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
.configure = xdgSurfaceConfigure,
|
||||
};
|
||||
|
||||
// XDG Surface listeners.
|
||||
|
||||
static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel,
|
||||
int32_t width, int32_t height, struct wl_array * states)
|
||||
{
|
||||
wlWm.width = width;
|
||||
wlWm.height = height;
|
||||
wlWm.fullscreen = false;
|
||||
|
||||
enum xdg_toplevel_state * state;
|
||||
wl_array_for_each(state, states)
|
||||
{
|
||||
if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN)
|
||||
wlWm.fullscreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void xdgToplevelClose(void * data, struct xdg_toplevel * xdgToplevel)
|
||||
{
|
||||
app_handleCloseEvent();
|
||||
}
|
||||
|
||||
static const struct xdg_toplevel_listener xdgToplevelListener = {
|
||||
.configure = xdgToplevelConfigure,
|
||||
.close = xdgToplevelClose,
|
||||
};
|
||||
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless)
|
||||
{
|
||||
if (!wlWm.compositor)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_compositor, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!wlWm.xdgWmBase)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing xdg_wm_base, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
xdg_wm_base_add_listener(wlWm.xdgWmBase, &xdgWmBaseListener, NULL);
|
||||
//wl_display_roundtrip(wlWm.display);
|
||||
|
||||
wlWm.surface = wl_compositor_create_surface(wlWm.compositor);
|
||||
if (!wlWm.surface)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create wl_surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlWm.xdgSurface = xdg_wm_base_get_xdg_surface(wlWm.xdgWmBase, wlWm.surface);
|
||||
xdg_surface_add_listener(wlWm.xdgSurface, &xdgSurfaceListener, NULL);
|
||||
|
||||
wlWm.xdgToplevel = xdg_surface_get_toplevel(wlWm.xdgSurface);
|
||||
xdg_toplevel_add_listener(wlWm.xdgToplevel, &xdgToplevelListener, NULL);
|
||||
xdg_toplevel_set_title(wlWm.xdgToplevel, title);
|
||||
xdg_toplevel_set_app_id(wlWm.xdgToplevel, "looking-glass-client");
|
||||
|
||||
if (fullscreen)
|
||||
xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL);
|
||||
|
||||
if (maximize)
|
||||
xdg_toplevel_set_maximized(wlWm.xdgToplevel);
|
||||
|
||||
wl_surface_commit(wlWm.surface);
|
||||
|
||||
if (wlWm.xdgDecorationManager)
|
||||
{
|
||||
wlWm.xdgToplevelDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||
wlWm.xdgDecorationManager, wlWm.xdgToplevel);
|
||||
if (wlWm.xdgToplevelDecoration)
|
||||
{
|
||||
zxdg_toplevel_decoration_v1_set_mode(wlWm.xdgToplevelDecoration,
|
||||
borderless ?
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE :
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandWindowFree(void)
|
||||
{
|
||||
wl_surface_destroy(wlWm.surface);
|
||||
}
|
||||
|
||||
void waylandSetWindowSize(int x, int y)
|
||||
{
|
||||
// FIXME: implement.
|
||||
}
|
||||
|
||||
void waylandSetFullscreen(bool fs)
|
||||
{
|
||||
if (fs)
|
||||
xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL);
|
||||
else
|
||||
xdg_toplevel_unset_fullscreen(wlWm.xdgToplevel);
|
||||
}
|
||||
|
||||
bool waylandGetFullscreen(void)
|
||||
{
|
||||
return wlWm.fullscreen;
|
||||
}
|
||||
|
||||
bool waylandIsValidPointerPos(int x, int y)
|
||||
{
|
||||
return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,15 @@ pkg_check_modules(DISPLAYSERVER_X11_PKGCONFIG REQUIRED
|
||||
xi
|
||||
xfixes
|
||||
xscrnsaver
|
||||
xinerama
|
||||
)
|
||||
|
||||
add_library(displayserver_X11 STATIC
|
||||
x11.c
|
||||
)
|
||||
|
||||
add_definitions(-D GLX_GLXEXT_PROTOTYPES)
|
||||
|
||||
target_link_libraries(displayserver_X11
|
||||
${DISPLAYSERVER_X11_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,19 +36,21 @@ struct Inst
|
||||
|
||||
static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
|
||||
{
|
||||
if (g_initCount++ == 0)
|
||||
bool ret = false;
|
||||
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
if (TTF_Init() < 0)
|
||||
{
|
||||
DEBUG_ERROR("TTF_Init Failed");
|
||||
return false;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_fontConfig = FcInitLoadConfigAndFonts();
|
||||
if (!g_fontConfig)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
return false;
|
||||
goto fail_init;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,50 +58,136 @@ static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
goto fail_config;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
if (!font_name)
|
||||
if (!font_name)
|
||||
font_name = "FreeMono";
|
||||
|
||||
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
|
||||
FcConfigSubstitute (g_fontConfig, pat, FcMatchPattern);
|
||||
if (!pat)
|
||||
{
|
||||
DEBUG_ERROR("FCNameParse failed");
|
||||
goto fail_opaque;
|
||||
}
|
||||
|
||||
FcConfigSubstitute(g_fontConfig, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * font = FcFontMatch(g_fontConfig, pat, &result);
|
||||
FcPattern * match = FcFontMatch(g_fontConfig, pat, &result);
|
||||
|
||||
if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch))
|
||||
if (!match)
|
||||
{
|
||||
DEBUG_ERROR("FcFontMatch Failed");
|
||||
goto fail_parse;
|
||||
}
|
||||
|
||||
if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch)
|
||||
{
|
||||
this->font = TTF_OpenFont((char *)file, size);
|
||||
if (!this->font)
|
||||
{
|
||||
DEBUG_ERROR("TTL_OpenFont Failed");
|
||||
return false;
|
||||
goto fail_match;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
|
||||
return false;
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
++g_initCount;
|
||||
ret = true;
|
||||
|
||||
fail_match:
|
||||
FcPatternDestroy(match);
|
||||
|
||||
fail_parse:
|
||||
FcPatternDestroy(pat);
|
||||
|
||||
return true;
|
||||
if (ret)
|
||||
return true;
|
||||
|
||||
fail_opaque:
|
||||
free(this);
|
||||
*opaque = NULL;
|
||||
|
||||
fail_config:
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
FcConfigDestroy(g_fontConfig);
|
||||
g_fontConfig = NULL;
|
||||
}
|
||||
|
||||
fail_init:
|
||||
if (g_initCount == 0)
|
||||
TTF_Quit();
|
||||
|
||||
fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void lgf_sdl_destroy(LG_FontObj opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (this->font)
|
||||
TTF_CloseFont(this->font);
|
||||
free(this);
|
||||
|
||||
if (--g_initCount == 0)
|
||||
{
|
||||
FcConfigDestroy(g_fontConfig);
|
||||
g_fontConfig = NULL;
|
||||
|
||||
TTF_Quit();
|
||||
}
|
||||
}
|
||||
|
||||
static int lgf_sdl_multiline_width(TTF_Font * font, const char * text)
|
||||
{
|
||||
int w, maxW = 0;
|
||||
const char * ptr = text;
|
||||
char * buf = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
while (*ptr)
|
||||
{
|
||||
const char * newLine = strchr(ptr, '\n');
|
||||
size_t line = newLine ? newLine - ptr : strlen(ptr);
|
||||
if (line > size)
|
||||
{
|
||||
size = line * 2 + 1;
|
||||
void * new = realloc(buf, size);
|
||||
if (!new)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
buf = new;
|
||||
}
|
||||
memcpy(buf, ptr, line);
|
||||
buf[line] = '\0';
|
||||
|
||||
if (TTF_SizeUTF8(font, buf, &w, NULL) < 0)
|
||||
{
|
||||
free(buf);
|
||||
DEBUG_ERROR("Failed to measure text: %s", TTF_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (w > maxW)
|
||||
maxW = w;
|
||||
ptr += line + 1;
|
||||
}
|
||||
free(buf);
|
||||
return maxW;
|
||||
}
|
||||
|
||||
static LG_FontBitmap * lgf_sdl_render(LG_FontObj opaque, unsigned int fg_color, const char * text)
|
||||
@@ -113,7 +201,17 @@ static LG_FontBitmap * lgf_sdl_render(LG_FontObj opaque, unsigned int fg_color,
|
||||
color.b = (fg_color & 0x0000ff00) >> 8;
|
||||
color.a = (fg_color & 0x000000ff) >> 0;
|
||||
|
||||
if (!(surface = TTF_RenderText_Blended(this->font, text, color)))
|
||||
if (strchr(text, '\n'))
|
||||
{
|
||||
int width = lgf_sdl_multiline_width(this->font, text);
|
||||
if (width < 1)
|
||||
return NULL;
|
||||
surface = TTF_RenderUTF8_Blended_Wrapped(this->font, text, color, width);
|
||||
}
|
||||
else
|
||||
surface = TTF_RenderUTF8_Blended(this->font, text, color);
|
||||
|
||||
if (!surface)
|
||||
{
|
||||
DEBUG_ERROR("Failed to render text: %s", TTF_GetError());
|
||||
return NULL;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,34 +17,107 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#ifndef _H_LG_APP_
|
||||
#define _H_LG_APP_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
LG_ALERT_INFO ,
|
||||
LG_ALERT_SUCCESS,
|
||||
LG_ALERT_WARNING,
|
||||
LG_ALERT_ERROR
|
||||
}
|
||||
LG_MsgAlert;
|
||||
|
||||
SDL_Window * app_getWindow(void);
|
||||
|
||||
bool app_getProp(LG_DSProperty prop, void * ret);
|
||||
bool app_isRunning(void);
|
||||
bool app_inputEnabled(void);
|
||||
bool app_cursorIsGrabbed(void);
|
||||
bool app_cursorWantsRaw(void);
|
||||
bool app_cursorInWindow(void);
|
||||
void app_updateCursorPos(double x, double y);
|
||||
void app_updateWindowPos(int x, int y);
|
||||
void app_handleResizeEvent(int w, int h);
|
||||
void app_handleMouseGrabbed(double ex, double ey);
|
||||
void app_handleMouseNormal(double ex, double ey);
|
||||
void app_handleResizeEvent(int w, int h, const struct Border border);
|
||||
|
||||
void app_handleMouseRelative(double normx, double normy,
|
||||
double rawx, double rawy);
|
||||
|
||||
void app_handleMouseBasic(void);
|
||||
void app_resyncMouseBasic(void);
|
||||
|
||||
void app_handleButtonPress(int button);
|
||||
void app_handleButtonRelease(int button);
|
||||
void app_handleKeyPress(int scancode);
|
||||
void app_handleKeyRelease(int scancode);
|
||||
void app_handleWindowEnter(void);
|
||||
void app_handleWindowLeave(void);
|
||||
void app_handleEnterEvent(bool entered);
|
||||
void app_handleFocusEvent(bool focused);
|
||||
void app_handleCloseEvent(void);
|
||||
|
||||
void app_setFullscreen(bool fs);
|
||||
bool app_getFullscreen(void);
|
||||
bool app_getProp(LG_DSProperty prop, void * ret);
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLDisplay app_getEGLDisplay(void);
|
||||
EGLNativeWindowType app_getEGLNativeWindow(void);
|
||||
void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
LG_DSGLContext app_glCreateContext(void);
|
||||
void app_glDeleteContext(LG_DSGLContext context);
|
||||
void app_glMakeCurrent(LG_DSGLContext context);
|
||||
void app_glSetSwapInterval(int interval);
|
||||
void app_glSwapBuffers(void);
|
||||
#endif
|
||||
|
||||
void app_clipboardRelease(void);
|
||||
void app_clipboardNotify(const LG_ClipboardData type, size_t size);
|
||||
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size);
|
||||
void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
* param fmt The alert message format
|
||||
@ param ... formatted message values
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*KeybindFn)(int sc, void * opaque);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param sc The scancode to register
|
||||
* @param callback The function to be called when the combination is pressed
|
||||
* @param opaque A pointer to be passed to the callback, may be NULL
|
||||
* @retval A handle for the binding or NULL on failure.
|
||||
* The caller is required to release the handle via `app_releaseKeybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
* @param handle A pointer to the keybind handle to release, may be NULL
|
||||
*/
|
||||
void app_releaseKeybind(KeybindHandle * handle);
|
||||
|
||||
/**
|
||||
* Release all keybindings
|
||||
*/
|
||||
void app_releaseAllKeybinds(void);
|
||||
|
||||
/**
|
||||
* Changes whether the help message is displayed or not.
|
||||
*/
|
||||
void app_showHelp(bool show);
|
||||
|
||||
/**
|
||||
* Changes whether the FPS is displayed or not.
|
||||
*/
|
||||
void app_showFPS(bool showFPS);
|
||||
|
||||
#endif
|
||||
|
||||
43
client/include/egl_dynprocs.h
Normal file
43
client/include/egl_dynprocs.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef EGLDisplay (*eglGetPlatformDisplayEXT_t)(EGLenum platform,
|
||||
void *native_display, const EGLint *attrib_list);
|
||||
typedef void (*glEGLImageTargetTexture2DOES_t)(GLenum target,
|
||||
GLeglImageOES image);
|
||||
|
||||
struct EGLDynProcs
|
||||
{
|
||||
eglGetPlatformDisplayEXT_t eglGetPlatformDisplay;
|
||||
eglGetPlatformDisplayEXT_t eglGetPlatformDisplayEXT;
|
||||
glEGLImageTargetTexture2DOES_t glEGLImageTargetTexture2DOES;
|
||||
};
|
||||
|
||||
extern struct EGLDynProcs g_egl_dynProcs;
|
||||
|
||||
void egl_dynProcsInit(void);
|
||||
|
||||
#else
|
||||
#define egl_dynProcsInit(...)
|
||||
#endif
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
LG_ALERT_INFO ,
|
||||
LG_ALERT_SUCCESS,
|
||||
LG_ALERT_WARNING,
|
||||
LG_ALERT_ERROR
|
||||
}
|
||||
LG_MsgAlert;
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*SuperEventFn)(uint32_t sc, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
* param fmt The alert message format
|
||||
@ param ... formatted message values
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param sc The scancode to register
|
||||
* @param callback The function to be called when the combination is pressed
|
||||
* @param opaque A pointer to be passed to the callback, may be NULL
|
||||
* @retval A handle for the binding or NULL on failure.
|
||||
* The caller is required to release the handle via `app_release_keybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_register_keybind(uint32_t sc, SuperEventFn callback, void * opaque);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
* @param handle A pointer to the keybind handle to release, may be NULL
|
||||
*/
|
||||
void app_release_keybind(KeybindHandle * handle);
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef enum LG_OutFormat
|
||||
{
|
||||
LG_OUTPUT_INVALID,
|
||||
|
||||
LG_OUTPUT_BGRA,
|
||||
LG_OUTPUT_RGBA,
|
||||
LG_OUTPUT_RGBA10,
|
||||
LG_OUTPUT_YUV420
|
||||
}
|
||||
LG_OutFormat;
|
||||
|
||||
typedef bool (* LG_DecoderCreate )(void ** opaque);
|
||||
typedef void (* LG_DecoderDestroy )(void * opaque);
|
||||
typedef bool (* LG_DecoderInitialize )(void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
typedef void (* LG_DecoderDeInitialize )(void * opaque);
|
||||
typedef LG_OutFormat (* LG_DecoderGetOutFormat )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFramePitch )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFrameStride)(void * opaque);
|
||||
typedef bool (* LG_DecoderDecode )(void * opaque, const uint8_t * src, size_t srcSize);
|
||||
typedef const uint8_t * (* LG_DecoderGetBuffer )(void * opaque);
|
||||
|
||||
typedef bool (* LG_DecoderInitGLTexture )(void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
typedef void (* LG_DecoderFreeGLTexture )(void * opaque, void * ref);
|
||||
typedef bool (* LG_DecoderUpdateGLTexture)(void * opaque, void * ref);
|
||||
|
||||
typedef struct LG_Decoder
|
||||
{
|
||||
// mandatory support
|
||||
const char * name;
|
||||
LG_DecoderCreate create;
|
||||
LG_DecoderDestroy destroy;
|
||||
LG_DecoderInitialize initialize;
|
||||
LG_DecoderDeInitialize deinitialize;
|
||||
LG_DecoderGetOutFormat get_out_format;
|
||||
LG_DecoderGetFramePitch get_frame_pitch;
|
||||
LG_DecoderGetFrameStride get_frame_stride;
|
||||
LG_DecoderDecode decode;
|
||||
LG_DecoderGetBuffer get_buffer;
|
||||
|
||||
// optional support
|
||||
const bool has_gl;
|
||||
LG_DecoderInitGLTexture init_gl_texture;
|
||||
LG_DecoderFreeGLTexture free_gl_texture;
|
||||
LG_DecoderUpdateGLTexture update_gl_texture;
|
||||
}
|
||||
LG_Decoder;
|
||||
@@ -21,8 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#define _H_I_DISPLAYSERVER_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
@@ -54,20 +53,50 @@ typedef enum LG_DSProperty
|
||||
}
|
||||
LG_DSProperty;
|
||||
|
||||
enum LG_DSWarpSupport
|
||||
{
|
||||
LG_DS_WARP_NONE,
|
||||
LG_DS_WARP_SURFACE,
|
||||
LG_DS_WARP_SCREEN,
|
||||
};
|
||||
|
||||
typedef struct LG_DSInitParams
|
||||
{
|
||||
const char * title;
|
||||
int x, y, w, h;
|
||||
bool center;
|
||||
bool fullscreen;
|
||||
bool resizable;
|
||||
bool borderless;
|
||||
bool maximize;
|
||||
bool minimizeOnFocusLoss;
|
||||
|
||||
// if true the renderer requires an OpenGL context
|
||||
bool opengl;
|
||||
}
|
||||
LG_DSInitParams;
|
||||
|
||||
typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size);
|
||||
|
||||
typedef struct LG_DSGLContext
|
||||
* LG_DSGLContext;
|
||||
|
||||
struct LG_DisplayServerOps
|
||||
{
|
||||
const SDL_SYSWM_TYPE subsystem;
|
||||
/* called before options are parsed, useful for registering options */
|
||||
void (*setup)(void);
|
||||
|
||||
/* called before SDL has been initialized */
|
||||
/* return true if the selected ds is valid for the current platform */
|
||||
bool (*probe)(void);
|
||||
|
||||
/* called before anything has been initialized */
|
||||
bool (*earlyInit)(void);
|
||||
|
||||
/* called after SDL has been initialized */
|
||||
bool (*init)(SDL_SysWMinfo * info);
|
||||
/* called when it's time to create and show the application window */
|
||||
bool (*init)(const LG_DSInitParams params);
|
||||
|
||||
/* called at startup after window creation, renderer and/or SPICE is ready */
|
||||
/* called at startup after window creation, renderer and SPICE is ready */
|
||||
void (*startup)();
|
||||
|
||||
/* called just before final window destruction, before final free */
|
||||
@@ -83,10 +112,24 @@ struct LG_DisplayServerOps
|
||||
*/
|
||||
bool (*getProp)(LG_DSProperty prop, void * ret);
|
||||
|
||||
/* event filter, return true if the event has been handled */
|
||||
bool (*eventFilter)(SDL_Event * event);
|
||||
#ifdef ENABLE_EGL
|
||||
/* EGL support */
|
||||
EGLDisplay (*getEGLDisplay)(void);
|
||||
EGLNativeWindowType (*getEGLNativeWindow)(void);
|
||||
void (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
/* opengl platform specific methods */
|
||||
LG_DSGLContext (*glCreateContext)(void);
|
||||
void (*glDeleteContext)(LG_DSGLContext context);
|
||||
void (*glMakeCurrent)(LG_DSGLContext context);
|
||||
void (*glSetSwapInterval)(int interval);
|
||||
void (*glSwapBuffers)(void);
|
||||
#endif
|
||||
|
||||
/* dm specific cursor implementations */
|
||||
void (*showPointer)(bool show);
|
||||
void (*grabPointer)();
|
||||
void (*ungrabPointer)();
|
||||
void (*grabKeyboard)();
|
||||
@@ -100,15 +143,68 @@ struct LG_DisplayServerOps
|
||||
* deltas */
|
||||
void (*realignPointer)();
|
||||
|
||||
/* returns true if the position specified is actually valid */
|
||||
bool (*isValidPointerPos)(int x, int y);
|
||||
|
||||
/* called to disable/enable the screensaver */
|
||||
void (*inhibitIdle)();
|
||||
void (*uninhibitIdle)();
|
||||
|
||||
/* clipboard support */
|
||||
bool (* cbInit)(void);
|
||||
void (* cbNotice)(LG_ClipboardData type);
|
||||
void (* cbRelease)(void);
|
||||
void (* cbRequest)(LG_ClipboardData type);
|
||||
/* wait for the specified time without blocking UI processing/event loops */
|
||||
void (*wait)(unsigned int time);
|
||||
|
||||
/* get/set the window dimensions */
|
||||
void (*setWindowSize)(int x, int y);
|
||||
bool (*getFullscreen)(void);
|
||||
void (*setFullscreen)(bool fs);
|
||||
|
||||
/* clipboard support, optional, if not supported set to NULL */
|
||||
bool (*cbInit)(void);
|
||||
void (*cbNotice)(LG_ClipboardData type);
|
||||
void (*cbRelease)(void);
|
||||
void (*cbRequest)(LG_ClipboardData type);
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
#define ASSERT_EGL_FN(x) assert(x);
|
||||
#else
|
||||
#define ASSERT_EGL_FN(x)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
#define ASSERT_OPENGL_FN(x) assert(x)
|
||||
#else
|
||||
#define ASSERT_OPENGL_FN(x)
|
||||
#endif
|
||||
|
||||
#define ASSERT_LG_DS_VALID(x) \
|
||||
assert((x)->setup ); \
|
||||
assert((x)->probe ); \
|
||||
assert((x)->earlyInit ); \
|
||||
assert((x)->init ); \
|
||||
assert((x)->startup ); \
|
||||
assert((x)->shutdown ); \
|
||||
assert((x)->free ); \
|
||||
assert((x)->getProp ); \
|
||||
ASSERT_EGL_FN((x)->getEGLDisplay ); \
|
||||
ASSERT_EGL_FN((x)->getEGLNativeWindow ); \
|
||||
ASSERT_EGL_FN((x)->eglSwapBuffers ); \
|
||||
ASSERT_OPENGL_FN((x)->glCreateContext ); \
|
||||
ASSERT_OPENGL_FN((x)->glDeleteContext ); \
|
||||
ASSERT_OPENGL_FN((x)->glMakeCurrent ); \
|
||||
ASSERT_OPENGL_FN((x)->glSetSwapInterval); \
|
||||
ASSERT_OPENGL_FN((x)->glSwapBuffers ); \
|
||||
assert((x)->showPointer ); \
|
||||
assert((x)->grabPointer ); \
|
||||
assert((x)->ungrabPointer ); \
|
||||
assert((x)->warpPointer ); \
|
||||
assert((x)->realignPointer ); \
|
||||
assert((x)->isValidPointerPos ); \
|
||||
assert((x)->inhibitIdle ); \
|
||||
assert((x)->uninhibitIdle ); \
|
||||
assert((x)->wait ); \
|
||||
assert((x)->setWindowSize ); \
|
||||
assert((x)->setFullscreen ); \
|
||||
assert((x)->getFullscreen );
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,9 +21,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/framebuffer.h"
|
||||
@@ -38,6 +35,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
(x)->on_mouse_shape && \
|
||||
(x)->on_mouse_event && \
|
||||
(x)->on_alert && \
|
||||
(x)->on_help && \
|
||||
(x)->on_show_fps && \
|
||||
(x)->render_startup && \
|
||||
(x)->render && \
|
||||
(x)->update_fps)
|
||||
@@ -46,7 +45,6 @@ typedef struct LG_RendererParams
|
||||
{
|
||||
// TTF_Font * font;
|
||||
// TTF_Font * alertFont;
|
||||
bool showFPS;
|
||||
bool quickSplash;
|
||||
}
|
||||
LG_RendererParams;
|
||||
@@ -106,8 +104,8 @@ typedef const char * (* LG_RendererGetName)();
|
||||
// called pre-creation to allow the renderer to register any options it might have
|
||||
typedef void (* LG_RendererSetup)();
|
||||
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params, bool * needsOpenGL);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque);
|
||||
typedef void (* LG_RendererDeInitialize )(void * opaque);
|
||||
typedef bool (* LG_RendererSupports )(void * opaque, LG_RendererSupport support);
|
||||
typedef void (* LG_RendererOnRestart )(void * opaque);
|
||||
@@ -117,8 +115,10 @@ typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visi
|
||||
typedef bool (* LG_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format, bool useDMA);
|
||||
typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD);
|
||||
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
|
||||
typedef bool (* LG_RendererRenderStartup)(void * opaque, SDL_Window *window);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window, LG_RendererRotate rotate);
|
||||
typedef void (* LG_RendererOnHelp )(void * opaque, const char * message);
|
||||
typedef void (* LG_RendererOnShowFPS )(void * opaque, bool showFPS);
|
||||
typedef bool (* LG_RendererRenderStartup)(void * opaque);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, LG_RendererRotate rotate);
|
||||
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
|
||||
|
||||
typedef struct LG_Renderer
|
||||
@@ -137,6 +137,8 @@ typedef struct LG_Renderer
|
||||
LG_RendererOnFrameFormat on_frame_format;
|
||||
LG_RendererOnFrame on_frame;
|
||||
LG_RendererOnAlert on_alert;
|
||||
LG_RendererOnHelp on_help;
|
||||
LG_RendererOnShowFPS on_show_fps;
|
||||
LG_RendererRenderStartup render_startup;
|
||||
LG_RendererRender render;
|
||||
LG_RendererUpdateFPS update_fps;
|
||||
|
||||
@@ -17,42 +17,20 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#ifndef _H_LG_UTIL_
|
||||
#define _H_LG_UTIL_
|
||||
|
||||
void egl_debug_printf(char * format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "common/types.h"
|
||||
|
||||
GLenum error = glGetError();
|
||||
switch(error)
|
||||
{
|
||||
case GL_NO_ERROR:
|
||||
fprintf(stderr, " (GL_NO_ERROR)\n");
|
||||
break;
|
||||
// reads the specified file into a new buffer
|
||||
// the callee must free the buffer
|
||||
bool util_fileGetContents(const char * filename, char ** buffer, size_t * length);
|
||||
|
||||
case GL_INVALID_ENUM:
|
||||
fprintf(stderr, " (GL_INVALID_ENUM)\n");
|
||||
break;
|
||||
void util_cursorToInt(double ex, double ey, int *x, int *y);
|
||||
bool util_guestCurToLocal(struct DoublePoint *local);
|
||||
void util_localCurToGuest(struct DoublePoint *guest);
|
||||
void util_rotatePoint(struct DoublePoint *point);
|
||||
|
||||
case GL_INVALID_VALUE:
|
||||
fprintf(stderr, " (GL_INVALID_VALUE)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_OPERATION:
|
||||
fprintf(stderr, " (GL_INVALID_OPERATION)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
fprintf(stderr, " (GL_INVALID_FRAMEBUFFER_OPERATION)\n");
|
||||
break;
|
||||
|
||||
case GL_OUT_OF_MEMORY:
|
||||
fprintf(stderr, " (GL_OUT_OF_MEMORY)\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -22,6 +22,9 @@ make_object(
|
||||
shader/fps.vert
|
||||
shader/fps.frag
|
||||
shader/fps_bg.frag
|
||||
shader/help.vert
|
||||
shader/help.frag
|
||||
shader/help_bg.frag
|
||||
shader/alert.vert
|
||||
shader/alert.frag
|
||||
shader/alert_bg.frag
|
||||
@@ -33,13 +36,14 @@ make_object(
|
||||
|
||||
add_library(renderer_EGL STATIC
|
||||
egl.c
|
||||
debug.c
|
||||
egldebug.c
|
||||
shader.c
|
||||
texture.c
|
||||
model.c
|
||||
desktop.c
|
||||
cursor.c
|
||||
fps.c
|
||||
help.c
|
||||
draw.c
|
||||
splash.c
|
||||
alert.c
|
||||
|
||||
@@ -22,6 +22,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "common/option.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
@@ -29,8 +30,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "interface/app.h"
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "desktop.vert.h"
|
||||
#include "desktop_rgb.frag.h"
|
||||
@@ -62,16 +61,15 @@ struct EGL_Desktop
|
||||
struct DesktopShader shader_generic;
|
||||
|
||||
// night vision
|
||||
KeybindHandle kbNV;
|
||||
int nvMax;
|
||||
int nvGain;
|
||||
int nvMax;
|
||||
int nvGain;
|
||||
|
||||
// colorblind mode
|
||||
int cbMode;
|
||||
int cbMode;
|
||||
};
|
||||
|
||||
// forwards
|
||||
void egl_desktop_toggle_nv(SDL_Scancode key, void * opaque);
|
||||
void egl_desktop_toggle_nv(int key, void * opaque);
|
||||
|
||||
static bool egl_init_desktop_shader(
|
||||
struct DesktopShader * shader,
|
||||
@@ -136,7 +134,7 @@ bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
|
||||
egl_model_set_default((*desktop)->model);
|
||||
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
|
||||
|
||||
(*desktop)->kbNV = app_register_keybind(KEY_N, egl_desktop_toggle_nv, *desktop);
|
||||
app_registerKeybind(KEY_N, egl_desktop_toggle_nv, *desktop, "Toggle night vision mode");
|
||||
|
||||
(*desktop)->nvMax = option_get_int("egl", "nvGainMax");
|
||||
(*desktop)->nvGain = option_get_int("egl", "nvGain" );
|
||||
@@ -145,8 +143,7 @@ bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void egl_desktop_toggle_nv(SDL_Scancode key, void * opaque)
|
||||
void egl_desktop_toggle_nv(int key, void * opaque)
|
||||
{
|
||||
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
|
||||
if (desktop->nvGain++ == desktop->nvMax)
|
||||
@@ -166,8 +163,6 @@ void egl_desktop_free(EGL_Desktop ** desktop)
|
||||
egl_shader_free (&(*desktop)->shader_generic.shader);
|
||||
egl_model_free (&(*desktop)->model );
|
||||
|
||||
app_release_keybind(&(*desktop)->kbNV);
|
||||
|
||||
free(*desktop);
|
||||
*desktop = NULL;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
#include "interface/renderer.h"
|
||||
|
||||
|
||||
@@ -24,19 +24,16 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "common/sysinfo.h"
|
||||
#include "common/time.h"
|
||||
#include "common/locking.h"
|
||||
#include "utils.h"
|
||||
#include "util.h"
|
||||
#include "dynamic/fonts.h"
|
||||
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
#include <wayland-egl.h>
|
||||
#endif
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "model.h"
|
||||
#include "shader.h"
|
||||
#include "desktop.h"
|
||||
@@ -44,6 +41,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "fps.h"
|
||||
#include "splash.h"
|
||||
#include "alert.h"
|
||||
#include "help.h"
|
||||
|
||||
#define SPLASH_FADE_TIME 1000000
|
||||
#define ALERT_TIMEOUT 2000000
|
||||
@@ -71,6 +69,7 @@ struct Inst
|
||||
EGL_FPS * fps; // the fps display
|
||||
EGL_Splash * splash; // the splash screen
|
||||
EGL_Alert * alert; // the alert display
|
||||
EGL_Help * help; // the help display
|
||||
|
||||
LG_RendererFormat format;
|
||||
bool formatValid;
|
||||
@@ -100,19 +99,9 @@ struct Inst
|
||||
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
LG_FontObj helpFontObj;
|
||||
};
|
||||
|
||||
static bool egl_vsync_option_validator(struct Option * opt, const char ** error)
|
||||
{
|
||||
if (opt->value.x_bool && getenv("WAYLAND_DISPLAY"))
|
||||
{
|
||||
DEBUG_WARN("Cannot disable vsync on Wayland, forcing egl:vsync=off");
|
||||
opt->value.x_bool = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct Option egl_options[] =
|
||||
{
|
||||
{
|
||||
@@ -121,7 +110,6 @@ static struct Option egl_options[] =
|
||||
.description = "Enable vsync",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
.validator = &egl_vsync_option_validator
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
@@ -173,15 +161,8 @@ void egl_setup(void)
|
||||
option_register(egl_options);
|
||||
}
|
||||
|
||||
bool egl_create(void ** opaque, const LG_RendererParams params)
|
||||
bool egl_create(void ** opaque, const LG_RendererParams params, bool * needsOpenGL)
|
||||
{
|
||||
// Fail if running on Wayland so that OpenGL is used instead. Wayland-EGL
|
||||
// is broken (https://github.com/gnif/LookingGlass/issues/306) and isn't
|
||||
// fixable until SDL is dropped entirely. Until then, the OpenGL renderer
|
||||
// "mostly works".
|
||||
if (getenv("WAYLAND_DISPLAY"))
|
||||
return false;
|
||||
|
||||
// check if EGL is even available
|
||||
if (!eglQueryString(EGL_NO_DISPLAY, EGL_VERSION))
|
||||
return false;
|
||||
@@ -216,10 +197,17 @@ bool egl_create(void ** opaque, const LG_RendererParams params)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->font->create(&this->helpFontObj, NULL, 14))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create a font instance");
|
||||
return false;
|
||||
}
|
||||
|
||||
*needsOpenGL = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
bool egl_initialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off");
|
||||
@@ -230,17 +218,35 @@ void egl_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (this->font && this->fontObj)
|
||||
this->font->destroy(this->fontObj);
|
||||
if (this->font)
|
||||
{
|
||||
if (this->fontObj)
|
||||
this->font->destroy(this->fontObj);
|
||||
|
||||
if (this->helpFontObj)
|
||||
this->font->destroy(this->helpFontObj);
|
||||
}
|
||||
|
||||
|
||||
egl_desktop_free(&this->desktop);
|
||||
egl_cursor_free (&this->cursor);
|
||||
egl_fps_free (&this->fps );
|
||||
egl_splash_free (&this->splash);
|
||||
egl_alert_free (&this->alert );
|
||||
egl_help_free (&this->help);
|
||||
|
||||
LG_LOCK_FREE(this->lock);
|
||||
|
||||
eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
if (this->frameContext)
|
||||
eglDestroyContext(this->display, this->frameContext);
|
||||
|
||||
if (this->context)
|
||||
eglDestroyContext(this->display, this->context);
|
||||
|
||||
eglTerminate(this->display);
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
@@ -483,69 +489,29 @@ void egl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message,
|
||||
this->showAlert = true;
|
||||
}
|
||||
|
||||
bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
void egl_on_help(void * opaque, const char * message)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
egl_help_set_text(this->help, message);
|
||||
}
|
||||
|
||||
void egl_on_show_fps(void * opaque, bool showFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
egl_fps_set_display(this->fps, showFPS);
|
||||
}
|
||||
|
||||
bool egl_render_startup(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
this->nativeWind = app_getEGLNativeWindow();
|
||||
if (!this->nativeWind)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool useNative = false;
|
||||
{
|
||||
const char *client_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
if (strstr(client_exts, "EGL_KHR_platform_base") != NULL)
|
||||
useNative = true;
|
||||
}
|
||||
|
||||
DEBUG_INFO("use native: %s", useNative ? "true" : "false");
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
{
|
||||
if (!useNative)
|
||||
this->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, wminfo.info.x11.display, NULL);
|
||||
else
|
||||
{
|
||||
EGLNativeDisplayType native = (EGLNativeDisplayType)wminfo.info.x11.display;
|
||||
this->display = eglGetDisplay(native);
|
||||
}
|
||||
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
{
|
||||
int width, height;
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
if (!useNative)
|
||||
this->display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wminfo.info.wl.display, NULL);
|
||||
else
|
||||
{
|
||||
EGLNativeDisplayType native = (EGLNativeDisplayType)wminfo.info.wl.display;
|
||||
this->display = eglGetDisplay(native);
|
||||
}
|
||||
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->display = app_getEGLDisplay();
|
||||
if (this->display == EGL_NO_DISPLAY)
|
||||
{
|
||||
DEBUG_ERROR("eglGetDisplay failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int maj, min;
|
||||
if (!eglInitialize(this->display, &maj, &min))
|
||||
@@ -568,7 +534,7 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
|
||||
EGLint attr[] =
|
||||
{
|
||||
EGL_BUFFER_SIZE , 32,
|
||||
EGL_BUFFER_SIZE , 24,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0,
|
||||
EGL_SAMPLES , maxSamples,
|
||||
@@ -636,20 +602,27 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
DEBUG_INFO("EGL APIs : %s", eglQueryString(this->display, EGL_CLIENT_APIS));
|
||||
DEBUG_INFO("Extensions: %s", client_exts);
|
||||
|
||||
if (strstr(client_exts, "EGL_EXT_image_dma_buf_import") != NULL)
|
||||
if (g_egl_dynProcs.glEGLImageTargetTexture2DOES)
|
||||
{
|
||||
/*
|
||||
* As of version 455.45.01 NVidia started advertising support for this
|
||||
* feature, however even on the latest version 460.27.04 this is still
|
||||
* broken and does not work, until this is fixed and we have way to detect
|
||||
* this early just disable dma for all NVIDIA devices.
|
||||
*
|
||||
* ref: https://forums.developer.nvidia.com/t/egl-ext-image-dma-buf-import-broken-egl-bad-alloc-with-tons-of-free-ram/165552
|
||||
*/
|
||||
if (strstr(vendor, "NVIDIA") != NULL)
|
||||
DEBUG_WARN("NVIDIA driver detected, ignoring broken DMA support");
|
||||
else
|
||||
this->dmaSupport = true;
|
||||
if (strstr(client_exts, "EGL_EXT_image_dma_buf_import") != NULL)
|
||||
{
|
||||
/*
|
||||
* As of version 455.45.01 NVidia started advertising support for this
|
||||
* feature, however even on the latest version 460.27.04 this is still
|
||||
* broken and does not work, until this is fixed and we have way to detect
|
||||
* this early just disable dma for all NVIDIA devices.
|
||||
*
|
||||
* ref: https://forums.developer.nvidia.com/t/egl-ext-image-dma-buf-import-broken-egl-bad-alloc-with-tons-of-free-ram/165552
|
||||
*/
|
||||
if (strstr(vendor, "NVIDIA") != NULL)
|
||||
DEBUG_WARN("NVIDIA driver detected, ignoring broken DMA support");
|
||||
else
|
||||
this->dmaSupport = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled");
|
||||
}
|
||||
|
||||
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
|
||||
@@ -684,10 +657,16 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_help_init(&this->help, this->font, this->helpFontObj))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert display");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
|
||||
bool egl_render(void * opaque, LG_RendererRotate rotate)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
@@ -753,16 +732,14 @@ bool egl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
|
||||
}
|
||||
|
||||
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
|
||||
eglSwapBuffers(this->display, this->surface);
|
||||
egl_help_render(this->help, this->screenScaleX, this->screenScaleY);
|
||||
app_eglSwapBuffers(this->display, this->surface);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->params.showFPS)
|
||||
return;
|
||||
|
||||
egl_fps_update(this->fps, avgUPS, avgFPS);
|
||||
}
|
||||
|
||||
@@ -781,6 +758,8 @@ struct LG_Renderer LGR_EGL =
|
||||
.on_frame_format = egl_on_frame_format,
|
||||
.on_frame = egl_on_frame,
|
||||
.on_alert = egl_on_alert,
|
||||
.on_help = egl_on_help,
|
||||
.on_show_fps = egl_on_show_fps,
|
||||
.render_startup = egl_render_startup,
|
||||
.render = egl_render,
|
||||
.update_fps = egl_update_fps
|
||||
|
||||
45
client/renderers/EGL/egldebug.c
Normal file
45
client/renderers/EGL/egldebug.c
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "egldebug.h"
|
||||
#include <GL/gl.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
const char * egl_getErrorStr(void)
|
||||
{
|
||||
switch (eglGetError())
|
||||
{
|
||||
case EGL_SUCCESS : return "EGL_SUCCESS";
|
||||
case EGL_NOT_INITIALIZED : return "EGL_NOT_INITIALIZED";
|
||||
case EGL_BAD_ACCESS : return "EGL_BAD_ACCESS";
|
||||
case EGL_BAD_ALLOC : return "EGL_BAD_ALLOC";
|
||||
case EGL_BAD_ATTRIBUTE : return "EGL_BAD_ATTRIBUTE";
|
||||
case EGL_BAD_CONTEXT : return "EGL_BAD_CONTEXT";
|
||||
case EGL_BAD_CONFIG : return "EGL_BAD_CONFIG";
|
||||
case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE";
|
||||
case EGL_BAD_DISPLAY : return "EGL_BAD_DISPLAY";
|
||||
case EGL_BAD_SURFACE : return "EGL_BAD_SURFACE";
|
||||
case EGL_BAD_MATCH : return "EGL_BAD_MATCH";
|
||||
case EGL_BAD_PARAMETER : return "EGL_BAD_PARAMETER";
|
||||
case EGL_BAD_NATIVE_PIXMAP : return "EGL_BAD_NATIVE_PIXMAP";
|
||||
case EGL_BAD_NATIVE_WINDOW : return "EGL_BAD_NATIVE_WINDOW";
|
||||
case EGL_CONTEXT_LOST : return "EGL_CONTEXT_LOST";
|
||||
default : return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,17 +17,13 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "interface/decoder.h"
|
||||
|
||||
extern const LG_Decoder LGD_NULL;
|
||||
extern const LG_Decoder LGD_YUV420;
|
||||
#include "common/debug.h"
|
||||
|
||||
const LG_Decoder * LG_Decoders[] =
|
||||
{
|
||||
&LGD_NULL,
|
||||
&LGD_YUV420,
|
||||
NULL // end of array sentinal
|
||||
};
|
||||
const char * egl_getErrorStr(void);
|
||||
|
||||
#define LG_DECODER_COUNT ((sizeof(LG_Decoders) / sizeof(LG_Decoder *)) - 1)
|
||||
#define DEBUG_EGL_WARN(fmt, ...) \
|
||||
DEBUG_WARN(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr())
|
||||
|
||||
#define DEBUG_EGL_ERROR(fmt, ...) \
|
||||
DEBUG_ERROR(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr())
|
||||
@@ -19,7 +19,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "fps.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
@@ -43,6 +42,7 @@ struct EGL_FPS
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
bool display;
|
||||
bool ready;
|
||||
int iwidth, iheight;
|
||||
float width, height;
|
||||
@@ -133,8 +133,16 @@ void egl_fps_free(EGL_FPS ** fps)
|
||||
*fps = NULL;
|
||||
}
|
||||
|
||||
void egl_fps_set_display(EGL_FPS * fps, bool display)
|
||||
{
|
||||
fps->display = display;
|
||||
}
|
||||
|
||||
void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
{
|
||||
if (!fps->display)
|
||||
return;
|
||||
|
||||
char str[128];
|
||||
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
|
||||
|
||||
@@ -175,7 +183,7 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
|
||||
{
|
||||
if (!fps->ready)
|
||||
if (!fps->display || !fps->ready)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
@@ -28,5 +28,6 @@ typedef struct EGL_FPS EGL_FPS;
|
||||
bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_fps_free(EGL_FPS ** fps);
|
||||
|
||||
void egl_fps_set_display(EGL_FPS * fps, bool display);
|
||||
void egl_fps_update(EGL_FPS * fps, const float avgUPS, const float avgFPS);
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY);
|
||||
209
client/renderers/EGL/help.c
Normal file
209
client/renderers/EGL/help.c
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen <quantum2048@gmail.com>
|
||||
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 "help.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "help.vert.h"
|
||||
#include "help.frag.h"
|
||||
#include "help_bg.frag.h"
|
||||
|
||||
struct EGL_Help
|
||||
{
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
|
||||
EGL_Texture * texture;
|
||||
EGL_Shader * shader;
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
_Atomic(LG_FontBitmap *) bmp;
|
||||
|
||||
bool shouldRender;
|
||||
int iwidth, iheight;
|
||||
float width, height;
|
||||
|
||||
// uniforms
|
||||
GLint uScreen , uSize;
|
||||
GLint uScreenBG, uSizeBG;
|
||||
};
|
||||
|
||||
bool egl_help_init(EGL_Help ** help, const LG_Font * font, LG_FontObj fontObj)
|
||||
{
|
||||
*help = (EGL_Help *)malloc(sizeof(EGL_Help));
|
||||
if (!*help)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Help");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*help, 0, sizeof(EGL_Help));
|
||||
|
||||
(*help)->font = font;
|
||||
(*help)->fontObj = fontObj;
|
||||
|
||||
if (!egl_texture_init(&(*help)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*help)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*help)->shaderBG))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help bg shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!egl_shader_compile((*help)->shader,
|
||||
b_shader_help_vert, b_shader_help_vert_size,
|
||||
b_shader_help_frag, b_shader_help_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*help)->shaderBG,
|
||||
b_shader_help_vert , b_shader_help_vert_size,
|
||||
b_shader_help_bg_frag, b_shader_help_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*help)->uSize = egl_shader_get_uniform_location((*help)->shader , "size" );
|
||||
(*help)->uScreen = egl_shader_get_uniform_location((*help)->shader , "screen");
|
||||
(*help)->uSizeBG = egl_shader_get_uniform_location((*help)->shaderBG, "size" );
|
||||
(*help)->uScreenBG = egl_shader_get_uniform_location((*help)->shaderBG, "screen");
|
||||
|
||||
if (!egl_model_init(&(*help)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*help)->model);
|
||||
egl_model_set_texture((*help)->model, (*help)->texture);
|
||||
|
||||
atomic_init(&(*help)->bmp, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_help_free(EGL_Help ** help)
|
||||
{
|
||||
if (!*help)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*help)->texture );
|
||||
egl_shader_free (&(*help)->shader );
|
||||
egl_shader_free (&(*help)->shaderBG);
|
||||
egl_model_free (&(*help)->model );
|
||||
|
||||
free(*help);
|
||||
*help = NULL;
|
||||
}
|
||||
|
||||
void egl_help_set_text(EGL_Help * help, const char * help_text)
|
||||
{
|
||||
LG_FontBitmap * bmp = NULL;
|
||||
if (help_text)
|
||||
{
|
||||
bmp = help->font->render(help->fontObj, 0xffffff00, help_text);
|
||||
if (!bmp)
|
||||
DEBUG_ERROR("Failed to render help text");
|
||||
} else
|
||||
help->shouldRender = false;
|
||||
|
||||
bmp = atomic_exchange(&help->bmp, bmp);
|
||||
if (bmp)
|
||||
{
|
||||
help->font->release(help->fontObj, bmp);
|
||||
}
|
||||
}
|
||||
|
||||
void egl_help_render(EGL_Help * help, const float scaleX, const float scaleY)
|
||||
{
|
||||
LG_FontBitmap * bmp = atomic_exchange(&help->bmp, NULL);
|
||||
if (bmp)
|
||||
{
|
||||
if (help->iwidth != bmp->width || help->iheight != bmp->height)
|
||||
{
|
||||
help->iwidth = bmp->width;
|
||||
help->iheight = bmp->height;
|
||||
help->width = (float)bmp->width;
|
||||
help->height = (float)bmp->height;
|
||||
|
||||
egl_texture_setup(
|
||||
help->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
egl_texture_update
|
||||
(
|
||||
help->texture,
|
||||
bmp->pixels
|
||||
);
|
||||
|
||||
help->shouldRender = true;
|
||||
help->font->release(help->fontObj, bmp);
|
||||
}
|
||||
|
||||
if (!help->shouldRender)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// render the background first
|
||||
egl_shader_use(help->shaderBG);
|
||||
glUniform2f(help->uScreenBG, scaleX , scaleY );
|
||||
glUniform2f(help->uSizeBG , help->width, help->height);
|
||||
egl_model_render(help->model);
|
||||
|
||||
// render the texture over the background
|
||||
egl_shader_use(help->shader);
|
||||
glUniform2f(help->uScreen, scaleX , scaleY );
|
||||
glUniform2f(help->uSize , help->width, help->height);
|
||||
egl_model_render(help->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
32
client/renderers/EGL/help.h
Normal file
32
client/renderers/EGL/help.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen <quantum2048@gmail.com>
|
||||
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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "interface/font.h"
|
||||
|
||||
typedef struct EGL_Help EGL_Help;
|
||||
|
||||
bool egl_help_init(EGL_Help ** help, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_help_free(EGL_Help ** help);
|
||||
|
||||
void egl_help_set_text(EGL_Help * help, const char * help_text);
|
||||
void egl_help_render(EGL_Help * help, const float scaleX, const float scaleY);
|
||||
@@ -22,7 +22,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "texture.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -30,8 +29,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
struct EGL_Model
|
||||
{
|
||||
bool rebuild;
|
||||
@@ -216,4 +213,4 @@ void update_uniform_bindings(EGL_Model * model)
|
||||
|
||||
const int count = egl_texture_count(model->texture);
|
||||
egl_shader_associate_textures(model->shader, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "shader.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
struct EGL_Shader
|
||||
{
|
||||
bool hasShader;
|
||||
@@ -63,7 +61,7 @@ bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * f
|
||||
char * vertex_code, * fragment_code;
|
||||
size_t vertex_size, fragment_size;
|
||||
|
||||
if (!file_get_contents(vertex_file, &vertex_code, &vertex_size))
|
||||
if (!util_fileGetContents(vertex_file, &vertex_code, &vertex_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read vertex shader");
|
||||
return false;
|
||||
@@ -71,7 +69,7 @@ bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * f
|
||||
|
||||
DEBUG_INFO("Loaded vertex shader: %s", vertex_file);
|
||||
|
||||
if (!file_get_contents(fragment_file, &fragment_code, &fragment_size))
|
||||
if (!util_fileGetContents(fragment_file, &fragment_code, &fragment_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read fragment shader");
|
||||
free(vertex_code);
|
||||
@@ -222,4 +220,4 @@ GLint egl_shader_get_uniform_location(EGL_Shader * this, const char * name)
|
||||
}
|
||||
|
||||
return glGetUniformLocation(this->shader, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,4 +84,6 @@ void main()
|
||||
color *= 1.0 + lumi;
|
||||
color *= nvGain;
|
||||
}
|
||||
|
||||
color.a = 1.0;
|
||||
}
|
||||
|
||||
11
client/renderers/EGL/shader/help.frag
Normal file
11
client/renderers/EGL/shader/help.frag
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texture(sampler1, uv);
|
||||
}
|
||||
22
client/renderers/EGL/shader/help.vert
Normal file
22
client/renderers/EGL/shader/help.vert
Normal file
@@ -0,0 +1,22 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec2 screen;
|
||||
uniform vec2 size;
|
||||
|
||||
out highp vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
gl_Position.xy *= screen.xy * size.xy;
|
||||
gl_Position.x -= 1.0 - (screen.x * size.x);
|
||||
gl_Position.y -= 1.0 - (screen.y * size.y);
|
||||
gl_Position.x += screen.x * 10.0;
|
||||
gl_Position.y += screen.y * 10.0;
|
||||
|
||||
uv = vertexUV;
|
||||
}
|
||||
8
client/renderers/EGL/shader/help_bg.frag
Normal file
8
client/renderers/EGL/shader/help_bg.frag
Normal file
@@ -0,0 +1,8 @@
|
||||
#version 300 es
|
||||
|
||||
out highp vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vec4(0.0, 0.0, 1.0, 0.5);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec3 pos;
|
||||
in highp float a;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
@@ -9,5 +8,5 @@ uniform sampler2D sampler1;
|
||||
void main()
|
||||
{
|
||||
highp float d = 1.0 - sqrt(pos.x * pos.x + pos.y * pos.y) / 2.0;
|
||||
color = vec4(0.234375 * d, 0.015625f * d, 0.425781f * d, a);
|
||||
color = vec4(0.234375 * d, 0.015625f * d, 0.425781f * d, 1);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
uniform float alpha;
|
||||
|
||||
out highp vec3 pos;
|
||||
out highp float a;
|
||||
|
||||
@@ -13,5 +11,4 @@ void main()
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
pos = vertexPosition_modelspace;
|
||||
a = alpha;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#version 300 es
|
||||
|
||||
out highp vec4 color;
|
||||
in highp float a;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vec4(1.0, 1.0, 1.0, a);
|
||||
color = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
uniform vec2 scale;
|
||||
|
||||
out highp float a;
|
||||
uniform float scale;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.y *= scale.y;
|
||||
gl_Position.y *= scale;
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
a = scale.x;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "splash.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "draw.h"
|
||||
#include "texture.h"
|
||||
@@ -46,7 +45,6 @@ struct EGL_Splash
|
||||
EGL_Model * logo;
|
||||
|
||||
// uniforms
|
||||
GLint uBGAlpha;
|
||||
GLint uScale;
|
||||
};
|
||||
|
||||
@@ -75,8 +73,6 @@ bool egl_splash_init(EGL_Splash ** splash)
|
||||
return false;
|
||||
}
|
||||
|
||||
(*splash)->uBGAlpha = egl_shader_get_uniform_location((*splash)->bgShader, "alpha");
|
||||
|
||||
if (!egl_model_init(&(*splash)->bg))
|
||||
{
|
||||
DEBUG_ERROR("Failed to intiailize the splash bg model");
|
||||
@@ -154,8 +150,12 @@ void egl_splash_free(EGL_Splash ** splash)
|
||||
if (!*splash)
|
||||
return;
|
||||
|
||||
egl_model_free(&(*splash)->bg );
|
||||
egl_model_free(&(*splash)->logo);
|
||||
|
||||
egl_shader_free(&(*splash)->bgShader );
|
||||
egl_shader_free(&(*splash)->logoShader);
|
||||
|
||||
free(*splash);
|
||||
*splash = NULL;
|
||||
}
|
||||
@@ -163,15 +163,15 @@ void egl_splash_free(EGL_Splash ** splash)
|
||||
void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY)
|
||||
{
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendColor(0, 0, 0, alpha);
|
||||
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
|
||||
|
||||
egl_shader_use(splash->bgShader);
|
||||
glUniform1f(splash->uBGAlpha, alpha);
|
||||
egl_model_render(splash->bg);
|
||||
|
||||
egl_shader_use(splash->logoShader);
|
||||
glUniform2f(splash->uScale, alpha, scaleY);
|
||||
glUniform1f(splash->uScale, scaleY);
|
||||
egl_model_render(splash->logo);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "texture.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/framebuffer.h"
|
||||
#include "debug.h"
|
||||
#include "utils.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "egldebug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -39,8 +39,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0')
|
||||
#define DRM_FORMAT_ABGR16161616F fourcc_code('A', 'B', '4', 'H')
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
/* this must be a multiple of 2 */
|
||||
#define BUFFER_COUNT 4
|
||||
|
||||
@@ -79,6 +77,18 @@ struct EGL_Texture
|
||||
int bufferCount;
|
||||
GLuint tex;
|
||||
struct Buffer buf[BUFFER_COUNT];
|
||||
|
||||
size_t dmaImageCount;
|
||||
size_t dmaImageUsed;
|
||||
struct
|
||||
{
|
||||
int fd;
|
||||
EGLImage image;
|
||||
}
|
||||
* dmaImages;
|
||||
|
||||
GLuint dmaFBO;
|
||||
GLuint dmaTex;
|
||||
};
|
||||
|
||||
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display)
|
||||
@@ -121,6 +131,10 @@ void egl_texture_free(EGL_Texture ** texture)
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glDeleteTextures(1, &(*texture)->tex);
|
||||
|
||||
for (size_t i = 0; i < (*texture)->dmaImageUsed; ++i)
|
||||
eglDestroyImage((*texture)->display, (*texture)->dmaImages[i].image);
|
||||
free((*texture)->dmaImages);
|
||||
|
||||
free(*texture);
|
||||
*texture = NULL;
|
||||
}
|
||||
@@ -141,7 +155,8 @@ static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
|
||||
|
||||
if (!texture->buf[i].map)
|
||||
{
|
||||
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
|
||||
DEBUG_EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i,
|
||||
texture->pboBufferSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -247,7 +262,15 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
|
||||
}
|
||||
|
||||
if (useDMA)
|
||||
{
|
||||
if (texture->dmaFBO)
|
||||
glDeleteFramebuffers(1, &texture->dmaFBO);
|
||||
if (texture->dmaTex)
|
||||
glDeleteTextures(1, &texture->dmaTex);
|
||||
glGenFramebuffers(1, &texture->dmaFBO);
|
||||
glGenTextures(1, &texture->dmaTex);
|
||||
return true;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->width,
|
||||
@@ -364,41 +387,93 @@ bool egl_texture_update_from_dma(EGL_Texture * texture, const FrameBuffer * fram
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLAttrib const attribs[] =
|
||||
{
|
||||
EGL_WIDTH , texture->width,
|
||||
EGL_HEIGHT , texture->height,
|
||||
EGL_LINUX_DRM_FOURCC_EXT , texture->fourcc,
|
||||
EGL_DMA_BUF_PLANE0_FD_EXT , dmaFd,
|
||||
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
|
||||
EGL_DMA_BUF_PLANE0_PITCH_EXT , texture->stride,
|
||||
EGL_NONE , EGL_NONE
|
||||
};
|
||||
EGLImage image = EGL_NO_IMAGE;
|
||||
|
||||
/* create the image backed by the dma buffer */
|
||||
EGLImage image = eglCreateImage(
|
||||
texture->display,
|
||||
EGL_NO_CONTEXT,
|
||||
EGL_LINUX_DMA_BUF_EXT,
|
||||
(EGLClientBuffer)NULL,
|
||||
attribs
|
||||
);
|
||||
for (int i = 0; i < texture->dmaImageUsed; ++i)
|
||||
{
|
||||
if (texture->dmaImages[i].fd == dmaFd)
|
||||
{
|
||||
image = texture->dmaImages[i].image;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (image == EGL_NO_IMAGE)
|
||||
{
|
||||
DEBUG_ERROR("failed to create ELGImage for DMA transfer");
|
||||
return false;
|
||||
}
|
||||
EGLAttrib const attribs[] =
|
||||
{
|
||||
EGL_WIDTH , texture->width,
|
||||
EGL_HEIGHT , texture->height,
|
||||
EGL_LINUX_DRM_FOURCC_EXT , texture->fourcc,
|
||||
EGL_DMA_BUF_PLANE0_FD_EXT , dmaFd,
|
||||
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
|
||||
EGL_DMA_BUF_PLANE0_PITCH_EXT , texture->stride,
|
||||
EGL_NONE , EGL_NONE
|
||||
};
|
||||
|
||||
/* bind the texture and initiate the transfer */
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex);
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
|
||||
/* create the image backed by the dma buffer */
|
||||
image = eglCreateImage(
|
||||
texture->display,
|
||||
EGL_NO_CONTEXT,
|
||||
EGL_LINUX_DMA_BUF_EXT,
|
||||
(EGLClientBuffer)NULL,
|
||||
attribs
|
||||
);
|
||||
|
||||
if (image == EGL_NO_IMAGE)
|
||||
{
|
||||
DEBUG_EGL_ERROR("Failed to create ELGImage for DMA transfer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (texture->dmaImageUsed == texture->dmaImageCount)
|
||||
{
|
||||
size_t newCount = texture->dmaImageCount * 2 + 2;
|
||||
void * new = realloc(texture->dmaImages, newCount * sizeof *texture->dmaImages);
|
||||
if (!new)
|
||||
{
|
||||
DEBUG_EGL_ERROR("Failed to allocate memory");
|
||||
eglDestroyImage(texture->display, image);
|
||||
return false;
|
||||
}
|
||||
texture->dmaImageCount = newCount;
|
||||
texture->dmaImages = new;
|
||||
}
|
||||
|
||||
const size_t index = texture->dmaImageUsed++;
|
||||
texture->dmaImages[index].fd = dmaFd;
|
||||
texture->dmaImages[index].image = image;
|
||||
}
|
||||
|
||||
/* wait for completion */
|
||||
framebuffer_wait(frame, texture->height * texture->stride);
|
||||
|
||||
/* destroy the image to prevent future writes corrupting the display image */
|
||||
eglDestroyImage(texture->display, image);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->dmaTex);
|
||||
g_egl_dynProcs.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, texture->dmaFBO);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->dmaTex, 0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex);
|
||||
glCopyTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, 0, 0, texture->width, texture->height, 0);
|
||||
|
||||
GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
|
||||
switch (glClientWaitSync(fence, 0, 10000000)) // 10ms
|
||||
{
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
break;
|
||||
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
egl_warn_slow();
|
||||
break;
|
||||
|
||||
case GL_WAIT_FAILED:
|
||||
case GL_INVALID_VALUE:
|
||||
DEBUG_EGL_ERROR("glClientWaitSync failed");
|
||||
}
|
||||
|
||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
||||
return true;
|
||||
@@ -480,7 +555,7 @@ enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
||||
case GL_INVALID_VALUE:
|
||||
glDeleteSync(texture->buf[b].sync);
|
||||
texture->buf[b].sync = 0;
|
||||
EGL_ERROR("glClientWaitSync failed");
|
||||
DEBUG_EGL_ERROR("glClientWaitSync failed");
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "shader.h"
|
||||
#include "common/framebuffer.h"
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
#include <GL/gl.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
typedef struct EGL_Texture EGL_Texture;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ project(renderer_Opengl LANGUAGES C)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(RENDERER_OPENGL_PKGCONFIG REQUIRED
|
||||
gl
|
||||
glu
|
||||
)
|
||||
|
||||
add_library(renderer_OpenGL STATIC
|
||||
|
||||
@@ -27,7 +27,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glu.h>
|
||||
#include <GL/glx.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
@@ -48,17 +47,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#define FADE_TIME 1000000
|
||||
|
||||
static bool opengl_vsync_option_validator(struct Option * opt, const char ** error)
|
||||
{
|
||||
if (opt->value.x_bool && getenv("WAYLAND_DISPLAY"))
|
||||
{
|
||||
DEBUG_WARN("Cannot disable vsync on Wayland, forcing opengl:vsync=off");
|
||||
opt->value.x_bool = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct Option opengl_options[] =
|
||||
{
|
||||
{
|
||||
@@ -74,7 +62,6 @@ static struct Option opengl_options[] =
|
||||
.description = "Enable vsync",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
.validator = &opengl_vsync_option_validator
|
||||
},
|
||||
{
|
||||
.module = "opengl",
|
||||
@@ -121,7 +108,7 @@ struct Inst
|
||||
bool renderStarted;
|
||||
bool configured;
|
||||
bool reconfigure;
|
||||
SDL_GLContext glContext;
|
||||
LG_DSGLContext glContext;
|
||||
|
||||
SDL_Point window;
|
||||
bool frameUpdate;
|
||||
@@ -161,6 +148,7 @@ struct Inst
|
||||
uint64_t waitFadeTime;
|
||||
bool waitDone;
|
||||
|
||||
bool showFPS;
|
||||
bool fpsTexture;
|
||||
SDL_Rect fpsRect;
|
||||
|
||||
@@ -190,7 +178,7 @@ enum ConfigStatus
|
||||
};
|
||||
|
||||
static void deconfigure(struct Inst * this);
|
||||
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window);
|
||||
static enum ConfigStatus configure(struct Inst * this);
|
||||
static void update_mouse_shape(struct Inst * this, bool * newShape);
|
||||
static bool draw_frame(struct Inst * this);
|
||||
static void draw_mouse(struct Inst * this);
|
||||
@@ -206,7 +194,8 @@ static void opengl_setup(void)
|
||||
option_register(opengl_options);
|
||||
}
|
||||
|
||||
bool opengl_create(void ** opaque, const LG_RendererParams params)
|
||||
bool opengl_create(void ** opaque, const LG_RendererParams params,
|
||||
bool * needsOpenGL)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
@@ -245,10 +234,11 @@ bool opengl_create(void ** opaque, const LG_RendererParams params)
|
||||
|
||||
this->alerts = ll_new();
|
||||
|
||||
*needsOpenGL = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
bool opengl_initialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
@@ -256,14 +246,6 @@ bool opengl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
|
||||
this->waiting = true;
|
||||
this->waitDone = false;
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -287,7 +269,7 @@ void opengl_deinitialize(void * opaque)
|
||||
|
||||
if (this->glContext)
|
||||
{
|
||||
SDL_GL_DeleteContext(this->glContext);
|
||||
app_glDeleteContext(this->glContext);
|
||||
this->glContext = NULL;
|
||||
}
|
||||
|
||||
@@ -331,7 +313,7 @@ void opengl_on_resize(void * opaque, const int width, const int height,
|
||||
glViewport(0, 0, this->window.x, this->window.y);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluOrtho2D(0, this->window.x, this->window.y, 0);
|
||||
glOrtho(0, this->window.x, this->window.y, 0, -1, 1);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
@@ -480,6 +462,17 @@ void opengl_on_alert(void * opaque, const LG_MsgAlert alert, const char * messag
|
||||
ll_push(this->alerts, a);
|
||||
}
|
||||
|
||||
void opengl_on_help(void * opaque, const char * message)
|
||||
{
|
||||
// TODO: Implement this.
|
||||
}
|
||||
|
||||
void opengl_on_show_fps(void * opaque, bool showFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->showFPS = showFPS;
|
||||
}
|
||||
|
||||
void bitmap_to_texture(LG_FontBitmap * bitmap, GLuint texture)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D , texture );
|
||||
@@ -505,16 +498,15 @@ void bitmap_to_texture(LG_FontBitmap * bitmap, GLuint texture)
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
bool opengl_render_startup(void * opaque, SDL_Window * window)
|
||||
bool opengl_render_startup(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
this->glContext = SDL_GL_CreateContext(window);
|
||||
this->glContext = app_glCreateContext();
|
||||
if (!this->glContext)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the OpenGL context");
|
||||
return false;
|
||||
}
|
||||
|
||||
app_glMakeCurrent(this->glContext);
|
||||
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
@@ -559,18 +551,18 @@ bool opengl_render_startup(void * opaque, SDL_Window * window)
|
||||
}
|
||||
this->hasTextures = true;
|
||||
|
||||
SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0);
|
||||
app_glSetSwapInterval(this->opt.vsync ? 1 : 0);
|
||||
this->renderStarted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
|
||||
bool opengl_render(void * opaque, LG_RendererRotate rotate)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
switch(configure(this, window))
|
||||
switch(configure(this))
|
||||
{
|
||||
case CONFIG_STATUS_ERROR:
|
||||
DEBUG_ERROR("configure failed");
|
||||
@@ -598,7 +590,7 @@ bool opengl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
|
||||
render_wait(this);
|
||||
}
|
||||
|
||||
if (this->fpsTexture)
|
||||
if (this->showFPS && this->fpsTexture)
|
||||
glCallList(this->fpsList);
|
||||
|
||||
struct Alert * alert;
|
||||
@@ -670,11 +662,11 @@ bool opengl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
|
||||
|
||||
if (this->opt.preventBuffer)
|
||||
{
|
||||
SDL_GL_SwapWindow(window);
|
||||
app_glSwapBuffers();
|
||||
glFinish();
|
||||
}
|
||||
else
|
||||
SDL_GL_SwapWindow(window);
|
||||
app_glSwapBuffers();
|
||||
|
||||
this->mouseUpdate = false;
|
||||
return true;
|
||||
@@ -683,7 +675,7 @@ bool opengl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
|
||||
void opengl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->params.showFPS)
|
||||
if (!this->showFPS)
|
||||
return;
|
||||
|
||||
char str[128];
|
||||
@@ -843,6 +835,8 @@ const LG_Renderer LGR_OpenGL =
|
||||
.on_frame_format = opengl_on_frame_format,
|
||||
.on_frame = opengl_on_frame,
|
||||
.on_alert = opengl_on_alert,
|
||||
.on_help = opengl_on_help,
|
||||
.on_show_fps = opengl_on_show_fps,
|
||||
.render_startup = opengl_render_startup,
|
||||
.render = opengl_render,
|
||||
.update_fps = opengl_update_fps
|
||||
@@ -854,12 +848,45 @@ static bool _check_gl_error(unsigned int line, const char * name)
|
||||
if (error == GL_NO_ERROR)
|
||||
return false;
|
||||
|
||||
const GLubyte * errStr = gluErrorString(error);
|
||||
const char * errStr;
|
||||
switch (error)
|
||||
{
|
||||
case GL_INVALID_ENUM:
|
||||
errStr = "GL_INVALID_ENUM";
|
||||
break;
|
||||
|
||||
case GL_INVALID_VALUE:
|
||||
errStr = "GL_INVALID_VALUE";
|
||||
break;
|
||||
|
||||
case GL_INVALID_OPERATION:
|
||||
errStr = "GL_INVALID_OPERATION";
|
||||
break;
|
||||
|
||||
case GL_STACK_OVERFLOW:
|
||||
errStr = "GL_STACK_OVERFLOW";
|
||||
break;
|
||||
|
||||
case GL_STACK_UNDERFLOW:
|
||||
errStr = "GL_STACK_UNDERFLOW";
|
||||
break;
|
||||
|
||||
case GL_OUT_OF_MEMORY:
|
||||
errStr = "GL_OUT_OF_MEMORY";
|
||||
break;
|
||||
|
||||
case GL_TABLE_TOO_LARGE:
|
||||
errStr = "GL_TABLE_TOO_LARGE";
|
||||
break;
|
||||
|
||||
default:
|
||||
errStr = "unknown error";
|
||||
}
|
||||
DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
|
||||
static enum ConfigStatus configure(struct Inst * this)
|
||||
{
|
||||
LG_LOCK(this->formatLock);
|
||||
if (!this->reconfigure)
|
||||
@@ -1282,7 +1309,7 @@ static bool draw_frame(struct Inst * this)
|
||||
break;
|
||||
|
||||
case GL_WAIT_FAILED:
|
||||
DEBUG_ERROR("Wait failed %s", gluErrorString(glGetError()));
|
||||
DEBUG_ERROR("Wait failed %d", glGetError());
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
506
client/src/app.c
506
client/src/app.c
@@ -17,13 +17,434 @@ 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 "main.h"
|
||||
#include "core.h"
|
||||
#include "util.h"
|
||||
#include "clipboard.h"
|
||||
|
||||
#include "ll.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
bool app_isRunning(void)
|
||||
{
|
||||
return
|
||||
g_state.state == APP_STATE_RUNNING ||
|
||||
g_state.state == APP_STATE_RESTART;
|
||||
}
|
||||
|
||||
void app_updateCursorPos(double x, double y)
|
||||
{
|
||||
g_cursor.pos.x = x;
|
||||
g_cursor.pos.y = y;
|
||||
g_cursor.valid = true;
|
||||
}
|
||||
|
||||
void app_handleFocusEvent(bool focused)
|
||||
{
|
||||
g_state.focused = focused;
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (!focused)
|
||||
{
|
||||
core_setGrabQuiet(false);
|
||||
core_setCursorInView(false);
|
||||
|
||||
if (g_params.releaseKeysOnFocusLoss)
|
||||
for (int key = 0; key < KEY_MAX; key++)
|
||||
if (g_state.keyDown[key])
|
||||
app_handleKeyRelease(key);
|
||||
}
|
||||
|
||||
g_cursor.realign = true;
|
||||
g_state.ds->realignPointer();
|
||||
}
|
||||
|
||||
void app_handleEnterEvent(bool entered)
|
||||
{
|
||||
if (entered)
|
||||
{
|
||||
g_cursor.inWindow = true;
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
g_cursor.realign = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_cursor.inWindow = false;
|
||||
core_setCursorInView(false);
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (!g_params.alwaysShowCursor)
|
||||
g_cursor.draw = false;
|
||||
g_cursor.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
void app_clipboardRelease(void)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
spice_clipboard_release();
|
||||
}
|
||||
|
||||
void app_clipboardNotify(const LG_ClipboardData type, size_t size)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
{
|
||||
spice_clipboard_release();
|
||||
return;
|
||||
}
|
||||
|
||||
g_state.cbType = cb_lgTypeToSpiceType(type);
|
||||
g_state.cbChunked = size > 0;
|
||||
g_state.cbXfer = size;
|
||||
|
||||
spice_clipboard_grab(g_state.cbType);
|
||||
|
||||
if (size)
|
||||
spice_clipboard_data_start(g_state.cbType, size);
|
||||
}
|
||||
|
||||
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)
|
||||
spice_clipboard_data_start(g_state.cbType, size);
|
||||
|
||||
spice_clipboard_data(g_state.cbType, data, (uint32_t)size);
|
||||
g_state.cbXfer -= size;
|
||||
}
|
||||
|
||||
void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
struct CBRequest * cbr = (struct CBRequest *)malloc(sizeof(struct CBRequest));
|
||||
|
||||
cbr->type = g_state.cbType;
|
||||
cbr->replyFn = replyFn;
|
||||
cbr->opaque = opaque;
|
||||
ll_push(g_state.cbRequestList, cbr);
|
||||
|
||||
spice_clipboard_request(g_state.cbType);
|
||||
}
|
||||
|
||||
void spiceClipboardNotice(const SpiceDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
if (!g_state.cbAvailable)
|
||||
return;
|
||||
|
||||
g_state.cbType = type;
|
||||
g_state.ds->cbNotice(cb_spiceTypeToLGType(type));
|
||||
}
|
||||
|
||||
void app_handleButtonPress(int button)
|
||||
{
|
||||
g_cursor.buttons |= (1U << button);
|
||||
|
||||
if (!core_inputEnabled() || !g_cursor.inView)
|
||||
return;
|
||||
|
||||
if (!spice_mouse_press(button))
|
||||
DEBUG_ERROR("app_handleButtonPress: failed to send message");
|
||||
}
|
||||
|
||||
void app_handleButtonRelease(int button)
|
||||
{
|
||||
g_cursor.buttons &= ~(1U << button);
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (!spice_mouse_release(button))
|
||||
DEBUG_ERROR("app_handleButtonRelease: failed to send message");
|
||||
}
|
||||
|
||||
void app_handleKeyPress(int sc)
|
||||
{
|
||||
if (sc == g_params.escapeKey && !g_state.escapeActive)
|
||||
{
|
||||
g_state.escapeActive = true;
|
||||
g_state.escapeAction = -1;
|
||||
app_showHelp(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_state.escapeActive)
|
||||
{
|
||||
g_state.escapeAction = sc;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
|
||||
return;
|
||||
|
||||
if (!g_state.keyDown[sc])
|
||||
{
|
||||
uint32_t ps2 = xfree86_to_ps2[sc];
|
||||
if (!ps2)
|
||||
return;
|
||||
|
||||
if (spice_key_down(ps2))
|
||||
g_state.keyDown[sc] = true;
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("app_handleKeyPress: failed to send message");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_handleKeyRelease(int sc)
|
||||
{
|
||||
if (g_state.escapeActive)
|
||||
{
|
||||
if (g_state.escapeAction == -1)
|
||||
{
|
||||
if (g_params.useSpiceInput)
|
||||
core_setGrab(!g_cursor.grab);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeybindHandle handle = g_state.bindings[sc];
|
||||
if (handle)
|
||||
{
|
||||
handle->callback(sc, handle->opaque);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sc == g_params.escapeKey)
|
||||
{
|
||||
g_state.escapeActive = false;
|
||||
app_showHelp(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
// 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;
|
||||
|
||||
uint32_t ps2 = xfree86_to_ps2[sc];
|
||||
if (!ps2)
|
||||
return;
|
||||
|
||||
if (spice_key_up(ps2))
|
||||
g_state.keyDown[sc] = false;
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("app_handleKeyRelease: failed to send message");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void app_handleMouseRelative(double normx, double normy,
|
||||
double rawx, double rawy)
|
||||
{
|
||||
if (g_cursor.grab)
|
||||
{
|
||||
if (g_params.rawMouse)
|
||||
core_handleMouseGrabbed(rawx, rawy);
|
||||
else
|
||||
core_handleMouseGrabbed(normx, normy);
|
||||
}
|
||||
else
|
||||
if (g_cursor.inWindow)
|
||||
core_handleMouseNormal(normx, normy);
|
||||
}
|
||||
|
||||
static inline double clamp(double x, double min, double max)
|
||||
{
|
||||
if (x < min) return min;
|
||||
if (x > max) return max;
|
||||
return x;
|
||||
}
|
||||
|
||||
// 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.
|
||||
void app_handleMouseBasic()
|
||||
{
|
||||
/* do not pass mouse events to the guest if we do not have focus */
|
||||
if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused)
|
||||
return;
|
||||
|
||||
if (!core_inputEnabled())
|
||||
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);
|
||||
|
||||
/* translate the current position to guest coordinate space */
|
||||
struct DoublePoint guest;
|
||||
util_localCurToGuest(&guest);
|
||||
|
||||
int x = (int) round(clamp(guest.x, 0, g_state.srcSize.x) - g_cursor.projected.x);
|
||||
int y = (int) round(clamp(guest.y, 0, g_state.srcSize.y) - g_cursor.projected.y);
|
||||
|
||||
if (!x && !y)
|
||||
return;
|
||||
|
||||
g_cursor.projected.x += x;
|
||||
g_cursor.projected.y += y;
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
|
||||
void app_resyncMouseBasic()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void app_updateWindowPos(int x, int y)
|
||||
{
|
||||
g_state.windowPos.x = x;
|
||||
g_state.windowPos.y = y;
|
||||
}
|
||||
|
||||
void app_handleResizeEvent(int w, int h, const struct Border border)
|
||||
{
|
||||
memcpy(&g_state.border, &border, sizeof(border));
|
||||
|
||||
/* don't do anything else if the window dimensions have not changed */
|
||||
if (g_state.windowW == w && g_state.windowH == h)
|
||||
return;
|
||||
|
||||
g_state.windowW = w;
|
||||
g_state.windowH = h;
|
||||
g_state.windowCX = w / 2;
|
||||
g_state.windowCY = h / 2;
|
||||
core_updatePositionInfo();
|
||||
|
||||
if (core_inputEnabled())
|
||||
{
|
||||
/* 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();
|
||||
}
|
||||
}
|
||||
|
||||
void app_handleCloseEvent(void)
|
||||
{
|
||||
if (!g_params.ignoreQuit || !g_cursor.inView)
|
||||
g_state.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
|
||||
void app_setFullscreen(bool fs)
|
||||
{
|
||||
g_state.ds->setFullscreen(fs);
|
||||
}
|
||||
|
||||
bool app_getFullscreen(void)
|
||||
{
|
||||
return g_state.ds->getFullscreen();
|
||||
}
|
||||
|
||||
bool app_getProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
return g_state.ds->getProp(prop, ret);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLDisplay app_getEGLDisplay(void)
|
||||
{
|
||||
return g_state.ds->getEGLDisplay();
|
||||
}
|
||||
|
||||
EGLNativeWindowType app_getEGLNativeWindow(void)
|
||||
{
|
||||
return g_state.ds->getEGLNativeWindow();
|
||||
}
|
||||
|
||||
void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface)
|
||||
{
|
||||
g_state.ds->eglSwapBuffers(display, surface);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
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);
|
||||
}
|
||||
|
||||
void app_glSwapBuffers(void)
|
||||
{
|
||||
g_state.ds->glSwapBuffers();
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...)
|
||||
{
|
||||
if (!g_state.lgr || !params.showAlerts)
|
||||
if (!g_state.lgr || !g_params.showAlerts)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
@@ -46,30 +467,101 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...)
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque)
|
||||
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
|
||||
{
|
||||
// don't allow duplicate binds
|
||||
if (g_state.bindings[key])
|
||||
if (g_state.bindings[sc])
|
||||
{
|
||||
DEBUG_INFO("Key already bound");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
KeybindHandle handle = (KeybindHandle)malloc(sizeof(struct KeybindHandle));
|
||||
handle->key = key;
|
||||
handle->sc = sc;
|
||||
handle->callback = callback;
|
||||
handle->opaque = opaque;
|
||||
|
||||
g_state.bindings[key] = handle;
|
||||
g_state.bindings[sc] = handle;
|
||||
g_state.keyDescription[sc] = description;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void app_release_keybind(KeybindHandle * handle)
|
||||
void app_releaseKeybind(KeybindHandle * handle)
|
||||
{
|
||||
if (!*handle)
|
||||
return;
|
||||
|
||||
g_state.bindings[(*handle)->key] = NULL;
|
||||
g_state.bindings[(*handle)->sc] = NULL;
|
||||
free(*handle);
|
||||
*handle = NULL;
|
||||
}
|
||||
|
||||
void app_releaseAllKeybinds(void)
|
||||
{
|
||||
for(int i = 0; i < KEY_MAX; ++i)
|
||||
if (g_state.bindings[i])
|
||||
{
|
||||
free(g_state.bindings[i]);
|
||||
g_state.bindings[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char * build_help_str()
|
||||
{
|
||||
size_t size = 50;
|
||||
size_t offset = 0;
|
||||
char * buffer = malloc(size);
|
||||
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
const char * escapeName = xfree86_to_display[g_params.escapeKey];
|
||||
|
||||
offset += snprintf(buffer, size, "%s %-10s Toggle capture mode\n", escapeName, "");
|
||||
if (offset >= size)
|
||||
{
|
||||
DEBUG_ERROR("Help string somehow overflowed. This should be impossible.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < KEY_MAX; ++i)
|
||||
{
|
||||
if (g_state.keyDescription[i])
|
||||
{
|
||||
const char * keyName = xfree86_to_display[i];
|
||||
const char * desc = g_state.keyDescription[i];
|
||||
int needed = snprintf(buffer + offset, size - offset, "%s+%-10s %s\n", escapeName, keyName, desc);
|
||||
if (offset + needed < size)
|
||||
offset += needed;
|
||||
else
|
||||
{
|
||||
size = size * 2 + needed;
|
||||
void * new = realloc(buffer, size);
|
||||
if (!new) {
|
||||
free(buffer);
|
||||
DEBUG_ERROR("Out of memory when constructing help text");
|
||||
return NULL;
|
||||
}
|
||||
buffer = new;
|
||||
offset += snprintf(buffer + offset, size - offset, "%s+%-10s %s\n", escapeName, keyName, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void app_showHelp(bool show)
|
||||
{
|
||||
char * help = show ? build_help_str() : NULL;
|
||||
g_state.lgr->on_help(g_state.lgrData, help);
|
||||
free(help);
|
||||
}
|
||||
|
||||
void app_showFPS(bool showFPS)
|
||||
{
|
||||
if (!g_state.lgr)
|
||||
return;
|
||||
|
||||
g_state.lgr->on_show_fps(g_state.lgrData, showFPS);
|
||||
}
|
||||
|
||||
116
client/src/clipboard.c
Normal file
116
client/src/clipboard.c
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "clipboard.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case SPICE_DATA_TEXT: return LG_CLIPBOARD_DATA_TEXT; break;
|
||||
case SPICE_DATA_PNG : return LG_CLIPBOARD_DATA_PNG ; break;
|
||||
case SPICE_DATA_BMP : return LG_CLIPBOARD_DATA_BMP ; break;
|
||||
case SPICE_DATA_TIFF: return LG_CLIPBOARD_DATA_TIFF; break;
|
||||
case SPICE_DATA_JPEG: return LG_CLIPBOARD_DATA_JPEG; break;
|
||||
default:
|
||||
DEBUG_ERROR("invalid spice data type");
|
||||
return LG_CLIPBOARD_DATA_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT: return SPICE_DATA_TEXT; break;
|
||||
case LG_CLIPBOARD_DATA_PNG : return SPICE_DATA_PNG ; break;
|
||||
case LG_CLIPBOARD_DATA_BMP : return SPICE_DATA_BMP ; break;
|
||||
case LG_CLIPBOARD_DATA_TIFF: return SPICE_DATA_TIFF; break;
|
||||
case LG_CLIPBOARD_DATA_JPEG: return SPICE_DATA_JPEG; break;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard data type");
|
||||
return SPICE_DATA_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void cb_spiceNotice(const SpiceDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
if (!g_state.cbAvailable)
|
||||
return;
|
||||
|
||||
g_state.cbType = type;
|
||||
g_state.ds->cbNotice(cb_spiceTypeToLGType(type));
|
||||
}
|
||||
|
||||
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
if (type == SPICE_DATA_TEXT)
|
||||
{
|
||||
// dos2unix
|
||||
uint8_t * p = buffer;
|
||||
uint32_t newSize = size;
|
||||
for(uint32_t i = 0; i < size; ++i)
|
||||
{
|
||||
uint8_t c = buffer[i];
|
||||
if (c == '\r')
|
||||
{
|
||||
--newSize;
|
||||
continue;
|
||||
}
|
||||
*p++ = c;
|
||||
}
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
struct CBRequest * cbr;
|
||||
if (ll_shift(g_state.cbRequestList, (void **)&cbr))
|
||||
{
|
||||
cbr->replyFn(cbr->opaque, cb_spiceTypeToLGType(type), buffer, size);
|
||||
free(cbr);
|
||||
}
|
||||
}
|
||||
|
||||
void cb_spiceRelease(void)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
if (g_state.cbAvailable)
|
||||
g_state.ds->cbRelease();
|
||||
}
|
||||
|
||||
void cb_spiceRequest(const SpiceDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
if (g_state.cbAvailable)
|
||||
g_state.ds->cbRequest(cb_spiceTypeToLGType(type));
|
||||
}
|
||||
29
client/src/clipboard.h
Normal file
29
client/src/clipboard.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "spice/spice.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type);
|
||||
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
|
||||
|
||||
void cb_spiceNotice(const SpiceDataType type);
|
||||
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size);
|
||||
void cb_spiceRelease(void);
|
||||
void cb_spiceRequest(const SpiceDataType type);
|
||||
@@ -19,6 +19,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "main.h"
|
||||
#include "config.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "common/option.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/stringutils.h"
|
||||
@@ -26,10 +28,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <sys/stat.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
//FIXME: this should really not be included here and is an ugly hack to retain
|
||||
//backwards compatibility with the escape key scancode
|
||||
extern uint32_t sdl_to_xfree86[];
|
||||
#include <string.h>
|
||||
|
||||
// forwards
|
||||
static bool optRendererParse (struct Option * opt, const char * str);
|
||||
@@ -41,6 +40,7 @@ static char * optPosToString (struct Option * opt);
|
||||
static bool optSizeParse (struct Option * opt, const char * str);
|
||||
static StringList optSizeValues (struct Option * opt);
|
||||
static char * optSizeToString (struct Option * opt);
|
||||
static bool optScancodeValidate(struct Option * opt, const char ** error);
|
||||
static char * optScancodeToString(struct Option * opt);
|
||||
static bool optRotateValidate (struct Option * opt, const char ** error);
|
||||
|
||||
@@ -224,6 +224,13 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "autoScreensaver",
|
||||
.description = "Prevent the screensaver from starting when guest requests it",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "alerts",
|
||||
@@ -264,14 +271,22 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "releaseKeysOnFocusLoss",
|
||||
.description = "On focus loss, send key up events to guest for all held keys",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "escapeKey",
|
||||
.description = "Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values",
|
||||
.description = "Specify the escape key, see <linux/input-event-codes.h> for valid values",
|
||||
.shortopt = 'm',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = SDL_SCANCODE_SCROLLLOCK,
|
||||
.toString = optScancodeToString
|
||||
.value.x_int = KEY_SCROLLLOCK,
|
||||
.validator = optScancodeValidate,
|
||||
.toString = optScancodeToString,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
@@ -411,9 +426,9 @@ static struct Option options[] =
|
||||
|
||||
void config_init(void)
|
||||
{
|
||||
params.center = true;
|
||||
params.w = 1024;
|
||||
params.h = 768;
|
||||
g_params.center = true;
|
||||
g_params.w = 1024;
|
||||
g_params.h = 768;
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
@@ -468,73 +483,79 @@ bool config_load(int argc, char * argv[])
|
||||
}
|
||||
|
||||
// setup the application params for the basic types
|
||||
params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
|
||||
params.framePollInterval = option_get_int ("app", "framePollInterval" );
|
||||
params.allowDMA = option_get_bool ("app", "allowDMA" );
|
||||
g_params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
|
||||
g_params.framePollInterval = option_get_int ("app", "framePollInterval" );
|
||||
g_params.allowDMA = option_get_bool ("app", "allowDMA" );
|
||||
|
||||
params.windowTitle = option_get_string("win", "title" );
|
||||
params.autoResize = option_get_bool ("win", "autoResize" );
|
||||
params.allowResize = option_get_bool ("win", "allowResize" );
|
||||
params.keepAspect = option_get_bool ("win", "keepAspect" );
|
||||
params.forceAspect = option_get_bool ("win", "forceAspect" );
|
||||
params.dontUpscale = option_get_bool ("win", "dontUpscale" );
|
||||
params.borderless = option_get_bool ("win", "borderless" );
|
||||
params.fullscreen = option_get_bool ("win", "fullScreen" );
|
||||
params.maximize = option_get_bool ("win", "maximize" );
|
||||
params.fpsMin = option_get_int ("win", "fpsMin" );
|
||||
params.showFPS = option_get_bool ("win", "showFPS" );
|
||||
params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
|
||||
params.noScreensaver = option_get_bool ("win", "noScreensaver");
|
||||
params.showAlerts = option_get_bool ("win", "alerts" );
|
||||
params.quickSplash = option_get_bool ("win", "quickSplash" );
|
||||
g_params.windowTitle = option_get_string("win", "title" );
|
||||
g_params.autoResize = option_get_bool ("win", "autoResize" );
|
||||
g_params.allowResize = option_get_bool ("win", "allowResize" );
|
||||
g_params.keepAspect = option_get_bool ("win", "keepAspect" );
|
||||
g_params.forceAspect = option_get_bool ("win", "forceAspect" );
|
||||
g_params.dontUpscale = option_get_bool ("win", "dontUpscale" );
|
||||
g_params.borderless = option_get_bool ("win", "borderless" );
|
||||
g_params.fullscreen = option_get_bool ("win", "fullScreen" );
|
||||
g_params.maximize = option_get_bool ("win", "maximize" );
|
||||
g_params.fpsMin = option_get_int ("win", "fpsMin" );
|
||||
g_params.showFPS = option_get_bool ("win", "showFPS" );
|
||||
g_params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
|
||||
g_params.noScreensaver = option_get_bool ("win", "noScreensaver" );
|
||||
g_params.autoScreensaver = option_get_bool ("win", "autoScreensaver");
|
||||
g_params.showAlerts = option_get_bool ("win", "alerts" );
|
||||
g_params.quickSplash = option_get_bool ("win", "quickSplash" );
|
||||
|
||||
if (g_params.noScreensaver && g_params.autoScreensaver)
|
||||
{
|
||||
fprintf(stderr, "win:noScreensaver (-S) and win:autoScreensaver "
|
||||
"can't be used simultaneously\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(option_get_int("win", "rotate"))
|
||||
{
|
||||
case 0 : params.winRotate = LG_ROTATE_0 ; break;
|
||||
case 90 : params.winRotate = LG_ROTATE_90 ; break;
|
||||
case 180: params.winRotate = LG_ROTATE_180; break;
|
||||
case 270: params.winRotate = LG_ROTATE_270; break;
|
||||
case 0 : g_params.winRotate = LG_ROTATE_0 ; break;
|
||||
case 90 : g_params.winRotate = LG_ROTATE_90 ; break;
|
||||
case 180: g_params.winRotate = LG_ROTATE_180; break;
|
||||
case 270: g_params.winRotate = LG_ROTATE_270; break;
|
||||
}
|
||||
|
||||
params.grabKeyboard = option_get_bool("input", "grabKeyboard" );
|
||||
params.grabKeyboardOnFocus = option_get_bool("input", "grabKeyboardOnFocus");
|
||||
params.escapeKey = option_get_int ("input", "escapeKey" );
|
||||
params.ignoreWindowsKeys = option_get_bool("input", "ignoreWindowsKeys" );
|
||||
params.hideMouse = option_get_bool("input", "hideCursor" );
|
||||
params.mouseSens = option_get_int ("input", "mouseSens" );
|
||||
params.mouseSmoothing = option_get_bool("input", "mouseSmoothing" );
|
||||
params.rawMouse = option_get_bool("input", "rawMouse" );
|
||||
params.mouseRedraw = option_get_bool("input", "mouseRedraw" );
|
||||
params.autoCapture = option_get_bool("input", "autoCapture" );
|
||||
params.captureInputOnly = option_get_bool("input", "captureOnly" );
|
||||
g_params.grabKeyboard = option_get_bool("input", "grabKeyboard" );
|
||||
g_params.grabKeyboardOnFocus = option_get_bool("input", "grabKeyboardOnFocus" );
|
||||
g_params.releaseKeysOnFocusLoss = option_get_bool("input", "releaseKeysOnFocusLoss");
|
||||
g_params.escapeKey = option_get_int ("input", "escapeKey" );
|
||||
g_params.ignoreWindowsKeys = option_get_bool("input", "ignoreWindowsKeys" );
|
||||
g_params.hideMouse = option_get_bool("input", "hideCursor" );
|
||||
g_params.mouseSens = option_get_int ("input", "mouseSens" );
|
||||
g_params.mouseSmoothing = option_get_bool("input", "mouseSmoothing" );
|
||||
g_params.rawMouse = option_get_bool("input", "rawMouse" );
|
||||
g_params.mouseRedraw = option_get_bool("input", "mouseRedraw" );
|
||||
g_params.autoCapture = option_get_bool("input", "autoCapture" );
|
||||
g_params.captureInputOnly = option_get_bool("input", "captureOnly" );
|
||||
|
||||
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
||||
g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
||||
|
||||
if (option_get_bool("spice", "enable"))
|
||||
{
|
||||
params.spiceHost = option_get_string("spice", "host");
|
||||
params.spicePort = option_get_int ("spice", "port");
|
||||
g_params.spiceHost = option_get_string("spice", "host");
|
||||
g_params.spicePort = option_get_int ("spice", "port");
|
||||
|
||||
params.useSpiceInput = option_get_bool("spice", "input" );
|
||||
params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
||||
g_params.useSpiceInput = option_get_bool("spice", "input" );
|
||||
g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
||||
|
||||
if (params.useSpiceClipboard)
|
||||
if (g_params.useSpiceClipboard)
|
||||
{
|
||||
params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
||||
params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
||||
g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
||||
g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
||||
|
||||
if (!params.clipboardToVM && !params.clipboardToLocal)
|
||||
params.useSpiceClipboard = false;
|
||||
if (!g_params.clipboardToVM && !g_params.clipboardToLocal)
|
||||
g_params.useSpiceClipboard = false;
|
||||
}
|
||||
|
||||
params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
|
||||
params.captureOnStart = option_get_bool("spice", "captureOnStart");
|
||||
params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor");
|
||||
g_params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
|
||||
g_params.captureOnStart = option_get_bool("spice", "captureOnStart");
|
||||
g_params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor");
|
||||
}
|
||||
|
||||
//FIXME, this should be using linux keycodes
|
||||
params.escapeKey = sdl_to_xfree86[params.escapeKey];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -574,15 +595,15 @@ static bool optRendererParse(struct Option * opt, const char * str)
|
||||
|
||||
if (strcasecmp(str, "auto") == 0)
|
||||
{
|
||||
params.forceRenderer = false;
|
||||
g_params.forceRenderer = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
||||
if (strcasecmp(str, LG_Renderers[i]->get_name()) == 0)
|
||||
{
|
||||
params.forceRenderer = true;
|
||||
params.forceRendererIndex = i;
|
||||
g_params.forceRenderer = true;
|
||||
g_params.forceRendererIndex = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -602,13 +623,13 @@ static StringList optRendererValues(struct Option * opt)
|
||||
|
||||
static char * optRendererToString(struct Option * opt)
|
||||
{
|
||||
if (!params.forceRenderer)
|
||||
if (!g_params.forceRenderer)
|
||||
return strdup("auto");
|
||||
|
||||
if (params.forceRendererIndex >= LG_RENDERER_COUNT)
|
||||
if (g_params.forceRendererIndex >= LG_RENDERER_COUNT)
|
||||
return NULL;
|
||||
|
||||
return strdup(LG_Renderers[params.forceRendererIndex]->get_name());
|
||||
return strdup(LG_Renderers[g_params.forceRendererIndex]->get_name());
|
||||
}
|
||||
|
||||
static bool optPosParse(struct Option * opt, const char * str)
|
||||
@@ -618,13 +639,13 @@ static bool optPosParse(struct Option * opt, const char * str)
|
||||
|
||||
if (strcmp(str, "center") == 0)
|
||||
{
|
||||
params.center = true;
|
||||
g_params.center = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sscanf(str, "%dx%d", ¶ms.x, ¶ms.y) == 2)
|
||||
if (sscanf(str, "%dx%d", &g_params.x, &g_params.y) == 2)
|
||||
{
|
||||
params.center = false;
|
||||
g_params.center = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -641,12 +662,12 @@ static StringList optPosValues(struct Option * opt)
|
||||
|
||||
static char * optPosToString(struct Option * opt)
|
||||
{
|
||||
if (params.center)
|
||||
if (g_params.center)
|
||||
return strdup("center");
|
||||
|
||||
int len = snprintf(NULL, 0, "%dx%d", params.x, params.y);
|
||||
int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y);
|
||||
char * str = malloc(len + 1);
|
||||
sprintf(str, "%dx%d", params.x, params.y);
|
||||
sprintf(str, "%dx%d", g_params.x, g_params.y);
|
||||
|
||||
return str;
|
||||
}
|
||||
@@ -656,9 +677,9 @@ static bool optSizeParse(struct Option * opt, const char * str)
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (sscanf(str, "%dx%d", ¶ms.w, ¶ms.h) == 2)
|
||||
if (sscanf(str, "%dx%d", &g_params.w, &g_params.h) == 2)
|
||||
{
|
||||
if (params.w < 1 || params.h < 1)
|
||||
if (g_params.w < 1 || g_params.h < 1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -675,18 +696,27 @@ static StringList optSizeValues(struct Option * opt)
|
||||
|
||||
static char * optSizeToString(struct Option * opt)
|
||||
{
|
||||
int len = snprintf(NULL, 0, "%dx%d", params.w, params.h);
|
||||
int len = snprintf(NULL, 0, "%dx%d", g_params.w, g_params.h);
|
||||
char * str = malloc(len + 1);
|
||||
sprintf(str, "%dx%d", params.w, params.h);
|
||||
sprintf(str, "%dx%d", g_params.w, g_params.h);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool optScancodeValidate(struct Option * opt, const char ** error)
|
||||
{
|
||||
if (opt->value.x_int >= 0 && opt->value.x_int < KEY_MAX)
|
||||
return true;
|
||||
|
||||
*error = "Out of range";
|
||||
return false;
|
||||
}
|
||||
|
||||
static char * optScancodeToString(struct Option * opt)
|
||||
{
|
||||
char * str;
|
||||
alloc_sprintf(&str, "%d = %s", opt->value.x_int,
|
||||
SDL_GetScancodeName(opt->value.x_int));
|
||||
xfree86_to_str[opt->value.x_int]);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
499
client/src/core.c
Normal file
499
client/src/core.c
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "common/time.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
|
||||
|
||||
bool core_inputEnabled(void)
|
||||
{
|
||||
return g_params.useSpiceInput && !g_state.ignoreInput &&
|
||||
((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly);
|
||||
}
|
||||
|
||||
void core_setCursorInView(bool enable)
|
||||
{
|
||||
// if the state has not changed, don't do anything else
|
||||
if (g_cursor.inView == enable)
|
||||
return;
|
||||
|
||||
if (enable && !g_state.focused)
|
||||
return;
|
||||
|
||||
// do not allow the view to become active if any mouse buttons are being held,
|
||||
// this fixes issues with meta window resizing.
|
||||
if (enable && g_cursor.buttons)
|
||||
return;
|
||||
|
||||
g_cursor.inView = enable;
|
||||
g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
|
||||
? true : enable;
|
||||
g_cursor.redraw = true;
|
||||
|
||||
/* if the display server does not support warp, then we can not operate in
|
||||
* always relative mode and we should not grab the pointer */
|
||||
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
|
||||
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
|
||||
|
||||
g_cursor.warpState = enable ? WARP_STATE_ON : WARP_STATE_OFF;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
if (g_params.hideMouse)
|
||||
g_state.ds->showPointer(false);
|
||||
|
||||
if (warpSupport != LG_DS_WARP_NONE && !g_params.captureInputOnly)
|
||||
g_state.ds->grabPointer();
|
||||
|
||||
if (g_params.grabKeyboardOnFocus)
|
||||
g_state.ds->grabKeyboard();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_params.hideMouse)
|
||||
g_state.ds->showPointer(true);
|
||||
|
||||
if (warpSupport != LG_DS_WARP_NONE)
|
||||
g_state.ds->ungrabPointer();
|
||||
|
||||
g_state.ds->ungrabKeyboard();
|
||||
}
|
||||
|
||||
g_cursor.warpState = WARP_STATE_ON;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
/* 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->showPointer(!enable);
|
||||
|
||||
if (g_cursor.grab == enable)
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
if (g_params.grabKeyboard)
|
||||
g_state.ds->grabKeyboard();
|
||||
|
||||
g_state.ds->grabPointer();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_params.grabKeyboard)
|
||||
{
|
||||
if (!g_params.grabKeyboardOnFocus ||
|
||||
!g_state.focused || g_params.captureInputOnly)
|
||||
g_state.ds->ungrabKeyboard();
|
||||
}
|
||||
|
||||
if (warpSupport != LG_DS_WARP_NONE || g_params.captureInputOnly || !g_state.formatValid)
|
||||
g_state.ds->ungrabPointer();
|
||||
|
||||
// if exiting capture when input on capture only, we want to show the cursor
|
||||
if (g_params.captureInputOnly || !g_params.hideMouse)
|
||||
core_alignToGuest();
|
||||
}
|
||||
}
|
||||
|
||||
bool core_warpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if (!g_cursor.inWindow && !exiting)
|
||||
return false;
|
||||
|
||||
if (g_cursor.warpState == WARP_STATE_OFF)
|
||||
return false;
|
||||
|
||||
if (exiting)
|
||||
g_cursor.warpState = WARP_STATE_OFF;
|
||||
|
||||
if (g_cursor.pos.x == x && g_cursor.pos.y == y)
|
||||
return true;
|
||||
|
||||
g_state.ds->warpPointer(x, y, exiting);
|
||||
return true;
|
||||
}
|
||||
|
||||
void core_updatePositionInfo(void)
|
||||
{
|
||||
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:
|
||||
assert(!"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 ((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 (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.guest.dpiScale != 100);
|
||||
|
||||
g_cursor.scale.x = (float)srcW / (float)g_state.dstRect.w;
|
||||
g_cursor.scale.y = (float)srcH / (float)g_state.dstRect.h;
|
||||
g_cursor.dpiScale = g_cursor.guest.dpiScale / 100.0f;
|
||||
|
||||
if (!g_state.posInfoValid)
|
||||
{
|
||||
g_state.posInfoValid = true;
|
||||
g_state.ds->realignPointer();
|
||||
}
|
||||
|
||||
done:
|
||||
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_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_handleMouseGrabbed(double ex, double ey)
|
||||
{
|
||||
if (!core_inputEnabled())
|
||||
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 (!spice_mouse_motion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void core_handleMouseNormal(double ex, double ey)
|
||||
{
|
||||
// prevent cursor handling outside of capture if the position is not known
|
||||
if (!g_cursor.guest.valid)
|
||||
return;
|
||||
|
||||
if (!core_inputEnabled())
|
||||
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;
|
||||
if (!g_cursor.inView)
|
||||
{
|
||||
const bool inView = isInView();
|
||||
core_setCursorInView(inView);
|
||||
if (inView)
|
||||
g_cursor.realign = true;
|
||||
}
|
||||
|
||||
/* nothing to do if we are outside the viewport */
|
||||
if (!g_cursor.inView)
|
||||
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)
|
||||
return;
|
||||
|
||||
/* if we have been instructed to realign */
|
||||
if (g_cursor.realign)
|
||||
{
|
||||
g_cursor.realign = false;
|
||||
|
||||
struct DoublePoint guest;
|
||||
util_localCurToGuest(&guest);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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) > 100.0 / g_cursor.scale.x || fabs(ey) > 100.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)
|
||||
{
|
||||
local.x += move.x;
|
||||
local.y += move.y;
|
||||
const int tx = (local.x <= 0.0) ? floor(local.x) : ceil(local.x);
|
||||
const int ty = (local.y <= 0.0) ? floor(local.y) : ceil(local.y);
|
||||
|
||||
switch (warpSupport)
|
||||
{
|
||||
case LG_DS_WARP_NONE:
|
||||
break;
|
||||
|
||||
case LG_DS_WARP_SURFACE:
|
||||
g_state.ds->ungrabPointer();
|
||||
core_warpPointer(tx, ty, true);
|
||||
|
||||
if (!isInView() && tx >= 0 && tx < g_state.windowW && ty >= 0 && ty < g_state.windowH)
|
||||
core_setCursorInView(false);
|
||||
break;
|
||||
|
||||
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))
|
||||
{
|
||||
core_setCursorInView(false);
|
||||
|
||||
/* preempt the window leave flag if the warp will leave our window */
|
||||
if (tx < 0 || ty < 0 || tx > g_state.windowW || ty > g_state.windowH)
|
||||
g_cursor.inWindow = false;
|
||||
|
||||
/* ungrab the pointer and move the local cursor to the exit point */
|
||||
g_state.ds->ungrabPointer();
|
||||
core_warpPointer(tx, ty, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (warpSupport == LG_DS_WARP_SURFACE && isInView())
|
||||
{
|
||||
/* regrab the pointer in case the user did not move off the surface */
|
||||
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;
|
||||
core_warpPointer(g_state.windowCX, g_state.windowCY, false);
|
||||
}
|
||||
|
||||
g_cursor.guest.x = g_state.srcSize.x / 2;
|
||||
g_cursor.guest.y = g_state.srcSize.y / 2;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
39
client/src/core.h
Normal file
39
client/src/core.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_CORE_
|
||||
#define _H_LG_CORE_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool core_inputEnabled(void);
|
||||
void core_setCursorInView(bool enable);
|
||||
void core_setGrab(bool enable);
|
||||
void core_setGrabQuiet(bool enable);
|
||||
bool core_warpPointer(int x, int y, bool exiting);
|
||||
void core_updatePositionInfo(void);
|
||||
void core_alignToGuest(void);
|
||||
bool core_isValidPointerPos(int x, int y);
|
||||
bool core_startFrameThread(void);
|
||||
void core_stopFrameThread(void);
|
||||
void core_handleMouseGrabbed(double ex, double ey);
|
||||
void core_handleMouseNormal(double ex, double ey);
|
||||
|
||||
|
||||
#endif
|
||||
36
client/src/egl_dynprocs.c
Normal file
36
client/src/egl_dynprocs.c
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
|
||||
#include "egl_dynprocs.h"
|
||||
|
||||
struct EGLDynProcs g_egl_dynProcs = {0};
|
||||
|
||||
void egl_dynProcsInit(void)
|
||||
{
|
||||
g_egl_dynProcs.eglGetPlatformDisplay = (eglGetPlatformDisplayEXT_t)
|
||||
eglGetProcAddress("eglGetPlatformDisplay");
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_t)
|
||||
eglGetProcAddress("eglGetPlatformDisplayEXT");
|
||||
g_egl_dynProcs.glEGLImageTargetTexture2DOES = (glEGLImageTargetTexture2DOES_t)
|
||||
eglGetProcAddress("glEGLImageTargetTexture2DOES");
|
||||
};
|
||||
|
||||
#endif
|
||||
394
client/src/kb.c
Normal file
394
client/src/kb.c
Normal file
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_KB_
|
||||
#define _H_LG_KB_
|
||||
|
||||
#include "kb.h"
|
||||
|
||||
const uint32_t xfree86_to_ps2[KEY_MAX] =
|
||||
{
|
||||
[KEY_RESERVED] /* = USB 0 */ = 0x000000,
|
||||
[KEY_ESC] /* = USB 41 */ = 0x000001,
|
||||
[KEY_1] /* = USB 30 */ = 0x000002,
|
||||
[KEY_2] /* = USB 31 */ = 0x000003,
|
||||
[KEY_3] /* = USB 32 */ = 0x000004,
|
||||
[KEY_4] /* = USB 33 */ = 0x000005,
|
||||
[KEY_5] /* = USB 34 */ = 0x000006,
|
||||
[KEY_6] /* = USB 35 */ = 0x000007,
|
||||
[KEY_7] /* = USB 36 */ = 0x000008,
|
||||
[KEY_8] /* = USB 37 */ = 0x000009,
|
||||
[KEY_9] /* = USB 38 */ = 0x00000A,
|
||||
[KEY_0] /* = USB 39 */ = 0x00000B,
|
||||
[KEY_MINUS] /* = USB 45 */ = 0x00000C,
|
||||
[KEY_EQUAL] /* = USB 46 */ = 0x00000D,
|
||||
[KEY_BACKSPACE] /* = USB 42 */ = 0x00000E,
|
||||
[KEY_TAB] /* = USB 43 */ = 0x00000F,
|
||||
[KEY_Q] /* = USB 20 */ = 0x000010,
|
||||
[KEY_W] /* = USB 26 */ = 0x000011,
|
||||
[KEY_E] /* = USB 8 */ = 0x000012,
|
||||
[KEY_R] /* = USB 21 */ = 0x000013,
|
||||
[KEY_T] /* = USB 23 */ = 0x000014,
|
||||
[KEY_Y] /* = USB 28 */ = 0x000015,
|
||||
[KEY_U] /* = USB 24 */ = 0x000016,
|
||||
[KEY_I] /* = USB 12 */ = 0x000017,
|
||||
[KEY_O] /* = USB 18 */ = 0x000018,
|
||||
[KEY_P] /* = USB 19 */ = 0x000019,
|
||||
[KEY_LEFTBRACE] /* = USB 47 */ = 0x00001A,
|
||||
[KEY_RIGHTBRACE] /* = USB 48 */ = 0x00001B,
|
||||
[KEY_ENTER] /* = USB 40 */ = 0x00001C,
|
||||
[KEY_LEFTCTRL] /* = USB 224 */ = 0x00001D,
|
||||
[KEY_A] /* = USB 4 */ = 0x00001E,
|
||||
[KEY_S] /* = USB 22 */ = 0x00001F,
|
||||
[KEY_D] /* = USB 7 */ = 0x000020,
|
||||
[KEY_F] /* = USB 9 */ = 0x000021,
|
||||
[KEY_G] /* = USB 10 */ = 0x000022,
|
||||
[KEY_H] /* = USB 11 */ = 0x000023,
|
||||
[KEY_J] /* = USB 13 */ = 0x000024,
|
||||
[KEY_K] /* = USB 14 */ = 0x000025,
|
||||
[KEY_L] /* = USB 15 */ = 0x000026,
|
||||
[KEY_SEMICOLON] /* = USB 51 */ = 0x000027,
|
||||
[KEY_APOSTROPHE] /* = USB 52 */ = 0x000028,
|
||||
[KEY_GRAVE] /* = USB 53 */ = 0x000029,
|
||||
[KEY_LEFTSHIFT] /* = USB 225 */ = 0x00002A,
|
||||
[KEY_BACKSLASH] /* = USB 49 */ = 0x00002B,
|
||||
[KEY_Z] /* = USB 29 */ = 0x00002C,
|
||||
[KEY_X] /* = USB 27 */ = 0x00002D,
|
||||
[KEY_C] /* = USB 6 */ = 0x00002E,
|
||||
[KEY_V] /* = USB 25 */ = 0x00002F,
|
||||
[KEY_B] /* = USB 5 */ = 0x000030,
|
||||
[KEY_N] /* = USB 17 */ = 0x000031,
|
||||
[KEY_M] /* = USB 16 */ = 0x000032,
|
||||
[KEY_COMMA] /* = USB 54 */ = 0x000033,
|
||||
[KEY_DOT] /* = USB 55 */ = 0x000034,
|
||||
[KEY_SLASH] /* = USB 56 */ = 0x000035,
|
||||
[KEY_RIGHTSHIFT] /* = USB 229 */ = 0x000036,
|
||||
[KEY_KPASTERISK] /* = USB 85 */ = 0x000037,
|
||||
[KEY_LEFTALT] /* = USB 226 */ = 0x000038,
|
||||
[KEY_SPACE] /* = USB 44 */ = 0x000039,
|
||||
[KEY_CAPSLOCK] /* = USB 57 */ = 0x00003A,
|
||||
[KEY_F1] /* = USB 58 */ = 0x00003B,
|
||||
[KEY_F2] /* = USB 59 */ = 0x00003C,
|
||||
[KEY_F3] /* = USB 60 */ = 0x00003D,
|
||||
[KEY_F4] /* = USB 61 */ = 0x00003E,
|
||||
[KEY_F5] /* = USB 62 */ = 0x00003F,
|
||||
[KEY_F6] /* = USB 63 */ = 0x000040,
|
||||
[KEY_F7] /* = USB 64 */ = 0x000041,
|
||||
[KEY_F8] /* = USB 65 */ = 0x000042,
|
||||
[KEY_F9] /* = USB 66 */ = 0x000043,
|
||||
[KEY_F10] /* = USB 67 */ = 0x000044,
|
||||
[KEY_NUMLOCK] /* = USB 83 */ = 0x000045,
|
||||
[KEY_SCROLLLOCK] /* = USB 71 */ = 0x000046,
|
||||
[KEY_KP7] /* = USB 95 */ = 0x000047,
|
||||
[KEY_KP8] /* = USB 96 */ = 0x000048,
|
||||
[KEY_KP9] /* = USB 97 */ = 0x000049,
|
||||
[KEY_KPMINUS] /* = USB 86 */ = 0x00004A,
|
||||
[KEY_KP4] /* = USB 92 */ = 0x00004B,
|
||||
[KEY_KP5] /* = USB 93 */ = 0x00004C,
|
||||
[KEY_KP6] /* = USB 94 */ = 0x00004D,
|
||||
[KEY_KPPLUS] /* = USB 87 */ = 0x00004E,
|
||||
[KEY_KP1] /* = USB 89 */ = 0x00004F,
|
||||
[KEY_KP2] /* = USB 90 */ = 0x000050,
|
||||
[KEY_KP3] /* = USB 91 */ = 0x000051,
|
||||
[KEY_KP0] /* = USB 98 */ = 0x000052,
|
||||
[KEY_KPDOT] /* = USB 99 */ = 0x000053,
|
||||
[KEY_102ND] /* = USB 100 */ = 0x000056,
|
||||
[KEY_F11] /* = USB 68 */ = 0x000057,
|
||||
[KEY_F12] /* = USB 69 */ = 0x000058,
|
||||
[KEY_RO] /* = USB 135 */ = 0x000073,
|
||||
[KEY_HENKAN] /* = USB 138 */ = 0x000079,
|
||||
[KEY_KATAKANAHIRAGANA] /* = USB 136 */ = 0x000070,
|
||||
[KEY_MUHENKAN] /* = USB 139 */ = 0x00007B,
|
||||
[KEY_KPENTER] /* = USB 88 */ = 0x00E01C,
|
||||
[KEY_RIGHTCTRL] /* = USB 228 */ = 0x00E01D,
|
||||
[KEY_KPSLASH] /* = USB 84 */ = 0x00E035,
|
||||
[KEY_SYSRQ] /* = USB 70 */ = 0x00E037,
|
||||
[KEY_RIGHTALT] /* = USB 230 */ = 0x00E038,
|
||||
[KEY_HOME] /* = USB 74 */ = 0x00E047,
|
||||
[KEY_UP] /* = USB 82 */ = 0x00E048,
|
||||
[KEY_PAGEUP] /* = USB 75 */ = 0x00E049,
|
||||
[KEY_LEFT] /* = USB 80 */ = 0x00E04B,
|
||||
[KEY_RIGHT] /* = USB 79 */ = 0x00E04D,
|
||||
[KEY_END] /* = USB 77 */ = 0x00E04F,
|
||||
[KEY_DOWN] /* = USB 81 */ = 0x00E050,
|
||||
[KEY_PAGEDOWN] /* = USB 78 */ = 0x00E051,
|
||||
[KEY_INSERT] /* = USB 73 */ = 0x00E052,
|
||||
[KEY_DELETE] /* = USB 76 */ = 0x00E053,
|
||||
[KEY_KPEQUAL] /* = USB 103 */ = 0x000059,
|
||||
[KEY_PAUSE] /* = USB 72 */ = 0x00E046,
|
||||
[KEY_KPCOMMA] /* = USB 133 */ = 0x00007E,
|
||||
[KEY_HANGEUL] /* = USB 144 */ = 0x0000F2,
|
||||
[KEY_HANJA] /* = USB 145 */ = 0x0000F1,
|
||||
[KEY_YEN] /* = USB 137 */ = 0x00007D,
|
||||
[KEY_LEFTMETA] /* = USB 227 */ = 0x00E05B,
|
||||
[KEY_RIGHTMETA] /* = USB 231 */ = 0x00E05C,
|
||||
[KEY_COMPOSE] /* = USB 101 */ = 0x00E05D,
|
||||
[KEY_F13] /* = USB 104 */ = 0x00005D,
|
||||
[KEY_F14] /* = USB 105 */ = 0x00005E,
|
||||
[KEY_F15] /* = USB 106 */ = 0x00005F,
|
||||
[KEY_PRINT] /* = USB 70 */ = 0x00E037,
|
||||
};
|
||||
|
||||
const char * xfree86_to_str[KEY_MAX] =
|
||||
{
|
||||
[KEY_RESERVED] = "KEY_RESERVED",
|
||||
[KEY_ESC] = "KEY_ESC",
|
||||
[KEY_1] = "KEY_1",
|
||||
[KEY_2] = "KEY_2",
|
||||
[KEY_3] = "KEY_3",
|
||||
[KEY_4] = "KEY_4",
|
||||
[KEY_5] = "KEY_5",
|
||||
[KEY_6] = "KEY_6",
|
||||
[KEY_7] = "KEY_7",
|
||||
[KEY_8] = "KEY_8",
|
||||
[KEY_9] = "KEY_9",
|
||||
[KEY_0] = "KEY_0",
|
||||
[KEY_MINUS] = "KEY_MINUS",
|
||||
[KEY_EQUAL] = "KEY_EQUAL",
|
||||
[KEY_BACKSPACE] = "KEY_BACKSPACE",
|
||||
[KEY_TAB] = "KEY_TAB",
|
||||
[KEY_Q] = "KEY_Q",
|
||||
[KEY_W] = "KEY_W",
|
||||
[KEY_E] = "KEY_E",
|
||||
[KEY_R] = "KEY_R",
|
||||
[KEY_T] = "KEY_T",
|
||||
[KEY_Y] = "KEY_Y",
|
||||
[KEY_U] = "KEY_U",
|
||||
[KEY_I] = "KEY_I",
|
||||
[KEY_O] = "KEY_O",
|
||||
[KEY_P] = "KEY_P",
|
||||
[KEY_LEFTBRACE] = "KEY_LEFTBRACE",
|
||||
[KEY_RIGHTBRACE] = "KEY_RIGHTBRACE",
|
||||
[KEY_ENTER] = "KEY_ENTER",
|
||||
[KEY_LEFTCTRL] = "KEY_LEFTCTRL",
|
||||
[KEY_A] = "KEY_A",
|
||||
[KEY_S] = "KEY_S",
|
||||
[KEY_D] = "KEY_D",
|
||||
[KEY_F] = "KEY_F",
|
||||
[KEY_G] = "KEY_G",
|
||||
[KEY_H] = "KEY_H",
|
||||
[KEY_J] = "KEY_J",
|
||||
[KEY_K] = "KEY_K",
|
||||
[KEY_L] = "KEY_L",
|
||||
[KEY_SEMICOLON] = "KEY_SEMICOLON",
|
||||
[KEY_APOSTROPHE] = "KEY_APOSTROPHE",
|
||||
[KEY_GRAVE] = "KEY_GRAVE",
|
||||
[KEY_LEFTSHIFT] = "KEY_LEFTSHIFT",
|
||||
[KEY_BACKSLASH] = "KEY_BACKSLASH",
|
||||
[KEY_Z] = "KEY_Z",
|
||||
[KEY_X] = "KEY_X",
|
||||
[KEY_C] = "KEY_C",
|
||||
[KEY_V] = "KEY_V",
|
||||
[KEY_B] = "KEY_B",
|
||||
[KEY_N] = "KEY_N",
|
||||
[KEY_M] = "KEY_M",
|
||||
[KEY_COMMA] = "KEY_COMMA",
|
||||
[KEY_DOT] = "KEY_DOT",
|
||||
[KEY_SLASH] = "KEY_SLASH",
|
||||
[KEY_RIGHTSHIFT] = "KEY_RIGHTSHIFT",
|
||||
[KEY_KPASTERISK] = "KEY_KPASTERISK",
|
||||
[KEY_LEFTALT] = "KEY_LEFTALT",
|
||||
[KEY_SPACE] = "KEY_SPACE",
|
||||
[KEY_CAPSLOCK] = "KEY_CAPSLOCK",
|
||||
[KEY_F1] = "KEY_F1",
|
||||
[KEY_F2] = "KEY_F2",
|
||||
[KEY_F3] = "KEY_F3",
|
||||
[KEY_F4] = "KEY_F4",
|
||||
[KEY_F5] = "KEY_F5",
|
||||
[KEY_F6] = "KEY_F6",
|
||||
[KEY_F7] = "KEY_F7",
|
||||
[KEY_F8] = "KEY_F8",
|
||||
[KEY_F9] = "KEY_F9",
|
||||
[KEY_F10] = "KEY_F10",
|
||||
[KEY_NUMLOCK] = "KEY_NUMLOCK",
|
||||
[KEY_SCROLLLOCK] = "KEY_SCROLLLOCK",
|
||||
[KEY_KP7] = "KEY_KP7",
|
||||
[KEY_KP8] = "KEY_KP8",
|
||||
[KEY_KP9] = "KEY_KP9",
|
||||
[KEY_KPMINUS] = "KEY_KPMINUS",
|
||||
[KEY_KP4] = "KEY_KP4",
|
||||
[KEY_KP5] = "KEY_KP5",
|
||||
[KEY_KP6] = "KEY_KP6",
|
||||
[KEY_KPPLUS] = "KEY_KPPLUS",
|
||||
[KEY_KP1] = "KEY_KP1",
|
||||
[KEY_KP2] = "KEY_KP2",
|
||||
[KEY_KP3] = "KEY_KP3",
|
||||
[KEY_KP0] = "KEY_KP0",
|
||||
[KEY_KPDOT] = "KEY_KPDOT",
|
||||
[KEY_102ND] = "KEY_102ND",
|
||||
[KEY_F11] = "KEY_F11",
|
||||
[KEY_F12] = "KEY_F12",
|
||||
[KEY_RO] = "KEY_RO",
|
||||
[KEY_HENKAN] = "KEY_HENKAN",
|
||||
[KEY_KATAKANAHIRAGANA] = "KEY_KATAKANAHIRAGANA",
|
||||
[KEY_MUHENKAN] = "KEY_MUHENKAN",
|
||||
[KEY_KPENTER] = "KEY_KPENTER",
|
||||
[KEY_RIGHTCTRL] = "KEY_RIGHTCTRL",
|
||||
[KEY_KPSLASH] = "KEY_KPSLASH",
|
||||
[KEY_SYSRQ] = "KEY_SYSRQ",
|
||||
[KEY_RIGHTALT] = "KEY_RIGHTALT",
|
||||
[KEY_HOME] = "KEY_HOME",
|
||||
[KEY_UP] = "KEY_UP",
|
||||
[KEY_PAGEUP] = "KEY_PAGEUP",
|
||||
[KEY_LEFT] = "KEY_LEFT",
|
||||
[KEY_RIGHT] = "KEY_RIGHT",
|
||||
[KEY_END] = "KEY_END",
|
||||
[KEY_DOWN] = "KEY_DOWN",
|
||||
[KEY_PAGEDOWN] = "KEY_PAGEDOWN",
|
||||
[KEY_INSERT] = "KEY_INSERT",
|
||||
[KEY_DELETE] = "KEY_DELETE",
|
||||
[KEY_KPEQUAL] = "KEY_KPEQUAL",
|
||||
[KEY_PAUSE] = "KEY_PAUSE",
|
||||
[KEY_KPCOMMA] = "KEY_KPCOMMA",
|
||||
[KEY_HANGEUL] = "KEY_HANGEUL",
|
||||
[KEY_HANJA] = "KEY_HANJA",
|
||||
[KEY_YEN] = "KEY_YEN",
|
||||
[KEY_LEFTMETA] = "KEY_LEFTMETA",
|
||||
[KEY_RIGHTMETA] = "KEY_RIGHTMETA",
|
||||
[KEY_COMPOSE] = "KEY_COMPOSE",
|
||||
[KEY_F13] = "KEY_F13",
|
||||
[KEY_F14] = "KEY_F14",
|
||||
[KEY_F15] = "KEY_F15",
|
||||
[KEY_PRINT] = "KEY_PRINT",
|
||||
};
|
||||
|
||||
const char * xfree86_to_display[KEY_MAX] =
|
||||
{
|
||||
[KEY_RESERVED] = "Reserved",
|
||||
[KEY_ESC] = "Esc",
|
||||
[KEY_1] = "1",
|
||||
[KEY_2] = "2",
|
||||
[KEY_3] = "3",
|
||||
[KEY_4] = "4",
|
||||
[KEY_5] = "5",
|
||||
[KEY_6] = "6",
|
||||
[KEY_7] = "7",
|
||||
[KEY_8] = "8",
|
||||
[KEY_9] = "9",
|
||||
[KEY_0] = "0",
|
||||
[KEY_MINUS] = "-",
|
||||
[KEY_EQUAL] = "=",
|
||||
[KEY_BACKSPACE] = "Backspace",
|
||||
[KEY_TAB] = "Tab",
|
||||
[KEY_Q] = "Q",
|
||||
[KEY_W] = "W",
|
||||
[KEY_E] = "E",
|
||||
[KEY_R] = "R",
|
||||
[KEY_T] = "T",
|
||||
[KEY_Y] = "Y",
|
||||
[KEY_U] = "U",
|
||||
[KEY_I] = "I",
|
||||
[KEY_O] = "O",
|
||||
[KEY_P] = "P",
|
||||
[KEY_LEFTBRACE] = "{",
|
||||
[KEY_RIGHTBRACE] = "}",
|
||||
[KEY_ENTER] = "Enter",
|
||||
[KEY_LEFTCTRL] = "LCtrl",
|
||||
[KEY_A] = "A",
|
||||
[KEY_S] = "S",
|
||||
[KEY_D] = "D",
|
||||
[KEY_F] = "F",
|
||||
[KEY_G] = "G",
|
||||
[KEY_H] = "H",
|
||||
[KEY_J] = "J",
|
||||
[KEY_K] = "K",
|
||||
[KEY_L] = "L",
|
||||
[KEY_SEMICOLON] = ";",
|
||||
[KEY_APOSTROPHE] = "'",
|
||||
[KEY_GRAVE] = "`",
|
||||
[KEY_LEFTSHIFT] = "LShift",
|
||||
[KEY_BACKSLASH] = "\\",
|
||||
[KEY_Z] = "Z",
|
||||
[KEY_X] = "X",
|
||||
[KEY_C] = "C",
|
||||
[KEY_V] = "V",
|
||||
[KEY_B] = "B",
|
||||
[KEY_N] = "N",
|
||||
[KEY_M] = "M",
|
||||
[KEY_COMMA] = ",",
|
||||
[KEY_DOT] = ".",
|
||||
[KEY_SLASH] = "/",
|
||||
[KEY_RIGHTSHIFT] = "RShift",
|
||||
[KEY_KPASTERISK] = "*",
|
||||
[KEY_LEFTALT] = "LAlt",
|
||||
[KEY_SPACE] = "Space",
|
||||
[KEY_CAPSLOCK] = "CapsLock",
|
||||
[KEY_F1] = "F1",
|
||||
[KEY_F2] = "F2",
|
||||
[KEY_F3] = "F3",
|
||||
[KEY_F4] = "F4",
|
||||
[KEY_F5] = "F5",
|
||||
[KEY_F6] = "F6",
|
||||
[KEY_F7] = "F7",
|
||||
[KEY_F8] = "F8",
|
||||
[KEY_F9] = "F9",
|
||||
[KEY_F10] = "F10",
|
||||
[KEY_NUMLOCK] = "NumLock",
|
||||
[KEY_SCROLLLOCK] = "ScrollLock",
|
||||
[KEY_KP7] = "KP7",
|
||||
[KEY_KP8] = "KP8",
|
||||
[KEY_KP9] = "KP9",
|
||||
[KEY_KPMINUS] = "KPMinus",
|
||||
[KEY_KP4] = "KP4",
|
||||
[KEY_KP5] = "KP5",
|
||||
[KEY_KP6] = "KP6",
|
||||
[KEY_KPPLUS] = "KPPlus",
|
||||
[KEY_KP1] = "KP1",
|
||||
[KEY_KP2] = "KP2",
|
||||
[KEY_KP3] = "KP3",
|
||||
[KEY_KP0] = "KP0",
|
||||
[KEY_KPDOT] = "KPDOT",
|
||||
[KEY_102ND] = "102ND",
|
||||
[KEY_F11] = "F11",
|
||||
[KEY_F12] = "F12",
|
||||
[KEY_RO] = "RO",
|
||||
[KEY_HENKAN] = "Henkan",
|
||||
[KEY_KATAKANAHIRAGANA] = "Kana",
|
||||
[KEY_MUHENKAN] = "Muhenkan",
|
||||
[KEY_KPENTER] = "KPEnter",
|
||||
[KEY_RIGHTCTRL] = "RCtrl",
|
||||
[KEY_KPSLASH] = "KPSlash",
|
||||
[KEY_SYSRQ] = "SysRQ",
|
||||
[KEY_RIGHTALT] = "RAlt",
|
||||
[KEY_HOME] = "Home",
|
||||
[KEY_UP] = "↑",
|
||||
[KEY_PAGEUP] = "PageUp",
|
||||
[KEY_LEFT] = "←",
|
||||
[KEY_RIGHT] = "→",
|
||||
[KEY_END] = "End",
|
||||
[KEY_DOWN] = "↓",
|
||||
[KEY_PAGEDOWN] = "PageDown",
|
||||
[KEY_INSERT] = "Insert",
|
||||
[KEY_DELETE] = "Delete",
|
||||
[KEY_KPEQUAL] = "KPEqual",
|
||||
[KEY_PAUSE] = "Pause",
|
||||
[KEY_KPCOMMA] = "KPComma",
|
||||
[KEY_HANGEUL] = "Hangul",
|
||||
[KEY_HANJA] = "Hanja",
|
||||
[KEY_YEN] = "Yen",
|
||||
[KEY_LEFTMETA] = "LWin",
|
||||
[KEY_RIGHTMETA] = "RWin",
|
||||
[KEY_COMPOSE] = "Compose",
|
||||
[KEY_F13] = "F13",
|
||||
[KEY_F14] = "F14",
|
||||
[KEY_F15] = "F15",
|
||||
[KEY_PRINT] = "Print",
|
||||
};
|
||||
|
||||
#endif
|
||||
128
client/src/kb.h
128
client/src/kb.h
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -18,126 +18,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <stdint.h>
|
||||
|
||||
const uint32_t xfree86_to_ps2[KEY_MAX] =
|
||||
{
|
||||
[KEY_RESERVED] /* = USB 0 */ = 0x000000,
|
||||
[KEY_ESC] /* = USB 41 */ = 0x000001,
|
||||
[KEY_1] /* = USB 30 */ = 0x000002,
|
||||
[KEY_2] /* = USB 31 */ = 0x000003,
|
||||
[KEY_3] /* = USB 32 */ = 0x000004,
|
||||
[KEY_4] /* = USB 33 */ = 0x000005,
|
||||
[KEY_5] /* = USB 34 */ = 0x000006,
|
||||
[KEY_6] /* = USB 35 */ = 0x000007,
|
||||
[KEY_7] /* = USB 36 */ = 0x000008,
|
||||
[KEY_8] /* = USB 37 */ = 0x000009,
|
||||
[KEY_9] /* = USB 38 */ = 0x00000A,
|
||||
[KEY_0] /* = USB 39 */ = 0x00000B,
|
||||
[KEY_MINUS] /* = USB 45 */ = 0x00000C,
|
||||
[KEY_EQUAL] /* = USB 46 */ = 0x00000D,
|
||||
[KEY_BACKSPACE] /* = USB 42 */ = 0x00000E,
|
||||
[KEY_TAB] /* = USB 43 */ = 0x00000F,
|
||||
[KEY_Q] /* = USB 20 */ = 0x000010,
|
||||
[KEY_W] /* = USB 26 */ = 0x000011,
|
||||
[KEY_E] /* = USB 8 */ = 0x000012,
|
||||
[KEY_R] /* = USB 21 */ = 0x000013,
|
||||
[KEY_T] /* = USB 23 */ = 0x000014,
|
||||
[KEY_Y] /* = USB 28 */ = 0x000015,
|
||||
[KEY_U] /* = USB 24 */ = 0x000016,
|
||||
[KEY_I] /* = USB 12 */ = 0x000017,
|
||||
[KEY_O] /* = USB 18 */ = 0x000018,
|
||||
[KEY_P] /* = USB 19 */ = 0x000019,
|
||||
[KEY_LEFTBRACE] /* = USB 47 */ = 0x00001A,
|
||||
[KEY_RIGHTBRACE] /* = USB 48 */ = 0x00001B,
|
||||
[KEY_ENTER] /* = USB 40 */ = 0x00001C,
|
||||
[KEY_LEFTCTRL] /* = USB 224 */ = 0x00001D,
|
||||
[KEY_A] /* = USB 4 */ = 0x00001E,
|
||||
[KEY_S] /* = USB 22 */ = 0x00001F,
|
||||
[KEY_D] /* = USB 7 */ = 0x000020,
|
||||
[KEY_F] /* = USB 9 */ = 0x000021,
|
||||
[KEY_G] /* = USB 10 */ = 0x000022,
|
||||
[KEY_H] /* = USB 11 */ = 0x000023,
|
||||
[KEY_J] /* = USB 13 */ = 0x000024,
|
||||
[KEY_K] /* = USB 14 */ = 0x000025,
|
||||
[KEY_L] /* = USB 15 */ = 0x000026,
|
||||
[KEY_SEMICOLON] /* = USB 51 */ = 0x000027,
|
||||
[KEY_APOSTROPHE] /* = USB 52 */ = 0x000028,
|
||||
[KEY_GRAVE] /* = USB 53 */ = 0x000029,
|
||||
[KEY_LEFTSHIFT] /* = USB 225 */ = 0x00002A,
|
||||
[KEY_BACKSLASH] /* = USB 49 */ = 0x00002B,
|
||||
[KEY_Z] /* = USB 29 */ = 0x00002C,
|
||||
[KEY_X] /* = USB 27 */ = 0x00002D,
|
||||
[KEY_C] /* = USB 6 */ = 0x00002E,
|
||||
[KEY_V] /* = USB 25 */ = 0x00002F,
|
||||
[KEY_B] /* = USB 5 */ = 0x000030,
|
||||
[KEY_N] /* = USB 17 */ = 0x000031,
|
||||
[KEY_M] /* = USB 16 */ = 0x000032,
|
||||
[KEY_COMMA] /* = USB 54 */ = 0x000033,
|
||||
[KEY_DOT] /* = USB 55 */ = 0x000034,
|
||||
[KEY_SLASH] /* = USB 56 */ = 0x000035,
|
||||
[KEY_RIGHTSHIFT] /* = USB 229 */ = 0x000036,
|
||||
[KEY_KPASTERISK] /* = USB 85 */ = 0x000037,
|
||||
[KEY_LEFTALT] /* = USB 226 */ = 0x000038,
|
||||
[KEY_SPACE] /* = USB 44 */ = 0x000039,
|
||||
[KEY_CAPSLOCK] /* = USB 57 */ = 0x00003A,
|
||||
[KEY_F1] /* = USB 58 */ = 0x00003B,
|
||||
[KEY_F2] /* = USB 59 */ = 0x00003C,
|
||||
[KEY_F3] /* = USB 60 */ = 0x00003D,
|
||||
[KEY_F4] /* = USB 61 */ = 0x00003E,
|
||||
[KEY_F5] /* = USB 62 */ = 0x00003F,
|
||||
[KEY_F6] /* = USB 63 */ = 0x000040,
|
||||
[KEY_F7] /* = USB 64 */ = 0x000041,
|
||||
[KEY_F8] /* = USB 65 */ = 0x000042,
|
||||
[KEY_F9] /* = USB 66 */ = 0x000043,
|
||||
[KEY_F10] /* = USB 67 */ = 0x000044,
|
||||
[KEY_NUMLOCK] /* = USB 83 */ = 0x000045,
|
||||
[KEY_SCROLLLOCK] /* = USB 71 */ = 0x000046,
|
||||
[KEY_KP7] /* = USB 95 */ = 0x000047,
|
||||
[KEY_KP8] /* = USB 96 */ = 0x000048,
|
||||
[KEY_KP9] /* = USB 97 */ = 0x000049,
|
||||
[KEY_KPMINUS] /* = USB 86 */ = 0x00004A,
|
||||
[KEY_KP4] /* = USB 92 */ = 0x00004B,
|
||||
[KEY_KP5] /* = USB 93 */ = 0x00004C,
|
||||
[KEY_KP6] /* = USB 94 */ = 0x00004D,
|
||||
[KEY_KPPLUS] /* = USB 87 */ = 0x00004E,
|
||||
[KEY_KP1] /* = USB 89 */ = 0x00004F,
|
||||
[KEY_KP2] /* = USB 90 */ = 0x000050,
|
||||
[KEY_KP3] /* = USB 91 */ = 0x000051,
|
||||
[KEY_KP0] /* = USB 98 */ = 0x000052,
|
||||
[KEY_KPDOT] /* = USB 99 */ = 0x000053,
|
||||
[KEY_102ND] /* = USB 100 */ = 0x000056,
|
||||
[KEY_F11] /* = USB 68 */ = 0x000057,
|
||||
[KEY_F12] /* = USB 69 */ = 0x000058,
|
||||
[KEY_RO] /* = USB 135 */ = 0x000073,
|
||||
[KEY_HENKAN] /* = USB 138 */ = 0x000079,
|
||||
[KEY_KATAKANAHIRAGANA] /* = USB 136 */ = 0x000070,
|
||||
[KEY_MUHENKAN] /* = USB 139 */ = 0x00007B,
|
||||
[KEY_KPENTER] /* = USB 88 */ = 0x00E01C,
|
||||
[KEY_RIGHTCTRL] /* = USB 228 */ = 0x00E01D,
|
||||
[KEY_KPSLASH] /* = USB 84 */ = 0x00E035,
|
||||
[KEY_SYSRQ] /* = USB 70 */ = 0x00E037,
|
||||
[KEY_RIGHTALT] /* = USB 230 */ = 0x00E038,
|
||||
[KEY_HOME] /* = USB 74 */ = 0x00E047,
|
||||
[KEY_UP] /* = USB 82 */ = 0x00E048,
|
||||
[KEY_PAGEUP] /* = USB 75 */ = 0x00E049,
|
||||
[KEY_LEFT] /* = USB 80 */ = 0x00E04B,
|
||||
[KEY_RIGHT] /* = USB 79 */ = 0x00E04D,
|
||||
[KEY_END] /* = USB 77 */ = 0x00E04F,
|
||||
[KEY_DOWN] /* = USB 81 */ = 0x00E050,
|
||||
[KEY_PAGEDOWN] /* = USB 78 */ = 0x00E051,
|
||||
[KEY_INSERT] /* = USB 73 */ = 0x00E052,
|
||||
[KEY_DELETE] /* = USB 76 */ = 0x00E053,
|
||||
[KEY_KPEQUAL] /* = USB 103 */ = 0x000059,
|
||||
[KEY_PAUSE] /* = USB 72 */ = 0x00E046,
|
||||
[KEY_KPCOMMA] /* = USB 133 */ = 0x00007E,
|
||||
[KEY_HANGEUL] /* = USB 144 */ = 0x0000F2,
|
||||
[KEY_HANJA] /* = USB 145 */ = 0x0000F1,
|
||||
[KEY_YEN] /* = USB 137 */ = 0x00007D,
|
||||
[KEY_LEFTMETA] /* = USB 227 */ = 0x00E05B,
|
||||
[KEY_RIGHTMETA] /* = USB 231 */ = 0x00E05C,
|
||||
[KEY_COMPOSE] /* = USB 101 */ = 0x00E05D,
|
||||
[KEY_F13] /* = USB 104 */ = 0x00005D,
|
||||
[KEY_F14] /* = USB 105 */ = 0x00005E,
|
||||
[KEY_F15] /* = USB 106 */ = 0x00005F,
|
||||
[KEY_PRINT] /* = USB 70 */ = 0x00E037,
|
||||
};
|
||||
extern const uint32_t xfree86_to_ps2[KEY_MAX];
|
||||
extern const char * xfree86_to_str[KEY_MAX];
|
||||
extern const char * xfree86_to_display[KEY_MAX];
|
||||
|
||||
161
client/src/keybind.c
Normal file
161
client/src/keybind.c
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "keybind.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "app.h"
|
||||
#include "core.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "spice/spice.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static void bind_fullscreen(int sc, void * opaque)
|
||||
{
|
||||
app_setFullscreen(!app_getFullscreen());
|
||||
}
|
||||
|
||||
static void bind_video(int sc, void * opaque)
|
||||
{
|
||||
g_state.stopVideo = !g_state.stopVideo;
|
||||
app_alert(
|
||||
LG_ALERT_INFO,
|
||||
g_state.stopVideo ? "Video Stream Disabled" : "Video Stream Enabled"
|
||||
);
|
||||
|
||||
if (g_state.stopVideo)
|
||||
core_stopFrameThread();
|
||||
else
|
||||
core_startFrameThread();
|
||||
}
|
||||
|
||||
static void bind_showFPS(int sc, void * opaque)
|
||||
{
|
||||
g_state.showFPS = !g_state.showFPS;
|
||||
app_showFPS(g_state.showFPS);
|
||||
}
|
||||
|
||||
static void bind_rotate(int sc, void * opaque)
|
||||
{
|
||||
if (g_params.winRotate == LG_ROTATE_MAX-1)
|
||||
g_params.winRotate = 0;
|
||||
else
|
||||
++g_params.winRotate;
|
||||
core_updatePositionInfo();
|
||||
}
|
||||
|
||||
static void bind_input(int sc, void * opaque)
|
||||
{
|
||||
g_state.ignoreInput = !g_state.ignoreInput;
|
||||
|
||||
if (g_state.ignoreInput)
|
||||
core_setCursorInView(false);
|
||||
else
|
||||
g_state.ds->realignPointer();
|
||||
|
||||
app_alert(
|
||||
LG_ALERT_INFO,
|
||||
g_state.ignoreInput ? "Input Disabled" : "Input Enabled"
|
||||
);
|
||||
}
|
||||
|
||||
static void bind_quit(int sc, void * opaque)
|
||||
{
|
||||
g_state.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
|
||||
static void bind_mouseSens(int sc, void * opaque)
|
||||
{
|
||||
bool inc = (bool)opaque;
|
||||
|
||||
if (inc)
|
||||
{
|
||||
if (g_cursor.sens < 9)
|
||||
++g_cursor.sens;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_cursor.sens > -9)
|
||||
--g_cursor.sens;
|
||||
}
|
||||
|
||||
char msg[20];
|
||||
snprintf(msg, sizeof(msg), "Sensitivity: %s%d",
|
||||
g_cursor.sens > 0 ? "+" : "", g_cursor.sens);
|
||||
|
||||
app_alert(
|
||||
LG_ALERT_INFO,
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
static void bind_ctrlAltFn(int sc, void * opaque)
|
||||
{
|
||||
const uint32_t ctrl = xfree86_to_ps2[KEY_LEFTCTRL];
|
||||
const uint32_t alt = xfree86_to_ps2[KEY_LEFTALT ];
|
||||
const uint32_t fn = xfree86_to_ps2[sc];
|
||||
spice_key_down(ctrl);
|
||||
spice_key_down(alt );
|
||||
spice_key_down(fn );
|
||||
|
||||
spice_key_up(ctrl);
|
||||
spice_key_up(alt );
|
||||
spice_key_up(fn );
|
||||
}
|
||||
|
||||
static void bind_passthrough(int sc, void * opaque)
|
||||
{
|
||||
sc = xfree86_to_ps2[sc];
|
||||
spice_key_down(sc);
|
||||
spice_key_up (sc);
|
||||
}
|
||||
|
||||
void keybind_register(void)
|
||||
{
|
||||
app_registerKeybind(KEY_F, bind_fullscreen, NULL, "Full screen toggle");
|
||||
app_registerKeybind(KEY_V, bind_video , NULL, "Video stream toggle");
|
||||
app_registerKeybind(KEY_D, bind_showFPS , NULL, "FPS display toggle");
|
||||
app_registerKeybind(KEY_R, bind_rotate , NULL, "Rotate the output clockwise by 90° increments");
|
||||
app_registerKeybind(KEY_Q, bind_quit , NULL, "Quit");
|
||||
|
||||
if (g_params.useSpiceInput)
|
||||
{
|
||||
app_registerKeybind(KEY_I , bind_input , NULL , "Spice keyboard & mouse toggle");
|
||||
app_registerKeybind(KEY_INSERT, bind_mouseSens, (void*)true , "Increase mouse sensitivity in capture mode");
|
||||
app_registerKeybind(KEY_DELETE, bind_mouseSens, (void*)false, "Descrease mouse sensitivity in capture mode");
|
||||
|
||||
app_registerKeybind(KEY_F1 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F1 to the guest");
|
||||
app_registerKeybind(KEY_F2 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F2 to the guest");
|
||||
app_registerKeybind(KEY_F3 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F3 to the guest");
|
||||
app_registerKeybind(KEY_F4 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F4 to the guest");
|
||||
app_registerKeybind(KEY_F5 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F5 to the guest");
|
||||
app_registerKeybind(KEY_F6 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F6 to the guest");
|
||||
app_registerKeybind(KEY_F7 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F7 to the guest");
|
||||
app_registerKeybind(KEY_F8 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F8 to the guest");
|
||||
app_registerKeybind(KEY_F9 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F9 to the guest");
|
||||
app_registerKeybind(KEY_F10, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F10 to the guest");
|
||||
app_registerKeybind(KEY_F11, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F11 to the guest");
|
||||
app_registerKeybind(KEY_F12, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F12 to the guest");
|
||||
|
||||
app_registerKeybind(KEY_LEFTMETA , bind_passthrough, NULL, "Send LWin to the guest");
|
||||
app_registerKeybind(KEY_RIGHTMETA, bind_passthrough, NULL, "Send RWin to the guest");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,11 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef _H_LG_KEYBIND_
|
||||
#define _H_LG_KEYBIND_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
void keybind_register(void);
|
||||
|
||||
// reads the specified file into a new buffer
|
||||
// the callee must free the buffer
|
||||
bool file_get_contents(const char * filename, char ** buffer, size_t * length);
|
||||
#endif
|
||||
1512
client/src/main.c
1512
client/src/main.c
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -19,12 +19,13 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include "interface/app.h"
|
||||
#include "dynamic/displayservers.h"
|
||||
#include "dynamic/renderers.h"
|
||||
|
||||
#include "common/thread.h"
|
||||
#include "common/types.h"
|
||||
#include "common/ivshmem.h"
|
||||
|
||||
#include "spice/spice.h"
|
||||
@@ -42,22 +43,25 @@ struct AppState
|
||||
enum RunState state;
|
||||
|
||||
struct LG_DisplayServerOps * ds;
|
||||
bool dsInitialized;
|
||||
|
||||
bool stopVideo;
|
||||
bool ignoreInput;
|
||||
bool showFPS;
|
||||
bool escapeActive;
|
||||
SDL_Scancode escapeAction;
|
||||
int escapeAction;
|
||||
KeybindHandle bindings[KEY_MAX];
|
||||
const char * keyDescription[KEY_MAX];
|
||||
bool keyDown[KEY_MAX];
|
||||
|
||||
bool haveSrcSize;
|
||||
SDL_Point windowPos;
|
||||
struct Point windowPos;
|
||||
int windowW, windowH;
|
||||
int windowCX, windowCY;
|
||||
LG_RendererRotate rotate;
|
||||
bool focused;
|
||||
SDL_Rect border;
|
||||
SDL_Point srcSize;
|
||||
struct Border border;
|
||||
struct Point srcSize;
|
||||
LG_RendererRect dstRect;
|
||||
bool posInfoValid;
|
||||
bool alignToGuest;
|
||||
@@ -72,14 +76,12 @@ struct AppState
|
||||
size_t cbXfer;
|
||||
struct ll * cbRequestList;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_Window * window;
|
||||
|
||||
struct IVSHMEM shm;
|
||||
PLGMPClient lgmp;
|
||||
PLGMPClientQueue frameQueue;
|
||||
PLGMPClientQueue pointerQueue;
|
||||
|
||||
LGThread * frameThread;
|
||||
bool formatValid;
|
||||
atomic_uint_least64_t frameTime;
|
||||
uint64_t lastFrameTime;
|
||||
@@ -91,15 +93,7 @@ struct AppState
|
||||
uint64_t resizeTimeout;
|
||||
bool resizeDone;
|
||||
|
||||
KeybindHandle kbFS;
|
||||
KeybindHandle kbVideo;
|
||||
KeybindHandle kbRotate;
|
||||
KeybindHandle kbInput;
|
||||
KeybindHandle kbQuit;
|
||||
KeybindHandle kbMouseSensInc;
|
||||
KeybindHandle kbMouseSensDec;
|
||||
KeybindHandle kbCtrlAltFn[12];
|
||||
KeybindHandle kbPass[2];
|
||||
bool autoIdleInhibitState;
|
||||
};
|
||||
|
||||
struct AppParams
|
||||
@@ -129,10 +123,12 @@ struct AppParams
|
||||
bool hideMouse;
|
||||
bool ignoreQuit;
|
||||
bool noScreensaver;
|
||||
bool autoScreensaver;
|
||||
bool grabKeyboard;
|
||||
bool grabKeyboardOnFocus;
|
||||
SDL_Scancode escapeKey;
|
||||
int escapeKey;
|
||||
bool ignoreWindowsKeys;
|
||||
bool releaseKeysOnFocusLoss;
|
||||
bool showAlerts;
|
||||
bool captureOnStart;
|
||||
bool quickSplash;
|
||||
@@ -163,9 +159,9 @@ struct CBRequest
|
||||
|
||||
struct KeybindHandle
|
||||
{
|
||||
SDL_Scancode key;
|
||||
SuperEventFn callback;
|
||||
void * opaque;
|
||||
int sc;
|
||||
KeybindFn callback;
|
||||
void * opaque;
|
||||
};
|
||||
|
||||
enum WarpState
|
||||
@@ -192,11 +188,6 @@ struct CursorInfo
|
||||
uint32_t dpiScale;
|
||||
};
|
||||
|
||||
struct DoublePoint
|
||||
{
|
||||
double x, y;
|
||||
};
|
||||
|
||||
struct CursorState
|
||||
{
|
||||
/* cursor is in grab mode */
|
||||
@@ -249,8 +240,14 @@ struct CursorState
|
||||
|
||||
/* the guest's cursor position */
|
||||
struct CursorInfo guest;
|
||||
|
||||
/* the projected position after move, for app_handleMouseBasic only */
|
||||
struct Point projected;
|
||||
};
|
||||
|
||||
// forwards
|
||||
extern struct AppState g_state;
|
||||
extern struct AppParams params;
|
||||
extern struct AppState g_state;
|
||||
extern struct CursorState g_cursor;
|
||||
extern struct AppParams g_params;
|
||||
|
||||
int main_frameThread(void * unused);
|
||||
|
||||
209
client/src/util.c
Normal file
209
client/src/util.c
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "util.h"
|
||||
#include "main.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
bool util_fileGetContents(const char * filename, char ** buffer, size_t * length)
|
||||
{
|
||||
FILE * fh = fopen(filename, "r");
|
||||
if (!fh)
|
||||
{
|
||||
DEBUG_ERROR("Failed to open the file: %s", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(fh, 0, SEEK_END) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to seek");
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
long fsize = ftell(fh);
|
||||
if (fseek(fh, 0, SEEK_SET) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to seek");
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
*buffer = malloc(fsize + 1);
|
||||
if (!*buffer)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate buffer of %lu bytes", fsize + 1);
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fread(*buffer, 1, fsize, fh) != fsize)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read the entire file");
|
||||
fclose(fh);
|
||||
free(*buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(fh);
|
||||
buffer[fsize] = 0;
|
||||
*length = fsize;
|
||||
return true;
|
||||
}
|
||||
|
||||
void util_cursorToInt(double ex, double ey, int *x, int *y)
|
||||
{
|
||||
/* only smooth if enabled and not using raw mode */
|
||||
if (g_params.mouseSmoothing && !(g_cursor.grab && g_params.rawMouse))
|
||||
{
|
||||
static struct DoublePoint last = { 0 };
|
||||
|
||||
/* only apply smoothing to small deltas */
|
||||
if (fabs(ex - last.x) < 5.0 && fabs(ey - last.y) < 5.0)
|
||||
{
|
||||
ex = last.x = (last.x + ex) / 2.0;
|
||||
ey = last.y = (last.y + ey) / 2.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
last.x = ex;
|
||||
last.y = ey;
|
||||
}
|
||||
}
|
||||
|
||||
/* convert to int accumulating the fractional error */
|
||||
ex += g_cursor.acc.x;
|
||||
ey += g_cursor.acc.y;
|
||||
g_cursor.acc.x = modf(ex, &ex);
|
||||
g_cursor.acc.y = modf(ey, &ey);
|
||||
|
||||
*x = (int)ex;
|
||||
*y = (int)ey;
|
||||
}
|
||||
|
||||
bool util_guestCurToLocal(struct DoublePoint *local)
|
||||
{
|
||||
if (!g_cursor.guest.valid || !g_state.posInfoValid)
|
||||
return false;
|
||||
|
||||
const struct DoublePoint point =
|
||||
{
|
||||
.x = g_cursor.guest.x + g_cursor.guest.hx,
|
||||
.y = g_cursor.guest.y + g_cursor.guest.hy
|
||||
};
|
||||
|
||||
switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
local->x = (point.x / g_cursor.scale.x) + g_state.dstRect.x;
|
||||
local->y = (point.y / g_cursor.scale.y) + g_state.dstRect.y;;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
local->x = (g_state.dstRect.x + g_state.dstRect.w) -
|
||||
point.y / g_cursor.scale.y;
|
||||
local->y = (point.x / g_cursor.scale.x) + g_state.dstRect.y;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_180:
|
||||
local->x = (g_state.dstRect.x + g_state.dstRect.w) -
|
||||
point.x / g_cursor.scale.x;
|
||||
local->y = (g_state.dstRect.y + g_state.dstRect.h) -
|
||||
point.y / g_cursor.scale.y;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_270:
|
||||
local->x = (point.y / g_cursor.scale.y) + g_state.dstRect.x;
|
||||
local->y = (g_state.dstRect.y + g_state.dstRect.h) -
|
||||
point.x / g_cursor.scale.x;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void util_localCurToGuest(struct DoublePoint *guest)
|
||||
{
|
||||
const struct DoublePoint point =
|
||||
g_cursor.pos;
|
||||
|
||||
switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
guest->x = (point.x - g_state.dstRect.x) * g_cursor.scale.x;
|
||||
guest->y = (point.y - g_state.dstRect.y) * g_cursor.scale.y;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
guest->x = (point.y - g_state.dstRect.y) * g_cursor.scale.y;
|
||||
guest->y = (g_state.dstRect.w - point.x + g_state.dstRect.x)
|
||||
* g_cursor.scale.x;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_180:
|
||||
guest->x = (g_state.dstRect.w - point.x + g_state.dstRect.x)
|
||||
* g_cursor.scale.x;
|
||||
guest->y = (g_state.dstRect.h - point.y + g_state.dstRect.y)
|
||||
* g_cursor.scale.y;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_270:
|
||||
guest->x = (g_state.dstRect.h - point.y + g_state.dstRect.y)
|
||||
* g_cursor.scale.y;
|
||||
guest->y = (point.x - g_state.dstRect.x) * g_cursor.scale.x;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(!"unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
void util_rotatePoint(struct DoublePoint *point)
|
||||
{
|
||||
double temp;
|
||||
|
||||
switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
temp = point->x;
|
||||
point->x = point->y;
|
||||
point->y = -temp;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_180:
|
||||
point->x = -point->x;
|
||||
point->y = -point->y;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_270:
|
||||
temp = point->x;
|
||||
point->x = -point->y;
|
||||
point->y = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "utils.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
bool file_get_contents(const char * filename, char ** buffer, size_t * length)
|
||||
{
|
||||
FILE * fh = fopen(filename, "r");
|
||||
if (!fh)
|
||||
{
|
||||
DEBUG_ERROR("Failed to open the file: %s", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(fh, 0, SEEK_END) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to seek");
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
long fsize = ftell(fh);
|
||||
if (fseek(fh, 0, SEEK_SET) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to seek");
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
*buffer = malloc(fsize + 1);
|
||||
if (!*buffer)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate buffer of %lu bytes", fsize + 1);
|
||||
fclose(fh);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fread(*buffer, 1, fsize, fh) != fsize)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read the entire file");
|
||||
fclose(fh);
|
||||
free(*buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(fh);
|
||||
buffer[fsize] = 0;
|
||||
*length = fsize;
|
||||
return true;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ set(COMMON_SOURCES
|
||||
src/option.c
|
||||
src/framebuffer.c
|
||||
src/KVMFR.c
|
||||
src/countedbuffer.c
|
||||
)
|
||||
|
||||
add_library(lg_common STATIC ${COMMON_SOURCES})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR)
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -16,9 +16,18 @@ 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
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_KVMFR_
|
||||
#define _H_LG_COMMON_KVMFR_
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "types.h"
|
||||
|
||||
#define KVMFR_MAGIC "KVMFR---"
|
||||
#define KVMFR_VERSION 9
|
||||
|
||||
#define LGMP_Q_POINTER 1
|
||||
#define LGMP_Q_FRAME 2
|
||||
@@ -26,47 +35,15 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#define LGMP_Q_FRAME_LEN 2
|
||||
#define LGMP_Q_POINTER_LEN 20
|
||||
|
||||
typedef enum FrameType
|
||||
{
|
||||
FRAME_TYPE_INVALID ,
|
||||
FRAME_TYPE_BGRA , // BGRA interleaved: B,G,R,A 32bpp
|
||||
FRAME_TYPE_RGBA , // RGBA interleaved: R,G,B,A 32bpp
|
||||
FRAME_TYPE_RGBA10 , // RGBA interleaved: R,G,B,A 10,10,10,2 bpp
|
||||
FRAME_TYPE_RGBA16F , // RGBA interleaved: R,G,B,A 16,16,16,16 bpp float
|
||||
FRAME_TYPE_MAX , // sentinel value
|
||||
}
|
||||
FrameType;
|
||||
|
||||
typedef enum FrameRotation
|
||||
{
|
||||
FRAME_ROT_0,
|
||||
FRAME_ROT_90,
|
||||
FRAME_ROT_180,
|
||||
FRAME_ROT_270
|
||||
}
|
||||
FrameRotation;
|
||||
|
||||
extern const char * FrameTypeStr[FRAME_TYPE_MAX];
|
||||
|
||||
enum
|
||||
{
|
||||
CURSOR_FLAG_POSITION = 0x1,
|
||||
CURSOR_FLAG_VISIBLE = 0x2,
|
||||
CURSOR_FLAG_SHAPE = 0x4
|
||||
};
|
||||
|
||||
typedef uint32_t KVMFRCursorFlags;
|
||||
|
||||
typedef enum CursorType
|
||||
{
|
||||
CURSOR_TYPE_COLOR ,
|
||||
CURSOR_TYPE_MONOCHROME ,
|
||||
CURSOR_TYPE_MASKED_COLOR
|
||||
}
|
||||
CursorType;
|
||||
|
||||
#define KVMFR_MAGIC "KVMFR---"
|
||||
#define KVMFR_VERSION 8
|
||||
|
||||
typedef struct KVMFR
|
||||
{
|
||||
char magic[8];
|
||||
@@ -97,5 +74,8 @@ typedef struct KVMFRFrame
|
||||
uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size)
|
||||
uint32_t offset; // offset from the start of this header to the FrameBuffer header
|
||||
uint32_t mouseScalePercent; // movement scale factor of the mouse (relates to DPI of display, 100 = no scale)
|
||||
bool blockScreensaver; // whether the guest has requested to block screensavers
|
||||
}
|
||||
KVMFRFrame;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2021 Guanzhong Chen <quantum2048@gmail.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,11 +17,19 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef _H_LG_COMMON_COUNTEDBUFFER_
|
||||
#define _H_LG_COMMON_COUNTEDBUFFER_
|
||||
|
||||
#include <common/debug.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define EGL_DEBUG_PRINT(type, fmt, ...) do {egl_debug_printf(type " %20s:%-4u | %-30s | " fmt, STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
|
||||
#define EGL_ERROR(fmt, ...) EGL_DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
|
||||
struct CountedBuffer {
|
||||
_Atomic(size_t) refs;
|
||||
size_t size;
|
||||
char data[];
|
||||
};
|
||||
|
||||
void egl_debug_printf(char * format, ...);
|
||||
struct CountedBuffer * countedBufferNew(size_t size);
|
||||
void countedBufferAddRef(struct CountedBuffer * buffer);
|
||||
void countedBufferRelease(struct CountedBuffer ** buffer);
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,6 +17,12 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_CRASH_
|
||||
#define _H_LG_COMMON_CRASH_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool installCrashHandler(const char * exe);
|
||||
bool installCrashHandler(const char * exe);
|
||||
void cleanupCrashHandler(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,14 +17,24 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_DEBUG_
|
||||
#define _H_LG_COMMON_DEBUG_
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include "time.h"
|
||||
|
||||
#ifdef ENABLE_BACKTRACE
|
||||
void printBacktrace(void);
|
||||
#else
|
||||
#define printBacktrace
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(__GNUC__)
|
||||
#define DIRECTORY_SEPARATOR '\\'
|
||||
#else
|
||||
@@ -53,16 +63,27 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \
|
||||
sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s))
|
||||
|
||||
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%12" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
|
||||
#define DEBUG_PRINT(type, fmt, ...) do { \
|
||||
fprintf(stderr, "%12" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", \
|
||||
microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define DEBUG_BREAK() DEBUG_PRINT("[ ]", "%s", "================================================================================")
|
||||
#define DEBUG_BREAK() DEBUG_PRINT("[ ]", "================================================================================")
|
||||
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)
|
||||
#define DEBUG_WARN(fmt, ...) DEBUG_PRINT("[W]", fmt, ##__VA_ARGS__)
|
||||
#define DEBUG_ERROR(fmt, ...) DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
|
||||
#define DEBUG_FIXME(fmt, ...) DEBUG_PRINT("[F]", fmt, ##__VA_ARGS__)
|
||||
#define DEBUG_FATAL(fmt, ...) do { \
|
||||
DEBUG_BREAK(); \
|
||||
DEBUG_PRINT("[!]", fmt, ##__VA_ARGS__); \
|
||||
printBacktrace(); \
|
||||
abort(); \
|
||||
} while(0)
|
||||
|
||||
#if defined(DEBUG_SPICE) | defined(DEBUG_IVSHMEM)
|
||||
#define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args)
|
||||
#else
|
||||
#define DEBUG_PROTO(fmt, ...) do {} while(0)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,9 +17,14 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_DPI_
|
||||
#define _H_LG_COMMON_DPI_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// At 100% scaling, Windows reports 96 DPI.
|
||||
#define DPI_100_PERCENT 96
|
||||
|
||||
UINT monitor_dpi(HMONITOR hMonitor);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,7 +17,8 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef _H_LG_COMMON_EVENT_
|
||||
#define _H_LG_COMMON_EVENT_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
@@ -40,3 +41,5 @@ LGEvent * lgWrapEvent(void * handle);
|
||||
// Posix specific, not implmented/possible in windows
|
||||
bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts);
|
||||
bool lgWaitEventNS (LGEvent * handle, unsigned int timeout);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,7 +17,8 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef _H_LG_COMMON_FRAMEBUFFER_
|
||||
#define _H_LG_COMMON_FRAMEBUFFER_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
@@ -58,3 +59,5 @@ void framebuffer_prepare(FrameBuffer * frame);
|
||||
* Write data from the src buffer into the KVMFRFrame
|
||||
*/
|
||||
bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,7 +17,8 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef _H_LG_COMMON_IVSHMEM_
|
||||
#define _H_LG_COMMON_IVSHMEM_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -41,3 +42,5 @@ void ivshmemFree(struct IVSHMEM * dev);
|
||||
/* Linux KVMFR support only for now (VM->VM) */
|
||||
bool ivshmemHasDMA (struct IVSHMEM * dev);
|
||||
int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -16,7 +16,9 @@ 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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifndef _H_LG_COMMON_LOCKING_
|
||||
#define _H_LG_COMMON_LOCKING_
|
||||
|
||||
#include "time.h"
|
||||
|
||||
@@ -34,7 +36,9 @@ typedef atomic_flag LG_Lock;
|
||||
#define INTERLOCKED_INC(x) atomic_fetch_add((x), 1)
|
||||
#define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1)
|
||||
|
||||
#define INTERLOCKED_SECTION(lock, x) \
|
||||
#define INTERLOCKED_SECTION(lock, ...) \
|
||||
LG_LOCK(lock) \
|
||||
x \
|
||||
__VA_ARGS__ \
|
||||
LG_UNLOCK(lock)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <tmmintrin.h>
|
||||
#include <immintrin.h>
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
#if defined(NATIVE_MEMCPY)
|
||||
#define memcpySSE memcpy
|
||||
#elif defined(_MSC_VER)
|
||||
extern "C" void * memcpySSE(void *dst, const void * src, size_t length);
|
||||
#elif (defined(__GNUC__) || defined(__GNUG__)) && defined(__i386__)
|
||||
inline static void * memcpySSE(void *dst, const void * src, size_t length)
|
||||
{
|
||||
if (length == 0 || dst == src)
|
||||
return;
|
||||
|
||||
// copies under 1MB are faster with the inlined memcpy
|
||||
// tell the dev to use that instead
|
||||
if (length < 1048576)
|
||||
{
|
||||
static bool smallBufferWarn = false;
|
||||
if (!smallBufferWarn)
|
||||
{
|
||||
DEBUG_WARN("Do not use memcpySSE for copies under 1MB in size!");
|
||||
smallBufferWarn = true;
|
||||
}
|
||||
memcpy(dst, src, length);
|
||||
return;
|
||||
}
|
||||
|
||||
const void * end = dst + (length & ~0x7F);
|
||||
const size_t off = (7 - ((length & 0x7F) >> 4)) * 9;
|
||||
|
||||
__asm__ __volatile__ (
|
||||
"cmp %[dst],%[end] \n\t"
|
||||
"je Remain_%= \n\t"
|
||||
|
||||
// perform SIMD block copy
|
||||
"loop_%=: \n\t"
|
||||
"movaps 0x00(%[src]),%%xmm0 \n\t"
|
||||
"movaps 0x10(%[src]),%%xmm1 \n\t"
|
||||
"movaps 0x20(%[src]),%%xmm2 \n\t"
|
||||
"movaps 0x30(%[src]),%%xmm3 \n\t"
|
||||
"movaps 0x40(%[src]),%%xmm4 \n\t"
|
||||
"movaps 0x50(%[src]),%%xmm5 \n\t"
|
||||
"movaps 0x60(%[src]),%%xmm6 \n\t"
|
||||
"movaps 0x70(%[src]),%%xmm7 \n\t"
|
||||
"movntdq %%xmm0 ,0x00(%[dst]) \n\t"
|
||||
"movntdq %%xmm1 ,0x10(%[dst]) \n\t"
|
||||
"movntdq %%xmm2 ,0x20(%[dst]) \n\t"
|
||||
"movntdq %%xmm3 ,0x30(%[dst]) \n\t"
|
||||
"movntdq %%xmm4 ,0x40(%[dst]) \n\t"
|
||||
"movntdq %%xmm5 ,0x50(%[dst]) \n\t"
|
||||
"movntdq %%xmm6 ,0x60(%[dst]) \n\t"
|
||||
"movntdq %%xmm7 ,0x70(%[dst]) \n\t"
|
||||
"add $0x80,%[dst] \n\t"
|
||||
"add $0x80,%[src] \n\t"
|
||||
"cmp %[dst],%[end] \n\t"
|
||||
"jne loop_%= \n\t"
|
||||
|
||||
"Remain_%=: \n\t"
|
||||
|
||||
// copy any remaining 16 byte blocks
|
||||
"call GetPC_%=\n\t"
|
||||
"Offset_%=:\n\t"
|
||||
"add $(BlockTable_%= - Offset_%=), %%eax \n\t"
|
||||
"add %[off],%%eax \n\t"
|
||||
"jmp *%%eax \n\t"
|
||||
|
||||
"GetPC_%=:\n\t"
|
||||
"mov (%%esp), %%eax \n\t"
|
||||
"ret \n\t"
|
||||
|
||||
"BlockTable_%=:\n\t"
|
||||
"movaps 0x60(%[src]),%%xmm6 \n\t"
|
||||
"movntdq %%xmm6 ,0x60(%[dst]) \n\t"
|
||||
"movaps 0x50(%[src]),%%xmm5 \n\t"
|
||||
"movntdq %%xmm5 ,0x50(%[dst]) \n\t"
|
||||
"movaps 0x40(%[src]),%%xmm4 \n\t"
|
||||
"movntdq %%xmm4 ,0x40(%[dst]) \n\t"
|
||||
"movaps 0x30(%[src]),%%xmm3 \n\t"
|
||||
"movntdq %%xmm3 ,0x30(%[dst]) \n\t"
|
||||
"movaps 0x20(%[src]),%%xmm2 \n\t"
|
||||
"movntdq %%xmm2 ,0x20(%[dst]) \n\t"
|
||||
"movaps 0x10(%[src]),%%xmm1 \n\t"
|
||||
"movntdq %%xmm1 ,0x10(%[dst]) \n\t"
|
||||
"movaps 0x00(%[src]),%%xmm0 \n\t"
|
||||
"movntdq %%xmm0 ,0x00(%[dst]) \n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
|
||||
: [dst]"+r" (dst),
|
||||
[src]"+r" (src)
|
||||
: [off]"r" (off),
|
||||
[end]"r" (end)
|
||||
: "eax",
|
||||
"xmm0",
|
||||
"xmm1",
|
||||
"xmm2",
|
||||
"xmm3",
|
||||
"xmm4",
|
||||
"xmm5",
|
||||
"xmm6",
|
||||
"xmm7",
|
||||
"memory"
|
||||
);
|
||||
|
||||
//copy any remaining bytes
|
||||
memcpy(dst, src, length & 0xF);
|
||||
}
|
||||
#else
|
||||
#define memcpySSE memcpy
|
||||
#endif
|
||||
@@ -17,6 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_COMMON_OPTION_
|
||||
#define _H_COMMON_OPTION_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "common/stringlist.h"
|
||||
|
||||
@@ -53,7 +56,7 @@ struct Option
|
||||
char * (*toString )(struct Option * opt);
|
||||
StringList (*getValues)(struct Option * opt);
|
||||
|
||||
void (*printHelp)();
|
||||
void (*printHelp)(void);
|
||||
|
||||
// internal use only
|
||||
bool failed_set;
|
||||
@@ -75,10 +78,12 @@ bool option_parse(int argc, char * argv[]);
|
||||
bool option_load(const char * filename);
|
||||
|
||||
// called by the main application to validate the option values
|
||||
bool option_validate();
|
||||
bool option_validate(void);
|
||||
|
||||
// print out the options, help, and their current values
|
||||
void option_print();
|
||||
void option_print(void);
|
||||
|
||||
// final cleanup
|
||||
void option_free();
|
||||
void option_free(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,6 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_STRINGLIST_
|
||||
#define _H_LG_COMMON_STRINGLIST_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct StringList * StringList;
|
||||
@@ -25,4 +28,6 @@ StringList stringlist_new (bool owns_strings);
|
||||
void stringlist_free (StringList * sl);
|
||||
int stringlist_push (StringList sl, char * str);
|
||||
unsigned int stringlist_count(StringList sl);
|
||||
char * stringlist_at (StringList sl, unsigned int index);
|
||||
char * stringlist_at (StringList sl, unsigned int index);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,6 +17,11 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_STRINGUTILS
|
||||
#define _H_LG_COMMON_STRINGUTILS
|
||||
|
||||
// sprintf but with buffer allocation
|
||||
int alloc_sprintf(char ** str, const char * format, ...)
|
||||
__attribute__ ((format (printf, 2, 3)));
|
||||
__attribute__ ((format (printf, 2, 3)));
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,5 +17,10 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_SYSUTILS
|
||||
#define _H_LG_COMMON_SYSUTILS
|
||||
|
||||
// returns the page size
|
||||
long sysinfo_getPageSize();
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,12 +17,16 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef _H_LG_COMMON_THREAD_
|
||||
#define _H_LG_COMMON_THREAD_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct LGThread LGThread;
|
||||
typedef int (*LGThreadFunction)(void * opaque);
|
||||
|
||||
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle);
|
||||
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque,
|
||||
LGThread ** handle);
|
||||
bool lgJoinThread (LGThread * handle, int * resultCode);
|
||||
|
||||
#endif
|
||||
|
||||
73
common/include/common/types.h
Normal file
73
common/include/common/types.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifndef _LG_TYPES_H_
|
||||
#define _LG_TYPES_H_
|
||||
|
||||
struct Point
|
||||
{
|
||||
int x, y;
|
||||
};
|
||||
|
||||
struct DoublePoint
|
||||
{
|
||||
double x, y;
|
||||
};
|
||||
|
||||
struct Rect
|
||||
{
|
||||
int x, y, w, h;
|
||||
};
|
||||
|
||||
struct Border
|
||||
{
|
||||
int left, top, right, bottom;
|
||||
};
|
||||
|
||||
typedef enum FrameType
|
||||
{
|
||||
FRAME_TYPE_INVALID ,
|
||||
FRAME_TYPE_BGRA , // BGRA interleaved: B,G,R,A 32bpp
|
||||
FRAME_TYPE_RGBA , // RGBA interleaved: R,G,B,A 32bpp
|
||||
FRAME_TYPE_RGBA10 , // RGBA interleaved: R,G,B,A 10,10,10,2 bpp
|
||||
FRAME_TYPE_RGBA16F , // RGBA interleaved: R,G,B,A 16,16,16,16 bpp float
|
||||
FRAME_TYPE_MAX , // sentinel value
|
||||
}
|
||||
FrameType;
|
||||
|
||||
typedef enum FrameRotation
|
||||
{
|
||||
FRAME_ROT_0,
|
||||
FRAME_ROT_90,
|
||||
FRAME_ROT_180,
|
||||
FRAME_ROT_270
|
||||
}
|
||||
FrameRotation;
|
||||
|
||||
extern const char * FrameTypeStr[FRAME_TYPE_MAX];
|
||||
|
||||
typedef enum CursorType
|
||||
{
|
||||
CURSOR_TYPE_COLOR ,
|
||||
CURSOR_TYPE_MONOCHROME ,
|
||||
CURSOR_TYPE_MASKED_COLOR
|
||||
}
|
||||
CursorType;
|
||||
|
||||
#endif
|
||||
@@ -1 +1,25 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_COMMON_VERSION_
|
||||
#define _H_LG_COMMON_VERSION_
|
||||
|
||||
extern char * BUILD_VERSION;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,7 +17,8 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef _H_LG_COMMON_WINDEBUG_
|
||||
#define _H_LG_COMMON_WINDEBUG_
|
||||
|
||||
#include "debug.h"
|
||||
#include <windows.h>
|
||||
@@ -35,4 +36,6 @@ bool IsWindows8();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
47
common/src/countedbuffer.c
Normal file
47
common/src/countedbuffer.c
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen <quantum2048@gmail.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
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 "common/countedbuffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
struct CountedBuffer * countedBufferNew(size_t size)
|
||||
{
|
||||
struct CountedBuffer * buffer = malloc(sizeof(struct CountedBuffer) + size);
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
atomic_init(&buffer->refs, 1);
|
||||
buffer->size = size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void countedBufferAddRef(struct CountedBuffer * buffer)
|
||||
{
|
||||
atomic_fetch_add(&buffer->refs, 1);
|
||||
}
|
||||
|
||||
void countedBufferRelease(struct CountedBuffer ** buffer)
|
||||
{
|
||||
if (atomic_fetch_sub(&(*buffer)->refs, 1) == 1)
|
||||
{
|
||||
free(*buffer);
|
||||
*buffer = NULL;
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
.code
|
||||
memcpySSE proc
|
||||
; dst = rcx
|
||||
; src = rdx
|
||||
; len = r8
|
||||
|
||||
mov rax, rcx
|
||||
|
||||
test r8, r8
|
||||
jz @Exit
|
||||
|
||||
cmp rcx, rdx
|
||||
je @Exit
|
||||
|
||||
sub rsp, 8 + 2*16 + 4*8
|
||||
movdqa oword ptr [rsp + 4*8 + 00 ], xmm6
|
||||
movdqa oword ptr [rsp + 4*8 + 16 ], xmm7
|
||||
|
||||
; void * end = dst + (length & ~0x7F);
|
||||
; end = r10
|
||||
mov r9 , r8
|
||||
and r9 , 0FFFFFFFFFFFFFF80h
|
||||
jz @RemainingBlocks
|
||||
mov r10, rcx
|
||||
add r10, r9
|
||||
|
||||
@FullLoop:
|
||||
movaps xmm0 , xmmword ptr [rdx + 000h]
|
||||
movaps xmm1 , xmmword ptr [rdx + 010h]
|
||||
movaps xmm2 , xmmword ptr [rdx + 020h]
|
||||
movaps xmm3 , xmmword ptr [rdx + 030h]
|
||||
movaps xmm4 , xmmword ptr [rdx + 040h]
|
||||
movaps xmm5 , xmmword ptr [rdx + 050h]
|
||||
movaps xmm6 , xmmword ptr [rdx + 060h]
|
||||
movaps xmm7 , xmmword ptr [rdx + 070h]
|
||||
movntdq xmmword ptr [rcx + 000h], xmm0
|
||||
movntdq xmmword ptr [rcx + 010h], xmm1
|
||||
movntdq xmmword ptr [rcx + 020h], xmm2
|
||||
movntdq xmmword ptr [rcx + 030h], xmm3
|
||||
movntdq xmmword ptr [rcx + 040h], xmm4
|
||||
movntdq xmmword ptr [rcx + 050h], xmm5
|
||||
movntdq xmmword ptr [rcx + 060h], xmm6
|
||||
movntdq xmmword ptr [rcx + 070h], xmm7
|
||||
add rdx, 080h
|
||||
add rcx, 080h
|
||||
cmp rcx, r10
|
||||
jne @FullLoop
|
||||
|
||||
@RemainingBlocks:
|
||||
; size_t rem = (length & 0x7F) >> 4);
|
||||
; rem = r11
|
||||
mov r11, r8
|
||||
and r11, 07Fh
|
||||
jz @RestoreExit
|
||||
shr r11, 4
|
||||
jz @FinalBytes
|
||||
|
||||
mov r10, 7
|
||||
sub r10, r11
|
||||
imul r10, 9
|
||||
lea r9 , @FinalBlocks
|
||||
add r9 , r10
|
||||
jmp r9
|
||||
|
||||
@RestoreExit:
|
||||
movdqa xmm6 , oword ptr [rsp + 4*8 + 00]
|
||||
movdqa xmm7 , oword ptr [rsp + 4*8 + 16]
|
||||
add rsp, 8 + 2*16 + 4*8
|
||||
|
||||
@Exit:
|
||||
ret
|
||||
|
||||
@FinalBlocks:
|
||||
movaps xmm6 , xmmword ptr [rdx + 060h]
|
||||
movntdq xmmword ptr [rcx + 060h], xmm6
|
||||
movaps xmm5 , xmmword ptr [rdx + 050h]
|
||||
movntdq xmmword ptr [rcx + 050h], xmm5
|
||||
movaps xmm4 , xmmword ptr [rdx + 040h]
|
||||
movntdq xmmword ptr [rcx + 040h], xmm4
|
||||
movaps xmm3 , xmmword ptr [rdx + 030h]
|
||||
movntdq xmmword ptr [rcx + 030h], xmm3
|
||||
movaps xmm2 , xmmword ptr [rdx + 020h]
|
||||
movntdq xmmword ptr [rcx + 020h], xmm2
|
||||
movaps xmm1 , xmmword ptr [rdx + 010h]
|
||||
movntdq xmmword ptr [rcx + 010h], xmm1
|
||||
movaps xmm0 , xmmword ptr [rdx + 000h]
|
||||
movntdq xmmword ptr [rcx + 000h], xmm0
|
||||
|
||||
movdqa xmm6 , oword ptr [rsp + 4*8 + 00]
|
||||
movdqa xmm7 , oword ptr [rsp + 4*8 + 16]
|
||||
add rsp, 8 + 2*16 + 4*8
|
||||
sfence
|
||||
|
||||
shl r11, 4
|
||||
add rdx, r11
|
||||
add rcx, r11
|
||||
|
||||
@FinalBytes:
|
||||
and r8, 0Fh
|
||||
jz @Exit
|
||||
imul r8, 5
|
||||
lea r9, @FinalBytesTable
|
||||
add r9, r8
|
||||
jmp r9
|
||||
|
||||
@FinalBytesTable:
|
||||
jmp @Copy1
|
||||
jmp @Copy2
|
||||
jmp @Copy3
|
||||
jmp @Copy4
|
||||
jmp @Copy5
|
||||
jmp @Copy6
|
||||
jmp @Copy7
|
||||
jmp @Copy8
|
||||
jmp @Copy9
|
||||
jmp @Copy10
|
||||
jmp @Copy11
|
||||
jmp @Copy12
|
||||
jmp @Copy13
|
||||
jmp @Copy14
|
||||
jmp @Copy15
|
||||
|
||||
db 128 DUP(0CCh)
|
||||
|
||||
; fall through - 1 byte
|
||||
@Copy1:
|
||||
mov al, byte ptr [rdx]
|
||||
mov byte ptr [rcx], al
|
||||
ret
|
||||
|
||||
@Copy2:
|
||||
mov r10w, word ptr [rdx]
|
||||
mov word ptr [rcx], r10w
|
||||
ret
|
||||
|
||||
@Copy3:
|
||||
mov r10w, word ptr [rdx]
|
||||
mov word ptr [rcx], r10w
|
||||
mov r11b, byte ptr [rdx + 02h]
|
||||
mov byte ptr [rcx + 02h], r11b
|
||||
ret
|
||||
|
||||
@Copy4:
|
||||
mov r9d, dword ptr [rdx]
|
||||
mov dword ptr [rcx], r9d
|
||||
ret
|
||||
|
||||
@Copy5:
|
||||
mov r9d, dword ptr [rdx ]
|
||||
mov r11b , byte ptr [rdx + 04h]
|
||||
mov dword ptr [rcx ], r9d
|
||||
mov byte ptr [rcx + 04h], r11b
|
||||
ret
|
||||
|
||||
@Copy6:
|
||||
mov r9d , dword ptr [rdx ]
|
||||
mov r10w, word ptr [rdx + 04h]
|
||||
mov dword ptr [rcx ], r9d
|
||||
mov word ptr [rcx + 04h], r10w
|
||||
ret
|
||||
|
||||
@Copy7:
|
||||
mov r9d , dword ptr [rdx ]
|
||||
mov r10w, word ptr [rdx + 04h]
|
||||
mov r11b, byte ptr [rdx + 06h]
|
||||
mov dword ptr [rcx ], r9d
|
||||
mov word ptr [rcx + 04h], r10w
|
||||
mov byte ptr [rcx + 06h], r11b
|
||||
ret
|
||||
|
||||
@Copy8:
|
||||
mov r8, qword ptr [rdx]
|
||||
mov qword ptr [rcx], r8
|
||||
ret
|
||||
|
||||
@Copy9:
|
||||
mov r8 , qword ptr [rdx ]
|
||||
mov r11b, byte ptr [rdx + 08h]
|
||||
mov qword ptr [rcx ], r8
|
||||
mov byte ptr [rcx + 08h], r11b
|
||||
ret
|
||||
|
||||
@Copy10:
|
||||
mov r8 , qword ptr [rdx ]
|
||||
mov r10w, word ptr [rdx + 08h]
|
||||
mov qword ptr [rcx ], r8
|
||||
mov word ptr [rcx + 08h], r10w
|
||||
ret
|
||||
|
||||
@Copy11:
|
||||
mov r8 , qword ptr [rdx ]
|
||||
mov r10w, word ptr [rdx + 08h]
|
||||
mov r11b, byte ptr [rdx + 0Ah]
|
||||
mov qword ptr [rcx ], r8
|
||||
mov word ptr [rcx + 08h], r10w
|
||||
mov byte ptr [rcx + 0Ah], r11b
|
||||
ret
|
||||
|
||||
@Copy12:
|
||||
mov r8 , qword ptr [rdx ]
|
||||
mov r9d, dword ptr [rdx + 08h]
|
||||
mov qword ptr [rcx ], r8
|
||||
mov dword ptr [rcx + 08h], r9d
|
||||
ret
|
||||
|
||||
@Copy13:
|
||||
mov r8 , qword ptr [rdx ]
|
||||
mov r9d , dword ptr [rdx + 08h]
|
||||
mov r11b, byte ptr [rdx + 0Ch]
|
||||
mov qword ptr [rcx ], r8
|
||||
mov dword ptr [rcx + 08h], r9d
|
||||
mov byte ptr [rcx + 0Ch], r11b
|
||||
ret
|
||||
|
||||
@Copy14:
|
||||
mov r8 , qword ptr [rdx ]
|
||||
mov r9d , dword ptr [rdx + 08h]
|
||||
mov r10w, word ptr [rdx + 0Ch]
|
||||
mov qword ptr [rcx ], r8
|
||||
mov dword ptr [rcx + 08h], r9d
|
||||
mov word ptr [rcx + 0Ch], r10w
|
||||
ret
|
||||
|
||||
; copy 15
|
||||
@Copy15:
|
||||
mov r8 , qword ptr [rdx + 00h]
|
||||
mov r9d , dword ptr [rdx + 08h]
|
||||
mov r10w, word ptr [rdx + 0Ch]
|
||||
mov r11b, byte ptr [rdx + 0Eh]
|
||||
mov qword ptr [rcx + 00h], r8
|
||||
mov dword ptr [rcx + 08h], r9d
|
||||
mov word ptr [rcx + 0Ch], r10w
|
||||
mov byte ptr [rcx + 0Eh], r11b
|
||||
ret
|
||||
|
||||
memcpySSE endp
|
||||
end
|
||||
@@ -223,6 +223,12 @@ void option_free(void)
|
||||
state.options = NULL;
|
||||
state.oCount = 0;
|
||||
|
||||
for(int g = 0; g < state.gCount; ++g)
|
||||
{
|
||||
struct OptionGroup * group = &state.groups[g];
|
||||
if (group->options)
|
||||
free(group->options);
|
||||
}
|
||||
free(state.groups);
|
||||
state.groups = NULL;
|
||||
state.gCount = 0;
|
||||
|
||||
@@ -50,18 +50,19 @@ struct crash
|
||||
asection * section;
|
||||
asymbol ** syms;
|
||||
long symCount;
|
||||
bool loaded;
|
||||
};
|
||||
|
||||
static struct crash crash = {0};
|
||||
|
||||
static void load_symbols(void)
|
||||
static bool load_symbols(void)
|
||||
{
|
||||
bfd_init();
|
||||
crash.fd = bfd_openr(crash.exe, NULL);
|
||||
if (!crash.fd)
|
||||
{
|
||||
DEBUG_ERROR("failed to open '%s'", crash.exe);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
crash.fd->flags |= BFD_DECOMPRESS;
|
||||
@@ -70,20 +71,20 @@ static void load_symbols(void)
|
||||
if (!bfd_check_format_matches(crash.fd, bfd_object, &matching))
|
||||
{
|
||||
DEBUG_ERROR("executable is not a bfd_object");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
crash.section = bfd_get_section_by_name(crash.fd, ".text");
|
||||
if (!crash.section)
|
||||
{
|
||||
DEBUG_ERROR("failed to find .text section");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((bfd_get_file_flags(crash.fd) & HAS_SYMS) == 0)
|
||||
{
|
||||
DEBUG_ERROR("executable '%s' has no symbols", crash.exe);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
long storage = bfd_get_symtab_upper_bound(crash.fd);
|
||||
@@ -92,8 +93,10 @@ static void load_symbols(void)
|
||||
if (crash.symCount < 0)
|
||||
{
|
||||
DEBUG_ERROR("failed to get the symbol count");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lookup_address(bfd_vma pc, const char ** filename, const char ** function, unsigned int * line, unsigned int * discriminator)
|
||||
@@ -131,7 +134,7 @@ static bool lookup_address(bfd_vma pc, const char ** filename, const char ** fun
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cleanup(void)
|
||||
void cleanupCrashHandler(void)
|
||||
{
|
||||
if (crash.syms)
|
||||
free(crash.syms);
|
||||
@@ -168,18 +171,12 @@ static int dl_iterate_phdr_callback(struct dl_phdr_info * info, size_t size, voi
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
|
||||
void printBacktrace(void)
|
||||
{
|
||||
void * array[50];
|
||||
char ** messages;
|
||||
int size, i;
|
||||
|
||||
dl_iterate_phdr(dl_iterate_phdr_callback, NULL);
|
||||
load_symbols();
|
||||
|
||||
DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION);
|
||||
DEBUG_ERROR("signal %d (%s), address is %p", sig_num, strsignal(sig_num), info->si_addr);
|
||||
|
||||
size = backtrace(array, 50);
|
||||
messages = backtrace_symbols(array, size);
|
||||
|
||||
@@ -210,7 +207,14 @@ static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
|
||||
}
|
||||
|
||||
free(messages);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
|
||||
{
|
||||
DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION);
|
||||
DEBUG_ERROR("signal %d (%s), address is %p", sig_num, strsignal(sig_num), info->si_addr);
|
||||
printBacktrace();
|
||||
cleanupCrashHandler();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -219,6 +223,13 @@ bool installCrashHandler(const char * exe)
|
||||
struct sigaction sigact = { 0 };
|
||||
|
||||
crash.exe = realpath(exe, NULL);
|
||||
if (!load_symbols())
|
||||
{
|
||||
DEBUG_WARN("Unable to load the binary symbols, not installing crash handler");
|
||||
return true;
|
||||
}
|
||||
dl_iterate_phdr(dl_iterate_phdr_callback, NULL);
|
||||
|
||||
sigact.sa_sigaction = crit_err_hdlr;
|
||||
sigact.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
|
||||
@@ -238,4 +249,12 @@ bool installCrashHandler(const char * exe)
|
||||
return true;
|
||||
}
|
||||
|
||||
void cleanupCrashHandler(void)
|
||||
{
|
||||
}
|
||||
|
||||
void printBacktrace(void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -37,9 +37,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
struct IVSHMEMInfo
|
||||
{
|
||||
int devFd;
|
||||
int dmaFd;
|
||||
int size;
|
||||
int devFd;
|
||||
int size;
|
||||
bool hasDMA;
|
||||
};
|
||||
|
||||
static bool ivshmemDeviceValidator(struct Option * opt, const char ** error)
|
||||
@@ -119,8 +119,7 @@ bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
|
||||
|
||||
unsigned int devSize;
|
||||
int devFd = -1;
|
||||
int dmaFd = -1;
|
||||
int mapFd = -1;
|
||||
bool hasDMA;
|
||||
|
||||
dev->opaque = NULL;
|
||||
|
||||
@@ -138,22 +137,7 @@ bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
|
||||
|
||||
// get the device size
|
||||
devSize = ioctl(devFd, KVMFR_DMABUF_GETSIZE, 0);
|
||||
const struct kvmfr_dmabuf_create create =
|
||||
{
|
||||
.flags = KVMFR_DMABUF_FLAG_CLOEXEC,
|
||||
.offset = 0x0,
|
||||
.size = devSize
|
||||
};
|
||||
|
||||
dmaFd = ioctl(devFd, KVMFR_DMABUF_CREATE, &create);
|
||||
if (dmaFd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the dma buffer");
|
||||
close(devFd);
|
||||
return false;
|
||||
}
|
||||
|
||||
mapFd = dmaFd;
|
||||
hasDMA = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -174,10 +158,10 @@ bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
|
||||
return false;
|
||||
}
|
||||
|
||||
mapFd = devFd;
|
||||
hasDMA = false;
|
||||
}
|
||||
|
||||
void * map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, mapFd, 0);
|
||||
void * map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, devFd, 0);
|
||||
if (map == MAP_FAILED)
|
||||
{
|
||||
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
|
||||
@@ -187,9 +171,9 @@ bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
|
||||
|
||||
struct IVSHMEMInfo * info =
|
||||
(struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo));
|
||||
info->size = devSize;
|
||||
info->devFd = devFd;
|
||||
info->dmaFd = dmaFd;
|
||||
info->size = devSize;
|
||||
info->devFd = devFd;
|
||||
info->hasDMA = hasDMA;
|
||||
|
||||
dev->opaque = info;
|
||||
dev->size = devSize;
|
||||
@@ -208,10 +192,6 @@ void ivshmemClose(struct IVSHMEM * dev)
|
||||
(struct IVSHMEMInfo *)dev->opaque;
|
||||
|
||||
munmap(dev->mem, info->size);
|
||||
|
||||
if (info->dmaFd >= 0)
|
||||
close(info->dmaFd);
|
||||
|
||||
close(info->devFd);
|
||||
|
||||
free(info);
|
||||
@@ -232,7 +212,7 @@ bool ivshmemHasDMA(struct IVSHMEM * dev)
|
||||
struct IVSHMEMInfo * info =
|
||||
(struct IVSHMEMInfo *)dev->opaque;
|
||||
|
||||
return info->dmaFd >= 0;
|
||||
return info->hasDMA;
|
||||
}
|
||||
|
||||
int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size)
|
||||
|
||||
@@ -20,3 +20,7 @@ target_link_libraries(lg_common_platform_code
|
||||
lg_common
|
||||
setupapi
|
||||
)
|
||||
|
||||
if (ENABLE_BACKTRACE)
|
||||
target_link_libraries(lg_common_platform_code dbghelp)
|
||||
endif()
|
||||
|
||||
@@ -18,9 +18,142 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "common/crash.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/version.h"
|
||||
|
||||
#ifdef ENABLE_BACKTRACE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
static const char * exception_name(DWORD code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
return "ACCESS_VIOLATION";
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
return "ARRAY_BOUNDS_EXCEEDED";
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
return "BREAKPOINT";
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
return "DATATYPE_MISALIGNMENT";
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
return "FLT_DENORMAL_OPERAND";
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
return "FLT_DIVIDE_BY_ZERO";
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
return "FLT_INEXACT_RESULT";
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
return "FLT_INVALID_OPERATION";
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
return "FLT_OVERFLOW";
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
return "FLT_STACK_CHECK";
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
return "FLT_UNDERFLOW";
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
return "ILLEGAL_INSTRUCTION";
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
return "IN_PAGE_ERROR";
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
return "INT_DIVIDE_BY_ZERO";
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
return "INT_OVERFLOW";
|
||||
case EXCEPTION_INVALID_DISPOSITION:
|
||||
return "INVALID_DISPOSITION";
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
return "NONCONTINUABLE_EXCEPTION";
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
return "PRIV_INSTRUCTION";
|
||||
case EXCEPTION_SINGLE_STEP:
|
||||
return "SINGLE_STEP";
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
return "STACK_OVERFLOW";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static LONG CALLBACK exception_filter(EXCEPTION_POINTERS * exc)
|
||||
{
|
||||
PEXCEPTION_RECORD excInfo = exc->ExceptionRecord;
|
||||
CONTEXT context;
|
||||
memcpy(&context, exc->ContextRecord, sizeof context);
|
||||
|
||||
DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION);
|
||||
DEBUG_ERROR("exception 0x%08lx (%s), address is %p", excInfo->ExceptionCode,
|
||||
exception_name(excInfo->ExceptionCode), excInfo->ExceptionAddress);
|
||||
|
||||
if (!SymInitialize(GetCurrentProcess(), NULL, TRUE))
|
||||
{
|
||||
DEBUG_ERROR("Failed to SymInitialize: 0x%08lx, could not generate stack trace", GetLastError());
|
||||
goto fail;
|
||||
}
|
||||
SymSetOptions(SYMOPT_LOAD_LINES);
|
||||
|
||||
STACKFRAME64 frame = { 0 };
|
||||
frame.AddrPC.Offset = context.Rip;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.Rbp;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.Rsp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
HANDLE hThread = GetCurrentThread();
|
||||
|
||||
for (int i = 1; StackWalk64(IMAGE_FILE_MACHINE_AMD64, hProcess, hThread, &frame, &context, NULL,
|
||||
SymFunctionTableAccess64, SymGetModuleBase64, NULL); ++i)
|
||||
{
|
||||
DWORD64 moduleBase = SymGetModuleBase64(hProcess, frame.AddrPC.Offset);
|
||||
char moduleName[MAX_PATH];
|
||||
|
||||
if (moduleBase && GetModuleFileNameA((HMODULE) moduleBase, moduleName, MAX_PATH))
|
||||
{
|
||||
DWORD64 disp;
|
||||
|
||||
char symbolBuf[sizeof(SYMBOL_INFO) + 255];
|
||||
PSYMBOL_INFO symbol = (PSYMBOL_INFO) symbolBuf;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = 256;
|
||||
|
||||
if (SymFromAddr(hProcess, frame.AddrPC.Offset, &disp, symbol))
|
||||
{
|
||||
IMAGEHLP_LINE line = { sizeof(IMAGEHLP_LINE), 0 };
|
||||
DWORD lineDisp;
|
||||
|
||||
if (SymGetLineFromAddr64(hProcess, frame.AddrPC.Offset, &lineDisp, &line))
|
||||
DEBUG_ERROR("[trace]: %2d: %s:%s+0x%" PRIx64 " (%s:%ld+0x%lx)", i, moduleName, symbol->Name, disp,
|
||||
line.FileName, line.LineNumber, lineDisp);
|
||||
else
|
||||
DEBUG_ERROR("[trace]: %2d: %s:%s+0x%" PRIx64, i, moduleName, symbol->Name, disp);
|
||||
}
|
||||
else
|
||||
DEBUG_ERROR("[trace]: %2d: %s+0x%08" PRIx64, i, moduleName, frame.AddrPC.Offset - moduleBase);
|
||||
}
|
||||
else
|
||||
DEBUG_ERROR("[trace]: %2d: 0x%016" PRIx64, i, frame.AddrPC.Offset);
|
||||
}
|
||||
|
||||
SymCleanup(hProcess);
|
||||
|
||||
fail:
|
||||
fflush(stderr);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool installCrashHandler(const char * exe)
|
||||
{
|
||||
//TODO
|
||||
SetUnhandledExceptionFilter(exception_filter);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
bool installCrashHandler(const char * exe)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6,12 +6,17 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
include(CheckCCompilerFlag)
|
||||
include(FeatureSummary)
|
||||
|
||||
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" ON)
|
||||
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" OFF)
|
||||
if(OPTIMIZE_FOR_NATIVE)
|
||||
CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
if(COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
add_compile_options("-march=native")
|
||||
endif()
|
||||
else()
|
||||
CHECK_C_COMPILER_FLAG("-march=nehalem" COMPILER_SUPPORTS_MARCH_NEHALEM)
|
||||
if(COMPILER_SUPPORTS_MARCH_NEHALEM)
|
||||
add_compile_options("-march=nehalem")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
||||
|
||||
@@ -54,16 +54,19 @@ The resulting installer will be at
|
||||
|
||||
## Where is the log?
|
||||
|
||||
It is in your user's temp directory:
|
||||
The log file for the host application is located at:
|
||||
|
||||
%TEMP%\looking-glass-host.txt
|
||||
%ProgramData%\Looking Glass (host)\looking-glass-host.txt
|
||||
|
||||
Or if running as a system service it will be located in:
|
||||
You can also find out where the file is by right clicking on the tray icon and
|
||||
selecting "Log File Location".
|
||||
|
||||
C:\Windows\Temp\looking-glass-host.txt
|
||||
The log file for the looking glass service is located at:
|
||||
|
||||
You can find out where the file is by right clicking on the tray icon and
|
||||
selecting "Log File Location"
|
||||
%ProgramData%\Looking Glass (host)\looking-glass-host-service.txt
|
||||
|
||||
This is useful for troubleshooting errors related to the host application not
|
||||
starting.
|
||||
|
||||
### High priority capture using DXGI and Secure Desktop (UAC) capture support
|
||||
|
||||
|
||||
@@ -38,3 +38,5 @@ void app_quit();
|
||||
// these must be implemented for each OS
|
||||
const char * os_getExecutable();
|
||||
const char * os_getDataPath();
|
||||
|
||||
bool os_blockScreensaver();
|
||||
|
||||
@@ -72,3 +72,8 @@ const char * os_getDataPath(void)
|
||||
{
|
||||
return app.dataPath;
|
||||
}
|
||||
|
||||
bool os_blockScreensaver()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ target_link_libraries(platform_Windows
|
||||
userenv
|
||||
wtsapi32
|
||||
psapi
|
||||
shlwapi
|
||||
powrprof
|
||||
)
|
||||
|
||||
target_include_directories(platform_Windows
|
||||
|
||||
@@ -743,7 +743,7 @@ static CaptureResult dxgi_capture(void)
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
|
||||
Texture * tex;
|
||||
Texture * tex = NULL;
|
||||
CaptureResult result;
|
||||
HRESULT status;
|
||||
DXGI_OUTDUPL_FRAME_INFO frameInfo;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user