Compare commits

...

121 Commits

Author SHA1 Message Date
Quantum
39a09ca565 [client] egl: remove dependency on SDL
After this PR, EGL functions are now accessed through <EGL/egl.h>
instead of through <SDL2/SDL_egl.h>, removing a pointless dependency
on SDL.
2021-02-21 10:31:49 +11:00
Quantum
5649d1ad95 [client] wayland: split Wayland display server into modules
The Wayland display server is getting unwieldy due to the sheer size.
To make it easier to edit in the future, I split it into many components
based on logical boundaries.
2021-02-21 10:31:49 +11:00
Quantum
1ba1108099 [client] wayland: add option to enable cursor warp
This is enabled on default. Specify wayland:warpSupport=no to disable it,
which may be useful on certain compositors that do not warp when the
pointer is confined.
2021-02-21 10:31:49 +11:00
Quantum
9b688909b0 [client] wayland: support LG_DS_WARP_SURFACE
This commit implements support for LG_DS_WARP_SURFACE, as well as a warp
routine based on cursor confines.

This may not necessarily work for all compositors. As such, the old cursor
routines are still kept, and used when wm.warpSupport is set to false.
2021-02-21 10:31:49 +11:00
Quantum
270631f1b9 [client] ds: add surface-only warp variant
This commit converts the output of ds->getProp(LG_DS_WARP_SUPPORT) to
an enum containing three items:

* LG_DS_WARP_NONE: warp is not supported at all
* LG_DS_WARP_SURFACE: warp is possible, but only inside the window
* LG_DS_WARP_SCREEN: warp is possible anywhere on the screen

LG_DS_WARP_NONE corresponds to the old false return value, and
LG_DS_WARP_SCREEN corresponds to the old true return value.

LG_DS_WARP_SURFACE is designed for Wayland, where warping is possible,
but only in our window. In this case, since we cannot warp outside
the window, we can warp the cursor to the edge when we attempt to exit.
If the cursor leaves, the normal leave routine gets called, and the
cursor disappears. If the cursor does not end up leaving, we grab it
again.
2021-02-21 10:31:49 +11:00
Jonathan Rubenstein
d86014e5ff [doc] Add client ini example to kernel module README.md 2021-02-21 10:31:49 +11:00
Tudor Brindus
1a407a67b1 [client] input: add releaseKeysOnFocusLoss option
This makes dealing with window manager shortcuts that overlap with guest
keys more pleasant, while retaining the previous functionality for users
who prefer it.

For instance, previously, using Alt+Tab (or $mod as Alt in i3/sway
movement commands) would result in the guest retaining Alt as pressed.

When the guest regained focus, it would continue thinking Alt is
pressed, leading to accidentally triggering obscure shortcuts. One had
to remember to press Alt again to "unstick" things, which was
suboptimal.
2021-02-21 10:31:49 +11:00
Quantum
98a327e99e [client] wayland: make clipboard writes asynchronous
This allows multiple Wayland clients to stream data from looking glass
without blocking the looking glass main thread.
2021-02-21 10:31:49 +11:00
Quantum
db16efe68b [common] add a reference counted buffer type
This allows buffers to be shared between different asynchronous operations.
Once all users no longer need the buffer, it will be freed.

The motivation for this is being able to stream Wayland clipboard data
asynchronously to multiple clients. The buffer should only be freed after
the clipboard has changed and all ongoing transfer completes.
2021-02-21 10:31:49 +11:00
Quantum
800f063a1d [client] wayland: make clipboard read async
This allows reading from the clipboard without blocking the UI thread.
2021-02-21 10:31:49 +11:00
Quantum
e01666b6ad [client] wayland: implement epoll registration mechanism
This will be used to register async clipboard I/O callbacks later.
2021-02-21 10:31:49 +11:00
Quantum
30a888711b [client] egl: force DMA copy into texture in on_frame
This commit forces the DMA'd memory to be copied into the texture in
the EGL on_frame handler. This avoids tearing when the LG host inevitably
updates the underlying memory. We need an additional copy inside the GPU,
but this is cheap compared to copying from system memory.

We could have used logic to lock the memory buffer, but that would require
performing DMA on every frame, which wastes memory bandwidth. This
manifests as reduced frame rate when moving the mouse compared to the
non-DMA implementation.

We also keep multiple EGLImages, one for each DMA fd, to avoid issues
with the OpenGL driver.
2021-02-21 10:31:48 +11:00
Quantum
62b27760ea [common] ivshmem: do not create dmabuf for simple mmap
It used to be the case that you need to create dmabuf for kvmfr devices
to be able to mmap them. But after #457, this is no longer needed.

Directly mmaping the kvmfr device has the advantage of avoiding the
creation of a dmabuf, which has cost (e.g. the list of pages, the
scatterlist, etc.).
2021-02-21 10:31:48 +11:00
Quantum
328f9078ee [module] test mmaping with offsets in test program
This commit makes the test program try the following cases:
* mmaping 0-offset dmabuf with 0 offset
* mmaping 0-offset dmabuf with 1 page offset
* mmaping page-offset dmabuf with 0 offset
* mmaping page-offset dmabuf with 1 page offset
* mmaping device with 0 offset
* mmaping device with 1 page offset
2021-02-21 10:31:48 +11:00
Quantum
5774e21965 [module] implement mmap on PCI kvmfr devices
This allows PCI kvmfr devices to be directly mmap'd just like in-memory
ones. Also, the more efficient mmap implementation is used for mapping
the dmabuf, avoiding the faulting code entirely.
2021-02-21 10:31:48 +11:00
Quantum
2c909f0af7 [module] improve dmabuf mmap logic for vmalloc'd memory
Instead of faulting the pages in one by one when mmaping on the dma fd,
we could instead use remap_vmalloc_range to map in all the memory at
once.
2021-02-21 10:31:48 +11:00
Quantum
f65aa6e089 [module] update README to reflect VM->host changes 2021-02-21 10:31:48 +11:00
Quantum
b447b78b17 [module] support in-memory kvmfr devices
Added an array option static_size_mb to the kvmfr module to create a
list of in-memory kvmfr devices. These devices support dmabuf just like
normal kvmfr devices. Additionally, they can be mmap'd, which allows
them to be passed to qemu as ivshmem devices.
2021-02-21 10:31:48 +11:00
Quantum
a450e0f8f5 [client] wayland: better self-copy detection
This new implementation uses a special mimetype to tag data copied from
the guest, instead of using flags. This should make it easier to
implement asynchronous transfers in the future. Also, it's simpler to
understand and less error-prone.

The pid is included in the mimetype in order to distinguish between
different instances of looking glass: you might want to copy between
two different VMs, for example.
2021-02-21 10:31:48 +11:00
Quantum
0c9ecdfcb5 [client] wm: optionally disable screensaver when requested in guest
This commit adds a new option, win:autoScreensaver, which when set to yes,
automatically disables the screensaver when requested by an application
running in the guest, and enables it when the application no longer wants
it disabled.

This is useful when doing media playback in the guest.
2021-02-21 10:31:48 +11:00
Quantum
8e98f863b6 [host] windows: detect whether screensaver is disabled in the guest
This will allow us to add an option to disable the screensaver on the client
when an application in the guest requests it. This behaviour may be useful
when the guest is doing media playback.
2021-02-21 10:31:48 +11:00
Quantum
cc2104c699 [client] wayland: cleanup surface and display
These used to be owned by SDL and can't be cleaned up. This has since
changed.
2021-02-21 10:31:48 +11:00
Geoffrey McRae
253b0e2a7a [res] added LG logo vector graphics 2021-02-21 10:31:48 +11:00
Geoffrey McRae
c6af5be1dc [client] app: always track the mouse button state
This change fixes a bug on re-grab of the cursor if window focus was
lost while a mouse button was held.
2021-02-21 10:31:48 +11:00
Quantum
06f6a96b56 [client] spice: fix input:grabKeyboardOnFocus
It appears that the keyboard should only be grabbed if the client is
focused and the cursor is in the view. However, the relevant logic was
missing from core_setCursorInView, and the keyboard was never actually
grabbed.

This commit adds the call to g_state.ds->grabKeyboard(), allowing grabbing
to work.
2021-02-21 10:31:48 +11:00
Quantum
89b73512ad [client] renderer: add ability to toggle the FPS display
Before, if you want to see the FPS, you need to close the client and
restart it with the -k switch to see the FPS. This is annoying.

This PR introduces a new keybind, ScrollLock+D, which, when pressed,
toggles the display of the FPS.

This is implemented for both EGL and OpenGL backends.
2021-02-21 10:31:48 +11:00
Quantum
b45f7a6733 [client] egl: fix race condition in help overlay
egl_help_set_text and egl_help_render were both accessing bmp->help from
different threads. This creates a race condition in which if the help text
is quickly toggled on and off, it stays on.

This has been fixed with an atomic exchange.
2021-02-21 10:31:48 +11:00
Geoffrey McRae
1d99c821eb [client] egl: decrease the font size for the help text 2021-02-21 10:31:48 +11:00
Quantum
2993f7ae7d [client] egl: create 24-bit colour context
This should prevent the looking-glass-client window from having an alpha
channel. On Wayland, the alpha channel is used to compose the window onto
the desktop, so the wallpaper would bleed through unless set to complete
opaque.

We worked around this by using constant alpha for rendering, but it was
not sustainable. Instead, we should just ask for 24-bit context.
2021-02-21 10:31:48 +11:00
Geoffrey McRae
5454053d96 [client] x11: set the window class name 2021-02-21 10:31:48 +11:00
Geoffrey McRae
17d423db06 [client] x11: handle window deletion properly 2021-02-21 10:31:48 +11:00
Quantum
5ac53362a3 [client] renderer/egl: implement support for rendering help text
The help text is rendered in the bottom left corner on a semi-transparent
background, very similar to how the FPS text is rendered.
2021-02-21 10:31:48 +11:00
Quantum
17b0e2cb22 [client] fonts: support rendering multiline text with SDL 2021-02-21 10:31:48 +11:00
Quantum
96dc8c602c [client] keybind: display help overlay while ScrollLock is held
This overlay will show the list of keybindings.
2021-02-21 10:31:48 +11:00
Quantum
ead8069dae [client] keybind: add descriptions for all keybindings 2021-02-21 10:31:48 +11:00
Quantum
4e765b063a [client] kb: add display names for all supported keys 2021-02-21 10:31:48 +11:00
Quantum
5dce97264b [client] renderers: add on_help to renderer interface
This will be used to tell a render to display a help message.
2021-02-21 10:31:48 +11:00
Geoffrey McRae
a00a6429d3 [client] app: fix spelling error 2021-02-21 10:31:48 +11:00
Geoffrey McRae
aafdec02df [client] wayland: fix mouse code post refactor 2021-02-21 10:31:48 +11:00
Geoffrey McRae
4e1c0cc0d0 [client] ds: refactor app and cursor state into app.c and core.c 2021-02-21 10:31:48 +11:00
Geoffrey McRae
33fed48277 [client] app: fix broken mouse sensitivity 2021-02-21 10:31:48 +11:00
Quantum
b0f9d2f713 [client] spice/wayland: improve cursor tracking logic
One of the major issues with the old tracking code is a data race
between the cursor thread updating g_cursor.guest and the
app_handleMouseBasic function. Specifically, the latter may have
sent mouse input via spice that has not been processed by the guest
and updated g_cursor.guest, but the guest may overwrite g_cursor.guest
to a previous state before the input is processed. This causes some
movements to be doubled. Eventually, the cursor positions will
synchronize, but this nevertheless causes a lot of jitter.

In this commit, we introduce a new field g_cursor.projected, which
is unambiguously the position of the cursor after taking into account
all the input already sent via spice. This is synced up to the guest
cursor upon entering the window and when the host restarts. Afterwards,
all mouse movements will be based on this position. This eliminates
all cursor jitter as far as I could tell.

Also, the cursor is now synced to the host position when exiting
capture mode.

A downside of this commit is that if the 1:1 movement patch is not
correctly applied, the cursor position would be wildly off instead
of simply jittering, but that is an unsupported configuration and
should not matter.

Also unsupported is when an application in guest moves the cursor
programmatically and bypassing spice. When using those applications,
capture mode must be on. Before this commit, we try to move the guest
cursor back to where it should be, but it's inherently fragile and
may lead to scenarios such as wild movements in first-person shooters.
2021-02-21 10:31:48 +11:00
Quantum
543d660ccc [client] wayland: check for the Wayland platform extension
We used to test for the EGL_KHR_platform_base and EGL_EXT_platform_base,
but those only really signal the availability of eglGetPlatformDisplay(EXT)
functions, not whether the constant EGL_PLATFORM_WAYLAND_KHR or
EGL_PLATFORM_WAYLAND_EXT is accepted by their respective functions.

Instead, we switch to test for the extensions that tells us whether the
Wayland platform is supported.
2021-02-21 10:31:48 +11:00
Quantum
ecebcc4c35 [client] opengl: make ds functions optional
Using a macro ENABLE_OPENGL just like ENABLE_EGL to optionally remove
OpenGL implementation code. This is mostly because on Wayland it's just
a rehash of the EGL code (as EGL is the only way to create OpenGL
contexts on Wayland).
2021-02-21 10:31:48 +11:00
Quantum
af2dafbdac [client] wayland: add ability to create OpenGL contexts
This should allow the OpenGL backend to work.
2021-02-21 10:31:48 +11:00
Quantum
a56e363e39 [client] wayland: add stubs for OpenGL functions
This allows the client to run on Wayland, even though OpenGL doesn't work.
2021-02-21 10:31:48 +11:00
Geoffrey McRae
06af101bf9 [client] x11: properly handle window destruction and fullscreen support 2021-02-21 10:31:48 +11:00
Geoffrey McRae
973806dd9c [client] opengl: implement & fix opengl support 2021-02-21 10:31:48 +11:00
Geoffrey McRae
740dad943b [client] x11: cleanup on failure to initialize 2021-02-21 10:31:48 +11:00
Geoffrey McRae
4dfe4b8e2b [client] allow renderers to pass back if they need an OpenGL context 2021-02-21 10:31:48 +11:00
Geoffrey McRae
cc521eab90 [client] app: fix reversed ds init logic 2021-02-21 10:31:48 +11:00
Geoffrey McRae
7d2c9ec447 [client] app: don't call ds->free if the ds was not initialized 2021-02-21 10:31:48 +11:00
Geoffrey McRae
8919d2718f [client] egl: fix building without EGL support 2021-02-21 10:31:48 +11:00
Tudor Brindus
cf3e816603 [client] probe Wayland backend first
`$DISPLAY` will be set even in a Wayland session, which causes LG to
initialize itself under Xwayland unless it is explicitly compiled with
`-DENABLE_X11=OFF`.

We could add a Wayland check within the X11 backend, but reordering the
code-generated array seems like a better solution.
2021-02-21 10:31:48 +11:00
Quantum
b8bf980a29 [client] wayland: decorate window unless borderless is requested
Show server-side decoration with the xdg_decoration protocol unless
win:borderless=yes.
2021-02-21 10:31:48 +11:00
Quantum
5dad69675b [client] wayland: respect request to maximize window
Request the compositor to maximize the window if win:maximize=yes.
2021-02-21 10:31:48 +11:00
Quantum
d0d1b31c10 [client] wayland: add handling for close event
When the xdg_toplevel receives a close event, call app_handleCloseEvent.
2021-02-21 10:31:48 +11:00
Quantum
6206d5dec4 [client] wayland: implement full screen handling
Implement waylandSetFullscreen and waylandGetFullscreen.
2021-02-21 10:31:48 +11:00
Quantum
265370b0f5 [client] wayland: implement cursor handling
This commit adds code to load the looking glass cursor and display it
when needed. Otherwise, the cursor is hidden.
2021-02-21 10:31:48 +11:00
Quantum
081d76268a [client] wayland: handle mouse/keyboard enter events
This allows mouse and keyboard input to work again on Wayland.
2021-02-21 10:31:48 +11:00
Quantum
8559b354ae [client] egl: always render desktop texture as opaque
We ask for 32-bit colour buffer when creating the EGL context. On Wayland,
this sometimes give contexts with alpha channels, resulting in unwanted
transparency. So we clear the alpha channel in the desktop shader.

We also switch to using constant alpha for blending the splash, which
avoids more alpha issues.
2021-02-21 10:31:48 +11:00
Quantum
9f0b99dac0 [client] wayland: implement window creation for egl
This commit implements window creation and resize logic, allowing the desktop
to be drawn.
2021-02-21 10:31:48 +11:00
Geoffrey McRae
f4c1927f56 [client] ds: added new getFullscreen operation
As the window manager may change our mode to full screen without our
request we must ask the ds backend for the current state when we want to
toggle the mode.
2021-02-21 10:31:48 +11:00
Geoffrey McRae
cfa9171465 [client] x11: don't report borders if in fullscreen mode 2021-02-21 10:31:48 +11:00
Geoffrey McRae
2eac3dcb56 [client] x11: implemented missing functionallity 2021-02-21 10:31:48 +11:00
Geoffrey McRae
2d1e3c8022 [client] ds: validate the ds before attempting to use it 2021-02-21 10:31:48 +11:00
Geoffrey McRae
f8ac860fde [client] app: rearrange code to reflect the header 2021-02-21 10:31:48 +11:00
Geoffrey McRae
6f4a116942 [client] ds: re-order as SDL is now the fallback 2021-02-21 10:31:48 +11:00
Geoffrey McRae
ca5c3938e4 [client] all: move all SDL specific code into displayservers/sdl 2021-02-21 10:31:48 +11:00
Geoffrey McRae
7ff5da4d62 [client] refactor keybinds out of main.c 2021-02-21 10:31:48 +11:00
Geoffrey McRae
37b3a26b9c [client] all: refactor keybind code & functions 2021-02-21 10:31:48 +11:00
Geoffrey McRae
e18f7d3365 [client] app: replace old SDL_Scancode with int 2021-02-21 10:31:48 +11:00
Geoffrey McRae
3d03699cc8 [client] all: move keybind implementation into app.c/h 2021-02-21 10:31:48 +11:00
Geoffrey McRae
9674421ce4 [client] x11: fix double initialization of event members 2021-02-21 10:31:48 +11:00
Geoffrey McRae
dbb18a6ecb [client] x11/sdl: get the border dimensions from the backend 2021-02-21 10:31:48 +11:00
Geoffrey McRae
6b1e310343 [client] move remaining code in core.c into the SDL backend 2021-02-21 10:31:48 +11:00
Geoffrey McRae
bf583290a4 [client/common] restructure project in prep for full SDL removal 2021-02-21 10:31:46 +11:00
Geoffrey McRae
6f1c19b3b0 [all] improve backtrace and debugging support 2021-02-21 10:30:57 +11:00
Quantum
2973319bff [client] opengl: remove glu dependency
We only use gluOrtho2D, which is trivially replaced with glOrtho, and
gluErrorString which can be replaced with a small lookup table.
2021-02-20 12:44:32 +11:00
Quantum
ec921d7f39 [client] spice: correctly ungrab keyboard when grabKeyboardOnFocus=no
When input:grabKeyboardOnFocus=no, exiting capture mode should ungrab
the keyboard. Otherwise, focusing the window doesn't grab the keyboard,
but toggling capture mode would leave the keyboard stuck in a grabbed
state until defocused.
2021-02-09 20:06:27 +11:00
Tudor Brindus
637a7625d2 [client] wayland: allow EGL/OpenGL vsync to be set to on
This effectively reverts 4bceaf5.

Upstream ticket: https://gitlab.freedesktop.org/mesa/mesa/-/issues/4180

Commit 941c651 makes working around the hang in LG itself not as
annoying as before.

In the future, we can bypass this entire issue by implementing our own
swapchain and listening to frame callbacks ourselves.
2021-02-09 09:12:57 +11:00
Jonathan Rubenstein
32d8a47cd9 [doc] Change Level1Techs link to Looking Glass forum
Originally linked to Triage thread, which has been locked and replaced
by the forum
2021-02-02 08:42:54 +11:00
Jonathan Rubenstein
b4787fcfd1 [doc] Add new Looking Glass discord to README.md 2021-02-02 08:42:54 +11:00
Tudor Brindus
e6ebcec689 [client] spice: don't send zero deltas for Wayland input
While a compositor will never send us 0-delta motion events, they can
still end up as 0-deltas post-projection, consuming QEMU buffer space
for no reason.

This should help with mouse skipping issues.
2021-02-01 11:09:46 +11:00
Quantum
ff0a859ceb [host] nvfbc: avoid recreating mouse hook and 1x1 window
The mouse hook code is very fragile, and we would like to avoid unhooking
and re-hooking as much as possible.

After this commit, this is done only once, and the hook and 1x1 window is
only destroyed upon exit. This, of course, comes with the downside of
the slight performance penalty if the guest machine is used directly while
the host is running and the client is not running.
2021-01-31 12:17:14 +11:00
Quantum
1b48ac842a [host] nvfbc: fix resource leak when pointer thread creation fails
Moving NvFBCToSysSetup to nvfbc_init means that when the pointer thread
fails to be created, NvFBCToSysRelease needs to be called.

To resolve such cleanup issues in the future, we instead call nvfbc_deinit,
which should cleanup everything that needs to be cleaned up. fails.
2021-01-31 11:21:24 +11:00
Quantum
a702c912ae [host] nvfbc: move NvFBCToSysCreate into nvfbc_init
When NvFBCToSysCapture reports recreation is required, we return
CAPTURE_RESULT_REINIT, which eventually calls nvfbc_deinit and then
nvfbc_init.

However, the NvFBC object is actually created in nvfbc_create, which
means the NvFBC object is never actually recreated. The result is an
endless cycle of NvFBC asking for recreation. This commonly manifests
as the client waiting endlessly for the host when the guest machine
reboots.

In this commit, the NvFBC object creation is moved into nvfbc_init,
and when recreation is required, it will actually be recreated.
2021-01-31 10:57:51 +11:00
Quantum
acc3298344 [host] nvfbc: cleanup threads created by nvfbc_init on failure
mouseHook_install and dwmForceComposition both create threads, but these
are only freed in nvfbc_deinit which is not called if nvfbc_init fails.
These should be freed if the pointer thread fails to be created, as
nothing else could be cleaning it up.
2021-01-31 09:57:07 +11:00
Quantum
25e74301be [client] wayland: fix copying rich text into guest
Before this, copying rich text ends up with a lot of funky behaviour,
for example:
* copying text from Discord shows up as HTML unless pasted into a text
  editor first
* copying text from Firefox shows up as the single letter h

This commit fixes all the above issues.

Due to the change in logic, we now use the first text format offered
instead of the last, which is almost certainly the preferred form.
Doing this gets us proper Unicode support, or Unicode characters would
end up as escapes of the form \uXXXX (this is used in the fallback
forms for applications without UTF-8 support).
2021-01-30 16:55:57 +11:00
Quantum
327d472d64 [all] update discord link for issue template
Use link to the new Looking Glass discord server. Also fixed a typo.
2021-01-30 15:41:44 +11:00
Geoffrey McRae
e951aaad2d [client] spice: fix errant keyboard grab/ungrab behaviour 2021-01-30 12:01:20 +11:00
Quantum
bbfe5aea37 [all] update issue template to reflect new log file path 2021-01-29 15:56:01 +11:00
Quantum
0d28ea160e [host] update README.md to reflect new log paths 2021-01-29 15:56:01 +11:00
Quantum
4fbaf18c89 [client] update host log file path 2021-01-29 15:56:01 +11:00
Quantum
c91b7f647d [host] installer: create start menu shortcut to log directory
This commit makes the installer create a shortcut to the log directory
introduced by the previous commit.
2021-01-29 15:56:01 +11:00
Quantum
1761ea2b9b [host] windows: move log path to %ProgramData%\Looking Glass (host)
Instead of using %windir%\Temp, which is not accessible by default and
contains a lot of unrelated files, as the location for our log files,
this commit moves it to %ProgramData%\Looking Glass (host), which will
be a dedicated directory just for the LG host log files. This applies
to both the host application logs and the service logs.

Also, we now switched to using PathCombineA from shlwapi.dll instead
of using snprintf, which greatly simplifies the code. PathCombineA
guarantees that the path would not overflow a buffer of MAX_PATH.
2021-01-29 15:56:01 +11:00
Quantum
fb916cbac1 [host] nvfbc: always update cursor shape on startup
This ensures that the top-left position of the cursor sprite is correctly
computed.
2021-01-28 11:18:02 +11:00
Quantum
b97130cf20 [host] nvfbc: generate cursor position update on startup
Before this commit, the NvFBC backend only generated the first cursor
position update when the mouse moves. Therefore, if the user does
not move the mouse, the cursor will be shown at (0, 0), which is not
ideal.

This commit changes this behaviour to unconditionally generate a
cursor update when the mouse hook initializes.
2021-01-28 11:18:02 +11:00
Geoffrey McRae
05f2305fa0 [client] correct error in variable name from last commit 2021-01-28 09:04:52 +11:00
Geoffrey McRae
b76fedeb67 [client] all: don't trigger cursor redraws if the cursor is not visible 2021-01-28 08:58:59 +11:00
Geoffrey McRae
6b5842d2ff [host] cmake: use -march=nehalem by default
Nehalem is the minimum requirement for the host application as it makes
use of SSE4.1 instructions, as such we should default to compling with
it instead of `-march=native` so that when the binary is distributed it
will operate on foreign systems.

Fixed #416
2021-01-28 08:09:31 +11:00
Quantum
7e15ec5e66 [common] windows: implement crash handler for stack traces
This commit uses the DbgHelp library which is shipped with Windows to
generate stack traces with function names and line number information.
It takes advantage of the pdb file generated by cv2pdb that is now
installed with looking-glass-host.exe.
2021-01-27 07:56:12 +11:00
Geoffrey McRae
1808adc2de [host] app: fix possible string overflow 2021-01-27 01:28:29 +11:00
Geoffrey McRae
e2e49bce13 [host] service: fix possible use of unitialized variable 2021-01-27 01:23:58 +11:00
Geoffrey McRae
0d7be70b56 [host] dxgi: fix maybe uninitialized warning 2021-01-27 01:21:06 +11:00
Geoffrey McRae
6b0699e664 [host] installer: include the debug PDB if it is available 2021-01-26 22:55:25 +11:00
Geoffrey McRae
9e96156912 [client] egl: use eglGetPlatformDisplay(EXT) if possible 2021-01-25 16:04:33 +11:00
Geoffrey McRae
837858c214 [client] prevent lgInit from resetting the run state
If the renderer fails to start it sets the run state to stopped, having
lgInit where it was causes this to be reset to running triggering
invalid usage of g_state.lgmp.
2021-01-25 15:25:52 +11:00
Geoffrey McRae
3783a25211 [spice] update the PureSpice submodule 2021-01-25 15:06:21 +11:00
Tudor Brindus
941c651fad [client] unconditionally quit on second SIGINT
Under some circumstances, Looking Glass can hang when SIGINT'd, for
instance, if it's stuck waiting on spice I/O that won't complete because
the guest is misbehaving.

This commit provides an escape hatch for such cases, so one doesn't have
to reach for `kill -9 $(pidof looking-glass-client)`.
2021-01-25 09:39:35 +11:00
Quantum
f9ec32b255 [host] service: disable buffering on the log file
Before this change, the log is buffered, so if the host application exits
for any reason, it usually would not show up in the log file immediately,
and the service has to be restarted for the logs to be flushed.

This commit disables the buffering so that any log entries shows up
immediately.
2021-01-25 09:35:03 +11:00
Geoffrey McRae
8caf951c41 [client] x11: don't attempt to grab the pointer on window resize 2021-01-25 09:25:01 +11:00
Geoffrey McRae
ef54e1be7f [client] x11: add error checking around XIGrabDevice 2021-01-25 06:52:23 +11:00
Geoffrey McRae
4c1893fe20 [all] fix numerous memory leaks at application shutdown 2021-01-24 21:47:53 +11:00
Geoffrey McRae
086f73721d [client] egl: remove accidental commit 2021-01-24 19:19:43 +11:00
Geoffrey McRae
202739c5be [client] egl: better debug output for EGL errors 2021-01-24 13:17:11 +11:00
Geoffrey McRae
88b15cb3fe [client] egl: nit, fix case of function name 2021-01-24 12:18:56 +11:00
Geoffrey McRae
6990d7f7e3 [client] egl: commit missed files for the last changeset 2021-01-24 12:16:39 +11:00
Geoffrey McRae
9941a4bb83 [client] egl: runtime detect support for glEGLImageTargetTexture2DOES 2021-01-24 12:06:10 +11:00
Quantum
d610aaf2cf [host] nvfbc: update cursor position on shape change
This is because we keep track of the top-left corner of the cursor, not
the location of the hotspot. When the cursor shape changes, the hotspot
location may also change. When it does, the position of the top-left
corner changes and requires an update.

In the case that we do not have the current cursor position, which
happens on startup, we do not generate this update.
2021-01-23 20:37:09 +11:00
Quantum
908aa84599 [client] wayland: use acceleration in capture mode unless rawMouse
We are forced to use accelerated movement in regular mode as that is how the
host machine cursor moves and we want the cursors to line up (since Wayland
cannot do warps). To avoid a change in sensitivity when toggling capture
mode on/off, we should use accelerated deltas for capture mode as well,
unless the user explicitly asks for raw input with input:rawMouse.
2021-01-23 20:18:20 +11:00
115 changed files with 7795 additions and 3999 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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" )

View File

@@ -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 |
|-------------------------------------------------------------|
```

View File

@@ -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)

View File

@@ -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,

View File

@@ -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")

View 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;
}

View 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);
}

View 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

View 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;
}
}

View 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();
}

View 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;
}

View 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, &registryListener, NULL);
wl_display_roundtrip(wlWm.display);
return true;
}
void waylandRegistryFree(void)
{
wl_registry_destroy(wlWm.registry);
}

View 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;

View File

@@ -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, &registryListener, 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,

View 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);

View 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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View 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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -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

View 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";
}
}

View File

@@ -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())

View File

@@ -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);

View File

@@ -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
View 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);
}

View 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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -84,4 +84,6 @@ void main()
color *= 1.0 + lumi;
color *= nvGain;
}
color.a = 1.0;
}

View 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);
}

View 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;
}

View File

@@ -0,0 +1,8 @@
#version 300 es
out highp vec4 color;
void main()
{
color = vec4(0.0, 0.0, 1.0, 0.5);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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
View 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
View 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);

View File

@@ -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", &params.x, &params.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", &params.w, &params.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
View 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
View 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
View 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
View 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

View File

@@ -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
View 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");
}
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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;
}
}

View File

@@ -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;
}

View File

@@ -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})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View 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;
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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();

View File

@@ -72,3 +72,8 @@ const char * os_getDataPath(void)
{
return app.dataPath;
}
bool os_blockScreensaver()
{
return false;
}

View File

@@ -32,6 +32,8 @@ target_link_libraries(platform_Windows
userenv
wtsapi32
psapi
shlwapi
powrprof
)
target_include_directories(platform_Windows

View File

@@ -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