Compare commits

..

1303 Commits
a9 ... B3-next

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
Geoffrey McRae
185c7764ba [client] spice: always show the pointer if using input:captureOnly 2021-01-21 17:36:22 +11:00
Geoffrey McRae
4113294d30 [client] spice: fix failure to ungrab the kb with captureOnly 2021-01-21 17:32:44 +11:00
Geoffrey McRae
aa92a7a90d [client] app: fix error: ‘dataSize’ may be used uninitialized (take 2) 2021-01-21 17:25:53 +11:00
Geoffrey McRae
c83243f22c [client] app: fix error: ‘dataSize’ may be used uninitialized 2021-01-21 17:21:24 +11:00
Geoffrey McRae
04774d9cd6 [host] fix faults caused by improper startup/shudown/restart ordering 2021-01-21 17:05:30 +11:00
Geoffrey McRae
6b8161972d [host] nvfbc: prevent possible double free 2021-01-21 16:28:20 +11:00
Geoffrey McRae
9965a4a3a6 [host] app: prevent double call to stopThreads 2021-01-21 16:27:57 +11:00
Geoffrey McRae
98ea8b0bb8 [host] nvfbc: remove invalid close of the HMONITOR handle 2021-01-21 16:17:24 +11:00
Geoffrey McRae
23e883f60f Revert "[client] sdl: move SDL specific screensaver inhibit"
This reverts commit afb0146d33.
Additional handling is required to implement this properly, postpone
this for Beta 4
2021-01-21 15:58:37 +11:00
Geoffrey McRae
8778827a42 [host] fix invalid LGMP free of cursor memory 2021-01-21 15:53:06 +11:00
Geoffrey McRae
536df254e0 [host] fix the return code for the platform specific init 2021-01-21 15:44:19 +11:00
Geoffrey McRae
dcd0cb7d8e [client] spice: release the mouse if the host stops 2021-01-21 15:39:15 +11:00
Geoffrey McRae
ef4df571f0 [host] use posix compatible exit codes (signed char) 2021-01-21 15:24:02 +11:00
Geoffrey McRae
e926bad759 [host] dont overwrite the exit code on shutdown 2021-01-21 15:14:50 +11:00
Geoffrey McRae
ad9e84eaaa [host] return a proper exit code 2021-01-21 15:07:19 +11:00
Geoffrey McRae
29ea8ecf6b [client] app: if spice is not in use, hide the local cursor (fixes #415) 2021-01-21 14:17:31 +11:00
Geoffrey McRae
afb0146d33 [client] sdl: move SDL specific screensaver inhibit out of main.c 2021-01-21 14:03:05 +11:00
Quantum
3385438095 [client] wm: use correct logic for screensaver inhibition
Namely, we should inhibitIdle if noScreensaver is true, not the other way
around.
2021-01-21 12:14:38 +11:00
Quantum
ffa72c7992 [host] nvfbc: force composition to capture some full screen apps
NvFBC is unable to capture certain applications that bypasses the DWM
compositor, for example, Firefox playing video in full screen. This
has been a known issue for a long time with Nvidia's ShadowPlay, see:
* https://www.nvidia.com/en-us/geforce/forums/geforce-experience/14/233709/
* https://crbug.com/609857

Nvidia won't fix this, but there are workarounds. For example, we
create a transparent 1x1 layered window, which forces desktop composition
to be enabled.

Note that SetLayeredWindowAttributes also supports alpha-based transparency,
but setting transparency to 0 will cause DWM to skip composition. We could
use a transparency of 1, but this ruins the image by the slightest bit,
which is unacceptable. Therefore, we must use chroma key-based
transparency, which tricks DWM into compositing despite being fully
transparent.
2021-01-21 12:14:03 +11:00
Geoffrey McRae
428b498cca [common] fix invalid read from unaligned addresses (fixes #410) 2021-01-20 23:18:46 +11:00
Quantum
6077dcc123 [client] spice/wayland: fix jitter when moving the cursor slowly
It does not make sense to accumulate fractional error in non-capture mode
as you know exactly where the cursor is supposed to be, at least on Wayland.

On Wayland, we base movements on the current guest position and desired
target position, and the accumulated errors only skew our movements.
2021-01-20 22:54:08 +11:00
Geoffrey McRae
115c226113 [github] update build workflow to update apt, and update deps 2021-01-20 22:51:00 +11:00
Geoffrey McRae
e758f88519 [client] spice: fix failure to align and grab the pointer 2021-01-20 22:45:48 +11:00
Geoffrey McRae
3bccd9c45e [client] spice: input:captureOnly shouldn't capture the pointer at start 2021-01-20 22:16:03 +11:00
Geoffrey McRae
947ba9bfe3 [client] spice: fix input:captureOnly support 2021-01-20 22:12:39 +11:00
Geoffrey McRae
4ca4fd35ad [client] doc: added win:rotate and keybind to README.md 2021-01-20 15:37:23 +11:00
Geoffrey McRae
8fa2b5f368 [client] config: make the help text for winRotate more descriptive 2021-01-20 15:35:13 +11:00
Geoffrey McRae
8cb0cbb91d [client] doc: update README.md with new libraries and backend config 2021-01-20 15:33:56 +11:00
Geoffrey McRae
d6f39d66bf [client] x11: xi is now a required library for the x11 backend 2021-01-20 15:33:34 +11:00
Geoffrey McRae
ab79dae0b8 [client] cmake: fix typo in cmake configuration 2021-01-20 15:33:10 +11:00
Geoffrey McRae
ee8c883201 [client] x11: implement screensaver (un)inhibit 2021-01-20 15:24:10 +11:00
Geoffrey McRae
d1043e590a [client] x11: register for absolute motion events while grabbed 2021-01-20 05:51:18 +11:00
Geoffrey McRae
5789a7efc0 [client] x11: fix failure to process the correct focus events 2021-01-20 05:41:33 +11:00
Geoffrey McRae
f883c630f6 [client] spice: set inView false if the focus is lost
Failure to do this results in loss of input on X11 as we need to be sure
the cursor is ungrabbed.
2021-01-20 05:07:55 +11:00
Geoffrey McRae
ac3333c0d2 [client] x11: switch to using XInput2 for all events and warp
On platforms like KDE, mixing X core events with XInput2 events causes
issues, so just switch entirely over to XInput2.
2021-01-20 04:53:43 +11:00
Geoffrey McRae
da65c47245 [client] spice: fix transposed cursor scale calculations 2021-01-20 04:53:43 +11:00
Xiretza
31ea93dd0d [client] Fix compiler warnings about potentially uninitialized variables
Build failed with _FORTIFY_SOURCE enabled because the compiler couldn't
ensure the switch statements didn't hit the default arm and thus wouldn't
define the variables. Adding a statically failing assert makes sure that
all code paths either define the variables or fail early.

$ cd client
$ env CFLAGS='-O1 -D_FORTIFY_SOURCE=1' cmake -B build/
$ make -C build
[...]
client/renderers/EGL/egl.c: In function ‘egl_calc_mouse_size’:
client/renderers/EGL/egl.c:299:36: error: ‘h’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
  299 |         (this->mouseHeight * (1.0f / h)) * this->scaleY
      |                              ~~~~~~^~~~
2021-01-20 03:05:30 +11:00
Geoffrey McRae
6d162cf92d [client] spice: remove useless check, the displayserver should do this 2021-01-20 01:34:52 +11:00
Geoffrey McRae
8f07744c98 [client] don't hide the cursor unconditionally at startup 2021-01-20 01:27:38 +11:00
Geoffrey McRae
dacc573650 [client] spice: align the guest pointer to local when entering the view 2021-01-20 01:24:31 +11:00
Geoffrey McRae
555891face [client] x11: filter out grab/ungrab focus/leave events 2021-01-20 01:21:19 +11:00
Geoffrey McRae
8e604667f9 [client] fix regression causing failure to warp when exiting the window 2021-01-20 00:52:33 +11:00
Geoffrey McRae
3774d2bfe9 [client] realign the pointer when input is re-enabled 2021-01-20 00:30:04 +11:00
Geoffrey McRae
31eafee468 [client] always show the cursor when not using spice (fixes #409) 2021-01-20 00:28:27 +11:00
Geoffrey McRae
2bfcfa36df [client] all: fix regression with input disable toggle 2021-01-20 00:23:24 +11:00
Geoffrey McRae
819562d906 [client] fix keybind regression for EGL 2021-01-19 21:12:20 +11:00
Geoffrey McRae
0c8ce9daba [client] x11: filter out duplicate button press events 2021-01-19 21:06:30 +11:00
Geoffrey McRae
c667322f25 [client] x11: cosmetic, rename device to raw for raw events 2021-01-19 20:47:25 +11:00
Geoffrey McRae
07c13a9d43 [client] x11: fix regression, raw event's dont give us the cursor pos 2021-01-19 20:46:12 +11:00
Geoffrey McRae
0bd1bb5075 [client] x11: removed left behind ifdef for XINPUT 2021-01-19 20:37:15 +11:00
Geoffrey McRae
f9faa0542b [client] x11: use raw keyboard and mouse button press events 2021-01-19 20:36:43 +11:00
Geoffrey McRae
b87004c597 [client] app: ignore resize events that don't change the size 2021-01-19 20:15:12 +11:00
Quantum
2f11024db8 [client] displayserver: move screensaver handling into displayservers
This also makes Wayland idle inhibition respect noScreensaver setting.
2021-01-19 19:58:11 +11:00
Tudor Brindus
44a949f5c6 [client] wayland: inhibit idle while Looking Glass has keyboard focus 2021-01-19 07:03:50 +11:00
Tudor Brindus
bf5602b062 [cmake] wayland: build idle-inhibit-unstable-v1 header 2021-01-19 07:03:50 +11:00
Geoffrey McRae
6005006dd4 [client] dont show the pointer at launch unless it really should be 2021-01-19 06:00:59 +11:00
Geoffrey McRae
b2ac2980d5 [client] don't attempt to align to the guest with invalid pos data 2021-01-19 05:56:40 +11:00
Geoffrey McRae
1da24af6ee [client] mouse: do not warp the cursor if it's outside the window 2021-01-19 05:38:25 +11:00
Geoffrey McRae
16f88a5285 [client] x11: don't care if SDL has XInput support anymore
As the X11 code is all self contained now and can be disabled at
configure time there is no longer any need to check if SDL has X11
XInput support.
2021-01-19 05:30:25 +11:00
Geoffrey McRae
85ee6737d5 [client] x11: implement keyboard event handing via xinput 2021-01-19 05:01:19 +11:00
Geoffrey McRae
dfe327301d [client] spice: do not allow the inView to be set if mouse buttons held
When using the meta resize feautre the cursor is over the client window,
and as such the application continues to receive motion events. This
causes the window size to spaz out.
2021-01-19 04:40:31 +11:00
Geoffrey McRae
b92e547d91 [client] egl: force the use of nearest if needed
As the screen output rotation can be changed on the fly, if it has been
rotated to 90 or 270 the nearest flag will be incorrect, so we perform
this check here and override the provided value.
2021-01-19 04:26:59 +11:00
Geoffrey McRae
083deff489 [client] add keybind <ScrollLock+R> to rotate the display at runtime 2021-01-19 03:49:09 +11:00
Geoffrey McRae
0451ec237e [client] spice: add not subtract to the rotation 2021-01-19 03:44:36 +11:00
Geoffrey McRae
d2458ff5d3 [client] retain backwards compatibillity for the capture keycode
This is an ugly hack for now that will get us over the line for Beta 3,
after which will need to be addressed and people will need to be
informed that their configured escape key will have changed.
2021-01-19 03:08:56 +11:00
Geoffrey McRae
8a1578230f [client] all: properly support guest rotation
If the guest has it's output rotated (ie, landscape) we must rotate and
translate the pointer draw location, as well as all the translations of
cursor coordinate spaces based on the rotation, along with any local
rotations that may also be applied.
2021-01-19 02:54:56 +11:00
Geoffrey McRae
733bbf5153 [client] app: rename up,right,down,left rotation to 0,90,180,270 2021-01-19 02:53:47 +11:00
Quantum
ff1dc32efe [client] spice/wayland: fix capture mode relative movement
It appears that Wayland pointer motion handlers are called even when relative
mouse mode is enabled. The events they generate break first-person games.
This commit disables those handlers when relative mouse is enabled.
2021-01-19 02:50:24 +11:00
Tudor Brindus
3935acf8a5 [client] add a SDL2 to uapi scancode table 2021-01-19 02:49:51 +11:00
Tudor Brindus
04908c3290 [client] add a uapi to PS/2 scancode table
This table was codegen'd from uapi/linux/input-event-codes.h and SDL's
events/scancodes_linux.h.
2021-01-19 02:49:51 +11:00
Tudor Brindus
7ae487057f [client] wayland: implement key handling 2021-01-19 02:49:51 +11:00
Tudor Brindus
1f943fbbab [client] use uapi keybindings internally
This commit moves SDL to uapi mapping to the SDL backend.
2021-01-19 02:49:51 +11:00
Tudor Brindus
36b70779b9 [client] app: move SDL key event handling out of main.c 2021-01-19 02:49:51 +11:00
Tudor Brindus
7c9b273f70 [client] app: add and expose app_handleKeyPress and app_handleKeyRelease 2021-01-19 02:49:51 +11:00
Geoffrey McRae
cac454d9cf [host] dxgi: reverse the rotation angle.
This is undocumented however testing yields that DXGI DD reports the
inverse rotation. Research shows that this is because of a difference in
coordiate spaces.

Ref: https://docs.microsoft.com/en-us/windows/uwp/gaming/supporting-screen-rotation-directx-and-cpp
2021-01-18 15:15:36 +11:00
Geoffrey McRae
7355c196ba [client] remove YUV420 support 2021-01-18 13:56:44 +11:00
Geoffrey McRae
14cc57071c [host] remove the remainder of the YUV420 support 2021-01-18 13:55:44 +11:00
Geoffrey McRae
f5587b6b6b [host] all: pass back the desktop rotation to the client 2021-01-18 13:53:29 +11:00
Tudor Brindus
842bb59955 [client] wayland: do not fall back on SDL for scroll events 2021-01-17 17:58:32 +11:00
Tudor Brindus
e7132f757e [client] wayland: do not fall back on SDL for button events 2021-01-17 17:03:24 +11:00
Quantum
d77da1ffc7 [client] wayland: clip desired guest cursor position
This avoids putting internal coordinates out of screen and causing cursor
spasms while dragging beyond the edge.
2021-01-17 16:31:33 +11:00
Geoffrey McRae
d22124519e [client] mouse: dont leave the window if any mouse buttons are held 2021-01-17 15:13:45 +11:00
Geoffrey McRae
bab7eba7f0 [client] x11: prevent default mouse button/wheel handling by SDL 2021-01-17 14:38:31 +11:00
Geoffrey McRae
41a0cfe516 [client] app: move SDL mouse event handling out of main.c 2021-01-17 14:28:50 +11:00
Geoffrey McRae
a1069ddffa [client] x11: use XInput for button events if possible 2021-01-17 14:24:42 +11:00
Geoffrey McRae
e106f2096b [client] app: add and expose new app_handleButtonPress/Release methods 2021-01-17 14:23:51 +11:00
Geoffrey McRae
570abeda52 [client] app: move SDL window event handing out of main.c 2021-01-17 13:42:52 +11:00
Geoffrey McRae
31c42e3676 [client] app: add new app_handleCloseEvent for displayserver backends 2021-01-17 13:42:52 +11:00
Geoffrey McRae
89a6686349 [client] app: always call the default eventFilter for unhandled events
This change allows much of the SDL code to be moved into the SDL
displayserver implementation.
2021-01-17 13:42:52 +11:00
Geoffrey McRae
076ed1180f [client] app: expose app_handleFocusEvent to displayservers 2021-01-17 13:42:52 +11:00
Quantum
95e1b48f83 [host] windows: make mousehook.c work on secure desktop
Basically, this creates a separate thread for the mouse events, and this
thread detects that the desktop has changed (say to the secure desktop),
and unhooks, switches to the new desktop, and then rehooks.

This allows the cursor location to be updated while using NvFBC on secure
desktop and the login screen.
2021-01-17 13:23:29 +11:00
Geoffrey McRae
e23d536af5 [client] mouse: correct issues with cursor alignment on enter/exit/focus 2021-01-17 12:53:55 +11:00
Geoffrey McRae
76182bbeb8 [client] mouse: do not send mouse updates to the guest if not focused 2021-01-17 10:44:30 +11:00
Tudor Brindus
f86800cc3a [client] mouse: process mouse up event even if not in view
Unless the corresponding mouse down event was on our surface, we should
not be receiving the mouse up.

This is always the case on Wayland. On some other platforms,
SDL_CaptureMouse can be used to obtain input that happens outside the
Looking Glass surface, but Looking Glass does not make use of that
function.

We may want to process a mouse up if the corresponding mouse down
initiated a drag (e.g., of a window) that was released slightly outside
of the Looking Glass surface. Previously, Looking Glass would ignore the
mouse up, and the guest would be confused into thinking the button had
never been released, not ending the drag.
2021-01-17 10:39:13 +11:00
Tudor Brindus
c69b86ab6e [client] mouse: remove erroneous guard for setCursorInView
This seems like a leftover from ef678ba, but the guard already exists in
setCursorInView itself.
2021-01-17 10:37:54 +11:00
Tudor Brindus
f1033fa4bb [client] rename warpMouse to warpPointer
This is more truthful in what it does; the pointer may be backed by a
non-mouse device.
2021-01-17 10:19:22 +11:00
Tudor Brindus
d926319230 [client] wayland: be explicit about lack of warp support
Falling back on SDL is misleading, since SDL will bail out as well.
2021-01-17 10:19:22 +11:00
Tudor Brindus
56c80a15e6 [client] wayland: gracefully degrade when protocols are unsupported
zwp_relative_pointer_manager_v1 and zwp_pointer_constraints_v1 are
supported by GNOME/KDE/sway (and most other compositors), but they are
not a required part of the protocol.

Some users also run software in one-off nested compositors like cage[0]
for an extra layer of isolation; cage, at least, does not support
pointer captures.

This commit makes Looking Glass warn when an optional protocol is
unsupported, and fail if a required one is missing. Pointer grab paths
have a new guard against the aforementioned protocols being missing.

[0]: https://github.com/Hjdskes/cage
2021-01-17 10:19:22 +11:00
Geoffrey McRae
2a69a19dbd [client] mouse: don't smooth large cursor jumps (ie, warp to align) 2021-01-17 03:12:02 +11:00
Quantum
fe835b98d5 [host] windows: sleep for 1 second instead of 1 millisecond
This is definitely supposed to sleep for 1 second.
1 ms is basically no throttling.
2021-01-17 02:58:27 +11:00
Quantum
c5c43d99f3 [host] windows: allow capture of login screen
WTSGetActiveConsoleSessionId will return a session even if it's not logged in,
unlike our old GetInteractiveSessionID function. Launching looking glass on
such a console session will allow the login screen to be captured.

Note that WTSGetActiveConsoleSessionId() will return 0xFFFFFFFF if there are
no sessions attached.
2021-01-17 02:56:29 +11:00
Geoffrey McRae
2738e822a4 [client] egl: remove external dependency on libdrm 2021-01-16 22:11:15 +11:00
Quantum
078a151e4f [client] wayland: update absolute mouse position
We are actually getting mouse events directly from Wayland instead of going
through SDL, so we call app_updateCursorPos in pointer motion handlers and
swallow the SDL event.

Also removed parameters for app_handleMouseBasic as it relies exclusively on
absolute positions provided by app_updateCursorPos. Wayland does not give
you relative movements at all unless grabbed and passing absolute movements
is semantically incorrect.

Note that when the cursor is grabbed, movements are handled entirely through
relativePointerMotionHandler in wayland.c and does not go through
app_handleMouseBasic at all.
2021-01-16 21:40:24 +11:00
Geoffrey McRae
0690eacc9b [client] mouse: prevent processing out of capture mode with invalid data
If the guest cursor state & position is unknown we can not rely on the
information to detect edge crossings. As such only allow cursor input if
LG is operating in capture mode.
2021-01-16 21:37:43 +11:00
Geoffrey McRae
165c2c3566 [client] mouse: grab the pointer when entering capture mode if needed
If the platform doesn't support warp then the move handler will never
grab the pointer, as such we need to unconditionally grab it in
`setGrabQuiet`
2021-01-16 21:32:38 +11:00
Geoffrey McRae
bb37a880f0 [client] wayland: report that wayland has no warp support 2021-01-16 21:19:20 +11:00
Geoffrey McRae
701dfd5694 [client] mouse: do not grab the pointer if the platform has no warp
Platforms such as Wayland have no abillity to warp the cursor, as such
can not operate in an always relative mode. This property allows
platforms to report the lack of warp support and prevent LG from
grabbing the pointer.
2021-01-16 21:17:35 +11:00
Geoffrey McRae
cebc728a67 [client] mouse: centralize the cursor view state management 2021-01-16 21:01:15 +11:00
Geoffrey McRae
ca5fc80af5 [client] wayland: fix early return preventing signal being set 2021-01-16 20:45:29 +11:00
Geoffrey McRae
9f74bb785e [client] wayland: move wayland early init code out of main.c 2021-01-16 20:41:13 +11:00
Geoffrey McRae
bad25c409c [client] displayserver: add new earlyInit to the interface
Some platforms such as Wayland need to set environment vairables before
SDL is initialized, as such this change detects the display server
before SDL has started and calls the new `earlyInit` method providing
the implementation an opportunity to set things up.
2021-01-16 20:41:13 +11:00
Geoffrey McRae
ef678bab1d [client] wayland: move wayland specific mouse code out of main.c 2021-01-16 20:41:13 +11:00
Geoffrey McRae
a4a042e90d [client] mouse: force the cursor to the in view state when capturing
When capture mode is set if the cursor is not already in the view area
we need to force it to the state it would be if it were in view as
capture mode overrides all.
2021-01-16 20:41:13 +11:00
Geoffrey McRae
f18f07deaf [client] mouse: window enter should not enable drawing the cursor
The pointer may not yet be in the view area so we should defer drawing
it until the mouse move handler determines that it's inside the view
area and turn it on itself.
2021-01-16 20:41:13 +11:00
Geoffrey McRae
dc3d07302f [client] mouse: stop setGrabQuiet from grabbing/ungrabbing the pointer
As LG always operates in relitive mode, the actual pointer grab/ungrab
is managed by the move handler, as such setGrabQuiet should not alter
the grab/ungrab state of the local pointer.
2021-01-16 20:41:13 +11:00
Quantum
48f002992a [client] wayland: fix mouse logic after refactor
Now correctly set inView to only be within the guest and not the letterboxed
areas. Also show the system cursor in the letterboxed area.
2021-01-16 20:41:13 +11:00
Geoffrey McRae
27a38294ea [client] major restructure of platform specific code 2021-01-16 20:41:13 +11:00
Quantum
062c4e32cb [host] windows: test native build on GitHub Actions 2021-01-16 11:48:36 +11:00
Quantum
4858bb5899 [host] windows: avoid quoting issues with CreateProcessAsUserA
To quote MSDN documentation:

> The lpApplicationName parameter can be NULL, in which case the executable
> name must be the first white space–delimited string in lpCommandLine. If
> the executable or path name has a space in it, there is a risk that a
> different executable could be run because of the way the function parses
> spaces. The following example is dangerous because the function will
> attempt to run "Program.exe", if it exists, instead of "MyApp.exe".
>
>   LPTSTR szCmdline[] = _tcsdup(TEXT("C:\\Program Files\\MyApp"));
>   CreateProcessAsUser(hToken, NULL, szCmdline, /*...*/ );
>
> If a malicious user were to create an application called "Program.exe" on
> a system, any program that incorrectly calls CreateProcessAsUser using the
> Program Files directory will run this application instead of the intended
> application.
>
> To avoid this problem, do not pass NULL for lpApplicationName.

So instead, we pass the executable to lpApplicationName instead, which avoids
the issue. MSDN says:

> The lpCommandLine parameter can be NULL. In that case, the function uses
> the string pointed to by lpApplicationName as the command line.

This also avoids the strdup since lpApplicationName is LPCSTR unlike
lpCommandLine which is LPSTR.
2021-01-16 11:48:03 +11:00
Tudor Brindus
c67bacbf5b [obs] build with both GCC and Clang in Github Actions 2021-01-16 11:47:29 +11:00
Tudor Brindus
a20930e5b6 [client] build with both GCC and Clang in Github Actions
This will let us catch issues that only one compiler wouldn't.
2021-01-16 11:47:29 +11:00
Quantum
8f27789d25 [host] windows: close handle to token in enablePriv
This should eliminate all handle leaks resulting from killing the host.
2021-01-15 20:44:50 +11:00
Quantum
e401513552 [host] windows: add timestamps to service logs
This makes it easier to identify when things in the logs happened.
2021-01-15 20:44:38 +11:00
Quantum
81561a242f [host] windows: remove ImpersonateLoggedOnUser call
It shouldn't have any effect, since the host application is created with
the token, and there is no need for the service itself to impersonate.

In practice, removal doesn't appear to have any effect on the ability to
capture privileged things like secure desktop.
2021-01-15 20:44:25 +11:00
Quantum
789f21ccb3 [host] windows: handle defined exit codes in service
The service should now react to host application exit codes.
For the exit codes that demands it, the service will exit instead of
restarting the host.
2021-01-15 20:44:13 +11:00
Geoffrey McRae
cbeae46c0b [client] egl: move rotation into the fragment shader 2021-01-15 13:13:39 +11:00
Geoffrey McRae
72c86d7125 [client] all: add screen rotation support win:rotate
Currently only supports EGL, if there is enough demand for OpenGL
support this can be added later.

Closes #231
2021-01-15 12:42:16 +11:00
Geoffrey McRae
c40a81ddf4 [client] egl: remove no longer used yuv shader 2021-01-15 12:42:16 +11:00
Quantum
323aab8ec2 [host] windows: improve restart logic and remove mutex hack
Use the process handle returned by CreateProcessAsUserA to wait on the
process. This results in faster response times and less polling.
For example, it now restarts instantly when UAC is activated.

This also removes the call to OpenProcess and rendering the mutex unnecessary.

As a bonus, it should fix #298.
2021-01-15 11:43:23 +11:00
Quantum
22920acc88 [host] windows: define exit codes for future use
The host process will be changed to return these codes, from which the
service process could decide whether to exit or restart the process and log.

Note that on Windows, return values are 32-bit unlike POSIX which is only 8.
2021-01-15 11:27:02 +11:00
Quantum
65009dcedc [host] windows: avoid leaking process and thread handles
The handles in PROCESS_INFORMATION must be closed if not used, or they
will leak.
2021-01-15 09:49:42 +11:00
Geoffrey McRae
25d6d88adb [client] minor nit: compare double to double const 2021-01-15 08:50:27 +11:00
Quantum
8217d5efa5 [obs] build pull requests and master with GitHub Actions 2021-01-15 08:49:30 +11:00
Quantum
d670913fd2 [host] windows: build pull requests and master with GitHub Actions 2021-01-15 08:49:30 +11:00
Quantum
afa277f8ee [common] ivshmem/linux: add stubs for ivshmemInit and ivshmemFree
These two functions were added in 9ff1859dc1
for Windows, but were never used on Linux.

Adding stubs will allow the host to compile on Linux.
These should be fixed later.
2021-01-15 08:49:30 +11:00
Quantum
a55b5e4b06 [host] linux: link with libX11 and libGL 2021-01-15 08:49:30 +11:00
Quantum
e467db64d8 [host] linux: include missing headers
- <pwd.h> for getpwuid
- <unistd.h> for getuid
- "common/stringutils.h" for alloc_sprintf
2021-01-15 08:49:30 +11:00
Quantum
dd2d69fa37 [host] xcb: remove undefined getPointer member from Capture_XCB 2021-01-15 08:49:30 +11:00
Quantum
ed9e3d253b [host] xcb: fix xcb_create signature error
Should have two arguments instead of zero.
2021-01-15 08:49:30 +11:00
Quantum
2723b4b7c0 [host] xcb: fix xcb_getFrame compile error
The argument should have been a pointer.
2021-01-15 08:49:30 +11:00
Quantum
69a4dffddc [host] linux: build pull requests and master with GitHub Actions 2021-01-15 08:49:30 +11:00
Quantum
ec69ae261f [module] build pull requests and master with GitHub Actions 2021-01-15 08:49:30 +11:00
Quantum
b8178b3e7d [client] build pull requests and master with GitHub Actions 2021-01-15 08:49:30 +11:00
Geoffrey McRae
bfc7b43758 [all] updated LGMP and PureSpice submodules 2021-01-15 08:49:05 +11:00
Quantum
78cb65a6a4 [client] spice: correctly use fabs for floating point
The prototype for abs is int abs (int n), which implicitly casts floating
point values to integers. The correct function is fabs.

This commit allows the client to compile under clang.
2021-01-15 08:48:15 +11:00
Quantum
369c49cdcf [client] render/opengl: remove braces so fallthrough comment is recognized
gcc -Wimplicit-fallthrough only detects comments if they are immediately
preceded before the next label. Braces stops it from recognizing the
fallthrough comment.
2021-01-15 08:06:56 +11:00
Quantum
5f20ee46a8 [client] spice: remove suprious const on function return type
const in the return type does nothing and triggers -Wignored-qualifiers.
2021-01-15 08:06:56 +11:00
Quantum
0495f5de26 [client] enable useful extra warnings
-Wno-sign-compare is used to suppress warnings related to comparing signed
values with unsigned ones. It's too pedantic.

-Wunused-parameter is also too pedantic, especially since all parameters
have to be named in C.

Otherwise, -Wextra lets us catch bugs, such as x < 0 for unsigned x.

On gcc, we pass -Wimplicit-fallthrough=2 so it will recognize our fall
through comment.
2021-01-15 08:06:56 +11:00
Tudor Brindus
5538a31f6b [client] add support for compiling with UndefinedBehaviorSanitizer 2021-01-14 17:48:28 +11:00
Tudor Brindus
a46a3a2668 [all] use explicit void parameter lists
This makes it a compile-time error to call a function that semantically
takes no parameters with a nonzero number of arguments.

Previously, such code would still compile, but risk blowing up the stack
if a compiler chose to use something other than caller-cleanup calling
conventions.
2021-01-14 17:29:37 +11:00
Tudor Brindus
dc17492750 [client] clipboard/wayland: make DnD a no-op
Wayland requires us to set all listeners, even for events we don't care
about. Not doing so caused Looking Glass to abort when used as a drop
target.
2021-01-14 17:09:54 +11:00
Quantum
17691f889b [client] clipboard/wayland: avoid writing back to guest clipboard
Copying rich text from the guest would be turned into plaintext on the client.
Prior to this change, this would be sent back to the guest, overwriting its
clipboard. This made it impossible to copy rich text inside the guest.

This commit detects such self-copies by checking if the receiver is the
current process, and rejecting it.
2021-01-14 10:41:32 +11:00
Quantum
02421ef269 [client] clipboard/wayland: fix null mimetype receive error
This prevents looking-glass-client from failing with an error message like:

error marshalling arguments for receive (signature sh): null value passed for arg 0
Error marshalling request: Invalid argument
2021-01-14 10:41:08 +11:00
Quantum
50c934db5a [client] wm/wayland: correctly handle double keyboard grab
When input:grabKeyboardOnFocus is set (default), entering capture mode grabs
the keyboard a second time. This commit makes the second grab a no-op on
Wayland to avoid a crash.
2021-01-13 15:53:02 +11:00
Geoffrey McRae
c650690bcc [client] wm: fix duplicate declaration of static struct 2021-01-13 14:00:25 +11:00
Tudor Brindus
fd009c6392 [client] wm/wayland: bypass SDL loop for pointer events 2021-01-13 13:59:00 +11:00
Tudor Brindus
96c10c2c2d [client] clipboard/wayland: make use of boilerplate from wm.c 2021-01-13 13:39:32 +11:00
Tudor Brindus
2aa2ec31ef [client] clipboard/wayland: address style nits 2021-01-13 13:39:32 +11:00
Tudor Brindus
bf223158d0 [client] clipboard: try each clipboard until one initializes
Previously, main.c would segfault at runtime if clipboards were disabled
via cmake flags, as the clipboards array would be empty but still
indexed during initialization.

Co-authored-by: Quantum <quantum2048@gmail.com>
2021-01-13 13:39:32 +11:00
Tudor Brindus
2627381021 [client] clipboard/wayland: ignore SIGPIPE from clients that hang up
Otherwise, a badly-behaving client causes Looking Glass to receive a
SIGPIPE during Wayland copy operations. Handle EPIPE at call-sites
instead.

Co-authored-by: Quantum <quantum2048@gmail.com>
2021-01-13 13:39:32 +11:00
Tudor Brindus
48941cb9c4 [client] clipboard/wayland: implement better text mimetype heuristic
This roughly matches the heuristic that wl-copy uses to detect text.
2021-01-13 13:39:32 +11:00
Tudor Brindus
fb7ee16f7b [client] clipboard/wayland: support keyboard capability hotplug 2021-01-13 13:39:32 +11:00
Tudor Brindus
c73d50f56a [client] clipboard/wayland: implement host-to-VM transfers
Co-authored-by: Quantum <quantum2048@gmail.com>
2021-01-13 13:39:32 +11:00
Tudor Brindus
2bfd066833 [client] clipboard/wayland: implement VM-to-host image copy
Co-authored-by: Quantum <quantum2048@gmail.com>
2021-01-13 13:39:32 +11:00
Tudor Brindus
c58f33f5ab [client] clipboard/wayland: implement VM-to-host text copy
Co-authored-by: Quantum <quantum2048@gmail.com>
2021-01-13 13:39:32 +11:00
Tudor Brindus
a205515d91 [client] clipboard/wayland: implement a no-op Wayland clipboard
Build scaffolding only; implementation in later commits.

Co-authored-by: Quantum <quantum2048@gmail.com>
2021-01-13 13:39:32 +11:00
Quantum
8f4e0f6b50 [client] spice/wayland: ignore mouse moves when dpiScale is unknown
This prevents division by zero and sending invalid information to spice.
2021-01-13 13:39:10 +11:00
Quantum
95205ca967 [client] spice/wayland: use correct coordinate space
This will now correctly respect the cursor hotspot.
2021-01-13 13:39:10 +11:00
Quantum
ab96f77d9e [client] wm/wayland: generate protocol files into the build dir
This avoids polluting the source tree.
2021-01-12 16:00:12 +11:00
Quantum
1f01eec3a2 [client] spice/wayland: correctly update guest cursor location 2021-01-12 15:59:49 +11:00
Quantum
24a4de6d65 [client] wm/wayload: implement keyboard grabbing 2021-01-12 15:26:42 +11:00
Geoffrey McRae
b58176fcdb [client] removed last remnents of the decoders & parsers 2021-01-12 12:42:06 +11:00
Tudor Brindus
c21f502414 [client] wm/wayland: implement grab mode for capture 2021-01-12 12:36:21 +11:00
Tudor Brindus
1040a7c168 [client] wm/wayland: don't grab keyboard
This is unimplemented for Wayland, and only has the effect of confining
the mouse.
2021-01-12 12:36:21 +11:00
Tudor Brindus
a97332025c [cmake] add option to build Wayland headers 2021-01-12 12:36:21 +11:00
Geoffrey McRae
3a29d1cf03 [client] spice: zero the error accumulators when switching grab modes 2021-01-12 11:49:27 +11:00
Geoffrey McRae
8365419262 [client] spice: use modf in cursorToInt for fractional tracking 2021-01-12 11:48:29 +11:00
Quantum
78bd41716a [client] spice: use simple custom Wayland mouse logic
The normal logic does not work due to Wayland not supporting mouse warp.
We use a simple logic that works for the desktop with 1:1 mouse patch and
require capture mode for all other cases.
2021-01-11 20:57:13 +11:00
Quantum
790c2b39ad [client] spice: set absolute position and focus state on Wayland 2021-01-11 20:57:13 +11:00
Geoffrey McRae
d68d82e5f7 [client] no need to set the hint here anymore as it's set in wm.c now 2021-01-11 00:26:37 +11:00
Tudor Brindus
ceab5f597b [client] wm: specify SDL_FALSE to SDL_SetWindowGrab in wmUngrabPointer 2021-01-11 00:21:23 +11:00
Geoffrey McRae
fc0d82d490 [client] wm: added new platform agnostic wmWarpMouse 2021-01-10 15:47:03 +11:00
Geoffrey McRae
8466e57468 [client] remove grab/ungrab stubs 2021-01-10 15:33:29 +11:00
Geoffrey McRae
76ed75f871 [client] spice: create and use platform agnostic grab/ungrab methods 2021-01-10 15:26:33 +11:00
Tudor Brindus
8982493239 [client] clipboard: fix heap-buffer overflow in clipboardRequest
=================================================================
==7680==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000ec010 at pc 0x5622fcf9f386 bp 0x7f36084ff680 sp 0x7f36084ff678
WRITE of size 4 at 0x6020000ec010 thread T1
    #0 0x5622fcf9f385 in clipboardRequest /code/LookingGlass/client/src/main.c:707
    #1 0x5622fd0036c9 in wayland_cb_notice /code/LookingGlass/client/clipboards/Wayland/src/wayland.c:521
    #2 0x5622fcf9f4dc in spiceClipboardNotice /code/LookingGlass/client/src/main.c:724
    #3 0x5622fcfc4d59 in spice_agent_process /code/LookingGlass/repos/PureSpice/src/spice.c:1106
    #4 0x5622fcfc16d6 in spice_on_main_channel_read /code/LookingGlass/repos/PureSpice/src/spice.c:655
    #5 0x5622fcfbee4f in spice_process /code/LookingGlass/repos/PureSpice/src/spice.c:361
    #6 0x5622fcf9e3a2 in spiceThread /code/LookingGlass/client/src/main.c:598
    #7 0x5622fd006b5e in threadWrapper /code/LookingGlass/common/src/platform/linux/thread.c:39
    #8 0x7f3614b2bf26 in start_thread /build/glibc-WZtAaN/glibc-2.30/nptl/pthread_create.c:479
    #9 0x7f3614a4c2ee in __clone (/lib/x86_64-linux-gnu/libc.so.6+0xfd2ee)

0x6020000ec011 is located 0 bytes to the right of 1-byte region [0x6020000ec010,0x6020000ec011)
allocated by thread T1 here:
    #0 0x7f36156f9628 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x107628)
    #1 0x5622fcf9f33f in clipboardRequest /code/LookingGlass/client/src/main.c:705
    #2 0x5622fd0036c9 in wayland_cb_notice /code/LookingGlass/client/clipboards/Wayland/src/wayland.c:521
    #3 0x5622fcf9f4dc in spiceClipboardNotice /code/LookingGlass/client/src/main.c:724
    #4 0x5622fcfc4d59 in spice_agent_process /code/LookingGlass/repos/PureSpice/src/spice.c:1106
    #5 0x5622fcfc16d6 in spice_on_main_channel_read /code/LookingGlass/repos/PureSpice/src/spice.c:655
    #6 0x5622fcfbee4f in spice_process /code/LookingGlass/repos/PureSpice/src/spice.c:361
    #7 0x5622fcf9e3a2 in spiceThread /code/LookingGlass/client/src/main.c:598
    #8 0x5622fd006b5e in threadWrapper /code/LookingGlass/common/src/platform/linux/thread.c:39
    #9 0x7f3614b2bf26 in start_thread /build/glibc-WZtAaN/glibc-2.30/nptl/pthread_create.c:479

Thread T1 created by T0 here:
    #0 0x7f361562b9b2 in pthread_create (/lib/x86_64-linux-gnu/libasan.so.5+0x399b2)
    #1 0x5622fd006cd0 in lgCreateThread /code/LookingGlass/common/src/platform/linux/thread.c:50
    #2 0x5622fcfa5a7d in lg_run /code/LookingGlass/client/src/main.c:1615
    #3 0x5622fcface28 in main /code/LookingGlass/client/src/main.c:2035
    #4 0x7f3614975e0a in __libc_start_main ../csu/libc-start.c:308

SUMMARY: AddressSanitizer: heap-buffer-overflow /code/LookingGlass/client/src/main.c:707 in clipboardRequest
Shadow bytes around the buggy address:
  0x0c04800157b0: fa fa 00 00 fa fa fd fa fa fa fd fa fa fa fd fd
  0x0c04800157c0: fa fa fd fd fa fa fd fa fa fa 00 fa fa fa 00 fa
  0x0c04800157d0: fa fa 00 fa fa fa fd fa fa fa fd fd fa fa fa fa
  0x0c04800157e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c04800157f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c0480015800: fa fa[01]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480015810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480015820: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480015830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480015840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0480015850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==7680==ABORTING
2021-01-10 14:52:58 +11:00
Quantum
4051cc6f93 [client] spice: fix cursor position mismatch with UI scaling 2021-01-10 13:51:20 +11:00
Netboy3
1727c7726b [client] fix screensaver enable
The hint "SDL_HINT_VIDEO_ALLOW_SCREENSAVER" only works if set
before SDL_Init(). Move it to the proper location.
2021-01-10 13:43:57 +11:00
Quantum
0b890ed1ac [client] add support for compiling with AddressSanitizer 2021-01-10 13:42:58 +11:00
Geoffrey McRae
fa1deafd58 Revert "[client] spice: better x11 grab/ungrab behaviour"
This reverts commit 18f9d936c6.
2021-01-10 01:00:40 +11:00
Geoffrey McRae
176cc394d1 [client] don't show capture message at startup unless it's on 2021-01-10 00:56:34 +11:00
Geoffrey McRae
18f9d936c6 [client] spice: better x11 grab/ungrab behaviour 2021-01-10 00:55:31 +11:00
Geoffrey McRae
4334912e01 [client] egl: actually set the dma texture member 2021-01-09 22:16:40 +11:00
Geoffrey McRae
19db67cfe5 [client] egl: detect the NVIDIA driver and disable DMA support
DMA suport for NVIDIA is advertised as available by the presense of the
extension `EGL_EXT_image_dma_buf_import`, however it is completely
broken. Until this is fixed refuse to use DMA support even if VM->VM
support is possible.

See: https://forums.developer.nvidia.com/t/egl-ext-image-dma-buf-import-broken-egl-bad-alloc-with-tons-of-free-ram/165552
2021-01-09 21:25:41 +11:00
Geoffrey McRae
21ba14f629 [client] spice: fix loss of click/focus on resize 2021-01-09 18:41:02 +11:00
Quantum
c9f41ea69e [client] fix ignoreQuit on Wayland
On Wayland, SDL_WINDOWEVENT_CLOSE is sent even when exiting with keyboard
shortcuts. This meant that the client is still closed even with -Q.

We now swallow SDL_WINDOWEVENT_CLOSE if the cursor is inside the VM. This
should prevent keyboard shortcuts from closing the client, while still
allowing the window to be closed by clicking X with the mouse per #138.
2021-01-09 18:28:43 +11:00
Geoffrey McRae
f47c8cb806 [client] add input:captureOnly to disable input when not captured 2021-01-09 18:01:08 +11:00
Geoffrey McRae
5e9cfb9033 [client] updated README with new option description 2021-01-09 14:56:28 +11:00
Geoffrey McRae
318759f54d [client] spice: allow mouse smoothing in capture but only if not RAW 2021-01-09 14:55:20 +11:00
Geoffrey McRae
1a4ac4c109 [client] updated README with new options 2021-01-09 14:41:48 +11:00
Geoffrey McRae
579be87597 [client] spice: added basic mouse smoothing for non-capture mode
Enabled by default, can be disabled with `input:mouseSmoothing`
2021-01-09 14:39:48 +11:00
Geoffrey McRae
ce96c77098 [client] spice: fixed grabKeyboardOnFocus regression 2021-01-08 23:15:48 +11:00
Geoffrey McRae
1c016ac0cd [client] cursor: ensure cursor is rendered at init and aligned 2021-01-08 23:12:15 +11:00
Geoffrey McRae
86ca1bbbd6 [client] fix issues with cursor not always showing 2021-01-08 21:46:34 +11:00
Geoffrey McRae
3ac178a305 [client] spice: when in autoCapture limit the exit delta
Rapid movements in games can cause large detas that may cause the client
to allow the mouse to exit when this is not desired. This change
attempts to limit this by ignoring movements large movements when using
this mode.
2021-01-08 21:00:38 +11:00
Geoffrey McRae
40c3c38681 [client] refactor struct CursorState to use struct DoublePoint 2021-01-08 20:49:20 +11:00
Geoffrey McRae
d2a4f8f346 [client] spice: dont ignore input that doesn't exit the window 2021-01-08 20:45:11 +11:00
Geoffrey McRae
59ea957d0d [client] spice: don't grab the keyboard input unless we are focused 2021-01-08 20:26:32 +11:00
Geoffrey McRae
f352463d19 [client] spice: obey input:grabKeyboard 2021-01-08 20:13:41 +11:00
Geoffrey McRae
2789e73296 [client] spice: added new input:autoCapture mode
This new mode if enabled (disabled by default) will cause the client to
attempt to hold onto the mouse when a title/game has it captured in the
guest. This is best effort and is not a replacement for full capture
mode.
2021-01-08 19:41:10 +11:00
Geoffrey McRae
6c8eba5f54 [client] spice: rewrite cursor handling code to take advantage of xinput 2021-01-08 15:37:40 +11:00
Geoffrey McRae
4b13e590e1 [client] updated the PureSpice submodule 2021-01-08 08:54:27 +11:00
Geoffrey McRae
6030d2f189 [client] spice: filter out events that have < 2 axis
This prevents the mouse wheel from being treated as a cursor movement
2021-01-08 08:42:19 +11:00
Geoffrey McRae
2788394631 [client] all: use nanosleep instead of usleep for better precision 2021-01-08 08:27:12 +11:00
Geoffrey McRae
b0f2a2e39f [client] spice: flag the cursor as inView unconditionally in capture 2021-01-08 03:27:03 +11:00
Geoffrey McRae
526572c9c9 [client] added new feature input:rawMouse for RAW mouse input
This option allows those that want it (gamers) to bypass all X11 mouse
acceleration and smoothing giving true 1:1 input to the guest while in
capture mode. Note: only supported for X11!
2021-01-08 03:12:42 +11:00
Geoffrey McRae
c99561c2ac [client] spice: filter out duplicate X xinput events 2021-01-08 02:52:47 +11:00
Geoffrey McRae
62f59ce50d [client] spice: don't use the raw data values for raw input
While using the RAW movement data sounds like a good idea the user
experience is quite bad as the mouse acceleration between host and guest
changes entering capture mode. This change alters this behaviour to use
the values after processing by the X server while retaining the simpler
code path for capture mode.
2021-01-08 02:04:30 +11:00
Geoffrey McRae
f85b6418b8 [common] linux: stop event signals accumulating after they are serviced 2021-01-08 01:18:02 +11:00
Geoffrey McRae
fb9cf6cfbc [client] spice: apply sensitivity when in raw capture mode 2021-01-08 01:01:53 +11:00
Geoffrey McRae
974b409e91 [client] spice: x11 use xinput2 raw mode if possible for captured mouse 2021-01-08 00:44:15 +11:00
Geoffrey McRae
27a5a0811b [client] spice: detect end of warp based on serial and x & y match
x11 serials are per server command, not per event, as such several
events may be processed by the server before a new event is received
causing the existing logic to fail. This changes the logic to check for
a larger serial instead of an exact match, and confirms the completion
of the warp by matching the target x/y.
2021-01-08 00:07:43 +11:00
Geoffrey McRae
d6bb518992 [client] spice: refactor g_cursor.last to g_cursor.pos 2021-01-07 02:16:55 +11:00
Geoffrey McRae
026251cfd9 [client] spice: fix reversed warp offset math and incorrect lookbehind 2021-01-07 02:05:47 +11:00
Geoffrey McRae
9b309db964 [client] spice: general improvements to cursor warp logic
Previously only up to two pending warp requests were possible, this
changes this so that additional warps can be queued if the cursor's
delta has moved more then 50px between each warp request.

The old code also had an error where it would null out any additional
movement since the warp was requested, but before it was processed, this
has been corrected.
2021-01-06 22:11:28 +11:00
Geoffrey McRae
271276a0a9 [client] ll: add new method ll_peek_tail 2021-01-06 22:11:10 +11:00
Geoffrey McRae
67022d664f [client] spice: look for x11 warp completion events in key/btn events. 2021-01-06 20:17:52 +11:00
Geoffrey McRae
09e02b0613 [client] spice: prevent the cursor from escaping while captured 2021-01-06 08:35:13 +11:00
Geoffrey McRae
e70f585cfc [client] spice: fix rounding issue causing entry->exit in the same event
This fixes an issue where the warp to center could break as the user
moves their cursor slowly over one of the bottom or right edges of the
screen while it's letterboxed.
2021-01-06 08:34:14 +11:00
Geoffrey McRae
c2ad9666bb [host] use the HotSpot information as provided by DXGI
I must have originally overlooked this member when I wrote this code. :S
2021-01-05 20:55:39 +11:00
Geoffrey McRae
d2d6ecd1c1 [client] spice: always flush XWarpPointer calls 2021-01-05 11:53:42 +11:00
Geoffrey McRae
6f99280fe3 [client] fix the warp logic to account for still pending warps to finish
As X11 is a server/client protocol, issuing commands such as
XWarpPointer do not happen immediately, as such we need to identify when
the warp is complete to know to null out the movement. To do this we
track each warp issued and look for it's completion in the event filter.
As some events come in via XInput2 we need to also make use of this
instead of just relying on MotionNotify, as such the support has been
implemented for XI_Motion events.
2021-01-05 11:47:17 +11:00
Geoffrey McRae
18e84c88a0 [client] ll: fix failure to properly track the list size 2021-01-05 11:42:26 +11:00
Geoffrey McRae
25d370ef22 [client] move new scaling variables into the CursorState struct 2021-01-05 09:48:30 +11:00
Geoffrey McRae
6c12990d26 [client] print out the client version too when there is a mismatch 2021-01-05 09:18:40 +11:00
Geoffrey McRae
12c83e82bb [client] if the host version doesn't match wait for it to be upgraded
This change allows one to upgrade the host application using the LG
spice client instead of immediately terminating
2021-01-05 09:13:54 +11:00
Quantum
a172d79f66 spice: scale mouse input based on host DPI information 2021-01-05 09:03:29 +11:00
Quantum
7e4d323427 get display DPI info to scale mouse movement 2021-01-05 09:03:29 +11:00
Geoffrey McRae
0bd5f0b2f1 [client/host] disable stack execution 2021-01-05 00:01:45 +11:00
Geoffrey McRae
523accf348 [client] spice: don't rely on the cursor position when it's not visible
DXGI DesktopDuplication does not send cursor positional updates when the
cursor is hidden, this happens when dragging a window around or when a
full screen application takes/hides the cursor. If this happened at the
same time as a resolution switch we don't know where the cursor really
is anymore.
2021-01-04 19:34:10 +11:00
Geoffrey McRae
53ae0ea9f1 [client] always update positional information when new format 2021-01-04 19:16:49 +11:00
Geoffrey McRae
4c31cef709 [client] doc: updated the readme with the latest options and bindings 2021-01-04 18:45:54 +11:00
Geoffrey McRae
8fd08cdd79 [client] spice: add option to ignore the windows key
If active this will prevent the client from sending keyboard events for
the windows key. The idea is to allow people to keep the windows key
bound to their WMs default action without causing the Windows start menu
to open
2021-01-04 18:22:39 +11:00
Geoffrey McRae
33b117e732 [client] spice: add new keybinds <ScrLck>+<LWin>, <ScrLck>+<RWin> 2021-01-04 18:07:09 +11:00
Geoffrey McRae
d775ed1ddb [client] cosmetics 2021-01-04 15:52:10 +11:00
Tudor Brindus
d997f0d18c [client] spice: properly handle high-precision scroll wheel input
Some setups (e.g. Wayland) have high precision scroll wheel input, such
that the y-delta on an event may exceed 1. In these cases, scrolling up
currently gets treated as scrolling down.

This commit changes the checks to use > 0 rather than == 1.

This is the approach suggested in
https://wiki.libsdl.org/SDL_MouseWheelEvent.
2021-01-04 15:51:52 +11:00
Quantum
78b1f64a61 [client] require 8 bit colour when creating OpenGL context
For some reason, if we don't ask for 8-bit, wayland will give a context with
lower bit depth, which looks terrible.
2021-01-04 15:51:36 +11:00
Tudor Brindus
1ca5e439c1 [client] egl: disable EGL when running on Wayland
This commit makes Looking Glass always use the OpenGL renderer when
running on Wayland. The EGL renderer is broken on Wayland and can't
reasonably be fixed until SDL is dropped entirely (as per
https://github.com/gnif/LookingGlass/issues/306).

Until that time, the OpenGL renderer provides a much better
Wayland-native experience.
2021-01-04 15:47:01 +11:00
Geoffrey McRae
3b0a98ede2 [client] spice: use xlib directly for warping
This change allows us to look for and filter out the warp completion
event as we can obtain and use the serial number of the warp request to
do so. This is far more elegant then the x/y match that we were doing
prior.
2021-01-04 15:40:23 +11:00
Geoffrey McRae
5d5b7b3d3c [client] spice: just minor refactoring and commenting of code 2021-01-04 15:40:02 +11:00
Geoffrey McRae
3016f0c53e [client] fix invalid method names 2021-01-04 14:44:33 +11:00
Tudor Brindus
4bceaf5505 [client] fix hang in eglSwapBuffers when exiting while not visible
eglSwapBuffers is allowed to block when called with a nonzero interval
parameter. On Wayland, Mesa will block until a frame callback arrives.
If an application is not visible, a compositor is free to not schedule
frame callbacks (in order to save CPU time rendering something that is
entirely invisible).

Currently, starting Looking Glass from a terminal, hiding it
entirely, and sending ^C will cause Looking Glass to hang joining the
render thread until the window is made visible again.

Calling eglDestroySurface is insufficient to unblock eglSwapBuffers, as
it attempts to grab the same underlying mutex.

Instead, this commit makes it so that we pass a 0 interval to
eglSwapBuffers when running on Wayland, such that we don't block waiting
for a frame callback. This is not entirely ideal as it *does* mean
Looking Glass submits buffers while hidden, but it seems better than
hanging on exit.

It also forces opengl:vsync and egl:vsync flags to off when running on
Wayland, as they are meaningless there.
2021-01-04 14:43:21 +11:00
Tudor Brindus
bc2f26b86d [client] set default opengl:vsync=off
This makes it consistent with the EGL renderer.
2021-01-04 14:43:21 +11:00
Tudor Brindus
012ac950ac [client] fix Wayland detection logic
$XDG_SESSION_TYPE is not guaranteed to be "wayland" when running on a
Wayland compositor. However, $WAYLAND_DISPLAY must always exist.
2021-01-04 14:43:21 +11:00
Geoffrey McRae
f7d4efe1c1 [client] spice: don't ignore all enter window events 2021-01-04 14:27:30 +11:00
Geoffrey McRae
1195a76368 [client] video: ensure that the renderer is always updated on resize 2021-01-04 13:40:03 +11:00
Geoffrey McRae
43e3999a95 [client] spice: also grab keyboard when in capture mode 2021-01-04 13:22:54 +11:00
Geoffrey McRae
bfc492421c [client] spice: use XGrabPointer when in grab mode
People using high DPI mice and mouse accleration require this otherwise
the mouse can escape the window before we can warp it back.
2021-01-04 13:18:02 +11:00
Geoffrey McRae
2bc767430c [client] spice: be more aggressive with window warp for high DPI mice 2021-01-04 12:59:14 +11:00
Geoffrey McRae
183d06f90c [client] spice: fix borderless due to another SDL bug
SDL2 reports the window position as 0x0 when in borderless mode until
the window is moved or resized.
2021-01-04 12:28:35 +11:00
Geoffrey McRae
f7d7fbdb73 [client] rename state to g_state 2021-01-04 12:06:54 +11:00
Geoffrey McRae
6878eee40a [client] refactor cursor variables into their own global struct 2021-01-04 12:04:43 +11:00
Geoffrey McRae
9c941239ea [module] bump the version 2021-01-03 23:42:43 +11:00
Geoffrey McRae
1313858889 [module] fix loading on 5.10, new member nr_range must be specified 2021-01-03 23:41:59 +11:00
Geoffrey McRae
c08aa8ece7 [client] spice: correct enter/leave event handling 2021-01-03 23:05:35 +11:00
Geoffrey McRae
5323d9833a [client] spice: fix mouse warp on window focus 2021-01-03 22:59:23 +11:00
Geoffrey McRae
3b580af194 [client] spice: correct oob hotspot 2021-01-03 22:28:06 +11:00
Geoffrey McRae
29a6365107 [client] spice: correct rounding errors when reaching edges 2021-01-03 22:27:48 +11:00
Geoffrey McRae
9f495863cd [client] spice: take into account the render rect for the new x/y pos 2021-01-03 17:42:58 +11:00
Geoffrey McRae
cd06fc251f [client] spice: don't process cursor input when the local cursor is oob 2021-01-03 17:21:04 +11:00
Geoffrey McRae
029640f1b3 [client] spice: don't use SDLs capture mode under X11, we have our own 2021-01-03 16:31:10 +11:00
Geoffrey McRae
5064a4ecdd [client] spice: take the cursor hotspot into account 2021-01-03 15:53:20 +11:00
Michael Golisch
8ae39fd346 [doc] update devicenames in module/README.md 2021-01-01 16:22:30 +11:00
JJRcop
d018781537 [doc] update Looking Glass website in README.md
Replace instances of https://looking-glass.hostfission.com/ with https://looking-glass.io/
2021-01-01 12:12:07 +11:00
Jonathan Rubenstein
393a879c0b [host] nsis: change 'Stopping' to imperative 'Stop'
This follows the rest of the output like Install, Delete
2021-01-01 12:12:07 +11:00
Jonathan Rubenstein
d01d9db9bf [host] nsis: log more details about the process
Uses nsExec:ExecToLog in a few places to log on the installation output
window
2021-01-01 12:12:02 +11:00
Jonathan Rubenstein
fabb5bd4a9 [cmake] remove --long from version.cmake
This means if someone checks out a tagged revision, the extra commit N
and commit hash are removed from the VERSION, leaving just the tag name

Adding any commits will cause -<commitssince>-g<commithash> to return.

The dirty worktree '+' still functions as normal, simply appended to
the end of the tag name, like 'v1.0.3+'. With both extra commits and a
dirty worktree, it will look like 'v1.0.3-2-gd6e00e4f34a+', as usual.
2021-01-01 12:10:35 +11:00
Jonathan Rubenstein
5cc4f5454f [host] nsis: amended installer welcome description 2021-01-01 12:10:23 +11:00
Jonathan Rubenstein
32c797e60a [host] nsis: adds colour to installer and welcome screen 2021-01-01 12:10:15 +11:00
Jonathan Rubenstein
b66715b042 [host] nsisi: add welcome page to installer 2021-01-01 12:10:09 +11:00
Jonathan Rubenstein
57a5488ac2 [cmake] replace git diff logic with git describe --dirty
The --dirty argument of git describe performs the same function as the
now-removed git diff logic.

It will add its argument (default '-dirty') to the end of the revision.

Providing '+' as the argument replicates old behavior.
2021-01-01 12:04:55 +11:00
Jonathan Rubenstein
9f787777b5 [cmake] ensure the version is flagged as dirty in all cases
A developer could have changes in the index which would not add the "+"
symbol.
2021-01-01 12:04:40 +11:00
Geoffrey McRae
bf1eba15d1 [client] egl: actually do front buffer rendering 2020-12-31 15:31:24 +11:00
Geoffrey McRae
1e4e582f67 [client] egl: only use a single texture but multiple PBOs 2020-12-31 14:18:38 +11:00
Geoffrey McRae
678ba0f484 [client] egl: check for EGLImage creation failure 2020-12-31 12:58:40 +11:00
Geoffrey McRae
2c2008c981 [client] egl: improve DMA logic 2020-12-31 12:58:22 +11:00
Geoffrey McRae
38198b1477 [host] dynamically locate CreateProcesssAsUserA for pre-win 10 2020-12-30 18:29:58 +11:00
Geoffrey McRae
5802bfb5eb [client] spice: calculate the entry point delta correctly 2020-12-06 11:07:05 +11:00
Geoffrey McRae
d61d7699e5 [client] spice: stop the cursor skipping the letterboxing 2020-12-06 09:58:50 +11:00
Geoffrey McRae
80d911f040 [client] spice: fix mouse exiting when the window is letterboxed/padded 2020-12-04 20:04:06 +11:00
Geoffrey McRae
1a8dfe1cc0 [client] spice: only check for a valid position if needed 2020-12-04 17:36:08 +11:00
Geoffrey McRae
c0a3b85580 [client] spice: don't disable warp when the future cursor pos is invalid 2020-12-04 17:32:28 +11:00
Geoffrey McRae
265b4544ef [client] spice: adjust ordering to avoid dropping mouse input events 2020-12-04 01:40:29 +11:00
Geoffrey McRae
c5befbba0e [client] spice: don't scale mouse if it's 1:1 2020-12-04 00:50:27 +11:00
Geoffrey McRae
3df23d6b73 [client] reworked the mouse tracking logic 2020-12-04 00:32:28 +11:00
Geoffrey McRae
e57f084c93 [client] added streaming clipboard support for large transfers 2020-12-03 12:01:51 +11:00
Geoffrey McRae
d700e19a32 [client] clipboard: correctly alert for large buffers 2020-12-03 02:35:40 +11:00
aspen
69b8c4b4eb module: fix kernel module compilation on Linux 5.10+
linux@a4574f63 caused the kernel module to fail to compile, due to
changing the dev_pagemap->res field to dev_pagemap->range.

Closes #328
2020-12-03 02:18:59 +11:00
Geoffrey McRae
018dae691a [client] remove left behind debug output 2020-12-03 02:03:47 +11:00
Geoffrey McRae
5c50efd074 [client] update the PureSpice submodule to fix large clipboard bug 2020-12-03 02:01:08 +11:00
Geoffrey McRae
aaf449442a [client] clipboard: refactor for readabillity 2020-12-03 01:58:05 +11:00
Geoffrey McRae
301ba45f0f [client] don't grab keyboard if spice input is disabled 2020-12-01 10:03:20 +11:00
Geoffrey McRae
806ff934b2 [client] egl: detect if egl is even available 2020-11-29 21:43:28 +11:00
Geoffrey McRae
bbcaaccdcc [client] added new option input:grabKeyboardOnFocus 2020-11-29 07:12:19 +11:00
Geoffrey McRae
dea7177d29 [client] added support for spice side/extra buttons
Note, QEMU must have support for these new buttons for this to have any
effect.
2020-11-28 14:59:21 +11:00
Geoffrey McRae
58dd352def [client] set fullscreen after window creation to keep window dimensions 2020-11-28 10:19:17 +11:00
Geoffrey McRae
f36c674791 [client] always set the minimize on focus loss hint 2020-11-28 10:16:11 +11:00
Geoffrey McRae
4a823d0e4f [client] grab the keyboard when the window has focus 2020-11-28 10:06:06 +11:00
jonpas
db51acdd8a [client] add option to always show cursor 2020-11-16 06:48:57 +11:00
Geoffrey McRae
b942085e6c [host] don't allocate LGMP_Q_POINTER_LEN cursor shape buffers
There is no need to allocate a buffer for each message as the client is
only required to show the latest version of the cursor. Whie the logic
should prevent cursor corruption, it's not guaranteed, however this is
not a problem as this can only happen if the client is lagging behind
and as such when it gets another update message it will re-read the
now new shape anyway.
2020-11-10 23:29:04 +11:00
Geoffrey McRae
cd4dfd7252 [client] egl: cleanup/refactor of cursor texture code 2020-11-10 20:42:14 +11:00
Geoffrey McRae
12da2fc0b7 [client] fixed incorrect warpState
Credit to @Adam in Discord VFIO #looking-glass
2020-11-09 22:24:40 +11:00
Geoffrey McRae
36726bb349 [client] egl: fixed typo 2020-11-09 07:42:59 +11:00
Geoffrey McRae
dd7e0ea8c6 [client] egl: added colorblind support (egl:cbMode=0/1/2/3)
Based on http://www.daltonize.org/search/label/Daltonize

0 = Off
1 = Protanope
2 = Deuteranope
3 = Tritanope
2020-11-09 07:08:15 +11:00
Geoffrey McRae
ed95f8863d [client] fix divide by zero fault with fpsMin disabled (set to 0) 2020-11-08 10:58:18 +11:00
Geoffrey McRae
8ace686df4 [repos] updated the submodules from a prior accdental reversal
closes #319
2020-11-02 13:17:52 +11:00
Geoffrey McRae
87a2fc2c9e [module] updated description, copyright and author 2020-11-02 13:12:48 +11:00
Geoffrey McRae
604b6bec9a [host] don't fail if windows is dumb and doesnt give us the cursor info 2020-11-01 04:45:57 +11:00
Geoffrey McRae
42ef9964de [host] enlarge the cursor buffer size for large cursor shapes 2020-11-01 04:34:26 +11:00
Geoffrey McRae
4c14797319 [client] egl: no need to create textures when using DMA mode 2020-10-30 22:19:15 +11:00
Geoffrey McRae
42fef7a98d [client] egl: remove deprecated YUV420 support 2020-10-30 19:31:48 +11:00
Geoffrey McRae
0badf2a84c [all] move defines for LGMP_QUEUE_*_LEN into KVMFR.h 2020-10-30 18:48:41 +11:00
four0four
c0acfd1228 [module] fix integer overflow in kvmfr_dmabuf_create 2020-10-30 18:24:02 +11:00
Geoffrey McRae
3de2641d92 [module] fix compile on >= 5.9.0
fixes #314
2020-10-30 16:14:06 +11:00
Geoffrey McRae
fd2801a670 [module] remove now deprecated uio support 2020-10-30 16:02:31 +11:00
Geoffrey McRae
267fa6e389 [client] egl: select the correct fourcc type for dma mode 2020-10-30 11:55:47 +11:00
Geoffrey McRae
6799d518a5 [client] common: added spin timeout to framebuffer wait 2020-10-30 03:27:28 +11:00
Geoffrey McRae
c8f740c34e [client] fix usage of uninitialized time variable 2020-10-30 02:36:45 +11:00
Geoffrey McRae
4f9544d61d [client] egl: added DMA texture support for direct upload
Note: This only works with the KVMFR kernel module in a VM->VM
configuration. If this causes issues it can be disabled with the new
option `app:allowDMA`
2020-10-30 02:36:45 +11:00
Geoffrey McRae
0bf73d862d [client] added initial framework for dma buffer support 2020-10-30 02:36:45 +11:00
Geoffrey McRae
5522e93fb9 [common] linux: added helpers for ivshmem DMA usage 2020-10-30 02:36:45 +11:00
Geoffrey McRae
0efe7dc63c [common] linux: added support for kvmfr dmabuf 2020-10-30 02:36:45 +11:00
Ali Abdel-Qader
5081c3ea88 convert clipboard data type variable to Looking Glass enum before passing it to function 2020-10-29 13:46:14 +11:00
Ali Abdel-Qader
3284431785 change 'frameCount' to be atomic 2020-10-29 13:46:14 +11:00
Geoffrey McRae
d42581027c [lgmp] updated the submodule 2020-10-28 14:08:30 +11:00
Geoffrey McRae
a70858aea0 [client] reworked frame timing waits for better responsiveness 2020-10-26 18:09:45 +11:00
Geoffrey McRae
e02ccd7c6f [client] fix transposed tsDiff arguments 2020-10-26 13:09:47 +11:00
Geoffrey McRae
b13904ec59 [repos] updated PureSpice submodule 2020-10-25 21:10:02 +11:00
Geoffrey McRae
59011b7bcb [all] updated the LGMP submodule 2020-10-20 19:59:02 +11:00
Geoffrey McRae
9d0ae23f9f [host] init ivshmem options before testing 2020-10-19 03:01:35 +11:00
Geoffrey McRae
9ff1859dc1 [host] windows: check the ivshmem device exists in the service 2020-10-19 02:49:15 +11:00
Geoffrey McRae
7a7e1d006b [host] set the program name/description in the app.manifest 2020-10-19 01:56:03 +11:00
Geoffrey McRae
0684ff401f [client] fix race with call to on_restart 2020-10-14 19:40:20 +11:00
Geoffrey McRae
757a90a643 [client] add new keybind to toggle the video stream (ScrLck+V) 2020-10-14 19:32:21 +11:00
Geoffrey McRae
46df25bb80 [client] added new option win:dontUpscale 2020-10-12 23:27:33 +11:00
Geoffrey McRae
38b05cda50 [host] dxgi: fix incorrect bpp value 2020-10-12 20:08:51 +11:00
Geoffrey McRae
58ba76a27f [client] seperate frame setup and data events 2020-10-12 19:43:29 +11:00
Geoffrey McRae
7a49f75d95 [host] dxgi: ensure formatVer is incremented on re-init 2020-10-12 19:39:57 +11:00
Geoffrey McRae
b2961c7939 [all] added new format version field to frame header 2020-10-12 18:52:37 +11:00
Geoffrey McRae
6650e58a4a [common] linux: print reason for failure to open the shm file/device 2020-10-12 17:48:31 +11:00
Geoffrey McRae
90d1ed64e4 [client] opengl: added RGBA16F support 2020-10-11 20:44:22 +11:00
Geoffrey McRae
a78d9c2b90 [obs] added RGBA16F support 2020-10-11 20:42:09 +11:00
Geoffrey McRae
919b77df71 [client] egl: fix RGBA16F support 2020-10-11 20:34:34 +11:00
Geoffrey McRae
98e0e5fc0b [client] egl: correct invalid value for texture row length 2020-10-11 19:59:44 +11:00
Geoffrey McRae
8a9f004ff6 [host/client] fix invalid initialization of RGBA16F 2020-10-11 19:39:47 +11:00
Geoffrey McRae
9c6bd888fd [host/client] added experimental RGBA16 float support (EGL only) 2020-10-11 19:22:31 +11:00
Geoffrey McRae
4f40ce4b40 [obs] added monochrome cursor support 2020-10-11 12:04:10 +11:00
Geoffrey McRae
eb343ca82e [obs] added masked color cursor support 2020-10-11 01:07:20 +11:00
Geoffrey McRae
e2f6621de9 [obs] added RGBA cursor support 2020-10-11 00:39:31 +11:00
Jonathan Rubenstein
66df00cee2 Add option to skip splash screen fade out 2020-10-09 03:28:58 +11:00
Geoffrey McRae
a15de57e58 [all] more versioning fixes 2020-10-09 03:09:45 +11:00
Geoffrey McRae
4d8a116849 [all] set the working directory for git versioning commands 2020-10-09 02:54:36 +11:00
Geoffrey McRae
a94d3734c2 [all] pass the project path to the version.cmake script 2020-10-09 02:51:28 +11:00
Geoffrey McRae
e1d7752165 [host] fix nsis build failure to do invalid path to new VERSION file 2020-10-09 02:36:48 +11:00
Geoffrey McRae
e6c88a4af3 [all] be smarter about getting the git version 2020-10-09 02:17:20 +11:00
Geoffrey McRae
76710ef201 [all] updated issue template and readme in preperation for B2 2020-10-08 20:04:52 +11:00
Geoffrey McRae
e20c8a5cc7 [host] dxgi: don't try to get the hotspot of a null cursor 2020-10-06 23:24:01 +11:00
Geoffrey McRae
4f4d2dbf42 [host] dxgi: fix memory leak if an error occurs 2020-10-06 22:32:10 +11:00
Geoffrey McRae
8692e9af80 [client] don't hide the cursor when SPICE is disabled
Fixes #304
2020-08-21 15:40:22 +10:00
Geoffrey McRae
7d2b39058c [client] ensure the cursor is updated when the window looses/gains focus 2020-08-20 16:05:55 +10:00
Geoffrey McRae
6927dbecd2 [client] added new input:mouseRedraw option
This new option, when enabled (the default) enables cursor movements to
trigger frame updates in the client, improving responsiveness at the
cost of increased FPS while the mouse is moving around.
2020-08-20 15:50:33 +10:00
Geoffrey McRae
f9b6dcc986 [client] only resync the timer if we got an early frame
This prevents a slow update (ie, 30ups) from pulling the refresh rate
below the minimum (ie, 60fps).
2020-08-20 15:18:45 +10:00
Geoffrey McRae
5c912e3c27 [client] spice: improve mouse syncronization with the host 2020-08-20 14:52:24 +10:00
Geoffrey McRae
7e362050f7 [all] update KVMFR to provide cursor hotspot information
This commit bumps the KVMFR protocol version as it adds additional
hotspot x & y fields to the KVMFRCursor struct. This corrects the issue
of invalid alignment of the local mouse when the shape has an offset
such as the 'I' beam.
2020-08-20 13:51:01 +10:00
Ash
10fbdeb294 update client/README.md: spice:captureOnStart from #278 2020-08-19 23:08:34 +10:00
camr0
72d70e8322 Update host/README.md: c-host -> host 2020-08-17 11:44:52 +10:00
Geoffrey McRae
c66a339bbc [client] egl: ensure overflow occurs for state value checks 2020-08-15 22:39:10 +10:00
Geoffrey McRae
1c7961daeb [host] dxgi: rework locking and retry logic for lower latency 2020-08-15 20:49:49 +10:00
Geoffrey McRae
cdc3384883 [host] dxgi: improve frame signaling mechanics 2020-08-15 18:16:11 +10:00
Geoffrey McRae
969effedde [host] update information about PsExec now LG can run as a service 2020-08-13 11:41:16 +10:00
Geoffrey McRae
dc4d1d49fa [host] updated the readme with regards to log file location 2020-08-12 22:15:22 +10:00
Geoffrey McRae
4e1f947a09 [host] Windows: fix uninstaller product name 2020-08-12 22:03:10 +10:00
Geoffrey McRae
15d1a74291 [host] Windows: multiple fixes to the installer 2020-08-12 21:50:48 +10:00
TheCakeIsNaOH
7dba6b9b08 [Host] Convert installer to setup service instead of scheduled task 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
a5ad531004 [Host] Change default install dir "Looking-Glass" to "Looking Glass" 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
c119b3dcca [Host] Correct installer and shortcut names 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
e2f2437ef4 [Host] Installer command line options and install location selection add 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
b2980fea63 [Host] Add instructions on how to build NSIS installer. 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
2b518690b8 [Host] NSIS script change names from C-Host to Host 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
92aca75792 [c-host] Add NSIS installer script 2020-08-12 21:32:15 +10:00
Geoffrey McRae
64fdb8b7bb [host] Windows: service (un)install now starts/stops the service
In addition to starting and stopping the service, it now also stops the
LG process if the service started it.
2020-08-12 20:56:02 +10:00
Geoffrey McRae
431ae3fc55 [common] linux: fix issue with infinite timeout events 2020-08-11 19:31:11 +10:00
Geoffrey McRae
380b5df9f9 [host] increase sleep timeout to 100ms 2020-08-11 19:11:17 +10:00
Geoffrey McRae
c7330167cf [host] shutdown capture if there are no subscribers
Fixes #33
2020-08-11 18:30:47 +10:00
Geoffrey McRae
ca02e1aba9 [host] Windows: change "Open Log File" to "Log File Location" 2020-08-11 17:45:00 +10:00
Geoffrey McRae
ca4b1f5592 [host] Windows: don't open the log file, instead show it's location
Now that it's recommended to run LG as the `SYSTEM` user, launching an
application to read the log file is dangerous as it will be launched
with the same access rights (`SYSTEM`). Instead so as Microsoft
recommends and only present a message box with the information.
2020-08-11 17:42:00 +10:00
Geoffrey McRae
0cf1e27709 [host] Windows: run with HIGH priority if started by the service 2020-08-11 17:37:40 +10:00
Geoffrey McRae
045932ce77 [host] send the correct cursor shape on client connection 2020-08-11 17:16:54 +10:00
Geoffrey McRae
bf5481446b [host] Windows: poll more freqently for a stopped LG process 2020-08-11 15:22:29 +10:00
Geoffrey McRae
e3f97e384b [client] rework the start/restart logic to use an enum 2020-08-11 15:14:58 +10:00
Geoffrey McRae
76e119f8ad [client] egl: don't fade the splash when restarting 2020-08-11 14:54:48 +10:00
Geoffrey McRae
bfb12c74fb [client] be quicker at detecting restart and quieter about it 2020-08-11 14:52:22 +10:00
Geoffrey McRae
fa50b7824c [client] fix crash on shutdown while waiting for a restart 2020-08-11 14:45:43 +10:00
Geoffrey McRae
da8b2d0cec [client] egl: properly wait for a new frame on restart 2020-08-11 14:45:08 +10:00
Geoffrey McRae
74649ddb96 [client] gracefully restart if the host application restarts 2020-08-11 14:30:44 +10:00
Geoffrey McRae
4619ddef5d [host] Windows: added missing linker library 2020-08-11 13:15:18 +10:00
Geoffrey McRae
ea74ee6e25 [host] windows: fix crosscompile take 2 2020-08-11 13:11:42 +10:00
Geoffrey McRae
ecd73aa670 [host] windows: fix linux crosscompile 2020-08-11 13:07:23 +10:00
Geoffrey McRae
10d9678b3d [host] Windows: improved service restart detection 2020-08-11 12:47:50 +10:00
Geoffrey McRae
e08d3afdbc [host] Windows: added missing service files 2020-08-11 12:27:04 +10:00
Geoffrey McRae
9a6b598438 [host] Windows: Implemented service to launch LG as the SYSTEM user
Experimental, use at your own peril!

This commit adds the ability for the LG host to install and launch with
Windows as a system service.

To install simply run `looking-glass-host.exe InstallService` or
conversely to uninstall `looking-glass-host.exe UninstallService`.
2020-08-11 12:22:22 +10:00
Geoffrey McRae
d9a80b16f0 [common] properly define _GNU_SOURCE and set the thread names 2020-08-10 16:22:02 +10:00
Geoffrey McRae
90d0cd873d [common] added a sleep to the framebuffer spinlock and a sane timeout 2020-08-10 16:18:08 +10:00
Geoffrey McRae
82e0b7b6ab [doc] readme updated with PsExec information 2020-08-09 20:11:19 +10:00
Geoffrey McRae
2e1b0f2550 [all] update the LGMP submodule 2020-08-09 18:13:43 +10:00
Geoffrey McRae
3302d353cf [client] always use spice mouse host mode
Since we only ever use offset movements as SPICE doesn't properly
support absolute x/y positional information without a virtual tablet
device (which breaks relative mode needed for capture), just always run
in this mode. This fixes an issue when the spice guest tools are
installed and the mouse fails to work when not captured.
2020-08-09 16:17:08 +10:00
Geoffrey McRae
1899d9f1da [client] reset the frame time when we get a frame signal
This stops a duplicate frame rendering bug due to failure to discipline
based on the signal timing.
2020-08-09 15:55:12 +10:00
Geoffrey McRae
fb9b772db0 [client] we are getting the clock anyway, just reset the time 2020-08-09 15:54:45 +10:00
Geoffrey McRae
302b988524 [client] use atomics to track frame counts and avoid extra signals 2020-08-09 15:14:17 +10:00
Geoffrey McRae
19c2fe9b5e Revert "[common] linux: improve event mechanics"
The logic here is wrong, this should be done externally as multiple
waiters will cause issues
2020-08-09 14:44:00 +10:00
Geoffrey McRae
88d25ee98c [common] linux: improve event mechanics 2020-08-09 13:26:55 +10:00
Geoffrey McRae
0f2ecdf5f1 [obs] cosmetic 2020-08-09 12:31:56 +10:00
Geoffrey McRae
3511fb8d59 [obs] microsttuer fix, be sure to always grab the latest frame 2020-08-09 12:29:52 +10:00
Geoffrey McRae
1d6d640b6e [host] dxgi: default to using the acquire lock 2020-08-07 20:31:46 +10:00
Geoffrey McRae
977d7b277d [host] dxgi: boost GPU thread priority if possible 2020-08-07 19:44:00 +10:00
Geoffrey McRae
be7820303f [common] fixed debug formatting across platforms 2020-08-03 15:05:35 +10:00
Geoffrey McRae
43503222c7 [common] framebuffer: fixed incorrect streaming usage 2020-08-03 14:41:57 +10:00
Geoffrey McRae
85b8c12abf [common] adjust framebuffer read/write strategy for better cache usage 2020-08-03 12:33:08 +10:00
Geoffrey McRae
7af053497e [common] unroll the framebuffer write loop and increase the chunk size 2020-08-03 12:24:17 +10:00
Geoffrey McRae
9e3a42cb62 [host] don't stop the timer when restarting capture 2020-08-03 12:04:50 +10:00
Geoffrey McRae
aa32c5ffad [common] framebuffer: added missing header include 2020-08-03 11:58:38 +10:00
Geoffrey McRae
62d1bd1ea2 [common] framebuffer: use stream load instead of plain load 2020-08-03 11:55:38 +10:00
Geoffrey McRae
2329e993ee [common] fixed framebuffer write SIMD code performance 2020-08-03 11:44:24 +10:00
Geoffrey McRae
da655b86c3 [common] improve frambuffer copy to avoid cache pollution (SIMD) 2020-08-03 11:16:30 +10:00
Max Sistemich
c5ff8bd4ce [common] linux: implement timers 2020-07-25 00:38:15 +10:00
Geoffrey McRae
06aee158de [client] egl: make better use of atomics and fix modulus bug 2020-07-24 17:39:16 +10:00
Samuel Bowman
bd42445ea7 [client] add option to capture input on start 2020-07-17 08:39:32 +10:00
Geoffrey McRae
ede96fa486 [client] egl: don't map the texture until it's needed
The texture buffer may still be in use if we try to re-map it
immediately, instead only map when we need it mapped, and unmap
immediately after advancing the offset allowing the render thread to
continue while the unmap operation occurs
2020-05-30 16:50:27 +10:00
Geoffrey McRae
67dec216d2 [host] search the applications local directory for the config 2020-05-30 12:31:26 +10:00
Geoffrey McRae
fcbdf7ba4f [client] egl: fix non-streaming texture updates 2020-05-29 16:54:25 +10:00
Geoffrey McRae
e8c949c1e7 [client] egl: dont re-setup the fps texture on each update 2020-05-29 16:47:21 +10:00
Geoffrey McRae
28c93ef5ac [client] egl: don't unmap/map all buffers for each frame 2020-05-29 15:48:59 +10:00
Geoffrey McRae
d7921c5d5f [client] report the host version on mismatch if possible 2020-05-29 14:24:06 +10:00
Geoffrey McRae
6d296f2b44 [client] stop people running the client as root 2020-05-29 14:18:02 +10:00
Geoffrey McRae
553e2830bb [client/host] share the host version with the client for diagnostics 2020-05-29 14:14:31 +10:00
Geoffrey McRae
667ab981ba [host] send the latest cusror information when a new client connects 2020-05-25 14:37:02 +10:00
Geoffrey McRae
bc7871f630 [c-host] renamed finall to just plain host 2020-05-25 13:42:43 +10:00
Geoffrey McRae
d579705b10 [misc] minor readme update 2020-05-22 22:53:21 +10:00
Geoffrey McRae
94d383a8c1 [obs] remove useless advance operation 2020-05-22 22:51:41 +10:00
Geoffrey McRae
08062e3fc3 [client] check for underflow when checking frame time 2020-05-22 22:02:44 +10:00
Geoffrey McRae
4441427943 [client] implemented better clock drift correction 2020-05-22 20:45:59 +10:00
Geoffrey McRae
f5da432d38 [client] put back the fps correction from drift/skew 2020-05-22 18:39:19 +10:00
Geoffrey McRae
60f665a65c [client] more fps limiter fixes 2020-05-22 18:28:16 +10:00
Geoffrey McRae
9b6174793a [client] revert cusror update render trigger
While it makes the mouse a bit nicer it causes frame skips during cursor
movement.
2020-05-22 18:16:48 +10:00
Geoffrey McRae
dedab38b99 [client] rename fpsLimit to fpsMin 2020-05-22 18:15:17 +10:00
Geoffrey McRae
4580b18b04 [client] fix the fps limiter 2020-05-22 18:06:29 +10:00
Geoffrey McRae
88dad36449 [client] allow mouse movements to trigger render updates
Now EGL is lockless we can allow cursor updates to trigger frame updates
directly.
2020-05-22 18:00:18 +10:00
Geoffrey McRae
075c82b32c [client] egl: fix context binding enabling a lock free implementation 2020-05-22 17:47:19 +10:00
Geoffrey McRae
ae2ffd0a28 [client] drop the default FPS target to 60 now that the fps is dynamic 2020-05-21 14:59:51 +10:00
Geoffrey McRae
26eea64689 [client] remove microstutter warning when using the fps display
This warning was added when it was thought to be the cause of the
microstutters, however this has been disproven with the latest batch of
changes.
2020-05-21 14:16:01 +10:00
Geoffrey McRae
c9ff1e1949 [client] egl: alter warning about low fps as it doesn't apply anymore 2020-05-21 14:09:51 +10:00
Geoffrey McRae
e31f38eadc [client] allow frame updates to be triggered by a timed event
This is a major change to how the LG client performs it's updates. In
the past LG would operate a fixed FPS regardless of incoming update
speed and/or frequency. This change allows LG to dynamically increase
it's FPS in order to better sync with the guest as it's rate changes.
2020-05-21 13:41:59 +10:00
Geoffrey McRae
756b57400b [client] egl: move context init to lock function 2020-05-21 11:55:35 +10:00
Geoffrey McRae
01bfd2e090 [client] egl: make better use of the second thread for streaming 2020-05-21 11:44:56 +10:00
Geoffrey McRae
dc3e89e65c [obs] add delay to fix startup
this delay is needed to allow the host clock to change so we can
validate the session.
2020-05-21 09:37:20 +10:00
Geoffrey McRae
240d0ff263 [client] add short delay to improve initial startup 2020-05-21 09:32:08 +10:00
Geoffrey McRae
3b47a4113f [client/obs] update to use new LGMP init api 2020-05-21 09:28:41 +10:00
Geoffrey McRae
a6d6a49f82 [client] egl: use atomic members instead of locking the entire state 2020-05-21 08:20:30 +10:00
Geoffrey McRae
f8ff3faf78 [obs] improvements to help prevent client timeouts 2020-05-21 07:31:12 +10:00
Geoffrey McRae
d899c26617 [client] egl: add low FPS warning when failing to keep up 2020-05-19 22:42:55 +10:00
Geoffrey McRae
73ba325072 [client] egl: reworked the streaming texture pipeline 2020-05-19 22:03:36 +10:00
Geoffrey McRae
aff19e13c7 [profiler] client: updated to use new lgmp API and path 2020-05-19 11:37:44 +10:00
Geoffrey McRae
007122df43 [all] remove github specific unused config file 2020-05-19 11:19:20 +10:00
Geoffrey McRae
06f8911ee1 [all] project cleanup 2020-05-19 11:06:39 +10:00
Geoffrey McRae
f96f0fecda [client] egl: use proper atomics for pbo counting 2020-05-18 09:06:11 +10:00
Geoffrey McRae
21987cb423 [obs] update to use new LGMP interface 2020-05-17 12:04:41 +10:00
Geoffrey McRae
18cc8d7cab [client] fix host wait logic and print more useful help 2020-05-17 11:54:07 +10:00
Geoffrey McRae
fc0dbd8782 [c-host] add kvmfr version to host output 2020-05-17 11:26:45 +10:00
Geoffrey McRae
b7ca3d7e37 [client] cleanup debug output 2020-05-17 11:25:27 +10:00
Geoffrey McRae
c4bf992c0c [client/host] added enforcement of KVMFR versioning 2020-05-17 11:13:08 +10:00
Geoffrey McRae
dcce288a98 [obs] fix another potential deadlock 2020-04-25 02:26:34 +10:00
Geoffrey McRae
cfd8126e5d [obs] remove debug printf 2020-04-25 02:26:16 +10:00
Geoffrey McRae
7a96642498 [client & host] update the LGMP project to fix timeout issues 2020-04-25 02:25:44 +10:00
Geoffrey McRae
8d5a42c233 [obs] fix potential deadlock 2020-04-24 23:03:40 +10:00
Geoffrey McRae
00a41be413 [obs] use thread to handle frame advance when obs is behind 2020-04-24 21:31:12 +10:00
Geoffrey McRae
fdb9a9cca8 use a timer for the LGMP host instead of a thread 2020-04-24 21:31:12 +10:00
feltcat
e7f088ef52 [client] egl: typo fix in info message
"Multsampling" to "Multisampling"
2020-04-24 19:01:42 +10:00
Geoffrey McRae
243efcd51a [client] fix missing release_key_binds for mouse sensitivity bindings 2020-04-23 18:00:17 +10:00
feltcat
e3cbdd18a0 [client] add quit keybind 2020-04-23 17:57:58 +10:00
Geoffrey McRae
b9cdaf8e19 update PureSpice to fix clipboard bug 2020-04-21 13:17:49 +10:00
Geoffrey McRae
4758caa772 updated PureSpice submodule 2020-04-21 11:36:24 +10:00
Geoffrey McRae
4058522f68 update PureSpice submodule 2020-04-20 09:54:12 +10:00
Geoffrey McRae
80437c564d update PureSpice submodule 2020-04-15 17:55:43 +10:00
Geoffrey McRae
503fc7c312 [spice] updated the submodule to fix a minor shutdown glitch 2020-04-14 16:46:55 +10:00
Geoffrey McRae
f6691a90c0 [client/obs] improve frambuffer_read functions to support copy pitch
Fixes #244
2020-04-14 13:27:07 +10:00
Mikko Rasa
ead09ed110 [client] opengl: render frame if config didn't change 2020-04-14 12:19:59 +10:00
Geoffrey McRae
ac1ecd2e7b [client] update PureSpice submodule to resolve build issue with -O3 2020-04-13 20:07:08 +10:00
Geoffrey McRae
3538e7f6f4 [c-host] dxgi: add more robust error handling on cursor shape failure
Closes #264 - Credit to https://github.com/DataBeaver
2020-04-12 14:43:50 +10:00
Geoffrey McRae
75bc038144 [client] removed accidental debug commit 2020-04-12 13:48:59 +10:00
Geoffrey McRae
7018a3e737 [c-host] dxgi: close the desktop on deinit 2020-04-12 13:46:56 +10:00
Geoffrey McRae
d3836d4548 [c-host] Enable secure desktop capture using SetThreadDesktop
Closes #263 - Credit to https://github.com/DataBeaver for this gem!
2020-04-12 13:35:40 +10:00
Geoffrey McRae
dbd7db7787 [common] fix framebuffer_prepare to use atomic_store 2020-04-12 13:16:55 +10:00
Geoffrey McRae
1222fd40b7 [common] fix FrameBuffer to use atomics correctly
Might Fix #248
2020-04-12 13:14:53 +10:00
Geoffrey McRae
b5f4c639fd [client] provide better mouse tracking when exiting/entering the window 2020-04-07 14:54:38 +10:00
Geoffrey McRae
cddeeff3fc [c-host] LGMP: increase the timeouts 2020-04-07 14:54:38 +10:00
fishery
94a35a6558 [client] fix buffer overflow in opengl_options
buffer overflow loading opengl_options
2020-04-02 00:17:19 +11:00
Geoffrey McRae
b953b2b807 [module] added missing kvmfr.h, fixes #253 2020-03-22 09:20:09 +11:00
Geoffrey McRae
367a73d033 [spice] updated the submodule, fixes #249 2020-03-22 09:12:54 +11:00
Geoffrey McRae
1ac13658e1 [module] fix compilation for linux 5.6 2020-03-11 00:28:44 +11:00
chrsm
2440272307 [common] fix build for newer versions of binutils
binutils has changed several macros. Added ifdef to allow building with
stable and bleeding edge versions.

refs #232
2020-02-25 22:31:55 +11:00
Geoffrey McRae
582ed6b5d1 [c-host] dxgi: dont send null movements when only the visibility changed 2020-02-12 18:40:28 +11:00
Geoffrey McRae
e2adbaa5c1 [c-host] dxgi: fix failure to provide cursor visibility information 2020-02-12 18:36:11 +11:00
Geoffrey McRae
4acf800ace [client] updated the PureSpice submodule 2020-02-03 17:31:56 +11:00
Geoffrey McRae
7cc305c2f5 [client] updated spice submodule to fix shutdown bug 2020-02-01 14:31:46 +11:00
Geoffrey McRae
95f5962186 [client] update to properly disconnect from spice 2020-02-01 14:24:23 +11:00
Geoffrey McRae
f4c2996a3a [repos] updated submodules 2020-02-01 14:22:07 +11:00
Geoffrey McRae
10c4037694 [doc] added new github sponsorship option to README.md 2020-02-01 11:44:51 +11:00
Geoffrey McRae
52be6deccf [github] updated sponsorship for github sponsors 2020-02-01 11:43:42 +11:00
feltcat
0d736efc88 Fixed typo in issue template 2020-02-01 11:37:11 +11:00
Geoffrey McRae
9cc21c2a62 [all] updated the main README.md file 2020-01-31 21:43:58 +11:00
Geoffrey McRae
0b7f422d5d [client] moved spice into a seperate repository 2020-01-31 21:39:57 +11:00
Geoffrey McRae
0ca760fad6 [c-host] revert locking series, this needs more thought
Revert "[c-host] make pointer post function thread safe"

This reverts commit 3feed7ba07.

Revert "[c-hots] fix incorrect unlock timing"

This reverts commit 57f1f2d1fe.

Revert "[c-host] increase the queue length and remove debug output"

This reverts commit b0f9f15a60.

Revert "[c-host] dxgi: use low level mouse input by default"

This reverts commit dc4d820666.

Revert "[c-host] nvfbc: no need for a cursor position event with LGMP"

This reverts commit e30b54ddb2.
2020-01-29 23:23:31 +11:00
Geoffrey McRae
3feed7ba07 [c-host] make pointer post function thread safe 2020-01-29 22:58:59 +11:00
Geoffrey McRae
57f1f2d1fe [c-hots] fix incorrect unlock timing 2020-01-29 22:12:59 +11:00
Geoffrey McRae
b0f9f15a60 [c-host] increase the queue length and remove debug output 2020-01-29 22:05:47 +11:00
Geoffrey McRae
dc4d820666 [c-host] dxgi: use low level mouse input by default
This is known to prevent cursor updates on the secure desktop
(UAC) but DXGI DD does not provide us with the real mouse
coordinates when applications have 'captured' the cursor.
2020-01-29 21:58:39 +11:00
Geoffrey McRae
e30b54ddb2 [c-host] nvfbc: no need for a cursor position event with LGMP 2020-01-29 21:58:00 +11:00
Geoffrey McRae
939bb07603 [all] cleanup use of atomic locking and switch to C11 stdatomic 2020-01-29 19:06:09 +11:00
Geoffrey McRae
cc2c49644d [spice] reworked to avoid locking requirements on the input channel
POSIX `send` is thread safe, to take advantage of this the code has been
changed to construct a contiguous buffer and perform the send in a
single operation preventing any risk of a race condition.

Only the main channel still requires an interlock as the VD agent
requires multiple sends to transmit a full buffer.
2020-01-29 18:53:33 +11:00
Geoffrey McRae
29f221d547 [spice] improve connection code to use a single buffer 2020-01-29 16:52:23 +11:00
Geoffrey McRae
2e32ceb6e0 [LGMP] update the submodule 2020-01-29 14:03:12 +11:00
Geoffrey McRae
2cbc9b6426 [kvmfr] stop the module building the test application by default 2020-01-29 14:01:52 +11:00
Geoffrey McRae
3f3a8f898d [common] 1e9 is a floating point notation 2020-01-29 14:01:14 +11:00
Geoffrey McRae
6e62ea5364 [common] fix building on mingw for linux 2020-01-28 05:10:38 +11:00
Geoffrey McRae
5d39b6160a [lgmp] update module again with actual fix! 2020-01-28 04:28:55 +11:00
Geoffrey McRae
a9e8187f28 [LGMP] updated the module again 2020-01-28 03:58:59 +11:00
Geoffrey McRae
228f5bfdff [c-host] don't hog CPU resources if queues are full 2020-01-28 03:58:28 +11:00
Geoffrey McRae
29e5f193f0 [common] added timestamps to log output 2020-01-28 03:57:19 +11:00
Geoffrey McRae
8f8ebab712 [c-host] respect the full queue 2020-01-28 01:04:46 +11:00
Geoffrey McRae
418149c9a6 [LGMP] updated the submodule with the fixed locking mechanics 2020-01-27 22:12:20 +11:00
Geoffrey McRae
e30e5da75a [c-host] nvfbc: correct frame change check logic 2020-01-27 16:16:43 +11:00
Geoffrey McRae
fc6681306e [c-host] nvfbc: do not send frames that have not changed 2020-01-27 16:01:31 +11:00
Geoffrey McRae
60acc3ef44 [obs] update the LGMP module to fix low frame rate capture issue 2020-01-27 15:05:25 +11:00
Geoffrey McRae
9958e557b7 [c-host] increase delay as lgmp clients can now remove empty messages 2020-01-27 14:48:20 +11:00
Geoffrey McRae
8dbc1daaf4 [common] linux: signal should signal all listeners 2020-01-27 14:33:57 +11:00
Geoffrey McRae
5a23d048bd [LGMP] submodule update again 2020-01-27 13:14:16 +11:00
Geoffrey McRae
b658ea6459 [LGMP] another submodule update 2020-01-27 13:06:46 +11:00
Geoffrey McRae
dc91a0d807 [LGMP] updated the submodule again 2020-01-27 12:49:36 +11:00
Geoffrey McRae
c1fd6552d2 [client] fix hang when trying to terminate an unconnected client 2020-01-27 12:25:47 +11:00
Geoffrey McRae
6b2e78acdf [all] updated LGMP module, a rebuild of host and client IS required 2020-01-27 11:29:54 +11:00
Geoffrey McRae
7b11ab04c6 [client] always update the renderer cursor state 2020-01-27 02:11:21 +11:00
Geoffrey McRae
bced5f95ff [all] make cursor visible a flag and send it seperate to position 2020-01-27 02:07:32 +11:00
Geoffrey McRae
9d7f773b9c [c-host] decrease LGMP polling interval to a sane value and comment 2020-01-27 01:55:14 +11:00
Geoffrey McRae
fea0a98b9e [c-host] dxgi: invisible cursors do not have position information 2020-01-27 01:47:40 +11:00
Geoffrey McRae
8745858bcf [lgmp] updated the lgmp submodule 2020-01-27 01:25:49 +11:00
Geoffrey McRae
2885c73a9a [c-host] increased the polling delay as there is a better fix for LGMP 2020-01-27 01:22:40 +11:00
Geoffrey McRae
893b23f3cd [c-host] increase lgmp host process resolution 2020-01-26 18:50:07 +11:00
Geoffrey McRae
d860d6b891 [c-host] win: fixed improper signal detection in event code 2020-01-26 17:49:04 +11:00
Geoffrey McRae
dcc9625803 [client] updated to use new cursor state flags 2020-01-26 17:30:16 +11:00
Geoffrey McRae
b7e4426002 [c-host] inform the client if we have positional cursor information 2020-01-26 17:25:14 +11:00
Geoffrey McRae
b4cf8f76c8 [c-host] mousehook: ignore repeated hook events 2020-01-26 16:23:35 +11:00
Geoffrey McRae
687eddcc63 [kvmfr] fixed incorrect buffer size calculation 2020-01-24 17:07:09 +11:00
Geoffrey McRae
9d6d137b50 [c-host] fix bounds checking on frame index 2020-01-24 16:31:03 +11:00
Geoffrey McRae
a75b95694b [c-host] actually use the 2nd LGMP frame 2020-01-24 16:06:38 +11:00
Geoffrey McRae
c7aa8871e4 [common] fixed improper comment parsing, fixes #233 2020-01-21 16:35:21 +11:00
Geoffrey McRae
f9d919bdbb [client] increase the lgmp queue timeouts 2020-01-20 14:18:45 +11:00
Geoffrey McRae
4d0f019ad5 [spice] prepare spice for external usage 2020-01-19 06:51:21 +11:00
Geoffrey McRae
e6154e685f [client] cosmetics 2020-01-19 06:49:56 +11:00
Geoffrey McRae
2c59b5f557 [client] added checking for invalid arguments to custom string options 2020-01-19 06:48:20 +11:00
Geoffrey McRae
4746c89227 [all] moved time and locking methods to the common library 2020-01-17 14:35:08 +11:00
Geoffrey McRae
278d851c7c [egl] added fallback for platforms not supporting eglGetPlatformDisplay 2020-01-17 11:50:00 +11:00
Geoffrey McRae
406e22a681 [client] override new behaviour in SDL 2.0.15 and disable xinput2
xinput2 is used to get touch interface events with the side effect of
consuming MotionNotify events which we use because of SDL2's inability
to correctly track the window size. Since we are not that intertested in
touch for our usecase, we just turn the events off again.
2020-01-13 22:21:12 +11:00
Geoffrey McRae
17e05c6fd5 [all] expose the FrameBuffer struct for correct sizeof calculations 2020-01-13 19:30:49 +11:00
Geoffrey McRae
9846762991 [all] align the frame data to the page boundary 2020-01-13 19:17:09 +11:00
Geoffrey McRae
17df1ebc6b [c-host] adjust maximum size to account for alignment 2020-01-13 16:06:53 +11:00
Geoffrey McRae
ad8a8b52be [c-host] ensure frames are page aligned 2020-01-13 15:52:54 +11:00
Geoffrey McRae
0d29527758 [common] added agnostic function sysinfo_getPageSize 2020-01-13 15:52:31 +11:00
Geoffrey McRae
7a96c9fe24 [kvmfr] don't recreate the pages for each map 2020-01-13 15:42:45 +11:00
Geoffrey McRae
c71e5c63ca [lgmp] updated the module to bring in support for aligned allocations 2020-01-13 15:19:25 +11:00
Geoffrey McRae
f82a164d75 [client] enable SDL_SYSWMEVENT on X11 to work around SDL2 bugs 2020-01-13 14:03:26 +11:00
Geoffrey McRae
5d4e9b1ead [kvmfr] bump the version in dkms.conf 2020-01-13 13:45:05 +11:00
Geoffrey McRae
788f885759 [kvmfr] added the ability to obtain a dmabuf of the ivshmem memory
This is to enable the ability to use dri3 to create dmabuf backed
pixmaps directly.
2020-01-13 13:39:24 +11:00
Geoffrey McRae
6aeafc6651 [common] add comment support to the ini parser 2020-01-12 22:44:41 +11:00
Geoffrey McRae
1aadf91901 [common] revert /dev/uio0 naming change behaviour 2020-01-12 22:37:10 +11:00
Geoffrey McRae
7de030bb69 [c-host] nvfbc: free event on deinit 2020-01-12 18:09:11 +11:00
Geoffrey McRae
b5d91ccc21 [c-host] nvfbc: fixed invalid nvfbc init 2020-01-11 22:28:52 +11:00
Geoffrey McRae
0eafa7de5d [c-host] update NvFBC to use new capture interface 2020-01-11 21:51:59 +11:00
Geoffrey McRae
e554635e48 [spice] turn on TCP_QUICKACK
https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf
2020-01-11 16:03:28 +11:00
Geoffrey McRae
5e915dd1ff [client] don't send mouse click events when out of view 2020-01-11 13:11:12 +11:00
Geoffrey McRae
13f55011c0 [client] don't draw the cursor if it leaves the frame 2020-01-11 12:56:46 +11:00
Geoffrey McRae
05dc713dac [client] more cursor tweaks for better integration with the WM 2020-01-11 06:03:16 +11:00
Geoffrey McRae
80f3c7934a [client] more cursor tweaks and some cleanup 2020-01-11 05:22:12 +11:00
Geoffrey McRae
1341bf8fbd [client] fix mouse acceleration when in capture mode
SDL2 really doesn't do this well, instead I have implemented our own
capture method that allows us to maintain better client/server cursor
sync.
2020-01-11 04:53:46 +11:00
Geoffrey McRae
5b163063c3 [client] improved sync with guest cursor position 2020-01-11 03:41:44 +11:00
Geoffrey McRae
c2a15ad89d [c-host] updated to use new LGMP API to increase the timeout 2020-01-10 20:04:46 +11:00
Geoffrey McRae
c92312a6c6 [obs] implemented intial OBS Looking Glass Client plugin
Yes, it works! but no cursor support yet
2020-01-10 18:14:08 +11:00
Geoffrey McRae
3253e7fd10 [all] updated LGMP submodule 2020-01-10 18:12:42 +11:00
Geoffrey McRae
e5178793b3 [client] don't fail on invalid magic at startup 2020-01-10 18:07:18 +11:00
Geoffrey McRae
bec4f83778 [profiler] updated to use LGMP 2020-01-10 18:04:22 +11:00
Geoffrey McRae
22f04a926f [common] numerious bad usage bug fixes 2020-01-10 18:04:22 +11:00
Geoffrey McRae
76fa390e3d [c-host] increase the pointer queue length 2020-01-10 11:40:56 +11:00
Geoffrey McRae
1ef406bbaf [lgmp] updated submodule 2020-01-10 11:19:34 +11:00
Geoffrey McRae
0aa8711796 [lgmp] updated submodule 2020-01-10 11:04:16 +11:00
Geoffrey McRae
bea7c94cae [client/c-host] updated to use new LGMP naming conventions 2020-01-10 11:01:35 +11:00
Geoffrey McRae
e7239c53fd [c-host] cleanup dxgi cursor code a bit 2020-01-09 21:20:01 +11:00
Geoffrey McRae
6f551c770c [client] handle pointer visibility properly 2020-01-09 21:18:35 +11:00
Geoffrey McRae
2d755a45e0 [client] added support for LGMP 2020-01-09 20:32:42 +11:00
Geoffrey McRae
7a98a886b6 [c-host] use the correct buffer for the cursor shape 2020-01-09 20:27:55 +11:00
Geoffrey McRae
b0fb7177bb [c-host] improved intial connection sync 2020-01-09 19:49:47 +11:00
Geoffrey McRae
73e8bc41cd [c-host] don't overflow the pointerMemory array 2020-01-09 16:15:04 +11:00
Geoffrey McRae
0b8f1a18b2 [LGMP] start of c-host conversion to use LGMP 2020-01-09 15:42:32 +11:00
Geoffrey McRae
8caa220ad5 [common] link setupapi for ivshmem windows implementation 2020-01-06 20:59:34 +11:00
Geoffrey McRae
b8203bec53 [common] properly detect all versions of Windows 8 2020-01-06 20:55:21 +11:00
Geoffrey McRae
5db4c32035 [c-host] dont use DX12 feature levels on Windows8
Fixes #218
2020-01-06 20:53:15 +11:00
Geoffrey McRae
9282ed19b2 [client] check for clock drift and correct for it
Fixes #224
2020-01-06 20:38:01 +11:00
Geoffrey McRae
45ee79014d [common] added back support for shared memory files 2020-01-06 00:20:30 +11:00
Geoffrey McRae
0dc0e6490c [c-host] dxgi: check for failure of getDesc1 2020-01-03 17:29:07 +11:00
Geoffrey McRae
127113a59b [client] fixed strange resize effect due to loss of precision 2020-01-03 17:23:48 +11:00
Geoffrey McRae
49bf115c84 [client] fix issue with windowmanager forcing the window size (i3wm) 2020-01-03 16:51:24 +11:00
Geoffrey McRae
2196516e2b [client] added new win:forceAspect option
Fixes #225
2020-01-03 15:53:44 +11:00
Geoffrey McRae
899dbff7e9 [client] use the event data instead of calling SDL_GetWindowSize 2020-01-03 15:26:07 +11:00
Geoffrey McRae
4345d94d68 [client] update client to use the common ivshmem* methods 2020-01-03 15:17:14 +11:00
Geoffrey McRae
074af5d16c [c-host] init platform app struct 2020-01-03 14:56:13 +11:00
Geoffrey McRae
89d6ea0b5d [common] move ivshmem code into the common library 2020-01-03 14:53:56 +11:00
Geoffrey McRae
c5baf212c8 [client] switch from SDL_Cond to LGEvent 2020-01-03 00:09:07 +11:00
Geoffrey McRae
ba31c78412 [client] switch from SDL_Thread to lgThread 2020-01-02 23:59:06 +11:00
Geoffrey McRae
1c1d2a0568 [common] moved linux agnostic code into the common library 2020-01-02 23:34:35 +11:00
Geoffrey McRae
0c6ff6822d [common/c-host] move agnostic code into common library 2020-01-02 22:21:42 +11:00
Jonathan (JJRcop) Rubenstein
491ffc3576 Fix client not building on void linux
Thanks to

SharkWipf#8539,
Aiber#4888,
and Hadet#6969 on the VFIO discord
2019-12-28 00:07:39 +11:00
Geoffrey McRae
da5ebee3f7 [c-host] fix #220, invalid handle provided to WaitForObjects 2019-12-19 13:38:05 +11:00
Rikard Falkeborn
6530ca62da [client] fix return value in spice_read_nl error path
Returning -1 from a function with bool as return argument is the same as
returning true. If the channel is not connected, return false instead to
indicate the error.
2019-12-18 08:55:27 +11:00
Geoffrey McRae
0bd19cfd38 [c-host] dxgi: fix segfault with maxTextures=1 on client reconnect 2019-12-17 20:56:14 +11:00
Geoffrey McRae
8ada29e25f [c-host] nvfbc: fix build attempt 2 :) 2019-12-17 16:42:48 +11:00
Geoffrey McRae
3b5c1bd09c [c-host] nvfbc: fix failure to build due to new event interface 2019-12-17 16:41:02 +11:00
Geoffrey McRae
c82a5e0523 [c-host] dxgi: futher event improvements 2019-12-17 16:36:43 +11:00
Geoffrey McRae
9c5f9906fa [c-host] add spinlock support to events and alter dxgi to use them 2019-12-17 14:59:58 +11:00
Geoffrey McRae
db2f5b85a9 [c-host] dxgi: added new useAcquireLock option for quirked GPUs 2019-12-17 13:45:08 +11:00
Geoffrey McRae
547598c61c [common] locked section macro should use it's argument 2019-12-16 15:47:23 +11:00
Geoffrey McRae
711fbc549a [c-host] dxgi: interlock so we can map outside of the capture thread 2019-12-16 15:18:26 +11:00
Geoffrey McRae
f85c017184 [c-host] DXGI profiled and tuned again :) 2019-12-15 16:21:21 +11:00
Geoffrey McRae
85d46ed2b0 [profile] added a tool to help profile the host capture perf 2019-12-14 16:20:17 +11:00
Geoffrey McRae
2d9f578719 [c-host] windows: don't attach to the debuggers console 2019-12-13 23:33:11 +11:00
Geoffrey McRae
e75f3a7278 [c-host] windows: fix --help output in command prompt 2019-12-13 23:22:11 +11:00
Geoffrey McRae
26fa5c8860 [c-host] readme: change windows instructions to use MSYS2 2019-12-13 21:55:34 +11:00
Geoffrey McRae
ed5140568a [c-host] readme: added dev setup instructions for Windows 2019-12-13 21:13:17 +11:00
Andrew Sheldon
70110b4a5a [client] Use eglGetPlatformDisplay() to fix surface creation
[Why]
Recent versions of Mesa may have trouble with surface creation, resulting in
errors like:
egl.c:428  | egl_render_startup             | Failed to create EGL surface (eglError: 0x300b)

[How]
Replace eglGetDisplay() with eglGetPlatformDisplay(). Requires EGL 1.5, but should
be supported with any desktop driver released in the past few years.
2019-12-13 00:35:35 +11:00
Geoffrey McRae
a6f23f00b4 [client] opengl: handle configuration failure properly 2019-12-12 23:32:31 +11:00
Geoffrey McRae
30e3a43311 [client] opengl: fixed failure to render full frame 2019-12-12 23:04:58 +11:00
Geoffrey McRae
dce6aaefea [client] fix rare race condition when renderer is not ready 2019-12-10 03:30:04 +11:00
thejavascriptman
4843a278ff respect minimizeOnFocusLoss 2019-11-15 18:13:11 +11:00
Geoffrey McRae
fe7d611fb9 [misc] added sponsorship config for github 2019-10-30 18:40:12 +11:00
Geoffrey McRae
0e7e918e2c [client] cleanup and re-order startup/shutdown code 2019-10-26 12:03:10 +11:00
Geoffrey McRae
7d6e061ade [client] properly shutdown on failure to connect to the spice server 2019-10-26 11:27:05 +11:00
Geoffrey McRae
66891aa536 [client] don't require wayland-egl, fixes #204 2019-10-26 11:23:04 +11:00
Geoffrey McRae
1d7a2ccf82 [c-host] windows: update ivshmem driver header and usage 2019-10-24 19:46:09 +11:00
Geoffrey McRae
e1bfb1234b [common] obey the destination buffer size 2019-10-14 18:08:06 +11:00
Geoffrey McRae
9377fdfc37 [all] bump KVMFR version due to incompatible changes 2019-10-14 17:19:19 +11:00
Geoffrey McRae
5f1d17ba1f [host] cosmetics 2019-10-09 19:52:31 +11:00
Geoffrey McRae
4c0ca1c8e7 [client] fix xor support for masked color cursors
fixes #200
2019-10-09 19:48:42 +11:00
Geoffrey McRae
8ef1aee35c [common] fix bug in framebuffer_read 2019-10-09 14:11:45 +11:00
Geoffrey McRae
4168cc8d78 [all] fix the version 2019-10-09 14:04:36 +11:00
Geoffrey McRae
bca54ab1f6 [client/host] added new asyncronous memory copy
This changes the method of the memory copy from the host application to
the guest. Instead of performing a full copy from the capture device
into shared memory, and then flagging the new frame, we instead set a
write pointer, flag the client that there is a new frame and then copy
in chunks of 1024 bytes until the entire frame is copied. The client
upon seeing the new frame flag begins to poll at high frequency the
write pointer and upon each update copies as much as it can into the
texture.

This should improve latency but also slightly increase CPU usage on the
client due to the high frequency polling.
2019-10-09 13:53:02 +11:00
Geoffrey McRae
6d2c464436 [client] egl: improved streaming texture syncronization 2019-08-30 12:09:05 +10:00
Geoffrey McRae
e93bd7a3bf [client] fix shutdown race condition with the frame thread 2019-08-30 11:54:26 +10:00
Geoffrey McRae
da94075e7b [client] egl: more verbose error on texture egl failures 2019-08-30 11:40:38 +10:00
Geoffrey McRae
69522495de [client] fix invalid shutdown of renderer outside of it's thread 2019-08-30 11:36:28 +10:00
Geoffrey McRae
fce88fc72c [EGL] add debug printf helper 2019-08-30 11:33:43 +10:00
Geoffrey McRae
163a2e5d0a [client] fix failure to build due to broken symlink, fixes #173 2019-07-23 11:06:51 +10:00
Geoffrey McRae
b979752989 [client] added missing include 2019-07-15 18:30:39 +10:00
orcephrye
8ad2d5f949 [client] autodetect monitor refresh rate for fps limit 2019-07-10 05:04:29 +10:00
Rokas Kupstys
745ba66119 Implement option to disable minimizing window on focus loss. Default behavior is not changed - not configuring these options unfocused window is minimized.
* Added config entry win:minimizeOnFocusLoss (default true).
2019-07-09 21:57:47 +10:00
Geoffrey McRae
4cf6dec592 [all] allow disable of backtrace support during build 2019-06-19 09:13:03 +10:00
Geoffrey McRae
d7fa0aeff9 [client] fix typo in SDL_VIDEODRIVER from prior patch, whoops :) 2019-06-19 09:03:15 +10:00
Geoffrey McRae
2def6346e6 [client] don't override SDL_VIDEODRIVER if it is already set 2019-06-19 09:01:28 +10:00
Geoffrey McRae
607539a2af [client] improve streaming texture performance 2019-06-13 08:54:51 +10:00
Geoffrey McRae
6d24dd52d6 [c-host] not all versions of mingw support wcstombs_s
While the _s functions are for security as they avoid exceeding the
supplied buffer, in our case they are not really required as we are
allocating a buffer large enough to store the entire result.

Fixes #171
2019-06-12 15:31:18 +10:00
Omar Pakker
e3343cbd01 Rewrite dkms.conf
1) With the change to the Makefile, this update allows dkms to build and install the module for different kernels.
2) As per dkms documentation, no use of ${dkms_tree}.
3) Removed the use of REMAKE_INITRD as this module is not needed that early in the boot process.
4) Updated version to match what's defined in the module
2019-06-06 13:40:06 +10:00
Omar Pakker
71ffa0a137 Update makefile to allow kernel override 2019-06-06 13:40:06 +10:00
Geoffrey McRae
2b4f8091f9 [client] README.md cosmetics 2019-05-31 16:45:55 +10:00
Geoffrey McRae
113da121e9 [client] updated documentation for new keybinds 2019-05-31 16:44:08 +10:00
Geoffrey McRae
dd7413f973 [client] added keybinds to send Ctrl+Alt+Fn
Fixes #165
2019-05-31 16:39:55 +10:00
Geoffrey McRae
0851fd13e6 [all] made a nicer icon, hopefully just a placeholder for now 2019-05-30 22:21:53 +10:00
Geoffrey McRae
97024041f3 [client] allow the screensaver to run 2019-05-30 20:54:39 +10:00
Geoffrey McRae
22238c3200 [client] fix invalid access on early termination 2019-05-30 20:24:51 +10:00
Geoffrey McRae
780bb248f7 [c-host] dxgi: fix invalid cursor type define 2019-05-28 15:17:11 +10:00
Geoffrey McRae
026bdb00f2 [c-host] take just the ivshmem headers and omit the kvm-guest submodule 2019-05-28 14:51:47 +10:00
Geoffrey McRae
373d4ac932 [host] removed old host application from the project, see c-host 2019-05-28 14:47:09 +10:00
Geoffrey McRae
7d26027752 [c-host] resend the last on client reconnect if a timeout occurs 2019-05-28 14:24:48 +10:00
Geoffrey McRae
3d426ccef8 [all] fix missing cursor when client reconnects 2019-05-28 14:06:15 +10:00
Geoffrey McRae
b31e8e1cee [client] egl: remove accidental commit of debug code 2019-05-27 18:46:05 +10:00
Geoffrey McRae
f0923c4ed7 [client] egl: expose a few new tuneables 2019-05-27 18:42:46 +10:00
Geoffrey McRae
aabf19e63b [client] main: properly shutdown if renderer fails to init 2019-05-27 18:40:36 +10:00
Geoffrey McRae
f4fc1eb5f6 [client] typo in config help text 2019-05-27 18:40:36 +10:00
Geoffrey McRae
5e201a32ca [c-host] dxgi: allow out of order frame mapping 2019-05-27 15:26:58 +10:00
Geoffrey McRae
438e9e0969 [common] option: fixed missing null terminator 2019-05-27 01:56:55 +10:00
Geoffrey McRae
9554e82c47 [common] fix failure to initialize structure 2019-05-27 01:50:38 +10:00
Geoffrey McRae
4cf2c7a350 [client] check for failure to map pbo memory 2019-05-27 01:39:01 +10:00
Geoffrey McRae
664d7dccdb [client] fix the binary path when debugging 2019-05-27 01:38:50 +10:00
Geoffrey McRae
21b02efb4d [c-host] dxgi: don't stall the GPU pipeline to map textures to ram
ID3D11DeviceContext_Map by default will force a CPU sync if the prior call to
CopyResource has not completed, this change defers the mapping and sets the
D3D11_MAP_FLAG_DO_NOT_WAIT when attempting to map the texture allowing the
capture to continue without incurring an expensive CPU/GPU sync.

A new tuneable has also been added

  * dxgi:maxTextures
2019-05-26 23:36:17 +10:00
Jonathan (JJRcop) Rubenstein
d07aa4b29e [client] Add win:maximize to maximize on startup 2019-05-26 20:47:59 +10:00
JJRcop
9f33043d17 [client] Fix typo in example of README.md 2019-05-25 10:25:57 +10:00
Geoffrey McRae
2e6301fca1 [common] fixed issue with building for windows (typo) 2019-05-24 21:39:51 +10:00
Geoffrey McRae
83c5df2c47 [client] main: add + sign to mouse sensitivit alerts 2019-05-24 05:35:16 +10:00
Geoffrey McRae
759b4ef811 [client] fix typo 2019-05-24 05:31:16 +10:00
Geoffrey McRae
437ebf6265 [client] main: new feature to increase/decrease mouse sensitivity 2019-05-24 05:29:38 +10:00
Geoffrey McRae
bffd02b8c7 [client] main: better UX with the escape key combinations 2019-05-23 20:31:01 +10:00
Geoffrey McRae
196b27ee9c [doc] client: document defaults instead of my preconfigured values 2019-05-23 20:26:37 +10:00
Geoffrey McRae
ff08540fd3 [doc] fixed another formatting snafu 2019-05-23 20:20:18 +10:00
Geoffrey McRae
07be380f34 [doc] fix formatting a bit 2019-05-23 20:19:37 +10:00
Geoffrey McRae
76d58deefa [doc] Updated project documentation in leu of Beta 1 release 2019-05-23 20:13:41 +10:00
Geoffrey McRae
dba9764c5e [egl] alert: fix fuzzy font bug and make alerts a little more plesant 2019-05-23 19:58:12 +10:00
Geoffrey McRae
ee5d6c7c3e [module] update instructions and add dkms.conf
Fixes #148
2019-05-23 18:46:27 +10:00
Geoffrey McRae
1492196bbf [client] shutdown application on window close even if ignoreQuit is set
Fixes #138
2019-05-23 18:27:21 +10:00
Geoffrey McRae
9378f69653 [all] corrected NV keybinding information in README.md 2019-05-23 17:00:00 +10:00
Geoffrey McRae
d2d427b533 [client] egl: query maximum multisample support for MSAA context
Based on @rLink234's work in 4ac781b4516678b6c59d9ecf4a61df64a01ec8c1

Fixes #128
2019-05-23 16:56:50 +10:00
Geoffrey McRae
78a6af8dae [common] added new sysinfo unit and multisample query support
Based on @rLink234's work in 4ac781b4516678b6c59d9ecf4a61df64a01ec8c1
2019-05-23 16:54:50 +10:00
Geoffrey McRae
3585e02993 [client] egl: add options for maximum NV level and initial level
Adds options:

 * egl:nvGain
 * egl:nvGainMax

Fixes #153
2019-05-23 16:33:40 +10:00
Geoffrey McRae
5af88ae61e Set theme jekyll-theme-cayman 2019-05-23 16:21:32 +10:00
Geoffrey McRae
f946117dac [all] futher tweaks to the readme.md 2019-05-23 16:15:42 +10:00
Geoffrey McRae
666a6a218f [all] updated the main readme in preperation of beta 1 2019-05-23 16:08:22 +10:00
Geoffrey McRae
1b031582a4 [c-host] nvfbc: expose option to disable cursor decoupling 2019-05-23 15:12:28 +10:00
Geoffrey McRae
afe072adf1 [c-host] nvfbc: print out the SDK version and enable NvFBC 2019-05-23 14:49:38 +10:00
Geoffrey McRae
09d4fea9e2 [c-host] correct NvFBC information in the README.md 2019-05-23 14:31:05 +10:00
Geoffrey McRae
58c3fba6b9 [c-host] just another minor readme update 2019-05-23 13:42:51 +10:00
Geoffrey McRae
773dd7773b [c-host] try to be more compatible with mingw headers 2019-05-22 19:46:18 +10:00
Geoffrey McRae
732ce05866 [c-host] minor readme updates 2019-05-22 18:33:04 +10:00
Geoffrey McRae
108c7d3aaa [c-host] fixed project for cross compliation for Win on Linux
Thanks @fatalis for your guidance on this
2019-05-22 14:59:19 +10:00
Geoffrey McRae
86f4256b5a [client] egl: fix streaming texture re-init crash 2019-05-22 12:19:03 +10:00
Geoffrey McRae
84b2917706 [client] app: new options to reduce CPU usage
This patch increases the default cursor and frame polling interval from
1us to 1000us which for most use cases should be more then fast enough.

It also adds two new configuration options to adjust these should it be
required:

  * app:cursorPollInterval
  * app:framePollInterval
2019-05-22 12:00:06 +10:00
Geoffrey McRae
fc66a4a19c [client] egl: use persistant mapped texture buffers
While it is recommended to use memory barriers when updating a buffer
like we are, since we double buffer it is unlikely we will corrupt a
prior frame, and even if we do since it's just texture data at worst
we might see a tear.
2019-05-22 11:37:27 +10:00
Geoffrey McRae
087387087e [client] fix race condition on initial uniform access 2019-05-22 11:36:47 +10:00
Geoffrey McRae
3f404905d2 [c-host] added tray icon and context menu 2019-05-21 17:52:58 +10:00
Geoffrey McRae
67595d6deb [client] added missing semi-colon (not sure how that happened) 2019-05-21 15:51:45 +10:00
Geoffrey McRae
77f942711a [client] fixed typo in option description 2019-05-21 15:51:14 +10:00
Geoffrey McRae
e3c98ddc35 [client] port all configuration parsing to use the new option helper 2019-05-21 15:03:59 +10:00
Geoffrey McRae
db0d966102 [common] option: add debug errors for invalid options 2019-05-21 14:58:11 +10:00
Geoffrey McRae
a29639fceb [common] option: fix another invalid usage of an unset variable 2019-05-21 13:24:28 +10:00
Geoffrey McRae
0605b7df8c [common] option: allow short options to toggle boolean values 2019-05-21 12:58:53 +10:00
Geoffrey McRae
51ca08719e [common] option: trim whitespace from option names and values 2019-05-21 12:34:41 +10:00
Geoffrey McRae
ce9b94e93d [common] option: fix crash on failure to parse invalid config file 2019-05-21 12:28:13 +10:00
Geoffrey McRae
7cc0f7cb99 [common] option: fix incorrect column header padding 2019-05-21 11:38:40 +10:00
Geoffrey McRae
06c229dfd4 [common] option: fix invalid access of null/invalid options 2019-05-21 11:34:50 +10:00
Geoffrey McRae
2d5f6d65ce [common] option: added shortopt support and pretty help print 2019-05-21 11:31:31 +10:00
Geoffrey McRae
b9841351b4 [common] added stringutils with alloc_sprintf helper 2019-05-21 11:31:19 +10:00
Geoffrey McRae
d9b6d115d1 [common] fix stringlist const free bug 2019-05-21 11:30:05 +10:00
Geoffrey McRae
cc6dd58778 [c-host] windows: fix dxgi option struct syntax 2019-05-17 09:27:04 +10:00
Geoffrey McRae
0ba931cbed [c-host] windows: add log file output option 2019-05-17 09:26:42 +10:00
Geoffrey McRae
a7daeb2a12 [c-host] option: fix memory corruption due to usage of old pointers 2019-05-17 09:25:57 +10:00
Geoffrey McRae
2fe9dc7ca1 [common] track if option set failed and print help if so 2019-05-12 16:51:37 +10:00
Geoffrey McRae
b662128708 [c-host] linux: implemented getValues support for shmDevice option 2019-05-12 16:14:25 +10:00
Geoffrey McRae
e22f33a44b [common] add getValues callback for options 2019-05-12 16:13:50 +10:00
Geoffrey McRae
5d69d2aba9 [common] added new StringList helper module 2019-05-12 16:12:12 +10:00
Geoffrey McRae
0090580a64 [c-host] be compatible with new option ABI 2019-05-11 20:59:31 +10:00
Geoffrey McRae
538a6dc08e [common] rework option API to allow for custom types 2019-05-11 20:58:49 +10:00
Geoffrey McRae
5b199d8f25 [common] make local struct local 2019-05-11 19:07:10 +10:00
Geoffrey McRae
51ddb62126 [c-host] load config from looking-glass-host.ini if available 2019-05-11 18:23:06 +10:00
Geoffrey McRae
785bc33192 [common] added config file loading capability 2019-05-11 18:22:01 +10:00
Geoffrey McRae
522bacb1f0 [c-host] linux: remove extra shm device name validation
This is now validated by the option validator callback
2019-05-11 11:59:26 +10:00
Geoffrey McRae
cf030f6f0c [common] remove "Option" from the option help output 2019-05-11 11:51:29 +10:00
Geoffrey McRae
823164a924 [common] further option help cleanup 2019-05-11 11:50:26 +10:00
Geoffrey McRae
2ddae623b8 [c-host] update to use new option validator and tidy up output 2019-05-11 11:35:42 +10:00
Geoffrey McRae
86c7286aad [common] seperate validator and clean up output 2019-05-11 11:35:17 +10:00
Geoffrey McRae
9886316e07 [c-host] linux: fix shmDevice file size check 2019-05-11 11:23:27 +10:00
Geoffrey McRae
8a3356859c [c-host] implement shmDevice option validator and help 2019-05-11 11:21:18 +10:00
Geoffrey McRae
32d5f1db85 [common] validate all options including defaults 2019-05-11 11:21:00 +10:00
Paul Götzinger
b5975e0f05 [host] [c-host] added support to specify IVSHMEM device
[host] basic IVSHMEM device selecting implemented

Minor fixes for IVSHMEM device scanning

[c-host] added support to specify IVSHMEM device
2019-05-10 22:28:27 +10:00
Geoffrey McRae
53ade56b4e [common] fix option parser memory leak 2019-05-09 23:05:33 +10:00
Geoffrey McRae
5677117c0d [c-host] nvfbc: remove debug line from prior commit 2019-05-09 22:53:02 +10:00
Geoffrey McRae
558ae5dc45 [c-host] dxgi: initialize option to NULL for consistancy 2019-05-09 22:51:12 +10:00
Geoffrey McRae
83f63f4c42 [c-host] dxgi: add the ability to specify the adapter and/or output.
Fixes #132
2019-05-09 22:48:39 +10:00
Geoffrey McRae
247e92937c [common] match the correct option 2019-05-09 22:47:48 +10:00
Geoffrey McRae
63314941f6 [c-host] fix build under windows 2019-05-09 22:13:31 +10:00
Geoffrey McRae
e7345b9711 [c-host] initial agnostic option api and parser 2019-05-09 22:06:58 +10:00
Geoffrey McRae
22f9fa3938 [c-host] windows: fix errors from prior commit 2019-05-09 19:32:19 +10:00
Geoffrey McRae
4617829d41 [c-host] provide a platform agnostic method of passing args to app_main 2019-05-09 19:30:09 +10:00
Geoffrey McRae
fc907b802f [c-host] linux: updated to use the correct headers 2019-05-09 19:07:23 +10:00
Geoffrey McRae
ba50fbdc3e [client] egl: implement pixel perfect upscaling 2019-04-19 11:23:51 +10:00
Geoffrey McRae
6f77ba8aea [client] perform proper shutdown on SIGTERM 2019-04-14 09:15:03 +10:00
Geoffrey McRae
972ff93e6c [common] fix build under arch, thanks @techfreak for pointing this out 2019-04-12 12:20:24 +10:00
Geoffrey McRae
338bc2e0dc [c-host] nvfbc: disable ARGB10 until NVIDIA fix the API (if ever) 2019-04-11 19:30:42 +10:00
Geoffrey McRae
8cedad8241 [c-host] fix NvFBC build after moving headers 2019-04-11 17:15:17 +10:00
Geoffrey McRae
32bd6d96e3 [common] implemented crash handler for linux (including backtrace) 2019-04-11 16:41:52 +10:00
Geoffrey McRae
611216286e [c-host] added initial crash handler stub 2019-04-11 11:34:46 +10:00
Geoffrey McRae
d8915dbfc9 [build] make "common" a static library (part 2/2) 2019-04-11 11:12:59 +10:00
Geoffrey McRae
28b12c85f4 [build] make "common" a static library (part 1/2) 2019-04-11 11:03:30 +10:00
Geoffrey McRae
bee221c18d [c-host] add ability to provide NvFBC privData 2019-04-10 22:23:56 +10:00
Geoffrey McRae
878eb057d1 [c-host] better formatting of README.md 2019-04-10 22:08:14 +10:00
Geoffrey McRae
da7c66419a [c-host] a few build tweaks and updated README.md 2019-04-10 22:04:36 +10:00
Geoffrey McRae
d5ad53dae7 [c-host] stop the capture when stopping threads 2019-04-10 21:36:43 +10:00
Geoffrey McRae
a03075416c [c-host] cmake: fix bad method of setting CFLAGS 2019-04-10 21:20:15 +10:00
Geoffrey McRae
e4d8cf2d76 [c-host] remove extra NvFBC noise 2019-04-10 21:14:11 +10:00
Geoffrey McRae
8b47d740a8 bump version 2019-04-10 21:10:03 +10:00
Geoffrey McRae
0cac3e1c40 [c-host] tons of windows specific fixes 2019-04-10 21:07:56 +10:00
Geoffrey McRae
3f13485ced [c-host] nvfbc: continued implementation of NvFBC 2019-04-10 16:25:13 +10:00
Geoffrey McRae
24c99c4ff9 [c-host] added initial nvfbc support 2019-04-10 13:07:42 +10:00
Geoffrey McRae
4002f2716d [c-host] fix multiple re-inits due to threads flagging for reinit 2019-04-10 13:06:33 +10:00
Geoffrey McRae
f0758768b9 [c-host] show the build version 2019-04-09 16:30:07 +10:00
Geoffrey McRae
a82b1a2e2f [c-host] restructure project to use cmake 2019-04-09 16:28:11 +10:00
Łukasz Kostka
ccd0fd8902 Add install binary target 2019-04-05 03:18:11 +11:00
Geoffrey McRae
1fbba5cf2d [client] egl: make nightvision enhance luminosity before gain 2019-03-31 00:08:52 +11:00
Geoffrey McRae
d6805cfa0f [client] main: move config free into new config_free method 2019-03-30 16:00:47 +11:00
Geoffrey McRae
4dee965fdf [client] main: move config and option parsing into a seperate unit 2019-03-30 15:52:00 +11:00
Geoffrey McRae
35094a57cb [client] more cleanup and added alerts for new events 2019-03-30 12:26:06 +11:00
Geoffrey McRae
5d254c7751 [client] main: don't ignore keybinds when spice is disabled 2019-03-29 02:33:09 +11:00
Geoffrey McRae
10217fc8d9 [all] fix typo in readme 2019-03-29 02:27:17 +11:00
Geoffrey McRae
226dd28be8 [all] fix readme table formatting 2019-03-29 02:26:28 +11:00
Geoffrey McRae
c6d2b6ea8a [all] updated README.md 2019-03-29 02:25:30 +11:00
Geoffrey McRae
7fd4ba3aad [client] main: added <escape>+I for spice input enable toggle 2019-03-29 02:17:06 +11:00
Geoffrey McRae
ecfcf11c05 [client] main: fix errornous double keybind registration 2019-03-29 02:08:16 +11:00
Geoffrey McRae
30ea57c644 [client] main: add full screen toggle key bind <escape>+F
Fixes #139
2019-03-29 02:06:37 +11:00
Geoffrey McRae
c4001c727a [client] egl: added new super+N binding to increase image gain
This feture is to allow the use of the key combination <super>+N to
increase the brightness of the screen when using monitors with poor
backlighting. Can help in some games.

N = Night vision
2019-03-29 00:15:14 +11:00
Geoffrey McRae
fd4cfc2ff3 [client] main: add interface for modules to register key binds 2019-03-29 00:15:04 +11:00
Geoffrey McRae
03cb61f746 [client] main: prevent the cursor thread starting too early
fixes #136
2019-03-28 21:23:24 +11:00
Geoffrey McRae
8eed25b469 [client] cmake: list enabled features in configure output 2019-03-28 20:27:38 +11:00
Geoffrey McRae
ee09594190 [client] cmake: cosmetics 2019-03-28 20:15:13 +11:00
Geoffrey McRae
66c3c0115f [client] added options to disable/enable interfaces 2019-03-28 20:12:18 +11:00
Geoffrey McRae
3e021f3a6b [client] use cmake to generate renderers/fonts/clipboards headers/code
This is in preperation of cmake options to enable/disable various
functionallity.
2019-03-28 19:56:14 +11:00
Geoffrey McRae
b524c077a4 [client] egl: remove the rest of the shaders into seperate files 2019-03-28 15:53:15 +11:00
Geoffrey McRae
10f7efecb2 [client] cmake: fix inconsistent versioning 2019-03-28 15:03:35 +11:00
Geoffrey McRae
f09ee0bdb3 [client] egl: fix minor error in CMakeLists 2019-03-28 15:01:52 +11:00
Geoffrey McRae
d5a52241b0 [client] egl: move shaders into seperate files and build into objects 2019-03-28 14:59:54 +11:00
Geoffrey McRae
52c4e15c76 [client] project restructure part 2/2 2019-03-28 12:42:41 +11:00
Geoffrey McRae
fdba14691c [client] egl: requires gl 2019-03-28 12:31:28 +11:00
Geoffrey McRae
3d136a28a0 [all] added pre-commit script and VERSION
This script belongs in .git/hooks/ to bump the version each commit.
2019-03-28 12:24:38 +11:00
Geoffrey McRae
db398d41a0 [client] project restructure part 1/2 2019-03-28 11:02:36 +11:00
Geoffrey McRae
7cbaf8b5be [egl] don't assume SDL is compiled with Wayland support 2019-03-26 17:30:16 +11:00
NamoDev
d1c0d2b5f8 [CLIENT] cosmetic code changes 2019-03-18 08:32:38 +11:00
NamoDev
909606627f [CLIENT] Fixed invalid value initialization 2019-03-18 08:32:38 +11:00
NamoDev
80f5d3a660 [CLIENT] Added option for custom window title 2019-03-18 08:32:38 +11:00
Geoffrey McRae
182c4752d5 [c-host] dxgi: added pointer support 2019-03-04 19:26:19 +11:00
Geoffrey McRae
273ef55857 [c-host] app: added pointer interface and support 2019-03-04 19:26:02 +11:00
Geoffrey McRae
88c2e55acf [c-host] change getFrame/Pointer to return a real status 2019-03-04 17:55:45 +11:00
Geoffrey McRae
496fd79714 [c-host] initial stubs for pointer support 2019-03-04 17:45:19 +11:00
Geoffrey McRae
40a1b860bf [c-host] linux: updated, but not working yet :) 2019-03-04 17:08:49 +11:00
Geoffrey McRae
8120913acb [c-host] dxgi: reworked for better pipelining 2019-03-04 16:56:45 +11:00
Geoffrey McRae
935eb0651d [c-host] dxgi: remove needsUnmap bool 2019-03-04 15:11:40 +11:00
Geoffrey McRae
925a93686b [c-host] dxgi: cleanup init code failure path 2019-03-04 15:09:41 +11:00
Geoffrey McRae
6f545483c9 [c-host] rework events 2019-03-04 15:03:11 +11:00
Geoffrey McRae
a8b018d5da [c-host] app: fix updateEvent race problem 2019-03-04 13:38:17 +11:00
Geoffrey McRae
6e35033f2e [c-host] app: reinit on failure to capture a frame
A failed to capture a frame should not be considered fatal as it may be due to a driver issue or bug, instead try to reinitialize first
2019-03-04 13:06:30 +11:00
Geoffrey McRae
f79a1b2533 [c-host] dxgi: fixed memory leak 2019-03-04 12:04:17 +11:00
Geoffrey McRae
79ce98116a [c-host] dxgi: allow a 2nd frame to be captured during a prior copy 2019-03-04 10:42:54 +11:00
Geoffrey McRae
942c417cbb [c-host] dxgi: only flag frame updates if there was actually an update 2019-03-04 10:17:19 +11:00
Geoffrey McRae
8df850023c [c-host] fix deadlock when there is no update 2019-03-04 10:16:51 +11:00
Geoffrey McRae
eedde4abcb [c-host] fixed build under linux 2019-03-04 09:45:45 +11:00
Geoffrey McRae
fcc06dfad4 [c-host] app: inital capture is now working 2019-03-04 09:37:50 +11:00
Geoffrey McRae
ff850c4251 [c-host] remove testing code 2019-03-03 23:47:04 +11:00
Geoffrey McRae
20f8c92bb2 [c-host] dxgi: implement getFrame 2019-03-03 23:46:03 +11:00
Geoffrey McRae
22dcb39adb [c-host] app: fix deadlock on reinit 2019-03-03 23:45:37 +11:00
Geoffrey McRae
f572a72c2a [c-host] windows: added event support 2019-03-03 23:30:02 +11:00
Geoffrey McRae
be736c48e9 [c-host] dxgi: release resources 2019-03-03 21:55:29 +11:00
Geoffrey McRae
67c7c79dae [c-host] linux: add getFrame support to xcb capture 2019-03-02 20:33:45 +11:00
Geoffrey McRae
61108ba760 [c-host] app: add initial frame capture support 2019-03-02 20:33:21 +11:00
Geoffrey McRae
7285f9e9ad [c-host] add app_quit for clean shutdown support 2019-03-02 20:31:33 +11:00
Geoffrey McRae
b29de8f370 [c-host] add platform event interface and linux support 2019-03-02 20:22:35 +11:00
Geoffrey McRae
7a828b3aee [c-host] linux: initial xcb capture 2019-03-02 11:59:03 +11:00
Geoffrey McRae
afc264e846 [c-host] linux: added initial stubs for XCB capture 2019-03-01 21:41:32 +11:00
Geoffrey McRae
37c1d7ea58 [c-host] dont use a interface that fails to create 2019-03-01 21:41:06 +11:00
Geoffrey McRae
4a72dab02a [c-host] linux: correct device name comparison 2019-03-01 21:17:16 +11:00
Geoffrey McRae
22e5b323c8 [c-host] linux: simplify read from device 2019-03-01 21:12:42 +11:00
Geoffrey McRae
b275ac5765 [c-host] linux: check the device name 2019-03-01 21:12:11 +11:00
Geoffrey McRae
1475845675 [c-host] correct buffer size for stncat 2019-03-01 21:03:10 +11:00
Geoffrey McRae
6d6034870e [c-host] implemented linux ivshmem support 2019-03-01 21:01:25 +11:00
Geoffrey McRae
0a3b1e930a [c-host] update linux startup 2019-03-01 15:59:53 +11:00
Geoffrey McRae
836e8a5654 [c-host] improve capture reinitialization 2019-03-01 15:57:48 +11:00
Geoffrey McRae
39ac07bfde [c-host] removed debug code 2019-03-01 15:47:50 +11:00
Geoffrey McRae
fc178b40bc [c-host] general windows fixes 2019-03-01 15:46:09 +11:00
Geoffrey McRae
9170b24fee [c-host] added linux thread support 2019-03-01 12:54:31 +11:00
Geoffrey McRae
3674b4ed96 [c-host] added cursor and frame thread stubs 2019-03-01 12:42:12 +11:00
Geoffrey McRae
c9d9205bb8 [c-host] add missing pointer initialization 2019-03-01 12:41:37 +11:00
Geoffrey McRae
2c54fd2357 [c-host] added platform agnostic thread interface 2019-03-01 12:24:23 +11:00
Geoffrey McRae
d881df916e [c-host] more windows basics and ivshmem pointer init 2019-02-28 20:50:22 +11:00
Geoffrey McRae
6894ed7d5c [c-host] don't include windows.h in the main app 2019-02-28 19:47:25 +11:00
Geoffrey McRae
25a2b2d5d3 [c-host] fix makefile for linux builds 2019-02-28 19:46:33 +11:00
Geoffrey McRae
4fd62a58bd [c-host] move dxgi capture into windows directory 2019-02-28 19:44:15 +11:00
Geoffrey McRae
532dc07c7b [c-host] move windows dll folder into windows dir 2019-02-28 19:35:42 +11:00
Geoffrey McRae
fb2a2076a2 [c-host] added linux platform stubs 2019-02-28 19:31:04 +11:00
Geoffrey McRae
a8622be1c6 [c-host] added windows ivshmem unmap support 2019-02-28 19:27:17 +11:00
Geoffrey McRae
810fb73362 [common] gnuc on windows still uses / as the directory separator 2019-02-28 19:21:00 +11:00
Geoffrey McRae
6950379d94 [c-host] initial ivshmem code and platform specific init 2019-02-28 19:20:35 +11:00
Geoffrey McRae
f9020659e6 [c-host] only include windows defines when building on windows 2019-02-28 16:45:58 +11:00
Geoffrey McRae
c99f4e31c5 [c-host] added new pure C host project, see README.md 2019-02-28 16:35:30 +11:00
Geoffrey McRae
526c09b7ff [host] added missing file from build 2019-02-28 16:26:09 +11:00
Geoffrey McRae
5a37a53cb0 [host] move windows specific debug code to the host 2019-02-28 16:23:31 +11:00
Geoffrey McRae
a57d68acd5 [client] main: fixed help text typo 2019-02-26 03:12:57 +11:00
Geoffrey McRae
a33734e2d3 [client] main: help output cosmetics 2019-02-26 03:09:59 +11:00
Geoffrey McRae
e5921b3949 [client] main: better error text for changed spice options 2019-02-26 03:08:26 +11:00
Geoffrey McRae
5de25f2b43 [client] main: add options to control spice features 2019-02-26 03:06:53 +11:00
Geoffrey McRae
41f4166aed [client] x11: remove noise about unsupported formats 2019-02-25 05:44:31 +11:00
Geoffrey McRae
4f8fa6e7aa [client] fixes #135, double free crash 2019-02-25 05:43:18 +11:00
Geoffrey McRae
dbd09a431a Revert "[client] x11: prevent race condition causing double free"
This reverts commit 8d48dd973a.
2019-02-25 04:59:51 +11:00
Geoffrey McRae
8d48dd973a [client] x11: prevent race condition causing double free 2019-02-25 04:42:58 +11:00
Geoffrey McRae
c7666b314b [client] x11: fix bidirectional clipboard functionallity 2019-02-24 15:35:31 +11:00
Geoffrey McRae
03628505ed [client] spice: correct sending of large va agent buffers 2019-02-24 15:35:17 +11:00
Geoffrey McRae
b368873f4d [client] x11: ignore clipboard select events from ourself 2019-02-24 12:16:32 +11:00
Geoffrey McRae
dd38f3ce13 [client] x11: don't notify to clear if no property 2019-02-24 12:10:57 +11:00
Geoffrey McRae
d8b01c0257 [client] initial host to client clipboard sync working 2019-02-24 11:43:32 +11:00
Geoffrey McRae
0a2fbe1f7f [client] spice: implement full clipboard guest copy support 2019-02-23 04:24:30 +11:00
Geoffrey McRae
de0b54ae70 [client] cosmetics 2019-02-22 22:40:57 +11:00
Geoffrey McRae
54e8cce33c [client] core: added initial clipboard interface and x11 stubs 2019-02-22 22:38:52 +11:00
Geoffrey McRae
08bf01b649 [all] update copyright dates 2019-02-22 22:16:14 +11:00
Geoffrey McRae
1a66c11091 [client] spice: better align the spice api for x11 cliboard integration 2019-02-22 19:51:14 +11:00
Geoffrey McRae
689a1de69b [client] spice: added clipboard callbacks to decouple spice from SDL 2019-02-22 18:59:45 +11:00
Geoffrey McRae
0dfa7425c1 [client] spice: fixed copying large amounts of text to the clipboard 2019-02-22 12:33:55 +11:00
Geoffrey McRae
4098db039e [client] spice: fix read to allow for larger amounts of data 2019-02-22 09:15:29 +11:00
Geoffrey McRae
a7834611d1 [client] spice: fix memory leak 2019-02-22 09:08:53 +11:00
Geoffrey McRae
9dd4e4756b [client] spice: implemented guest -> client clipboard sync 2019-02-22 09:02:34 +11:00
Geoffrey McRae
108369414e [client] spice: implement channel caps and initial clipboard support 2019-02-22 07:40:43 +11:00
Geoffrey McRae
00e07c0384 [client] spice: remove extra debug output 2019-02-22 07:39:55 +11:00
Geoffrey McRae
1ebee561bc [client] spice: fix incorrect message size for agent messages 2019-02-22 07:38:05 +11:00
Geoffrey McRae
ec0db86663 [client] spice: fix unitialized value 2019-02-22 04:13:27 +11:00
Geoffrey McRae
3df4bb3a54 [client] spice: protocol updates for performance and agent support
Note: agent support is not complete at this point due to lack of documentation.
2019-02-22 03:04:06 +11:00
Geoffrey McRae
5bd748680f client: cosmetics 2019-02-22 03:04:06 +11:00
Marius Barbu
e09ff31c09 [client] update viewport size after window is created
Make sure glViewport gets called as soon as we know the window
dimensions, otherwise nothing gets rendered until KVMFR communication is
established (mostly the splash).

Without the fix, './looking-glass-client -s' behaves differently than
'./looking-glass-client -s -F' in that the latter triggers a
SDL_WINDOWEVENT_SIZE_CHANGED and updates the viewport immediately after
window creation while the former doesn't and all rendering is delayed
until the frameThread successfully decodes the first frame.

As all the possible ways of updating the viewport (window creation,
window size change, frame size change) are covered with
updatePositionInfo(), the 'started' state becomes redundant and is
removed.

Note: this might be the wrong way to fix it (possible driver bug?),
glViewport's specification mentions that the default viewport size
matches the window size when the GL context is first attached.

Tested on:

Debian Buster with GNOME 3.30.2 on X.Org 1.20.3
Looking Glass (a12-21-g07e4c1c20f)
Locking Method: Atomic
Using: EGL
Vendor  : Intel Open Source Technology Center
Renderer: Mesa DRI Intel(R) Haswell Desktop
Version : OpenGL ES 3.1 Mesa 18.3.2

Signed-off-by: Marius Barbu <msb@avengis.com>
2019-02-21 07:56:25 +11:00
Geoffrey McRae
07e4c1c20f [client] spice: correct issue with new channel list support 2019-02-21 05:31:51 +11:00
Geoffrey McRae
daf854c692 [client] spice: initial agent support 2019-02-21 05:06:13 +11:00
Geoffrey McRae
65c1e0391c [client] spice: minor fixes, don't assume inputs channel exists 2019-02-21 03:43:11 +11:00
Geoffrey McRae
769edba1a5 [client] spice: remove useless mouse queue, this is not needed
When this was first developed the official spice client was used as an example refrence, however upon inspecting the source of spice-server it has been determined that there is no server side queueing going on, and acks are simply sent after every 4 to reduce bandwidth.
2019-02-19 14:50:30 +11:00
Frediano Ziglio
2567447b24 [client] spice: use correct enumeration for SpiceLinkReply
SPICEC_ERROR_CODE_xxx constants are supposed to be used by old
client (obsoleted years ago).
SpiceLinkReply error field uses SPICE_LINK_ERR_xxx enumeration
constants.

Signed-off-by: Frediano Ziglio <fziglio@redhat.com>
2019-02-10 09:06:07 +11:00
Geoffrey McRae
263b412fdf [host] dxgi: fixed reversed logic for fallback 2019-02-07 15:13:07 +11:00
Geoffrey McRae
037ea5b1fc [host] fix compiler warnings 2019-02-07 14:43:18 +11:00
Geoffrey McRae
18634fa805 [host] fallback to IDXGIOutput1 if IDXGIOutput5 is not available 2019-02-07 14:43:18 +11:00
Geoffrey McRae
473e4716fc [client] spice: debug print unsigned int re #127 2019-01-25 14:17:06 +11:00
Dominik Csapak
59cac9c0cc change spice port type to unsigned short
so that ports >32767 get displayed correctly,
also signed overflow is undefined behaviour

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2019-01-25 14:15:22 +11:00
Geoffrey McRae
92d87d983b [client] spice: fix incorrect cursor button state being sent 2019-01-17 02:45:46 +11:00
Geoffrey McRae
bfc4a1bc16 [client] update client to handle new cursor move code 2019-01-12 00:00:21 +11:00
Geoffrey McRae
1ef61f6cd3 [host] use a global hook to obtain cursor move pos 2019-01-11 23:58:50 +11:00
Geoffrey McRae
5518ccb795 [host] NvFBC: user specified privateData for debug 2019-01-09 16:10:46 +11:00
Luke Brown
027b27dda1 updated links to latest accurate urls.
added trello board url.
2019-01-08 10:33:16 +11:00
Geoffrey McRae
6e1180ce06 [host] nvfbc: initial updates to re-enable support 2019-01-03 17:08:05 +11:00
Geoffrey McRae
e4ae9134ae [client] egl: fix graphical glitch with splash 2019-01-02 10:36:17 +11:00
Geoffrey McRae
640bc03c6b [client] [Patch 2/2] fixes #106 2019-01-02 10:30:55 +11:00
Geoffrey McRae
2a86339b1d [host] [Patch 1/2] fix copy of padded resolutions 2019-01-02 10:29:46 +11:00
Geoffrey McRae
667aed635d [client] egl: added untested support for wayland 2019-01-02 00:04:40 +11:00
Alexander Olofsson
1d3a23e051 Store the initial window size in the state
Fixes #107
2018-12-17 16:07:21 +11:00
Geoffrey McRae
507732587e [client] egl: fixed uninitialized variable bug 2018-12-16 12:17:12 +11:00
Geoffrey McRae
d1e3508d55 [client] fix early render issue 2018-12-16 11:02:51 +11:00
Geoffrey McRae
3a8998f1f9 [client] make EGL the default renderer 2018-12-16 11:02:40 +11:00
Geoffrey McRae
de5795e368 [client] egl: implemented alerts and some minor fixes to fps 2018-12-16 10:57:01 +11:00
Geoffrey McRae
fca71e2b95 [client] egl: slight correction to splash 2018-12-16 00:56:35 +11:00
Geoffrey McRae
0e2b371e59 [client] egl: added splash screen rendering 2018-12-16 00:54:37 +11:00
Geoffrey McRae
e1fa6b4057 [client] egl: fix cursor regression 2018-12-13 02:11:37 +11:00
Geoffrey McRae
b6c8d3fae5 [client] egl: fix mono cursor double height regression 2018-12-13 01:39:52 +11:00
Geoffrey McRae
eb1c61f335 [client] warn about poor FPS display performance 2018-12-13 01:34:23 +11:00
Geoffrey McRae
5842ce23a3 [client] pre-calculate the frame time 2018-12-13 01:34:14 +11:00
Geoffrey McRae
692d48df87 [client] don't calculate FPS if we are not showing it 2018-12-13 01:28:00 +11:00
Geoffrey McRae
49bd091359 [client] use clock_nanosleep for more accurate frame timing 2018-12-13 01:22:57 +11:00
Geoffrey McRae
5fe2db7e56 [client] rename fps variables to be more correct 2018-12-12 23:59:22 +11:00
Geoffrey McRae
b927f991d6 Revert "[client] improve fps target accuracy"
This reverts commit 4d7e1054bd.
This causes FPS runaway after a time
2018-12-12 23:40:29 +11:00
Geoffrey McRae
4d7e1054bd [client] improve fps target accuracy 2018-12-12 23:33:35 +11:00
Geoffrey McRae
42fa0e1d1f [client] egl: corrected fps alpha blending 2018-12-12 22:38:08 +11:00
Geoffrey McRae
abfe3a9b4d [client] egl: moved desktop rendering into seperate unit 2018-12-12 21:41:51 +11:00
Geoffrey McRae
b9f8f1a0ad [client] egl: add and use default quad helper for models 2018-12-12 20:08:52 +11:00
Geoffrey McRae
608b67af77 [client] egl: moved fps code into seperate unit 2018-12-12 20:04:43 +11:00
Geoffrey McRae
2a65e39848 [client] egl: added missing files from last commit 2018-12-12 18:57:31 +11:00
Geoffrey McRae
c23bf6a0c4 [client] egl: migrate cursor code into seperate unit 2018-12-12 18:53:55 +11:00
Geoffrey McRae
50c460df5a [client] define GL_GLEXT_PROTOTYPES globally 2018-12-12 16:53:30 +11:00
Geoffrey McRae
61f0577ab2 [client] egl: costmetics 2018-12-12 16:41:29 +11:00
Geoffrey McRae
a9aab3c1ee [client] egl: moved egl sources into subdirectory 2018-12-12 16:39:04 +11:00
Geoffrey McRae
73da86ac0e [client] egl: add checking to egl_shader_get_uniform_location 2018-12-12 16:32:16 +11:00
Geoffrey McRae
43d08df6b3 [client] egl: rename "shader" to "this" for consistancy 2018-12-12 16:31:25 +11:00
Geoffrey McRae
4654f317ca [client] opengl: fixed incorrect colors and added 10-bit RGBA support 2018-12-12 10:55:18 +11:00
Geoffrey McRae
d2b83027b4 [client] egl: removed accidental commit of test code 2018-12-11 16:35:53 +11:00
Geoffrey McRae
7be930a69c [client] removed use of now removed frame type, fixed #105 2018-12-11 16:34:41 +11:00
Geoffrey McRae
a1b1ed0060 [host] initiate the texture copy earlier 2018-12-07 20:54:30 +11:00
Geoffrey McRae
2cb18a3f8f [host] removed incomplete h264 2018-12-07 20:54:30 +11:00
Geoffrey McRae
2a30bb718a [host] updated to take advantage of DXGI v1.5 2018-12-04 21:26:46 +11:00
Geoffrey McRae
75ffcacfe4 [client] added support for RGBA, BGRA and 10-bit RGBA 2018-12-04 21:24:01 +11:00
Geoffrey McRae
1beeac545d [client] added missing lg-fonts.c to the repo 2018-11-20 22:34:01 +11:00
Geoffrey McRae
ab98c87e7c [client] egl: added FPS rendering 2018-11-20 09:50:09 +11:00
Geoffrey McRae
5b453d604e [client] remove other render modes from font ABI 2018-11-20 05:50:22 +11:00
Geoffrey McRae
90fc2a8164 [client] move FPS calculations out of renderers 2018-11-20 05:26:51 +11:00
Geoffrey McRae
0ed9301ed9 [client] font: implemented font ABI and updated OpenGL to use it 2018-11-20 04:38:53 +11:00
Geoffrey McRae
d235d076c4 [host] simplify capture logic and fix re-init bug 2018-11-02 21:38:02 +11:00
Geoffrey McRae
9f67f42f94 [host] fix hang on capture error 2018-10-19 21:16:47 +11:00
Geoffrey McRae
31a25c94c6 [host] fix failure to re-init 2018-10-19 21:16:42 +11:00
Geoffrey McRae
6a9f687eae [host] increase cursor ring size to avoid a race 2018-10-19 20:20:01 +11:00
Geoffrey McRae
df7e9b1184 [host] remove unused critical sections 2018-10-19 20:15:42 +11:00
Geoffrey McRae
1350ba6c4b [host] accumulate cursor updates rather then queue 2018-10-19 20:14:43 +11:00
Geoffrey McRae
2692ccc7b3 [client] use mouse visibility info properly 2018-10-09 18:33:18 +11:00
Geoffrey McRae
f36fd5ac1a [host] correct cursor visibility information 2018-10-09 18:28:08 +11:00
Geoffrey McRae
0e8678b182 [host] correct mouse position with hotspot offset 2018-10-09 18:10:59 +11:00
Geoffrey McRae
ce4f1be2a6 [host] fix cursor visibility bug 2018-10-09 17:52:13 +11:00
Geoffrey McRae
db907b1b67 [host] improve mouse sync with the client 2018-10-09 17:48:59 +11:00
Geoffrey McRae
d8b4d0c1ce [client] consume all SDL events in the filter 2018-10-04 17:18:09 +10:00
Geoffrey McRae
fb37174e5f [dxgi] cleaned up retry logic 2018-10-04 17:05:32 +10:00
Geoffrey McRae
9613127162 [client] better usage of SDL event loops 2018-10-04 17:03:09 +10:00
Geoffrey McRae
4e7de236d3 [egl] implement window positioning 2018-10-04 02:31:37 +10:00
Geoffrey McRae
741dfd418d [egl] improve texture upload performance 2018-10-04 00:09:47 +10:00
Geoffrey McRae
1d6dfa048e [client] tighten timings 2018-10-04 00:09:47 +10:00
Geoffrey McRae
8f0a6cd810 [host] general performance improvements 2018-10-04 00:07:34 +10:00
Geoffrey McRae
471303a179 [host] better sync, helps enormously with 4K!!! 2018-09-30 03:50:43 +10:00
Geoffrey McRae
73a2597c8a [dxgi] fix crash caused by failure to release in some instances. 2018-09-27 12:49:52 +10:00
Geoffrey McRae
3cd152c9d5 [host] DXGI capture improvements 2018-09-26 21:20:17 +10:00
Geoffrey McRae
e70928d603 [egl] fix incorrect xor blending for monochrome cursors 2018-09-25 23:32:45 +10:00
Geoffrey McRae
e2b33348f3 [egl] added monochrome cursor rendering 2018-09-25 23:04:29 +10:00
Geoffrey McRae
3ff712fea5 [egl] fix performance issue with cursor updates and add todo message 2018-09-24 20:26:31 +10:00
Geoffrey McRae
2db26ae37e [egl] fix incorrect mouse size and position scaling 2018-09-24 20:11:42 +10:00
Geoffrey McRae
375b97ca6f [egl] fix incorrect mouse colors 2018-09-24 19:52:44 +10:00
Geoffrey McRae
d331a3dd5a [egl] added intial cursor support 2018-09-24 19:48:11 +10:00
Geoffrey McRae
c0c63fd93b [egl] simplify yuv to rgb shader 2018-09-23 20:56:18 +10:00
Geoffrey McRae
b5a47cae25 [egl] implemented YUV420 decode support in hardware 2018-09-23 20:45:20 +10:00
Geoffrey McRae
1f1c9dfa59 [egl] don't re-create the buffer each frame 2018-09-23 16:56:09 +10:00
Geoffrey McRae
0903b4a610 [egl] make new OpenGL ES renderer available
Note that this renderer is incomplete at this time as it doesn't
render the cursor.
2018-09-23 16:04:20 +10:00
Geoffrey McRae
884ad6557b [egl] cleanup texture API 2018-09-23 15:56:47 +10:00
Geoffrey McRae
00658f3d64 [egl] split out texture code into it's own object 2018-09-23 15:48:44 +10:00
Geoffrey McRae
fff3ec30b8 [egl] added basic shaders and use dma to xfer buffer to the gpu 2018-09-22 18:00:52 +10:00
Geoffrey McRae
26434f7baf [egl] initial commit of new modern OpenGL ES renderer 2018-09-22 16:26:55 +10:00
Andy Chun
f75e2fe8db Default XDG_SESSION_TYPE to unspecified
Minimal systems in cases may not have XDG_SESSION_TYPE set at all, causing the program to segfault at the `strcmp`. This commit sets XDG_SESSION_TYPE to `unspecified` (according to https://www.freedesktop.org/software/systemd/man/pam_systemd.html) if it is not defined in the environment.
2018-08-03 10:02:54 +10:00
Yvan da Silva
0674e04597 Corrects an error in a debug message
* This happened during the last edit.
2018-07-30 08:07:53 +10:00
Yvan da Silva
29f1d6cd42 [client] Adds back support for wayland
* Since LG is now using SDL2, the SDL_VIDEODRIVER must be set.
* This fixes SDL error 'Couldn't find matching GLX visual' when creating the window.
2018-07-30 08:07:53 +10:00
Geoffrey McRae
83592f7e4a [client] cleanup of renderer API for better usage
* Added new on_render_start for render initialization
* Changed on_resize to execute inside the render thread
2018-07-28 14:49:37 +10:00
Geoffrey McRae
13cd50f92c [client] disable multisample after logo is gone
We only use multisample to smooth out the edges of the LG logo, it is
pointless to leave it on after the logo is gone.
2018-07-28 10:36:41 +10:00
Geoffrey McRae
a989914fef [host] remove the invalid usage of SafeRelease
SafeRelease was really useless, derefencing the smart pointers through
the use of & releases the value before SafeRelease get's to it. Instead
either allow the destructor to handle it's release, or explicityly
release it by assigning NULL
2018-07-28 10:27:50 +10:00
Geoffrey McRae
f692284f27 [host] don't uselessly try to scale 1:1 textures 2018-07-28 10:27:50 +10:00
Geoffrey McRae
05bd587c74 [client] implemented initial slow yuv420 support 2018-07-28 08:41:39 +10:00
Geoffrey McRae
d292d46fcb [host] correct YUV output, do not copy padding bytes 2018-07-28 07:48:10 +10:00
Geoffrey McRae
b899a65726 [host] correct RGBtoYUV shader output 2018-07-28 07:47:49 +10:00
Geoffrey McRae
63b4dd633c [host] correct invalid copy size for U & V planes 2018-07-28 07:25:00 +10:00
Geoffrey McRae
eba99f6968 [host] fix compiler warnings 2018-07-28 06:29:34 +10:00
Geoffrey McRae
354bef94ee [host] fixed project Release shader build 2018-07-28 06:25:41 +10:00
Geoffrey McRae
e515cdc8dd [host] added YUV420 output support 2018-07-28 06:19:59 +10:00
Geoffrey McRae
2a03d1c4a9 [host] removed unused shader view 2018-07-28 06:19:59 +10:00
Geoffrey McRae
3e3c409fc4 [host] ignore compiled shader headers 2018-07-28 06:19:59 +10:00
Geoffrey McRae
62e3dd250b [host] remove compiled shader headers from repo 2018-07-28 06:19:59 +10:00
Geoffrey McRae
3799929f59 [host] remove the depth buffer, it's unused 2018-07-28 06:19:59 +10:00
Geoffrey McRae
2019766989 [host] added format converter class 2018-07-28 06:19:58 +10:00
Geoffrey McRae
58c3b37e49 [h264] cosmetics 2018-07-28 06:19:58 +10:00
Geoffrey McRae
c650c2e474 [dxgi/h264] fix failure to re-init h264 correctly 2018-07-28 06:19:58 +10:00
Geoffrey McRae
ef336d552c [parser/nal] no need to check for null before free (fixes #87) 2018-07-26 06:09:12 +10:00
Geoffrey McRae
e4cdc58399 [host] move H264 out of DXGI into seperate class 2018-07-26 05:50:06 +10:00
Geoffrey McRae
48d3403c40 [memcpy] fix error caused by switch to shorter OPs 2018-07-26 05:49:24 +10:00
Geoffrey McRae
af143bdd82 [dxgi] update DXGI to use timeout return value 2018-07-26 03:09:59 +10:00
Geoffrey McRae
343983d9af [host] add timeout return value for repeated frame
This is to allow a repeat frame without incuring an additional
memory copy when the frame is already in shared memory.
2018-07-26 03:08:52 +10:00
Geoffrey McRae
5cabf155ab [host] flag paused when waiting for sec desktop 2018-07-24 01:12:24 +10:00
Geoffrey McRae
60070e6076 [client] implement stream paused alert 2018-07-24 01:09:53 +10:00
Geoffrey McRae
697dbc7a96 Updated README.md 2018-07-23 15:28:36 +10:00
Geoffrey McRae
43593d8aea [host] replaced MultiMemcpy with plain memcpySSE 2018-07-23 15:21:43 +10:00
Geoffrey McRae
1f90010cbd [client] add switch to disable alert messages, fixes #83 2018-07-20 01:09:51 +10:00
Geoffrey McRae
d839026ade [opengl] added fade out to wait screen 2018-07-20 01:01:16 +10:00
Geoffrey McRae
34de213926 [opengl] render alerts and fps on wait screen 2018-07-20 00:10:29 +10:00
Geoffrey McRae
b5ec4dd305 [client] scale up the logo and put a gradient behind it 2018-07-19 23:48:35 +10:00
Geoffrey McRae
023d3f811b [client] render the looking glass logo (almost) 2018-07-19 23:33:51 +10:00
Geoffrey McRae
53c32cc5a4 [client] enable multisampling 2018-07-19 23:33:26 +10:00
Geoffrey McRae
eb6ee8ea46 [client] allow window resize event's before startup 2018-07-19 23:32:42 +10:00
Geoffrey McRae
9f8c20c3e7 [common] no need to use AVX operands here either 2018-07-10 16:38:07 +10:00
Geoffrey McRae
a72ad4e46c [host] replaced use of AVX in memcpySSE
There is no need to use AVX instructions as we are not using the
wider registers. Removing their use removes the requirement to run
a AVX capable guest CPU.
2018-07-10 16:32:50 +10:00
williamvds
b19518a1f8 [client] Allow keyboard capture (#26) 2018-06-05 12:09:19 +10:00
Geoffrey McRae
8a9d0b0bfb [host] fix crash on screen blanking 2018-06-01 00:39:18 +10:00
Geoffrey McRae
14954cc426 [client] fix too early release of OpenGL context
Fixes a problem where resolution changes would require a restart of
the client
2018-05-31 18:54:29 +10:00
Geoffrey McRae
32dca9ea3f [client] only provide linker flags at link time 2018-05-31 18:54:10 +10:00
Geoffrey McRae
d4c41d2d94 [host] improve capture performance
Contrary to the MS documentation, benchmarking shows a substantial
increase in performance when releasing the captured frame as soon
as possible. This change makes it possible to achieve 60FPS at 4K
resolutions.
2018-05-31 18:53:11 +10:00
Txanton
3f331f2e62 Fixed typo in word wish 2018-05-31 13:28:36 +10:00
Geoffrey McRae
d753af9d17 [host] remove debug tracing noise 2018-05-29 18:37:21 +10:00
Geoffrey McRae
b23c7808c0 [host] don't count re-init requests as failures 2018-05-29 18:37:21 +10:00
Geoffrey McRae
792200cac4 [client] use atomic locking by default 2018-05-29 18:18:22 +10:00
Geoffrey McRae
526b607e37 [client] fixed warning/error text 2018-05-29 18:10:58 +10:00
Geoffrey McRae
298885083b [client] remove xlib shim as it is no longer needed 2018-05-29 18:09:27 +10:00
Geoffrey McRae
26c4804892 [client] replace custom Makefile with cmake build 2018-05-29 18:08:26 +10:00
Geoffrey McRae
a507dd0c51 [client] remove h264 decoder for now 2018-05-29 17:44:25 +10:00
Geoffrey McRae
c6830bab16 [client] remove useless linking against libssl 2018-05-29 17:44:07 +10:00
Geoffrey McRae
a0457a2dd9 [client] added nettle/bignum.h to rsa.c, CentOS 7 seems to need it 2018-05-29 14:55:28 +10:00
Geoffrey McRae
ce60cafa19 [client] removed invalid MS Windows specific hint
See: https://wiki.libsdl.org/SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4

> A hint that specifies that SDL should not to generate
> SDL_WINDOWEVENT_CLOSE events for Alt+F4 on Microsoft Windows.
2018-05-29 14:09:44 +10:00
Geoffrey McRae
9e02131525 [client] don't scale the mouse when in capture mode 2018-05-29 11:55:28 +10:00
Geoffrey McRae
6918eeca26 [client] added missing library for floor function 2018-05-29 11:08:56 +10:00
Geoffrey McRae
fbbee1cdac [opengl] added support for alerts 2018-05-29 11:08:25 +10:00
Geoffrey McRae
89959b48a7 [client] added linked list utility 2018-05-29 11:08:01 +10:00
Geoffrey McRae
b26a535451 [client] added support for alert text 2018-05-29 11:06:42 +10:00
Geoffrey McRae
86207993b8 [client] alert the user when capture mode is on or off 2018-05-29 09:02:34 +10:00
Geoffrey McRae
ee9213da76 [client] add alert method to renderer api 2018-05-29 08:59:07 +10:00
Geoffrey McRae
a084b2b32f [client] added the ability to specify the capture key (-m) 2018-05-29 08:51:58 +10:00
Geoffrey McRae
afdae8efc0 [client] allow the system screensaver to operate, -S to disable 2018-05-29 08:34:52 +10:00
Geoffrey McRae
64ad862116 [client] remove noise about scaling/alignment as this is fixed 2018-05-29 08:28:55 +10:00
r4m0n
4d81aaa763 Code cleanup 2018-05-29 02:18:17 +10:00
r4m0n
8cb25792ba Fixing scaled mouse movement 2018-05-29 02:18:17 +10:00
Geoffrey McRae
f715034fc4 [doc] make the module README a Markdown file 2018-05-28 17:51:03 +10:00
Geoffrey McRae
c97ebb135f [doc] Updated module readme 2018-05-28 17:50:32 +10:00
Geoffrey McRae
97749b335a [client] start rendering a little earlier 2018-05-28 15:36:12 +10:00
Geoffrey McRae
a647a602bf [opengl] render a blue screen while waiting for sync 2018-05-28 15:30:31 +10:00
commander kotori
80581a4aa2 [host] ask for unicode with mingw-w64 makefile
Pass -DUNICODE as a CFLAG.  The visual studio project asks for
a unicode (wide-string) build, but the unix makefile did not.

This fixes the build on msys2.
2018-05-28 12:10:52 +10:00
Geoffrey McRae
882b31aeaa [client] add support for masked colour cursors (fixes #61)
Also allows early SDL usage for cursor and keyboard control before
the host application starts
2018-05-28 11:40:56 +10:00
Geoffrey McRae
871aee2aae [host] fixed missed cursor shape updates 2018-05-28 10:34:24 +10:00
Geoffrey McRae
62e67c345c [client] reduce weight of FPS limiter feedback 2018-05-25 08:35:52 +10:00
Geoffrey McRae
5de9a8dce6 [client] prevent usleep underflow in FPS limiter 2018-05-24 18:10:23 +10:00
Geoffrey McRae
3adcbfaa7d [host] cosmetics 2018-05-24 18:08:59 +10:00
Geoffrey McRae
213c220d83 [host] don't assume the capture was successful 2018-05-24 17:05:49 +10:00
Geoffrey McRae
eef18dd655 [host] add critical section for m_cursorInfo 2018-05-24 16:50:50 +10:00
Geoffrey McRae
a4600e7278 [client] added FPS limiter for when running without vsync 2018-05-24 11:56:11 +10:00
Geoffrey McRae
c42bff99e2 [client] adjustments for better sync 2018-05-24 11:26:09 +10:00
Geoffrey McRae
b29f1c62bb [host] update to KVMFR v6 and decouple mouse 2018-05-24 11:24:24 +10:00
Geoffrey McRae
df7183a572 [kvmfr] decouple cursor flags from frame flags and fix timings 2018-05-24 09:01:53 +10:00
Geoffrey McRae
7a5bbb1e59 [client] add unix socket support, fixes #67 2018-05-23 12:16:44 +10:00
Geoffrey McRae
a3cd0385d0 [doc] Fix formatting 2018-05-23 08:46:03 +10:00
Geoffrey McRae
8fdc11813d [doc] Rename DEBUGGING file for Markdown formatting 2018-05-23 08:45:07 +10:00
Geoffrey McRae
fb412e8440 [doc] Added some documentaion for how to debug the LG client 2018-05-23 08:44:11 +10:00
Geoffrey McRae
15a337fee8 [host] use the new memcpySSE implementation 2018-05-22 18:59:24 +10:00
Geoffrey McRae
6f141fe393 [client] removed unused define in rsa.c 2018-05-22 15:40:08 +10:00
Geoffrey McRae
9b0f974648 [client] switch to nettle and gmp for RSA EME-OAEP support
Fixes #68
2018-05-22 15:36:36 +10:00
Geoffrey McRae
ceac6a60e6 [spice] initial GnuTLS implementation (incomplete) 2018-05-22 09:49:35 +10:00
Geoffrey McRae
cba6630aa0 [spice] relocate openssl code into seperate function
This is in preperation of switching to an alternative SSL library
as OpenSSL conflicts with the GNU licence.
2018-05-22 09:39:03 +10:00
Geoffrey McRae
6e0eac0abc [common] fix getopt license to be compatible 2018-05-22 03:21:35 +10:00
Geoffrey McRae
b3aadccfc4 [client] use glFinish to prevent buffering and re-enable by default 2018-05-21 23:16:16 +10:00
Geoffrey McRae
116926f7c0 [module] enable write-through mapping for the device
This is the magic I have been looking for the last few weeks, with
this change VM->VM is now useful at 60+fps :D
2018-05-21 21:56:53 +10:00
Geoffrey McRae
d0a7b8df9c [module] added 'test' to Makefile to simplify development testing 2018-05-21 21:56:23 +10:00
Geoffrey McRae
b8a1743d8f [client] fixed crash when specifying the shmFile as a command 2018-05-21 21:46:48 +10:00
Geoffrey McRae
e8b1b8fbdf [common] tune windows memcpySSE asm implementation:wq 2018-05-19 21:40:13 +10:00
Geoffrey McRae
e9d77e6c52 [common] inline memcpy into memcpySSE for the final bytes 2018-05-19 18:31:49 +10:00
Geoffrey McRae
56f0a8525b [common] more SSE improvements
* 32bit inlined is slow for only large copies, warn if memcpySSE is
used when it shouldn't be.

* Removed 64bit memcpySSE as native inlined is faster

See: https://stackoverflow.com/questions/50422510/why-is-i386-memcpy-slow-on-x86-64
2018-05-19 18:27:04 +10:00
Geoffrey McRae
778af24d82 [common] inline get_pc to memcpySSE 2018-05-19 16:21:12 +10:00
Geoffrey McRae
cd6caea4b0 [x86] use a proper call/ret to obtain the current IP
See: https://blogs.msdn.microsoft.com/oldnewthing/20041216-00/?p=36973
2018-05-19 16:16:01 +10:00
Geoffrey McRae
f63c8043af [common] new sse2 memcpy improvements 2018-05-18 20:56:57 +10:00
Geoffrey McRae
3c77c1eb2b NASM version of a SSE2 memcpy 2018-05-18 18:50:07 +10:00
Geoffrey McRae
ffec6c2014 Incoming new memcpy implementation 2018-05-18 01:59:00 +10:00
Geoffrey McRae
d097531926 [client] allow the mouse and keyboard to operate early 2018-05-16 18:19:32 +10:00
Geoffrey McRae
d339ca3599 [client] remove dependencies on libva until h264 is ready 2018-05-16 18:14:08 +10:00
Geoffrey McRae
adb1ca58b9 [opengl-basic] removed basic renderer as it is no longer needed 2018-05-16 18:13:20 +10:00
Geoffrey McRae
70ffe1de43 [h264] disable the module until I or someone finds time to finish it 2018-05-16 18:12:29 +10:00
Geoffrey McRae
cf4d16b528 [opengl] numerous improvements to buffer transfer 2018-05-16 17:58:36 +10:00
Geoffrey McRae
da2bcfdf9a [module] rename defines from LG to KVMFR 2018-05-15 20:53:10 +10:00
Geoffrey McRae
7f81d21aaa [module] added kvmfr kernel module for VM->VM shared memory 2018-05-15 20:50:52 +10:00
Geoffrey McRae
d0756cf00c [main] make it possible to manually specify the memory size 2018-05-15 20:07:48 +10:00
Geoffrey McRae
4fd59ce8c9 [opengl] fix free bug with new contiguous buffer 2018-05-15 19:25:22 +10:00
Geoffrey McRae
adca879fb9 [opengl] use a single contiguous buffer for the AMD pinned buffer 2018-05-15 19:23:57 +10:00
Geoffrey McRae
3a2d612b41 [decoders] change the API to allow more flexability in the future 2018-05-15 19:19:39 +10:00
Geoffrey McRae
332d53e016 [opengl] Add support for AMD_pinned_memory if it is available 2018-05-15 13:23:44 +10:00
Geoffrey McRae
ae1344d1a0 [spice] fix out by one error in call to strncpy 2018-05-15 09:56:42 +10:00
Geoffrey McRae
ae382949c8 [opengl] glxWaitVideoSyncSGI is not well supported in Mesa 18
It seems Mesa 18 has problems with the glxWaitVideoSyncSGI API so
we disable the 'preventBuffer' option by default
2018-05-15 09:54:24 +10:00
Geoffrey McRae
fd8d4d3d38 [client] enable configuration of spice via config 2018-02-05 19:08:31 +11:00
Geoffrey McRae
a2216e4b68 [client] cosmetics 2018-02-05 19:08:15 +11:00
Alam Arias
4fb9fc3b3f [client]: ignore Alt-F4 in ignoreQuit mode (#46)
* [client]: ignore Alt-F4 in ignoreQuit mode

* [client]: turn down the SNR for SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4

* [client]: turn down the SNR for SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS
2018-02-02 10:32:46 +11:00
arcnmx
35b4d75eea [host] mingw-w64 compile fixes (#44)
* [host] rename min() to LG_MIN()

* [host] format string type fixes, %Ix doesn't exist in mingw

* [host] DXGI minor fixes

* [host] mingw lacks media foundation api headers and QISearch
2018-01-30 21:07:46 +11:00
arcnmx
d7321d5f5f [client] fix event loop sleep (#45) 2018-01-30 21:07:14 +11:00
Geoffrey McRae
35eda57cb2 [client] remoted accidential commit of experimental method from header 2018-01-29 17:30:46 +11:00
Geoffrey McRae
78a100135b [client] fix improper spice socket shutdown 2018-01-29 17:27:12 +11:00
Geoffrey McRae
dc6932a9ba [client] no need to supress bin directory creation command 2018-01-29 17:04:08 +11:00
Geoffrey McRae
d765674913 [client] fix concurrent make, fixes #43 2018-01-29 17:02:41 +11:00
Geoffrey McRae
2af522aea7 [client] prevent 100% CPU usage in event loop, fixes #41 2018-01-29 16:56:23 +11:00
arcnmx
9aba969296 [client] fix SDL mutex compile errors (#42) 2018-01-29 16:51:14 +11:00
Geoffrey McRae
2114b73c11 [client] makefile: use $(CC) instead of assuming gcc 2018-01-25 09:58:03 +11:00
Geoffrey McRae
d591e2fd36 [client] added xlib-shim to disable calls to XSync
The compiled xlib-shim.so can be used to intercept and prevent SDL
from calling XSync, which causes latency issues on some video
hardware.

To use specify the full path to the file in the LD_PRELOAD
environment variable, like so:

LD_PRELOAD=/full/path/xlib-shim.so ./looking-glass
2018-01-25 09:55:21 +11:00
Geoffrey McRae
c61d97b0ac [client] spice: add channel and mouse locking
This fixes a race condition which causes the mouse ringbuffer to
overflow. It also corrects out of order message index IDs due to
multiple threads sending messages asyncronously.
2018-01-25 07:41:11 +11:00
Geoffrey McRae
37ea662998 [client] use SDL_SetEventFilter for better mouse performance
This partially resolves lag issues on hosts running the amdgpu driver.
If mouse caputure is enable the lag issue returns, this is because SDL
calls `XSync` in `X11_WarpMouse` and `X11_WarpMouseGlobal`, if these
calls are removed all input lag issues dissapear.

This issue has been reported to SDL as the calls to `XSync` are not
required per the xlib documentation.
2018-01-24 23:46:11 +11:00
Aaron
3d9d275d61 Ignore SDL_MOUSEBUTTONDOWN events incompatible with SPICE (#38)
* Ignore SDL_MOUSEBUTTONDOWN events that aren't compatible with the SPICE PS/2 mouse
2018-01-15 10:55:17 +11:00
Geoffrey McRae
a02087e5e4 [client] h264: unroll silly loop 2018-01-06 13:47:35 +11:00
Geoffrey McRae
2ccf17b9b7 [client] h264: setup slice parameters correctly (incomplete) 2018-01-06 13:43:24 +11:00
Geoffrey McRae
8ccce5666c [client] nal: corrections to parsing logic 2018-01-06 13:40:31 +11:00
Geoffrey McRae
859e984827 [client] h264: vaapi deprecated these fields, FMO is not supported
fbed1dbb5b/va/va.h (L2840)
2018-01-06 09:47:49 +11:00
Geoffrey McRae
5e84cfb3f1 [client] h264: use parameters from nal (incomplete) 2018-01-06 00:11:38 +11:00
Geoffrey McRae
5a84d3bef7 [client] h264: don't treat parse failure as fatal 2018-01-05 23:18:52 +11:00
Geoffrey McRae
634be5b096 [client] nal: removed useless debug print 2018-01-05 23:13:22 +11:00
Geoffrey McRae
80c9e24604 [client] nal: added final SLICE parser features 2018-01-05 23:09:43 +11:00
Geoffrey McRae
5808089fce [client] nal: added SLICE parser 2018-01-05 21:36:26 +11:00
Geoffrey McRae
d6f84ddd12 [client] nal: added PPS parser 2018-01-05 16:18:28 +11:00
Geoffrey McRae
c809eeb2a8 [client] fixed nal parser memory leaks 2018-01-05 15:27:36 +11:00
Geoffrey McRae
2dfb1cf1a6 [client] Makefile: stop at the first error 2018-01-05 11:45:30 +11:00
Geoffrey McRae
50ba9b4899 [client] added initial NAL unit parser 2018-01-05 11:36:45 +11:00
Geoffrey McRae
a36d312844 [host] dxgi: fixed missed header update for timeout fix 2018-01-04 09:30:55 +11:00
Geoffrey McRae
6653340bac [host] dxgi: follow suit and use h264 high profile 2018-01-04 09:30:27 +11:00
Geoffrey McRae
b9723adc30 [client] h264: use high profile as it's more compatible on newer cards 2018-01-04 09:27:24 +11:00
Geoffrey McRae
7648ea712c [client] opengl: fix termination on configure failure 2018-01-04 09:25:42 +11:00
Geoffrey McRae
3f29897506 [client] fix application termination on error 2018-01-04 09:25:17 +11:00
Geoffrey McRae
bebbdc4089 [client] h264 switch to contrained decoder as baseline is deprecated 2018-01-01 12:56:26 +11:00
Geoffrey McRae
9000fdf6fc [host] fix frame duplication problem with new MFT implementation 2017-12-31 00:32:39 +11:00
Geoffrey McRae
fbf08b94aa [client] initial vaapi h264 decode support (unfinished) 2017-12-31 00:27:26 +11:00
Geoffrey McRae
a6d2fe73ae [common] fixed bug in memcpySSE skipping remaining bytes 2017-12-30 18:39:57 +11:00
Geoffrey McRae
e854723aa3 [client] fixed incorrect cursor dataPos validation 2017-12-30 13:48:32 +11:00
Geoffrey McRae
9b7f54fa35 [host] service restarts now restart capture interfaces 2017-12-30 13:35:45 +11:00
Geoffrey McRae
9ef9f60505 [host] dxgi: fixed MFT memory leak and re-init failure 2017-12-30 13:35:45 +11:00
Geoffrey McRae
076a45acc5 [client] added initial decoder framework 2017-12-29 22:48:21 +11:00
Geoffrey McRae
c239306d82 [client] initial support for compressed frames 2017-12-29 21:20:51 +11:00
Geoffrey McRae
b5f2092e9c [host] return the compressed frame size in the pitch field 2017-12-29 21:01:02 +11:00
Geoffrey McRae
03622f61b0 [host] Added experimental H264 compression to DXGI (disabled by default)
This is not yet working, the client is yet to be updated to support
decompressing this stream.
2017-12-29 20:53:52 +11:00
Geoffrey McRae
3d9230ac93 [host] dxgi: fixed frame update regression 2017-12-29 07:15:59 +11:00
Geoffrey McRae
2d746cbfd4 [host] dxgi: performance improvements 2017-12-29 07:01:13 +11:00
Geoffrey McRae
2f2813037b [host] improved latency of multimemcpy with hybrid locking and preempt 2017-12-29 07:00:27 +11:00
Geoffrey McRae
f6f4c8070a [host] adjusted frame and cursor offset calculations 2017-12-29 06:11:32 +11:00
Geoffrey McRae
40bfdcdf8c [client] added configuration file loading support
the client now will look for a configuration file in the following
locations by default.

* /etc/looking-glass.conf
* ~/.looking-glass.conf

All configuration files are loaded and may override values specified by
any prior configuration files loaded.

Sample Config:

    global:
    {
      fullScreen=false;
      showFPS=true;
      x=0;
      y=0;
      w=800;
      h=600;
    }

    OpenGL:
    {
      mipmap="false";
    }
2017-12-28 19:58:19 +11:00
Geoffrey McRae
59fa025292 [host] add store fence to shm writes as we are using writecombine 2017-12-28 15:42:44 +11:00
Geoffrey McRae
e09d7f0ad0 [host] Update IVSHMEM class to support incoming driver version 2017-12-28 15:42:44 +11:00
Geoffrey McRae
6a6e53f728 [client] removed the dependency on the ivshmem-server
Since we do not use IRQs anymore we can use the ivshmem-plain device
which doesn't need the ivshmem-server. The QEMU arguments now should be
as follows:

-device ivshmem-plain,memdev=ivshmem
-object memory-backend-file,id=ivshmem,share=on,mem-path=/dev/shm/looking-glass,size=32M

Obviously adjusting the memory size as required. It is suggested that
the shared memory file be created before the guest is started with the
appropriate permissions, for example:

touch /dev/shm/looking-glass
chown user:kvm /dev/shm/looking-glass
chmod 660 /dev/shm/looking-glass
2017-12-28 15:34:18 +11:00
Geoffrey McRae
16e804b068 [host] added tracing class to help profile slow code points 2017-12-23 18:15:15 +11:00
Geoffrey McRae
db52a55b36 [client] opengl: remove deprecated glScissor, fixes FPS display 2017-12-23 17:40:50 +11:00
Geoffrey McRae
0574daca13 [client] removed unused function argument 2017-12-23 17:38:25 +11:00
242 changed files with 28327 additions and 8859 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: gnif
patreon: gnif
open_collective: # Replace with a single Open Collective username
ko_fi: lookingglass
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,11 +1,66 @@
### Required information
### Issues are for Bug Reports and Feature Requests Only!
Host CPU:
Host GPU:
Guest GPU:
Host Kernel version:
Host QEMU version:
If you are looking for help or support please use one of the following methods
Please describe what were you doing when the problem occured. If the Windows host application crashed please check for file named `looking-glass-host.dmp` and attach it to this bug report.
Create a New Topic on the Level1Tech's forum under the Looking Glass category:
* https://forum.level1techs.com/c/software/lookingGlass/142
**Reports that do no include this information will be ignored and closed**
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*
### Errors that are not bugs
Some errors generated by the LG client are not bugs, but rather issues with your
system's configuration and/or timing. Please do not report these, but rather use
one of the above resources to ask for advice/help.
* `LGMP_ERR_QUEUE_UNSUBSCRIBED` - Failure to heed advice on things such as
using `isolcpus` and CPU pinning may result in this message, especially if you
are over-taxing your CPU.
* `Could not create an SDL window: *` - Failure to create a SDL window is not an
issue with Looking Glass but rather a more substantial issue with your system,
such as missing hardware support for the RGBA32 pixmap format, or missing
required OpenGL EGL features.
* `The host application is not compatible with this client` - The Looking Glass
Host application in Windows is the incorrect version and is not compatible,
you need to make sure you run matching versions of both the host and client
applications.
### Bug Report Required Information
The entire (not truncated) output from the client application (if applicable).
To obtain this run `looking-glass-client` in a terminal.
```
PASTE CLIENT OUTPUT HERE
```
The entire (not truncated) log file from the host application (if applicable).
Normally, this is found on the guest system at:
%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
pertinent information.
```
PASTE HOST LOG FILE CONTENTS HERE
```
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.
https://www.youtube.com/watch?v=EqxxJK9Yo64
```
PASTE FULL BACKTRACE HERE
```

136
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,136 @@
name: build
on: [push, pull_request]
jobs:
client:
runs-on: ubuntu-20.04
strategy:
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Update apt
run: |
sudo apt-get update
- name: Install client dependencies
run: |
sudo apt-get install \
binutils-dev \
libsdl2-dev libsdl2-ttf-dev \
libspice-protocol-dev nettle-dev \
libx11-dev libxss-dev libxi-dev \
wayland-protocols
- name: Configure client
run: |
mkdir client/build
cd client/build
CC=/usr/bin/${{ matrix.cc }} cmake ..
- name: Build client
run: |
cd client/build
make -j$(nproc)
module:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Build kernel module
run: |
cd module
make
host-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Update apt
run: |
sudo apt-get update
- name: Install Linux host dependencies
run: |
sudo apt-get install binutils-dev libgl1-mesa-dev
- name: Configure Linux host
run: |
mkdir host/build
cd host/build
cmake ..
- name: Build Linux host
run: |
cd host/build
make -j$(nproc)
host-windows-cross:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Update apt
run: |
sudo apt-get update
- name: Install Windows host cross-compile dependencies
run: |
sudo apt-get install gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 nsis
- name: Configure Windows host for cross-compile
run: |
mkdir host/build
cd host/build
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
- name: Cross-compile Windows host
run: |
cd host/build
make -j$(nproc)
- name: Build Windows host installer
run: |
cd host/build
makensis platform/Windows/installer.nsi
host-windows-native:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Configure Windows host for native MinGW-w64
run: |
mkdir host\build
cd host\build
cmake -G "MinGW Makefiles" ..
- name: Build Windows host on native MinGW-w64
run: |
cd host\build
mingw32-make "-j$([Environment]::ProcessorCount)"
- name: Build Windows host installer
run: |
cd host\build
makensis platform\Windows\installer.nsi
obs:
runs-on: ubuntu-latest
strategy:
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Update apt
run: |
sudo apt-get update
- name: Install obs plugin dependencies
run: |
sudo apt-get install binutils-dev libobs-dev libgl1-mesa-dev
- name: Configure obs plugin
run: |
mkdir obs/build
cd obs/build
CC=/usr/bin/${{ matrix.cc }} cmake ..
- name: Build obs plugin
run: |
cd obs/build
make -j$(nproc)

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
module/*.ko
module/*.o
module/*.mod.c
module/.*
module/Module.symvers
module/modules.order
*.a
*.o
*.exe
*/build

9
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "vendor/kvm-guest-drivers-windows"]
path = vendor/kvm-guest-drivers-windows
url = https://github.com/virtio-win/kvm-guest-drivers-windows.git
[submodule "LGMP"]
path = repos/LGMP
url = https://github.com/gnif/LGMP.git
[submodule "repos/PureSpice"]
path = repos/PureSpice
url = https://github.com/gnif/PureSpice

View File

@@ -60,9 +60,9 @@ Members of the community that donated the funding to obtain the remaining hardwa
Michael Hillman
Andreas Jacobsen
NikkyAi
Michael Lindman
Michael Lindman
And another 41 people that whish to remain anonymous.
And another 41 people that wish to remain anonymous.
Thank you everyone for making this project possible.
- Geoffrey McRae <geoff@hostfission.com (gnif)

View File

@@ -1,14 +1,67 @@
# LookingGlass
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough.
# Looking Glass
* Project Website: https://looking-glass.hostfission.com
* Support Forum: https://forum.level1techs.com/t/looking-glass-guides-help-and-support/122387
* Windows Builds of the host application: https://looking-glass.hostfission.com/downloads
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
VGA PCI Passthrough.
* Project Website: https://looking-glass.io
* Getting Started: https://looking-glass.io/wiki/Installation
## Donations
I (Geoffrey McRae) am the primary developer behind this project and I have
invested thousands of hours of development time into it.
If you like this project and find it useful and would like to help out you can
support me directly using the following platforms.
* [GitHub](https://github.com/sponsors/gnif)
* [Ko-Fi](https://ko-fi.com/lookingglass)
* [Patreon](https://www.patreon.com/gnif)
* [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ESQ72XUPGKXRY)
* BTC - 14ZFcYjsKPiVreHqcaekvHGL846u3ZuT13
## Documentation
** IMPORTANT **
This project contains submodules that must be checked out if building from the
git repository! If you are not a developer and just want to compile Looking
Glass please download the source archive from the website instead:
https://looking-glass.io/downloads
Please also be sure to see the following files for more information
Note: The `README.md` files are slowly being deprecated from this project in
favor of the wiki at https://looking-glass.io/wiki, and as such the
information in these files may be dated.
* [client/README.md](client/README.md)
* [host/README.md](host/README.md)
* [module/README.md](module/README.md)
## Latest Version
If you would like to use the latest bleeding edge version of Looking Glass please
be aware there will be no support at this time.
Latest bleeding edge builds of the Windows host application can be obtained from:
https://looking-glass.io/downloads
# Help and support
## Web
https://forum.level1techs.com/t/looking-glass-guides-help-and-support/122387
https://forum.level1techs.com/c/software/lookingglass/142
## Discord
* Looking Glass: https://discord.gg/52SMupxkvt
* VFIO: https://discord.gg/4ahCn4c
## IRC
Join us in the #LookingGlass channel on the FreeNode network
## Trello
* https://trello.com/b/tI1Xbwsg/looking-glass

144
client/CMakeLists.txt Normal file
View File

@@ -0,0 +1,144 @@
cmake_minimum_required(VERSION 3.0)
project(looking-glass-client C)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
include(GNUInstallDirs)
include(CheckCCompilerFlag)
include(FeatureSummary)
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" ON)
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()
endif()
option(ENABLE_OPENGL "Enable the OpenGL renderer" ON)
add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.")
option(ENABLE_EGL "Enable the EGL renderer" ON)
add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.")
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
option(ENABLE_ASAN "Build with AddressSanitizer" OFF)
add_feature_info(ENABLE_ASAN ENABLE_ASAN "AddressSanitizer support.")
option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF)
add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.")
option(ENABLE_X11 "Build with X11 support" ON)
add_feature_info(ENABLE_X11 ENABLE_X11 "X11 support.")
option(ENABLE_WAYLAND "Build with Wayland support" ON)
add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
add_compile_options(
"-Wall"
"-Wextra"
"-Wno-sign-compare"
"-Wno-unused-parameter"
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
"-Werror"
"-Wfatal-errors"
"-ffast-math"
"-fdata-sections"
"-ffunction-sections"
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
)
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")
endif()
if(ENABLE_UBSAN)
add_compile_options("-fsanitize=undefined")
set(EXE_FLAGS "${EXE_FLAGS} -fsanitize=undefined")
endif()
find_package(PkgConfig)
pkg_check_modules(PKGCONFIG REQUIRED
sdl2
)
find_package(GMP)
add_definitions(-D ATOMIC_LOCKING)
add_definitions(-D GL_GLEXT_PROTOTYPES)
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/version.c
${CMAKE_BINARY_DIR}/_version.c
COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P
${PROJECT_TOP}/version.cmake
)
include_directories(
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
${PKGCONFIG_INCLUDE_DIRS} ${PKGCONFIG_OPT_INCLUDE_DIRS}
${GMP_INCLUDE_DIR}
)
link_libraries(
${PKGCONFIG_LIBRARIES} ${PKGCONFIG_OPT_LIBRARIES}
${GMP_LIBRARIES}
${CMAKE_DL_LIBS}
rt
m
)
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/util.c
src/clipboard.c
src/kb.c
src/egl_dynprocs.c
)
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" )
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" )
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
add_subdirectory(displayservers)
add_subdirectory(renderers)
add_subdirectory(fonts)
add_executable(looking-glass-client ${SOURCES})
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER} ${PKGCONFIG_OPT_CFLAGS_OTHER})
target_link_libraries(looking-glass-client
${EXE_FLAGS}
lg_common
displayservers
lgmp
purespice
renderers
fonts
)
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-client DESTINATION bin/ COMPONENT binary)
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)

32
client/DEBUGGING.md Normal file
View File

@@ -0,0 +1,32 @@
# Debugging the Looking Glass Client
If you are asked to provide debugging information to resolve an issue please
follow the following procedure.
## If you're experiencing a crash:
Run the program under the `gdb` debugger (you may need to install gdb), for
example:
gdb ./looking-glass-client
If you need to set any arguments, do so now by running `set args ARGS`, for
example:
set args -F -k
Now start the program by typing `r`. When the application crashes you will be
dumped back into the debugger, the application may appear to be frozen. Run
the following command:
thread apply all bt
Once you have this information please pastebin the log from looking-glass as
well as the information resulting from this command.
## If you're experencing high CPU load and/or poor performance.
The steps here are identical to the above, except instead of waiting for the
program to crash, in the debugger press `CTRL+C` while the program is
exhibiting the problem, then run `thread apply all bt` and pastebin your log
and the results of the command.

View File

@@ -1,39 +0,0 @@
BINARY = looking-glass-client
CFLAGS = -g -O3 -std=gnu99 -march=native -Wall -Werror -I./ -I../common -DDEBUG -DATOMIC_LOCKING
LDFLAGS = -lrt
CFLAGS += -ffast-math
CFLAGS += -fdata-sections -ffunction-sections
LDFLAGS += -Wl,--gc-sections
LIBS = sdl2 SDL2_ttf gl glu libssl openssl spice-protocol fontconfig x11
CFLAGS += $(shell pkg-config --cflags $(LIBS))
LDFLAGS += $(shell pkg-config --libs $(LIBS))
BUILD ?= .build
BIN ?= bin
CFLAGS += -DBUILD_VERSION='"$(shell git describe --always --long --dirty --abbrev=10 --tags)"'
OBJS = main.o \
lg-renderer.o \
spice/spice.o \
ivshmem/ivshmem.o \
renderers/opengl.o
# renderers/opengl-basic.o
BUILD_OBJS = $(foreach obj,$(OBJS),$(BUILD)/$(obj))
all: $(BIN)/$(BINARY)
$(BUILD)/%.o: %.c
@mkdir -p $(dir $@)
gcc -c $(CFLAGS) -o $@ $<
$(BIN)/$(BINARY): $(BUILD_OBJS)
@mkdir -p $(dir $@)
gcc -o $@ $(BUILD_OBJS) $(LDFLAGS)
clean:
rm -rf $(BUILD) $(BIN)
.PHONY: clean

205
client/README.md Normal file
View File

@@ -0,0 +1,205 @@
# Looking Glass Client
This is the Looking Glass client application that is designed to work in tandem with the Looking Glass Host application
---
## Building the Application
### Build Dependencies
* binutils-dev
* cmake
* fonts-freefont-ttf
* libsdl2-dev
* libsdl2-ttf-dev
* libspice-protocol-dev
* libfontconfig1-dev
* libx11-dev
* nettle-dev
* libxss-dev
* libxi-dev
#### Debian (and maybe Ubuntu)
apt-get install binutils-dev cmake fonts-freefont-ttf libsdl2-dev libsdl2-ttf-dev libspice-protocol-dev libfontconfig1-dev libx11-dev nettle-dev libxss-dev libxi-dev
### Building
mkdir build
cd build
cmake ../
make
Should this all go well you should be left with the file `looking-glass-client`
### Removing Wayland or X11 support
Wayland and/or X11 support can be disabled with the compile options
`ENABLE_WAYLAND` and `ENABLE_X11`, if both are specified only `SDL2` will remain
and the client will fallback to using it.
cmake ../ -DENABLE_WAYLAND=OFF
At this time, X11 is the perferred and best supported interface. Wayland is not
far behind, however it lacks some of the seamless interaction features that X11
has due to the lack of cursor warp (programmatic movement of the local cusror) on
Wayland.
---
## Usage Tips
### Key Bindings
By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key.
Below are a list of current key bindings:
| Command | Description |
|-|-|
| <kbd>ScrLk</kbd> | Toggle cursor screen capture |
| <kbd>ScrLk</kbd>+<kbd>F</kbd> | Full Screen toggle |
| <kbd>ScrLk</kbd>+<kbd>V</kbd> | Video stream toggle |
| <kbd>ScrLk</kbd>+<kbd>I</kbd> | Spice keyboard & mouse enable toggle |
| <kbd>ScrLk</kbd>+<kbd>N</kbd> | Toggle night vision mode (EGL renderer only!) |
| <kbd>ScrLk</kbd>+<kbd>R</kbd> | Rotate the output clockwise by 90 degree increments |
| <kbd>ScrLk</kbd>+<kbd>Q</kbd> | Quit |
| <kbd>ScrLk</kbd>+<kbd>Insert</kbd> | Increase mouse sensitivity (in capture mode only) |
| <kbd>ScrLk</kbd>+<kbd>Del</kbd> | Decrease mouse sensitivity (in capture mode only) |
| <kbd>ScrLk</kbd>+<kbd>F1</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F1</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F2</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F2</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F3</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F3</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F4</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F4</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F5</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F5</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F6</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F6</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F7</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F7</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F8</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F8</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F9</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F9</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F10</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F10</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F11</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F11</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F12</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F12</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>LWin</kbd> | Send <kbd>LWin</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>RWin</kbd> | Send <kbd>RWin</kbd> to the guest |
### Setting options via command line arguments
The syntax is simple: `module:name=value`, for example:
./looking-glass-client win:fullScreen=yes egl:nvGain=1
### Setting options via configuration files
By default the application will look for and load the config files in the following locations
* /etc/looking-glass-client.ini
* ~/.looking-glass-client.ini
The format of this file is the commonly known INI format, for example:
[win]
fullScreen=yes
[egl]
nvGain=1
Command line arguments will override any options loaded from the config files.
### Supported options
```
|--------------------------------------------------------------------------------------------------------------------------------------------------|
| Long | Short | Value | Description |
|--------------------------------------------------------------------------------------------------------------------------------------------------|
| app:configFile | -C | NULL | A file to read additional configuration from |
| app:renderer | -g | auto | Specify the renderer to use |
| app:license | -l | no | Show the license for this application and then terminate |
| app:cursorPollInterval | | 1000 | How often to check for a cursor update in microseconds |
| app:framePollInterval | | 1000 | How often to check for a frame update in microseconds |
| app:allowDMA | | yes | Allow direct DMA transfers if possible (VM-VM only for now) |
| app:shmFile | -f | /dev/shm/looking-glass | The path to the shared memory file, or the name of the kvmfr device to use, ie: kvmfr0 |
|--------------------------------------------------------------------------------------------------------------------------------------------------|
|---------------------------------------------------------------------------------------------------------------------------------|
| Long | Short | Value | Description |
|---------------------------------------------------------------------------------------------------------------------------------|
| win:title | | Looking Glass (client) | The window title |
| win:position | | center | Initial window position at startup |
| win:size | | 1024x768 | Initial window size at startup |
| win:autoResize | -a | no | Auto resize the window to the guest |
| win:allowResize | -n | yes | Allow the window to be manually resized |
| win:keepAspect | -r | yes | Maintain the correct aspect ratio |
| win:forceAspect | | yes | Force the window to maintain the aspect ratio |
| win:dontUpscale | | no | Never try to upscale the window |
| win:borderless | -d | no | Borderless mode |
| win:fullScreen | -F | no | Launch in fullscreen borderless mode |
| win:maximize | -T | no | Launch window maximized |
| win:minimizeOnFocusLoss | | yes | Minimize window on focus loss |
| win:fpsMin | -K | -1 | Frame rate minimum (0 = disable - not recommended, -1 = auto detect) |
| win:showFPS | -k | no | Enable the FPS & UPS display |
| win:ignoreQuit | -Q | no | Ignore requests to quit (ie: Alt+F4) |
| win:noScreensaver | -S | no | Prevent the screensaver from starting |
| win:alerts | -q | yes | Show on screen alert messages |
| win:quickSplash | | no | Skip fading out the splash screen when a connection is established |
| win:rotate | | 0 | Rotate the displayed image (0, 90, 180, 270) |
|---------------------------------------------------------------------------------------------------------------------------------|
|-----------------------------------------------------------------------------------------------------------------------------------------------|
| 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 |
|------------------------------------------------------------------------------------------------------------------|
| spice:enable | -s | yes | Enable the built in SPICE client for input and/or clipboard support |
| spice:host | -c | 127.0.0.1 | The SPICE server host or UNIX socket |
| spice:port | -p | 5900 | The SPICE server port (0 = unix socket) |
| spice:input | | yes | Use SPICE to send keyboard and mouse input events to the guest |
| spice:clipboard | | yes | Use SPICE to syncronize the clipboard contents with the guest |
| spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM |
| spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
| spice:captureOnStart | | no | Capture mouse and keyboard on start |
| spice:alwaysShowCursor | | no | Always show host cursor |
|------------------------------------------------------------------------------------------------------------------|
|--------------------------------------------------------------------------------------------------------------|
| Long | Short | Value | Description |
|--------------------------------------------------------------------------------------------------------------|
| egl:vsync | | no | Enable vsync |
| egl:doubleBuffer | | no | Enable double buffering |
| egl:multisample | | yes | Enable Multisampling |
| egl:nvGainMax | | 1 | The maximum night vision gain |
| egl:nvGain | | 0 | The initial night vision gain at startup |
| egl:cbMode | | 0 | Color Blind Mode (0 = Off, 1 = Protanope, 2 = Deuteranope, 3 = Tritanope) |
|--------------------------------------------------------------------------------------------------------------|
|------------------------------------------------------------------------------------|
| Long | Short | Value | Description |
|------------------------------------------------------------------------------------|
| opengl:mipmap | | yes | Enable mipmapping |
| opengl:vsync | | no | Enable vsync |
| 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

@@ -0,0 +1,19 @@
# Try to find the GMP librairies
# GMP_FOUND - system has GMP lib
# GMP_INCLUDE_DIR - the GMP include directory
# GMP_LIBRARIES - Libraries needed to use GMP
if (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
# Already in cache, be silent
set(GMP_FIND_QUIETLY TRUE)
endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
find_path(GMP_INCLUDE_DIR NAMES gmp.h )
find_library(GMP_LIBRARIES NAMES gmp libgmp )
find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx )
MESSAGE(STATUS "GMP libs: " ${GMP_LIBRARIES} " " ${GMPXX_LIBRARIES} )
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES)
mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES)

View File

@@ -0,0 +1,34 @@
function(make_object out_var)
set(result)
set(result_h)
foreach(in_f ${ARGN})
file(RELATIVE_PATH out_f ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}")
set(out_h "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.h")
set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.o")
string(REGEX REPLACE "[/.]" "_" sym_in ${in_f})
add_custom_command(OUTPUT ${out_f}
COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} ${in_f}
COMMAND ${CMAKE_OBJCOPY} --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_start=b_${sym_in} ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_end=b_${sym_in}_end ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --strip-symbol _binary_${sym_in}_size ${out_f} ${out_f}
DEPENDS ${in_f}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Creating object from ${in_f}"
VERBATIM
)
file(WRITE ${out_h} "extern const char b_${sym_in}[];\n")
file(APPEND ${out_h} "extern const char b_${sym_in}_end[];\n")
file(APPEND ${out_h} "#define b_${sym_in}_size (b_${sym_in}_end - b_${sym_in})\n")
get_filename_component(h_dir ${out_h} DIRECTORY)
list(APPEND result_h ${h_dir})
list(APPEND result ${out_f})
endforeach()
list(REMOVE_DUPLICATES result_h)
set(${out_var}_OBJS "${result}" PARENT_SCOPE)
set(${out_var}_INCS "${result_h}" PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,50 @@
cmake_minimum_required(VERSION 3.0)
project(displayservers LANGUAGES C)
set(DISPLAYSERVER_H "${CMAKE_BINARY_DIR}/include/dynamic/displayservers.h")
set(DISPLAYSERVER_C "${CMAKE_BINARY_DIR}/src/displayservers.c")
file(WRITE ${DISPLAYSERVER_H} "#include \"interface/displayserver.h\"\n\n")
file(APPEND ${DISPLAYSERVER_H} "extern struct LG_DisplayServerOps * LG_DisplayServers[];\n\n")
file(WRITE ${DISPLAYSERVER_C} "#include \"interface/displayserver.h\"\n\n")
file(APPEND ${DISPLAYSERVER_C} "#include <stddef.h>\n\n")
set(DISPLAYSERVERS "_")
set(DISPLAYSERVERS_LINK "_")
function(add_displayserver name)
set(DISPLAYSERVERS "${DISPLAYSERVERS};${name}" PARENT_SCOPE)
set(DISPLAYSERVERS_LINK "${DISPLAYSERVERS_LINK};displayserver_${name}" PARENT_SCOPE)
add_subdirectory(${name})
endfunction()
# Add/remove displayservers here!
if (ENABLE_WAYLAND)
add_displayserver(Wayland)
endif()
if (ENABLE_X11)
add_displayserver(X11)
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)
list(LENGTH DISPLAYSERVERS DISPLAYSERVER_COUNT)
file(APPEND ${DISPLAYSERVER_H} "#define LG_DISPLAYSERVER_COUNT ${DISPLAYSERVER_COUNT}\n")
foreach(displayserver ${DISPLAYSERVERS})
file(APPEND ${DISPLAYSERVER_C} "extern struct LG_DisplayServerOps LGDS_${displayserver};\n")
endforeach()
file(APPEND ${DISPLAYSERVER_C} "\nconst struct LG_DisplayServerOps * LG_DisplayServers[] =\n{\n")
foreach(displayserver ${DISPLAYSERVERS})
file(APPEND ${DISPLAYSERVER_C} " &LGDS_${displayserver},\n")
endforeach()
file(APPEND ${DISPLAYSERVER_C} " NULL\n};")
add_library(displayservers STATIC ${DISPLAYSERVER_C})
target_link_libraries(displayservers ${DISPLAYSERVERS_LINK})

View File

@@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.0)
project(displayserver_SDL LANGUAGES C)
#find_package(PkgConfig)
#pkg_check_modules(DISPLAYSERVER_SDL_PKGCONFIG REQUIRED
# #sdl2
#)
add_library(displayserver_SDL STATIC
sdl.c
)
target_link_libraries(displayserver_SDL
${DISPLAYSERVER_SDL_PKGCONFIG_LIBRARIES}
${DISPLAYSERVER_SDL_OPT_PKGCONFIG_LIBRARIES}
lg_common
)
target_include_directories(displayserver_SDL
PRIVATE
src
${DISPLAYSERVER_SDL_PKGCONFIG_INCLUDE_DIRS}
${DISPLAYSERVER_SDL_OPT_PKGCONFIG_INCLUDE_DIRS}
)

View File

@@ -0,0 +1,196 @@
/*
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 <linux/input.h>
#include <SDL2/SDL.h>
//FIXME: this should be made static once config.c is no longer using SDL
//scancodes
uint32_t sdl_to_xfree86[SDL_NUM_SCANCODES] =
{
[SDL_SCANCODE_UNKNOWN] /* = USB 0 */ = KEY_RESERVED,
[SDL_SCANCODE_A] /* = USB 4 */ = KEY_A,
[SDL_SCANCODE_B] /* = USB 5 */ = KEY_B,
[SDL_SCANCODE_C] /* = USB 6 */ = KEY_C,
[SDL_SCANCODE_D] /* = USB 7 */ = KEY_D,
[SDL_SCANCODE_E] /* = USB 8 */ = KEY_E,
[SDL_SCANCODE_F] /* = USB 9 */ = KEY_F,
[SDL_SCANCODE_G] /* = USB 10 */ = KEY_G,
[SDL_SCANCODE_H] /* = USB 11 */ = KEY_H,
[SDL_SCANCODE_I] /* = USB 12 */ = KEY_I,
[SDL_SCANCODE_J] /* = USB 13 */ = KEY_J,
[SDL_SCANCODE_K] /* = USB 14 */ = KEY_K,
[SDL_SCANCODE_L] /* = USB 15 */ = KEY_L,
[SDL_SCANCODE_M] /* = USB 16 */ = KEY_M,
[SDL_SCANCODE_N] /* = USB 17 */ = KEY_N,
[SDL_SCANCODE_O] /* = USB 18 */ = KEY_O,
[SDL_SCANCODE_P] /* = USB 19 */ = KEY_P,
[SDL_SCANCODE_Q] /* = USB 20 */ = KEY_Q,
[SDL_SCANCODE_R] /* = USB 21 */ = KEY_R,
[SDL_SCANCODE_S] /* = USB 22 */ = KEY_S,
[SDL_SCANCODE_T] /* = USB 23 */ = KEY_T,
[SDL_SCANCODE_U] /* = USB 24 */ = KEY_U,
[SDL_SCANCODE_V] /* = USB 25 */ = KEY_V,
[SDL_SCANCODE_W] /* = USB 26 */ = KEY_W,
[SDL_SCANCODE_X] /* = USB 27 */ = KEY_X,
[SDL_SCANCODE_Y] /* = USB 28 */ = KEY_Y,
[SDL_SCANCODE_Z] /* = USB 29 */ = KEY_Z,
[SDL_SCANCODE_1] /* = USB 30 */ = KEY_1,
[SDL_SCANCODE_2] /* = USB 31 */ = KEY_2,
[SDL_SCANCODE_3] /* = USB 32 */ = KEY_3,
[SDL_SCANCODE_4] /* = USB 33 */ = KEY_4,
[SDL_SCANCODE_5] /* = USB 34 */ = KEY_5,
[SDL_SCANCODE_6] /* = USB 35 */ = KEY_6,
[SDL_SCANCODE_7] /* = USB 36 */ = KEY_7,
[SDL_SCANCODE_8] /* = USB 37 */ = KEY_8,
[SDL_SCANCODE_9] /* = USB 38 */ = KEY_9,
[SDL_SCANCODE_0] /* = USB 39 */ = KEY_0,
[SDL_SCANCODE_RETURN] /* = USB 40 */ = KEY_ENTER,
[SDL_SCANCODE_ESCAPE] /* = USB 41 */ = KEY_ESC,
[SDL_SCANCODE_BACKSPACE] /* = USB 42 */ = KEY_BACKSPACE,
[SDL_SCANCODE_TAB] /* = USB 43 */ = KEY_TAB,
[SDL_SCANCODE_SPACE] /* = USB 44 */ = KEY_SPACE,
[SDL_SCANCODE_MINUS] /* = USB 45 */ = KEY_MINUS,
[SDL_SCANCODE_EQUALS] /* = USB 46 */ = KEY_EQUAL,
[SDL_SCANCODE_LEFTBRACKET] /* = USB 47 */ = KEY_LEFTBRACE,
[SDL_SCANCODE_RIGHTBRACKET] /* = USB 48 */ = KEY_RIGHTBRACE,
[SDL_SCANCODE_BACKSLASH] /* = USB 49 */ = KEY_BACKSLASH,
[SDL_SCANCODE_SEMICOLON] /* = USB 51 */ = KEY_SEMICOLON,
[SDL_SCANCODE_APOSTROPHE] /* = USB 52 */ = KEY_APOSTROPHE,
[SDL_SCANCODE_GRAVE] /* = USB 53 */ = KEY_GRAVE,
[SDL_SCANCODE_COMMA] /* = USB 54 */ = KEY_COMMA,
[SDL_SCANCODE_PERIOD] /* = USB 55 */ = KEY_DOT,
[SDL_SCANCODE_SLASH] /* = USB 56 */ = KEY_SLASH,
[SDL_SCANCODE_CAPSLOCK] /* = USB 57 */ = KEY_CAPSLOCK,
[SDL_SCANCODE_F1] /* = USB 58 */ = KEY_F1,
[SDL_SCANCODE_F2] /* = USB 59 */ = KEY_F2,
[SDL_SCANCODE_F3] /* = USB 60 */ = KEY_F3,
[SDL_SCANCODE_F4] /* = USB 61 */ = KEY_F4,
[SDL_SCANCODE_F5] /* = USB 62 */ = KEY_F5,
[SDL_SCANCODE_F6] /* = USB 63 */ = KEY_F6,
[SDL_SCANCODE_F7] /* = USB 64 */ = KEY_F7,
[SDL_SCANCODE_F8] /* = USB 65 */ = KEY_F8,
[SDL_SCANCODE_F9] /* = USB 66 */ = KEY_F9,
[SDL_SCANCODE_F10] /* = USB 67 */ = KEY_F10,
[SDL_SCANCODE_F11] /* = USB 68 */ = KEY_F11,
[SDL_SCANCODE_F12] /* = USB 69 */ = KEY_F12,
[SDL_SCANCODE_PRINTSCREEN] /* = USB 70 */ = KEY_SYSRQ,
[SDL_SCANCODE_SCROLLLOCK] /* = USB 71 */ = KEY_SCROLLLOCK,
[SDL_SCANCODE_PAUSE] /* = USB 72 */ = KEY_PAUSE,
[SDL_SCANCODE_INSERT] /* = USB 73 */ = KEY_INSERT,
[SDL_SCANCODE_HOME] /* = USB 74 */ = KEY_HOME,
[SDL_SCANCODE_PAGEUP] /* = USB 75 */ = KEY_PAGEUP,
[SDL_SCANCODE_DELETE] /* = USB 76 */ = KEY_DELETE,
[SDL_SCANCODE_END] /* = USB 77 */ = KEY_END,
[SDL_SCANCODE_PAGEDOWN] /* = USB 78 */ = KEY_PAGEDOWN,
[SDL_SCANCODE_RIGHT] /* = USB 79 */ = KEY_RIGHT,
[SDL_SCANCODE_LEFT] /* = USB 80 */ = KEY_LEFT,
[SDL_SCANCODE_DOWN] /* = USB 81 */ = KEY_DOWN,
[SDL_SCANCODE_UP] /* = USB 82 */ = KEY_UP,
[SDL_SCANCODE_NUMLOCKCLEAR] /* = USB 83 */ = KEY_NUMLOCK,
[SDL_SCANCODE_KP_DIVIDE] /* = USB 84 */ = KEY_KPSLASH,
[SDL_SCANCODE_KP_MULTIPLY] /* = USB 85 */ = KEY_KPASTERISK,
[SDL_SCANCODE_KP_MINUS] /* = USB 86 */ = KEY_KPMINUS,
[SDL_SCANCODE_KP_PLUS] /* = USB 87 */ = KEY_KPPLUS,
[SDL_SCANCODE_KP_ENTER] /* = USB 88 */ = KEY_KPENTER,
[SDL_SCANCODE_KP_1] /* = USB 89 */ = KEY_KP1,
[SDL_SCANCODE_KP_2] /* = USB 90 */ = KEY_KP2,
[SDL_SCANCODE_KP_3] /* = USB 91 */ = KEY_KP3,
[SDL_SCANCODE_KP_4] /* = USB 92 */ = KEY_KP4,
[SDL_SCANCODE_KP_5] /* = USB 93 */ = KEY_KP5,
[SDL_SCANCODE_KP_6] /* = USB 94 */ = KEY_KP6,
[SDL_SCANCODE_KP_7] /* = USB 95 */ = KEY_KP7,
[SDL_SCANCODE_KP_8] /* = USB 96 */ = KEY_KP8,
[SDL_SCANCODE_KP_9] /* = USB 97 */ = KEY_KP9,
[SDL_SCANCODE_KP_0] /* = USB 98 */ = KEY_KP0,
[SDL_SCANCODE_KP_PERIOD] /* = USB 99 */ = KEY_KPDOT,
[SDL_SCANCODE_NONUSBACKSLASH] /* = USB 100 */ = KEY_102ND,
[SDL_SCANCODE_APPLICATION] /* = USB 101 */ = KEY_COMPOSE,
[SDL_SCANCODE_POWER] /* = USB 102 */ = KEY_POWER,
[SDL_SCANCODE_KP_EQUALS] /* = USB 103 */ = KEY_KPEQUAL,
[SDL_SCANCODE_F13] /* = USB 104 */ = KEY_CONFIG,
[SDL_SCANCODE_F14] /* = USB 105 */ = KEY_F14,
[SDL_SCANCODE_F15] /* = USB 106 */ = KEY_F15,
[SDL_SCANCODE_F16] /* = USB 107 */ = KEY_F16,
[SDL_SCANCODE_F17] /* = USB 108 */ = KEY_F17,
[SDL_SCANCODE_F18] /* = USB 109 */ = KEY_F18,
[SDL_SCANCODE_F19] /* = USB 110 */ = KEY_F19,
[SDL_SCANCODE_F20] /* = USB 111 */ = KEY_F20,
[SDL_SCANCODE_HELP] /* = USB 117 */ = KEY_HELP,
[SDL_SCANCODE_MENU] /* = USB 118 */ = KEY_MENU,
[SDL_SCANCODE_STOP] /* = USB 120 */ = KEY_CANCEL,
[SDL_SCANCODE_AGAIN] /* = USB 121 */ = KEY_AGAIN,
[SDL_SCANCODE_UNDO] /* = USB 122 */ = KEY_UNDO,
[SDL_SCANCODE_CUT] /* = USB 123 */ = KEY_CUT,
[SDL_SCANCODE_COPY] /* = USB 124 */ = KEY_COPY,
[SDL_SCANCODE_PASTE] /* = USB 125 */ = KEY_PASTE,
[SDL_SCANCODE_FIND] /* = USB 126 */ = KEY_FIND,
[SDL_SCANCODE_MUTE] /* = USB 127 */ = KEY_MUTE,
[SDL_SCANCODE_VOLUMEUP] /* = USB 128 */ = KEY_VOLUMEUP,
[SDL_SCANCODE_VOLUMEDOWN] /* = USB 129 */ = KEY_VOLUMEDOWN,
[SDL_SCANCODE_KP_COMMA] /* = USB 133 */ = KEY_KPCOMMA,
[SDL_SCANCODE_INTERNATIONAL1] /* = USB 135 */ = KEY_RO,
[SDL_SCANCODE_INTERNATIONAL2] /* = USB 136 */ = KEY_KATAKANAHIRAGANA,
[SDL_SCANCODE_INTERNATIONAL3] /* = USB 137 */ = KEY_YEN,
[SDL_SCANCODE_INTERNATIONAL4] /* = USB 138 */ = KEY_HENKAN,
[SDL_SCANCODE_INTERNATIONAL5] /* = USB 139 */ = KEY_MUHENKAN,
[SDL_SCANCODE_LANG1] /* = USB 144 */ = KEY_HANGEUL,
[SDL_SCANCODE_LANG2] /* = USB 145 */ = KEY_HANJA,
[SDL_SCANCODE_SYSREQ] /* = USB 154 */ = KEY_RIGHTSHIFT,
[SDL_SCANCODE_CANCEL] /* = USB 155 */ = KEY_STOP,
[SDL_SCANCODE_KP_LEFTPAREN] /* = USB 182 */ = KEY_KPLEFTPAREN,
[SDL_SCANCODE_KP_RIGHTPAREN] /* = USB 183 */ = KEY_KPRIGHTPAREN,
[SDL_SCANCODE_KP_PLUSMINUS] /* = USB 215 */ = KEY_KPPLUSMINUS,
[SDL_SCANCODE_LCTRL] /* = USB 224 */ = KEY_LEFTCTRL,
[SDL_SCANCODE_LSHIFT] /* = USB 225 */ = KEY_LEFTSHIFT,
[SDL_SCANCODE_LALT] /* = USB 226 */ = KEY_LEFTALT,
[SDL_SCANCODE_LGUI] /* = USB 227 */ = KEY_LEFTMETA,
[SDL_SCANCODE_RCTRL] /* = USB 228 */ = KEY_RIGHTCTRL,
[SDL_SCANCODE_RSHIFT] /* = USB 229 */ = KEY_RIGHTSHIFT,
[SDL_SCANCODE_RALT] /* = USB 230 */ = KEY_RIGHTALT,
[SDL_SCANCODE_RGUI] /* = USB 231 */ = KEY_RIGHTMETA,
[SDL_SCANCODE_MODE] /* = USB 257 */ = KEY_ZENKAKUHANKAKU,
[SDL_SCANCODE_AUDIONEXT] /* = USB 258 */ = KEY_NEXTSONG,
[SDL_SCANCODE_AUDIOPREV] /* = USB 259 */ = KEY_PREVIOUSSONG,
[SDL_SCANCODE_AUDIOSTOP] /* = USB 260 */ = KEY_STOPCD,
[SDL_SCANCODE_AUDIOPLAY] /* = USB 261 */ = KEY_PLAYPAUSE,
[SDL_SCANCODE_MEDIASELECT] /* = USB 263 */ = KEY_MEDIA,
[SDL_SCANCODE_WWW] /* = USB 264 */ = KEY_WWW,
[SDL_SCANCODE_MAIL] /* = USB 265 */ = KEY_MAIL,
[SDL_SCANCODE_CALCULATOR] /* = USB 266 */ = KEY_CALC,
[SDL_SCANCODE_COMPUTER] /* = USB 267 */ = KEY_COMPUTER,
[SDL_SCANCODE_AC_SEARCH] /* = USB 268 */ = KEY_SEARCH,
[SDL_SCANCODE_AC_HOME] /* = USB 269 */ = KEY_HOMEPAGE,
[SDL_SCANCODE_AC_BACK] /* = USB 270 */ = KEY_BACK,
[SDL_SCANCODE_AC_FORWARD] /* = USB 271 */ = KEY_FORWARD,
[SDL_SCANCODE_AC_REFRESH] /* = USB 273 */ = KEY_REFRESH,
[SDL_SCANCODE_AC_BOOKMARKS] /* = USB 274 */ = KEY_BOOKMARKS,
[SDL_SCANCODE_BRIGHTNESSDOWN] /* = USB 275 */ = KEY_BRIGHTNESSDOWN,
[SDL_SCANCODE_BRIGHTNESSUP] /* = USB 276 */ = KEY_BRIGHTNESSUP,
[SDL_SCANCODE_DISPLAYSWITCH] /* = USB 277 */ = KEY_SWITCHVIDEOMODE,
[SDL_SCANCODE_KBDILLUMTOGGLE] /* = USB 278 */ = KEY_KBDILLUMTOGGLE,
[SDL_SCANCODE_KBDILLUMDOWN] /* = USB 279 */ = KEY_KBDILLUMDOWN,
[SDL_SCANCODE_KBDILLUMUP] /* = USB 280 */ = KEY_KBDILLUMUP,
[SDL_SCANCODE_EJECT] /* = USB 281 */ = KEY_EJECTCD,
[SDL_SCANCODE_SLEEP] /* = USB 282 */ = KEY_SLEEP,
[SDL_SCANCODE_APP1] /* = USB 283 */ = KEY_PROG1,
[SDL_SCANCODE_APP2] /* = USB 284 */ = KEY_PROG2,
[SDL_SCANCODE_AUDIOREWIND] /* = USB 285 */ = KEY_REWIND,
[SDL_SCANCODE_AUDIOFASTFORWARD] /* = USB 286 */ = KEY_FASTFORWARD,
};

View File

@@ -0,0 +1,551 @@
/*
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 "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;
};
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(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)
{
}
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)
{
return false;
}
#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();
break;
case SDL_MOUSEMOTION:
// stop motion events during the warp out of the window
if (sdl.exiting)
break;
app_updateCursorPos(event->motion.x, event->motion.y);
app_handleMouseRelative(event->motion.xrel, event->motion.yrel,
event->motion.xrel, event->motion.yrel);
break;
case SDL_MOUSEBUTTONDOWN:
{
int button = event->button.button;
if (button > 3)
button += 2;
app_handleButtonPress(button);
break;
}
case SDL_MOUSEBUTTONUP:
{
int button = event->button.button;
if (button > 3)
button += 2;
app_handleButtonRelease(button);
break;
}
case SDL_MOUSEWHEEL:
{
int button = event->wheel.y > 0 ? 4 : 5;
app_handleButtonPress(button);
app_handleButtonRelease(button);
break;
}
case SDL_KEYDOWN:
{
SDL_Scancode sc = event->key.keysym.scancode;
app_handleKeyPress(sdl_to_xfree86[sc]);
break;
}
case SDL_KEYUP:
{
SDL_Scancode sc = event->key.keysym.scancode;
app_handleKeyRelease(sdl_to_xfree86[sc]);
break;
}
case SDL_WINDOWEVENT:
switch(event->window.event)
{
case SDL_WINDOWEVENT_ENTER:
app_handleEnterEvent(true);
break;
case SDL_WINDOWEVENT_LEAVE:
sdl.exiting = false;
app_handleEnterEvent(false);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
app_handleFocusEvent(true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
app_handleFocusEvent(false);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
{
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);
break;
case SDL_WINDOWEVENT_CLOSE:
app_handleCloseEvent();
break;
}
break;
}
return 0;
}
static void sdlShowPointer(bool show)
{
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
}
static void sdlGrabPointer(void)
{
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
SDL_SetRelativeMouseMode(SDL_TRUE);
sdl.pointerGrabbed = true;
}
static void sdlUngrabPointer(void)
{
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
SDL_SetRelativeMouseMode(SDL_FALSE);
sdl.pointerGrabbed = false;
}
static void sdlGrabKeyboard(void)
{
if (sdl.pointerGrabbed)
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
else
{
DEBUG_WARN("SDL does not support grabbing only the keyboard, grabbing all");
sdl.pointerGrabbed = true;
}
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
sdl.keyboardGrabbed = true;
}
static void sdlUngrabKeyboard(void)
{
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0");
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
if (sdl.pointerGrabbed)
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
sdl.keyboardGrabbed = false;
}
static void sdlWarpPointer(int x, int y, bool exiting)
{
if (sdl.exiting)
return;
sdl.exiting = exiting;
// if exiting turn off relative mode
if (exiting)
SDL_SetRelativeMouseMode(SDL_FALSE);
// issue the warp
SDL_WarpMouseInWindow(sdl.window, x, y);
}
static void sdlRealignPointer(void)
{
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)
{
SDL_DisableScreenSaver();
}
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 =
{
.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

@@ -0,0 +1,76 @@
cmake_minimum_required(VERSION 3.0)
project(displayserver_Wayland LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED
wayland-client
)
#pkg_check_modules(DISPLAYSERVER_Wayland_OPT_PKGCONFIG
#)
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
${DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES}
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES}
lg_common
)
target_include_directories(displayserver_Wayland
PRIVATE
src
${DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS}
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_INCLUDE_DIRS}
)
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15)
pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir)
macro(wayland_generate protocol_file output_file)
add_custom_command(OUTPUT "${output_file}.h"
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h"
DEPENDS "${protocol_file}"
VERBATIM)
add_custom_command(OUTPUT "${output_file}.c"
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c"
DEPENDS "${protocol_file}"
VERBATIM)
target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c")
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")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-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

@@ -0,0 +1,201 @@
/*
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
*/
#define _GNU_SOURCE
#include "wayland.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#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"
static struct Option waylandOptions[] =
{
{
.module = "wayland",
.name = "warpSupport",
.description = "Enable cursor warping",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{0}
};
static bool waylandEarlyInit(void)
{
// 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
// that pipe.
signal(SIGPIPE, SIG_IGN);
return true;
}
static void waylandSetup(void)
{
option_register(waylandOptions);
}
static bool waylandProbe(void)
{
return getenv("WAYLAND_DISPLAY") != NULL;
}
static bool waylandInit(const LG_DSInitParams params)
{
memset(&wlWm, 0, sizeof(wlWm));
wlWm.warpSupport = option_get_bool("wayland", "warpSupport");
wlWm.display = wl_display_connect(NULL);
if (!waylandPollInit())
return false;
if (!waylandRegistryInit())
return false;
if (!waylandIdleInit())
return false;
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;
}
static void waylandStartup(void)
{
}
static void waylandShutdown(void)
{
}
static void waylandFree(void)
{
waylandIdleFree();
waylandWindowFree();
waylandInputFree();
waylandRegistryFree();
wl_display_disconnect(wlWm.display);
}
static bool waylandGetProp(LG_DSProperty prop, void * ret)
{
if (prop == LG_DS_WARP_SUPPORT)
{
*(enum LG_DSWarpSupport*)ret = wlWm.warpSupport ? LG_DS_WARP_SURFACE : LG_DS_WARP_NONE;
return true;
}
return false;
}
struct LG_DisplayServerOps LGDS_Wayland =
{
.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,
.cbRelease = waylandCBRelease,
.cbRequest = waylandCBRequest
};

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

@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.0)
project(displayserver_X11 LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(DISPLAYSERVER_X11_PKGCONFIG REQUIRED
x11
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
)
target_include_directories(displayserver_X11
PRIVATE
src
${DISPLAYSERVER_X11_PKGCONFIG_INCLUDE_DIRS}
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.0)
project(fonts LANGUAGES C)
set(FONT_H "${CMAKE_BINARY_DIR}/include/dynamic/fonts.h")
set(FONT_C "${CMAKE_BINARY_DIR}/src/fonts.c")
file(WRITE ${FONT_H} "#include \"interface/font.h\"\n\n")
file(APPEND ${FONT_H} "extern LG_Font * LG_Fonts[];\n\n")
file(WRITE ${FONT_C} "#include \"interface/font.h\"\n\n")
file(APPEND ${FONT_C} "#include <stddef.h>\n\n")
set(FONTS "_")
set(FONTS_LINK "_")
function(add_font name)
set(FONTS "${FONTS};${name}" PARENT_SCOPE)
set(FONTS_LINK "${FONTS_LINK};font_${name}" PARENT_SCOPE)
add_subdirectory(${name})
endfunction()
# Add/remove fonts here!
add_font(SDL)
list(REMOVE_AT FONTS 0)
list(REMOVE_AT FONTS_LINK 0)
list(LENGTH FONTS FONT_COUNT)
file(APPEND ${FONT_H} "#define LG_FONT_COUNT ${FONT_COUNT}\n")
foreach(font ${FONTS})
file(APPEND ${FONT_C} "extern LG_Font LGF_${font};\n")
endforeach()
file(APPEND ${FONT_C} "\nconst LG_Font * LG_Fonts[] =\n{\n")
foreach(font ${FONTS})
file(APPEND ${FONT_C} " &LGF_${font},\n")
endforeach()
file(APPEND ${FONT_C} " NULL\n};\n\n")
add_library(fonts STATIC ${FONT_C})
target_link_libraries(fonts ${FONTS_LINK})

View File

@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.0)
project(font_SDL LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(FONT_SDL_PKGCONFIG REQUIRED
SDL2_ttf
fontconfig
)
add_library(font_SDL STATIC
src/sdl.c
)
target_link_libraries(font_SDL
${FONT_SDL_PKGCONFIG_LIBRARIES}
lg_common
)
target_include_directories(font_SDL
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src
${FONT_SDL_PKGCONFIG_INCLUDE_DIRS}
)

251
client/fonts/SDL/src/sdl.c Normal file
View File

@@ -0,0 +1,251 @@
/*
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 <stdlib.h>
#include <stdbool.h>
#include "interface/font.h"
#include "common/debug.h"
#include <SDL2/SDL_ttf.h>
#include <fontconfig/fontconfig.h>
static int g_initCount = 0;
static FcConfig * g_fontConfig = NULL;
struct Inst
{
TTF_Font * font;
};
static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
{
bool ret = false;
if (g_initCount == 0)
{
if (TTF_Init() < 0)
{
DEBUG_ERROR("TTF_Init Failed");
goto fail;
}
g_fontConfig = FcInitLoadConfigAndFonts();
if (!g_fontConfig)
{
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
goto fail_init;
}
}
*opaque = malloc(sizeof(struct Inst));
if (!*opaque)
{
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
goto fail_config;
}
memset(*opaque, 0, sizeof(struct Inst));
struct Inst * this = (struct Inst *)*opaque;
if (!font_name)
font_name = "FreeMono";
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
if (!pat)
{
DEBUG_ERROR("FCNameParse failed");
goto fail_opaque;
}
FcConfigSubstitute(g_fontConfig, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
FcResult result;
FcChar8 * file = NULL;
FcPattern * match = FcFontMatch(g_fontConfig, pat, &result);
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");
goto fail_match;
}
}
else
{
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
goto fail_match;
}
++g_initCount;
ret = true;
fail_match:
FcPatternDestroy(match);
fail_parse:
FcPatternDestroy(pat);
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)
{
struct Inst * this = (struct Inst *)opaque;
SDL_Surface * surface;
SDL_Color color;
color.r = (fg_color & 0xff000000) >> 24;
color.g = (fg_color & 0x00ff0000) >> 16;
color.b = (fg_color & 0x0000ff00) >> 8;
color.a = (fg_color & 0x000000ff) >> 0;
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;
}
LG_FontBitmap * out = malloc(sizeof(LG_FontBitmap));
if (!out)
{
SDL_FreeSurface(surface);
DEBUG_ERROR("Failed to allocate memory for font bitmap");
return NULL;
}
out->reserved = surface;
out->width = surface->w;
out->height = surface->h;
out->bpp = surface->format->BytesPerPixel;
out->pixels = surface->pixels;
return out;
}
static void lgf_sdl_release(LG_FontObj opaque, LG_FontBitmap * font)
{
LG_FontBitmap * bitmap = (LG_FontBitmap *)font;
SDL_FreeSurface(bitmap->reserved);
free(bitmap);
}
struct LG_Font LGF_SDL =
{
.name = "SDL",
.create = lgf_sdl_create,
.destroy = lgf_sdl_destroy,
.render = lgf_sdl_render,
.release = lgf_sdl_release
};

123
client/include/app.h Normal file
View File

@@ -0,0 +1,123 @@
/*
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_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;
bool app_isRunning(void);
bool app_inputEnabled(void);
void app_updateCursorPos(double x, double y);
void app_updateWindowPos(int x, int y);
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_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

@@ -0,0 +1,210 @@
/*
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_I_DISPLAYSERVER_
#define _H_I_DISPLAYSERVER_
#include <stdbool.h>
#include <EGL/egl.h>
typedef enum LG_ClipboardData
{
LG_CLIPBOARD_DATA_TEXT = 0,
LG_CLIPBOARD_DATA_PNG,
LG_CLIPBOARD_DATA_BMP,
LG_CLIPBOARD_DATA_TIFF,
LG_CLIPBOARD_DATA_JPEG,
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
}
LG_ClipboardData;
typedef enum LG_DSProperty
{
/**
* returns the maximum number of samples supported
* if not implemented LG assumes no multisample support
* return data type: int
*/
LG_DS_MAX_MULTISAMPLE,
/**
* returns if the platform is warp capable
* if not implemented LG assumes that the platform is warp capable
* return data type: bool
*/
LG_DS_WARP_SUPPORT,
}
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
{
/* called before options are parsed, useful for registering options */
void (*setup)(void);
/* 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 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 SPICE is ready */
void (*startup)();
/* called just before final window destruction, before final free */
void (*shutdown)();
/* final free */
void (*free)();
/*
* return a system specific property, returns false if unsupported or failure
* if the platform does not support/implement the requested property the value
* of `ret` must not be altered.
*/
bool (*getProp)(LG_DSProperty prop, void * ret);
#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)();
void (*ungrabKeyboard)();
/* exiting = true if the warp is to leave the window */
void (*warpPointer)(int x, int y, bool exiting);
/* called when the client needs to realign the pointer. This should simply
* call the appropriate app_handleMouse* method for the platform with zero
* 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)();
/* 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

@@ -0,0 +1,50 @@
/*
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 <stdint.h>
#include <stdbool.h>
typedef void * LG_FontObj;
typedef struct LG_FontBitmap
{
void * reserved;
unsigned int width, height;
unsigned int bpp; // bytes per pixel
uint8_t * pixels;
}
LG_FontBitmap;
typedef bool (* LG_FontCreate )(LG_FontObj * opaque, const char * font_name, unsigned int size);
typedef void (* LG_FontDestroy )(LG_FontObj opaque);
typedef LG_FontBitmap * (* LG_FontRender )(LG_FontObj opaque, unsigned int fg_color, const char * text);
typedef void (* LG_FontRelease )(LG_FontObj opaque, LG_FontBitmap * bitmap);
typedef struct LG_Font
{
// mandatory support
const char * name;
LG_FontCreate create;
LG_FontDestroy destroy;
LG_FontRender render;
LG_FontRelease release;
}
LG_Font;

View File

@@ -0,0 +1,146 @@
/*
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 <stdint.h>
#include <stdbool.h>
#include "app.h"
#include "common/KVMFR.h"
#include "common/framebuffer.h"
#define IS_LG_RENDERER_VALID(x) \
((x)->get_name && \
(x)->create && \
(x)->initialize && \
(x)->deinitialize && \
(x)->on_restart && \
(x)->on_resize && \
(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)
typedef struct LG_RendererParams
{
// TTF_Font * font;
// TTF_Font * alertFont;
bool quickSplash;
}
LG_RendererParams;
typedef enum LG_RendererSupport
{
LG_SUPPORTS_DMABUF
}
LG_RendererSupport;
typedef enum LG_RendererRotate
{
LG_ROTATE_0,
LG_ROTATE_90,
LG_ROTATE_180,
LG_ROTATE_270
}
LG_RendererRotate;
// kept out of the enum so gcc doesn't warn when it's missing from a switch
// statement.
#define LG_ROTATE_MAX (LG_ROTATE_270+1)
typedef struct LG_RendererFormat
{
FrameType type; // frame type
unsigned int width; // image width
unsigned int height; // image height
unsigned int stride; // scanline width (zero if compresed)
unsigned int pitch; // scanline bytes (or compressed size)
unsigned int bpp; // bits per pixel (zero if compressed)
LG_RendererRotate rotate; // guest rotation
}
LG_RendererFormat;
typedef struct LG_RendererRect
{
bool valid;
int x;
int y;
int w;
int h;
}
LG_RendererRect;
typedef enum LG_RendererCursor
{
LG_CURSOR_COLOR ,
LG_CURSOR_MONOCHROME ,
LG_CURSOR_MASKED_COLOR
}
LG_RendererCursor;
// returns the friendly name of the renderer
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, 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);
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect, LG_RendererRotate rotate);
typedef bool (* LG_RendererOnMouseShape )(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visible , const int x, const int y);
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 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
{
LG_RendererGetName get_name;
LG_RendererSetup setup;
LG_RendererCreate create;
LG_RendererInitialize initialize;
LG_RendererDeInitialize deinitialize;
LG_RendererSupports supports;
LG_RendererOnRestart on_restart;
LG_RendererOnResize on_resize;
LG_RendererOnMouseShape on_mouse_shape;
LG_RendererOnMouseEvent on_mouse_event;
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;
}
LG_Renderer;

32
client/include/ll.h Normal file
View File

@@ -0,0 +1,32 @@
/*
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
*/
#include <stdbool.h>
struct ll;
struct ll * ll_new();
void ll_free (struct ll * list);
void ll_push (struct ll * list, void * data);
bool ll_shift (struct ll * list, void ** data);
bool ll_peek_head(struct ll * list, void ** data);
bool ll_peek_tail(struct ll * list, void ** data);
unsigned int ll_count (struct ll * list);
void ll_reset (struct ll * list);
bool ll_walk (struct ll * list, void ** data);

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
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
@@ -17,19 +17,20 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sys/types.h>
#ifndef _H_LG_UTIL_
#define _H_LG_UTIL_
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include "common/types.h"
bool spice_connect(const char * host, const short port, const char * password);
void spice_disconnect();
bool spice_process();
bool spice_ready();
// 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);
bool spice_key_down (uint32_t code);
bool spice_key_up (uint32_t code);
bool spice_mouse_mode (bool server);
bool spice_mouse_position(uint32_t x, uint32_t y);
bool spice_mouse_motion ( int32_t x, int32_t y);
bool spice_mouse_press (uint32_t button);
bool spice_mouse_release (uint32_t button);
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);
#endif

View File

@@ -1,548 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 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 "ivshmem.h"
#include "debug.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <sys/mman.h>
#define MAX_IRQS 32
struct IVSHMEMServer
{
int64_t version;
int64_t clientID;
int sharedFD;
int irqs[MAX_IRQS];
int irqCount;
};
struct IVSHMEMClient
{
uint16_t clientID;
int irqs[MAX_IRQS];
int irqCount;
struct IVSHMEMClient * last;
struct IVSHMEMClient * next;
};
struct IVSHMEM
{
bool connected;
bool shutdown;
int socket;
struct IVSHMEMServer server;
struct IVSHMEMClient * clients;
off_t mapSize;
void * map;
};
struct IVSHMEM ivshmem =
{
.connected = false,
.shutdown = false,
.socket = -1
};
// ============================================================================
// internal functions
void ivshmem_cleanup();
bool ivshmem_read(void * buffer, const ssize_t size);
bool ivshmem_read_msg(int64_t * index, int *fd);
struct IVSHMEMClient * ivshmem_get_client(uint16_t clientID);
void ivshmem_remove_client(struct IVSHMEMClient * client);
// ============================================================================
bool ivshmem_connect(const char * unix_socket)
{
ivshmem.shutdown = false;
ivshmem.socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (ivshmem.socket < 0)
{
DEBUG_ERROR("socket creation failed");
return false;
}
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, unix_socket, sizeof(addr.sun_path));
if (connect(ivshmem.socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
{
DEBUG_ERROR("socket connect failed");
ivshmem_cleanup();
return false;
}
ivshmem.connected = true;
if (!ivshmem_read(&ivshmem.server.version, sizeof(ivshmem.server.version)))
{
DEBUG_ERROR("read protocol version failed");
ivshmem_cleanup();
return false;
}
if (ivshmem.server.version != 0)
{
DEBUG_ERROR("unsupported protocol version %ld", ivshmem.server.version);
ivshmem_cleanup();
return false;
}
if (!ivshmem_read(&ivshmem.server.clientID, sizeof(ivshmem.server.clientID)))
{
DEBUG_ERROR("read client id failed");
ivshmem_cleanup();
return false;
}
DEBUG_PROTO("Protocol : %ld", ivshmem.server.version );
DEBUG_PROTO("Client ID: %ld", ivshmem.server.clientID);
if (!ivshmem_read_msg(NULL, &ivshmem.server.sharedFD))
{
DEBUG_ERROR("failed to read shared memory file descriptor");
ivshmem_cleanup();
return false;
}
struct stat stat;
if (fstat(ivshmem.server.sharedFD, &stat) != 0)
{
DEBUG_ERROR("failed to stat shared FD");
ivshmem_cleanup();
return false;
}
ivshmem.mapSize = stat.st_size;
DEBUG_INFO("RAM Size : %ld", ivshmem.mapSize);
ivshmem.map = mmap(
NULL,
stat.st_size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
ivshmem.server.sharedFD,
0);
if (!ivshmem.map)
{
DEBUG_ERROR("failed to map memory");
ivshmem_cleanup();
return false;
}
return true;
}
// ============================================================================
void ivshmem_cleanup()
{
struct IVSHMEMClient * client, * next;
client = ivshmem.clients;
while(client)
{
for(int i = 0; i < client->irqCount; ++i)
close(client->irqs[i]);
next = client->next;
free(client);
client = next;
}
ivshmem.clients = NULL;
for(int i = 0; i < ivshmem.server.irqCount; ++i)
close(ivshmem.server.irqs[i]);
ivshmem.server.irqCount = 0;
if (ivshmem.map)
munmap(ivshmem.map, ivshmem.mapSize);
ivshmem.map = NULL;
ivshmem.mapSize = 0;
if (ivshmem.socket >= 0)
{
ivshmem.shutdown = true;
shutdown(ivshmem.socket, SHUT_RDWR);
close(ivshmem.socket);
ivshmem.socket = -1;
}
ivshmem.connected = false;
}
// ============================================================================
void ivshmem_disconnect()
{
if (!ivshmem.connected)
{
DEBUG_WARN("socket not connected");
return;
}
ivshmem_cleanup();
}
// ============================================================================
bool ivshmem_read(void * buffer, const ssize_t size)
{
if (!ivshmem.connected)
{
DEBUG_ERROR("not connected");
return false;
}
ssize_t len = read(ivshmem.socket, buffer, size);
if (len != size)
{
DEBUG_ERROR("incomplete read");
return false;
}
return true;
}
// ============================================================================
bool ivshmem_read_msg(int64_t * index, int * fd)
{
if (!ivshmem.connected)
{
DEBUG_ERROR("not connected");
return false;
}
struct msghdr msg;
struct iovec iov[1];
union {
struct cmsghdr cmsg;
char control[CMSG_SPACE(sizeof(int))];
} msg_control;
int64_t tmp;
if (!index)
index = &tmp;
iov[0].iov_base = index;
iov[0].iov_len = sizeof(*index);
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = &msg_control;
msg.msg_controllen = sizeof(msg_control);
int ret = recvmsg(ivshmem.socket, &msg, 0);
if (ret < sizeof(*index))
{
if (!ivshmem.shutdown)
DEBUG_ERROR("failed to read message\n");
return false;
}
if (ret == 0)
{
if (!ivshmem.shutdown)
DEBUG_ERROR("lost connetion to server\n");
return false;
}
if (!fd)
return true;
*fd = -1;
struct cmsghdr *cmsg;
for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
{
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_RIGHTS)
{
continue;
}
memcpy(fd, CMSG_DATA(cmsg), sizeof(int));
}
return true;
}
// ============================================================================
uint16_t ivshmem_get_id()
{
if (!ivshmem.connected)
{
DEBUG_ERROR("not connected");
return -1;
}
return ivshmem.server.clientID;
}
// ============================================================================
void * ivshmem_get_map()
{
if (!ivshmem.connected)
{
DEBUG_ERROR("not connected");
return NULL;
}
if (!ivshmem.map)
{
DEBUG_ERROR("not mapped");
return NULL;
}
return ivshmem.map;
}
// ============================================================================
size_t ivshmem_get_map_size()
{
if (!ivshmem.connected)
{
DEBUG_ERROR("not connected");
return 0;
}
if (!ivshmem.map)
{
DEBUG_ERROR("not mapped");
return 0;
}
return ivshmem.mapSize;
}
// ============================================================================
struct IVSHMEMClient * ivshmem_get_client(uint16_t clientID)
{
struct IVSHMEMClient * client = NULL;
if (ivshmem.clients == NULL)
{
client = (struct IVSHMEMClient *)malloc(sizeof(struct IVSHMEMClient));
client->clientID = clientID;
client->last = NULL;
client->next = NULL;
client->irqCount = 0;
ivshmem.clients = client;
return client;
}
client = ivshmem.clients;
while(client)
{
if (client->clientID == clientID)
return client;
client = client->next;
}
client = (struct IVSHMEMClient *)malloc(sizeof(struct IVSHMEMClient));
client->clientID = clientID;
client->last = NULL;
client->next = ivshmem.clients;
client->irqCount = 0;
client->next->last = client;
ivshmem.clients = client;
return client;
}
// ============================================================================
void ivshmem_remove_client(struct IVSHMEMClient * client)
{
if (client->last)
client->last->next = client->next;
if (client->next)
client->next->last = client->last;
if (ivshmem.clients == client)
ivshmem.clients = client->next;
free(client);
}
// ============================================================================
bool ivshmem_process()
{
int64_t index;
int fd;
if (!ivshmem_read_msg(&index, &fd))
{
if (!ivshmem.shutdown)
DEBUG_ERROR("failed to read message");
return false;
}
if (index == -1)
{
DEBUG_ERROR("invalid index -1");
return false;
}
if (index > 0xFFFF)
{
DEBUG_ERROR("invalid index > 0xFFFF");
return false;
}
if (index == ivshmem.server.clientID)
{
if (fd == -1)
{
DEBUG_ERROR("server sent disconnect");
return false;
}
if (ivshmem.server.irqCount == MAX_IRQS)
{
DEBUG_WARN("maximum IRQs reached, closing extra");
close(fd);
return true;
}
ivshmem.server.irqs[ivshmem.server.irqCount++] = fd;
return true;
}
struct IVSHMEMClient * client = ivshmem_get_client(index);
if (!client)
{
DEBUG_ERROR("failed to get/create client record");
return false;
}
if (fd == -1)
{
DEBUG_PROTO("remove client %u", client->clientID);
ivshmem_remove_client(client);
return true;
}
if (client->irqCount == MAX_IRQS)
{
DEBUG_WARN("maximum client IRQs reached, closing extra");
close(fd);
return true;
}
client->irqs[client->irqCount++] = fd;
return true;
}
// ============================================================================
enum IVSHMEMWaitResult ivshmem_wait_irq(uint16_t vector, unsigned int timeout)
{
if (vector > ivshmem.server.irqCount - 1)
return false;
int fd = ivshmem.server.irqs[vector];
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv;
tv.tv_sec = timeout / 1000000L;
tv.tv_usec = timeout % 1000000L;
while(true)
{
int ret = select(fd+1, &fds, NULL, NULL, &tv);
if (ret < 0)
{
if (errno == EINTR)
continue;
DEBUG_ERROR("select error");
break;
}
if (ret == 0)
return IVSHMEM_WAIT_RESULT_TIMEOUT;
if (FD_ISSET(fd, &fds))
{
uint64_t kick;
int unused = read(fd, &kick, sizeof(kick));
(void)unused;
return IVSHMEM_WAIT_RESULT_OK;
}
}
return IVSHMEM_WAIT_RESULT_ERROR;
}
// ============================================================================
bool ivshmem_kick_irq(uint16_t clientID, uint16_t vector)
{
struct IVSHMEMClient * client = ivshmem_get_client(clientID);
if (!client)
{
DEBUG_ERROR("invalid client");
return false;
}
if (vector > client->irqCount - 1)
{
DEBUG_ERROR("invalid vector for client");
return false;
}
int fd = client->irqs[vector];
uint64_t kick = ivshmem.server.clientID;
if (write(fd, &kick, sizeof(kick)) == sizeof(kick))
return true;
DEBUG_ERROR("failed to send kick");
return false;
}

View File

@@ -1,56 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 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
*/
static uint32_t usb_to_ps2[] =
{
0x000000, 0x000000, 0x000000, 0x000000, 0x00001e, 0x000030, 0x00002e,
0x000020, 0x000012, 0x000021, 0x000022, 0x000023, 0x000017, 0x000024,
0x000025, 0x000026, 0x000032, 0x000031, 0x000018, 0x000019, 0x000010,
0x000013, 0x00001f, 0x000014, 0x000016, 0x00002f, 0x000011, 0x00002d,
0x000015, 0x00002c, 0x000002, 0x000003, 0x000004, 0x000005, 0x000006,
0x000007, 0x000008, 0x000009, 0x00000a, 0x00000b, 0x00001c, 0x000001,
0x00000e, 0x00000f, 0x000039, 0x00000c, 0x00000d, 0x00001a, 0x00001b,
0x00002b, 0x00002b, 0x000027, 0x000028, 0x000029, 0x000033, 0x000034,
0x000035, 0x00003a, 0x00003b, 0x00003c, 0x00003d, 0x00003e, 0x00003f,
0x000040, 0x000041, 0x000042, 0x000043, 0x000044, 0x000057, 0x000058,
0x00e037, 0x000046, 0x00e046, 0x00e052, 0x00e047, 0x00e049, 0x00e053,
0x00e04f, 0x00e051, 0x00e04d, 0x00e04b, 0x00e050, 0x00e048, 0x000045,
0x00e035, 0x000037, 0x00004a, 0x00004e, 0x00e01c, 0x00004f, 0x000050,
0x000051, 0x00004b, 0x00004c, 0x00004d, 0x000047, 0x000048, 0x000049,
0x000052, 0x000053, 0x000056, 0x00e05d, 0x000000, 0x000059, 0x00005d,
0x00005e, 0x00005f, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x00007e, 0x000000, 0x000073, 0x000070, 0x00007d, 0x000079, 0x00007b,
0x00005c, 0x000000, 0x000000, 0x000000, 0x0000f2, 0x0000f1, 0x000078,
0x000077, 0x000076, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x00001d, 0x00002a, 0x000038, 0x00e05b, 0x00e01d, 0x000036, 0x00e038,
0x00e05c
};

View File

@@ -1,121 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 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 <stdint.h>
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#define IS_LG_RENDERER_VALID(x) \
((x)->get_name && \
(x)->create && \
(x)->initialize && \
(x)->deinitialize && \
(x)->on_resize && \
(x)->on_mouse_shape && \
(x)->on_mouse_event && \
(x)->render)
#define LGR_OPTION_COUNT(x) (sizeof(x) / sizeof(LG_RendererOpt))
typedef bool(* LG_RendererOptValidator)(const char * value);
typedef void(* LG_RendererOptHandler )(void * opaque, const char * value);
typedef struct LG_RendererOpt
{
const char * name;
const char * desc;
LG_RendererOptValidator validator;
LG_RendererOptHandler handler;
}
LG_RendererOpt;
typedef struct LG_RendererOptValue
{
const LG_RendererOpt * opt;
const char * value;
} LG_RendererOptValue;
typedef LG_RendererOpt * LG_RendererOptions;
typedef struct LG_RendererParams
{
TTF_Font * font;
bool showFPS;
}
LG_RendererParams;
typedef struct LG_RendererFormat
{
unsigned int width; // image width
unsigned int height; // image height
unsigned int stride; // scanline width
unsigned int pitch; // scanline bytes
unsigned int bpp; // bits per pixel
}
LG_RendererFormat;
typedef struct LG_RendererRect
{
int x;
int y;
unsigned int w;
unsigned int h;
}
LG_RendererRect;
typedef enum LG_RendererCursor
{
LG_CURSOR_COLOR ,
LG_CURSOR_MONOCHROME ,
LG_CURSOR_MASKED_COLOR
}
LG_RendererCursor;
typedef const char * (* LG_RendererGetName )();
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
typedef void (* LG_RendererDeInitialize)(void * opaque);
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const uint8_t * data);
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
typedef struct LG_Renderer
{
LG_RendererCreate create;
LG_RendererGetName get_name;
LG_RendererOptions options;
unsigned int option_count;
LG_RendererInitialize initialize;
LG_RendererDeInitialize deinitialize;
LG_RendererOnResize on_resize;
LG_RendererOnMouseShape on_mouse_shape;
LG_RendererOnMouseEvent on_mouse_event;
LG_RendererOnFrameEvent on_frame_event;
LG_RendererRender render;
}
LG_Renderer;
// generic option helpers
bool LG_RendererValidatorBool(const char * value);
bool LG_RendererValueToBool (const char * value);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.0)
project(renderers LANGUAGES C)
set(RENDERER_H "${CMAKE_BINARY_DIR}/include/dynamic/renderers.h")
set(RENDERER_C "${CMAKE_BINARY_DIR}/src/renderers.c")
file(WRITE ${RENDERER_H} "#include \"interface/renderer.h\"\n\n")
file(APPEND ${RENDERER_H} "extern LG_Renderer * LG_Renderers[];\n\n")
file(WRITE ${RENDERER_C} "#include \"interface/renderer.h\"\n\n")
file(APPEND ${RENDERER_C} "#include <stddef.h>\n\n")
set(RENDERERS "_")
set(RENDERERS_LINK "_")
function(add_renderer name)
set(RENDERERS "${RENDERERS};${name}" PARENT_SCOPE)
set(RENDERERS_LINK "${RENDERERS_LINK};renderer_${name}" PARENT_SCOPE)
add_subdirectory(${name})
endfunction()
# Add/remove renderers here!
if(ENABLE_EGL)
add_renderer(EGL)
endif()
if (ENABLE_OPENGL)
add_renderer(OpenGL)
endif()
list(REMOVE_AT RENDERERS 0)
list(REMOVE_AT RENDERERS_LINK 0)
list(LENGTH RENDERERS RENDERER_COUNT)
file(APPEND ${RENDERER_H} "#define LG_RENDERER_COUNT ${RENDERER_COUNT}\n")
foreach(renderer ${RENDERERS})
file(APPEND ${RENDERER_C} "extern LG_Renderer LGR_${renderer};\n")
endforeach()
file(APPEND ${RENDERER_C} "\nconst LG_Renderer * LG_Renderers[] =\n{\n")
foreach(renderer ${RENDERERS})
file(APPEND ${RENDERER_C} " &LGR_${renderer},\n")
endforeach()
file(APPEND ${RENDERER_C} " NULL\n};")
add_library(renderers STATIC ${RENDERER_C})
target_link_libraries(renderers ${RENDERERS_LINK})

View File

@@ -0,0 +1,66 @@
cmake_minimum_required(VERSION 3.0)
project(renderer_EGL LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(RENDERER_EGL_PKGCONFIG REQUIRED
egl
gl
)
pkg_check_modules(RENDERER_EGL_OPT_PKGCONFIG
wayland-egl
)
include(MakeObject)
make_object(
EGL_SHADER
shader/desktop.vert
shader/desktop_rgb.frag
shader/cursor.vert
shader/cursor_rgb.frag
shader/cursor_mono.frag
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
shader/splash_bg.vert
shader/splash_bg.frag
shader/splash_logo.vert
shader/splash_logo.frag
)
add_library(renderer_EGL STATIC
egl.c
egldebug.c
shader.c
texture.c
model.c
desktop.c
cursor.c
fps.c
help.c
draw.c
splash.c
alert.c
${EGL_SHADER_OBJS}
)
target_link_libraries(renderer_EGL
${RENDERER_EGL_PKGCONFIG_LIBRARIES}
${RENDERER_EGL_OPT_PKGCONFIG_LIBRARIES}
lg_common
fonts
)
target_include_directories(renderer_EGL
PRIVATE
src
${EGL_SHADER_INCS}
${RENDERER_EGL_PKGCONFIG_INCLUDE_DIRS}
${RENDERER_EGL_OPT_PKGCONFIG_INCLUDE_DIRS}
)

View File

@@ -0,0 +1,219 @@
/*
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
cahe 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 "alert.h"
#include "common/debug.h"
#include "common/locking.h"
#include "texture.h"
#include "shader.h"
#include "model.h"
#include <stdlib.h>
#include <string.h>
// these headers are auto generated by cmake
#include "alert.vert.h"
#include "alert.frag.h"
#include "alert_bg.frag.h"
struct EGL_Alert
{
const LG_Font * font;
LG_FontObj fontObj;
EGL_Texture * texture;
EGL_Shader * shader;
EGL_Shader * shaderBG;
EGL_Model * model;
LG_Lock lock;
bool update;
LG_FontBitmap * bmp;
bool ready;
float width , height ;
float bgWidth, bgHeight;
float r, g, b, a;
// uniforms
GLint uScreen , uSize;
GLint uScreenBG, uSizeBG, uColorBG;
};
bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj)
{
*alert = (EGL_Alert *)malloc(sizeof(EGL_Alert));
if (!*alert)
{
DEBUG_ERROR("Failed to malloc EGL_Alert");
return false;
}
memset(*alert, 0, sizeof(EGL_Alert));
(*alert)->font = font;
(*alert)->fontObj = fontObj;
LG_LOCK_INIT((*alert)->lock);
if (!egl_texture_init(&(*alert)->texture, NULL))
{
DEBUG_ERROR("Failed to initialize the alert texture");
return false;
}
if (!egl_shader_init(&(*alert)->shader))
{
DEBUG_ERROR("Failed to initialize the alert shader");
return false;
}
if (!egl_shader_init(&(*alert)->shaderBG))
{
DEBUG_ERROR("Failed to initialize the alert bg shader");
return false;
}
if (!egl_shader_compile((*alert)->shader,
b_shader_alert_vert, b_shader_alert_vert_size,
b_shader_alert_frag, b_shader_alert_frag_size))
{
DEBUG_ERROR("Failed to compile the alert shader");
return false;
}
if (!egl_shader_compile((*alert)->shaderBG,
b_shader_alert_vert , b_shader_alert_vert_size,
b_shader_alert_bg_frag, b_shader_alert_bg_frag_size))
{
DEBUG_ERROR("Failed to compile the alert shader");
return false;
}
(*alert)->uSize = egl_shader_get_uniform_location((*alert)->shader , "size" );
(*alert)->uScreen = egl_shader_get_uniform_location((*alert)->shader , "screen");
(*alert)->uSizeBG = egl_shader_get_uniform_location((*alert)->shaderBG, "size" );
(*alert)->uScreenBG = egl_shader_get_uniform_location((*alert)->shaderBG, "screen");
(*alert)->uColorBG = egl_shader_get_uniform_location((*alert)->shaderBG, "color" );
if (!egl_model_init(&(*alert)->model))
{
DEBUG_ERROR("Failed to initialize the alert model");
return false;
}
egl_model_set_default((*alert)->model);
egl_model_set_texture((*alert)->model, (*alert)->texture);
return true;
}
void egl_alert_free(EGL_Alert ** alert)
{
if (!*alert)
return;
egl_texture_free(&(*alert)->texture );
egl_shader_free (&(*alert)->shader );
egl_shader_free (&(*alert)->shaderBG);
egl_model_free (&(*alert)->model );
free(*alert);
*alert = NULL;
}
void egl_alert_set_color(EGL_Alert * alert, const uint32_t color)
{
alert->r = (1.0f / 0xff) * ((color >> 24) & 0xFF);
alert->g = (1.0f / 0xff) * ((color >> 16) & 0xFF);
alert->b = (1.0f / 0xff) * ((color >> 8) & 0xFF);
alert->a = (1.0f / 0xff) * ((color >> 0) & 0xFF);
}
void egl_alert_set_text (EGL_Alert * alert, const char * str)
{
LG_LOCK(alert->lock);
alert->bmp = alert->font->render(alert->fontObj, 0xffffff00, str);
if (!alert->bmp)
{
alert->update = false;
LG_UNLOCK(alert->lock);
DEBUG_ERROR("Failed to render alert text");
return;
}
alert->update = true;
LG_UNLOCK(alert->lock);
}
void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY)
{
if (alert->update)
{
LG_LOCK(alert->lock);
egl_texture_setup(
alert->texture,
EGL_PF_BGRA,
alert->bmp->width ,
alert->bmp->height,
alert->bmp->width * alert->bmp->bpp,
false,
false
);
egl_texture_update(alert->texture, alert->bmp->pixels);
alert->width = alert->bgWidth = alert->bmp->width;
alert->height = alert->bgHeight = alert->bmp->height;
if (alert->bgWidth < 200)
alert->bgWidth = 200;
alert->bgHeight += 4;
alert->ready = true;
alert->font->release(alert->fontObj, alert->bmp);
alert->update = false;
alert->bmp = NULL;
LG_UNLOCK(alert->lock);
}
if (!alert->ready)
return;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// render the background first
egl_shader_use(alert->shaderBG);
glUniform2f(alert->uScreenBG, scaleX , scaleY );
glUniform2i(alert->uSizeBG , alert->bgWidth, alert->bgHeight);
glUniform4f(alert->uColorBG , alert->r, alert->g, alert->b, alert->a);
egl_model_render(alert->model);
// render the texture over the background
egl_shader_use(alert->shader);
glUniform2f(alert->uScreen, scaleX , scaleY );
glUniform2i(alert->uSize , alert->width, alert->height);
egl_model_render(alert->model);
glDisable(GL_BLEND);
}

View File

@@ -0,0 +1,33 @@
/*
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 <stdbool.h>
#include "interface/font.h"
typedef struct EGL_Alert EGL_Alert;
bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj);
void egl_alert_free(EGL_Alert ** alert);
void egl_alert_set_color(EGL_Alert * alert, const uint32_t color);
void egl_alert_set_text (EGL_Alert * alert, const char * str);
void egl_alert_render (EGL_Alert * alert, const float scaleX, const float scaleY);

View File

@@ -0,0 +1,311 @@
/*
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
cahe 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 "cursor.h"
#include "common/debug.h"
#include "common/locking.h"
#include "common/option.h"
#include "texture.h"
#include "shader.h"
#include "model.h"
#include <stdlib.h>
#include <string.h>
// these headers are auto generated by cmake
#include "cursor.vert.h"
#include "cursor_rgb.frag.h"
#include "cursor_mono.frag.h"
struct CursorTex
{
struct EGL_Texture * texture;
struct EGL_Shader * shader;
GLuint uMousePos;
GLuint uRotate;
GLuint uCBMode;
};
struct EGL_Cursor
{
LG_Lock lock;
LG_RendererCursor type;
int width;
int height;
int stride;
uint8_t * data;
size_t dataSize;
bool update;
// cursor state
bool visible;
float x, y, w, h;
LG_RendererRotate rotate;
int cbMode;
struct CursorTex norm;
struct CursorTex mono;
struct EGL_Model * model;
};
static bool egl_cursor_tex_init(struct CursorTex * t,
const char * vertex_code , size_t vertex_size,
const char * fragment_code, size_t fragment_size)
{
if (!egl_texture_init(&t->texture, NULL))
{
DEBUG_ERROR("Failed to initialize the cursor texture");
return false;
}
if (!egl_shader_init(&t->shader))
{
DEBUG_ERROR("Failed to initialize the cursor shader");
return false;
}
if (!egl_shader_compile(t->shader,
vertex_code, vertex_size, fragment_code, fragment_size))
{
DEBUG_ERROR("Failed to compile the cursor shader");
return false;
}
t->uMousePos = egl_shader_get_uniform_location(t->shader, "mouse" );
t->uRotate = egl_shader_get_uniform_location(t->shader, "rotate");
t->uCBMode = egl_shader_get_uniform_location(t->shader, "cbMode");
return true;
}
static inline void egl_cursor_tex_uniforms(EGL_Cursor * cursor, struct CursorTex * t, bool mono)
{
if (mono)
{
glUniform4f(t->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
}
else
{
glUniform4f(t->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
}
}
static void egl_cursor_tex_free(struct CursorTex * t)
{
egl_texture_free(&t->texture);
egl_shader_free (&t->shader );
};
bool egl_cursor_init(EGL_Cursor ** cursor)
{
*cursor = (EGL_Cursor *)malloc(sizeof(EGL_Cursor));
if (!*cursor)
{
DEBUG_ERROR("Failed to malloc EGL_Cursor");
return false;
}
memset(*cursor, 0, sizeof(EGL_Cursor));
LG_LOCK_INIT((*cursor)->lock);
if (!egl_cursor_tex_init(&(*cursor)->norm,
b_shader_cursor_vert , b_shader_cursor_vert_size,
b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size))
return false;
if (!egl_cursor_tex_init(&(*cursor)->mono,
b_shader_cursor_vert , b_shader_cursor_vert_size,
b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size))
return false;
if (!egl_model_init(&(*cursor)->model))
{
DEBUG_ERROR("Failed to initialize the cursor model");
return false;
}
egl_model_set_default((*cursor)->model);
(*cursor)->cbMode = option_get_int("egl", "cbMode");
return true;
}
void egl_cursor_free(EGL_Cursor ** cursor)
{
if (!*cursor)
return;
LG_LOCK_FREE((*cursor)->lock);
if ((*cursor)->data)
free((*cursor)->data);
egl_cursor_tex_free(&(*cursor)->norm);
egl_cursor_tex_free(&(*cursor)->mono);
egl_model_free(&(*cursor)->model);
free(*cursor);
*cursor = NULL;
}
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type,
const int width, const int height, const int stride, const uint8_t * data)
{
LG_LOCK(cursor->lock);
cursor->type = type;
cursor->width = width;
cursor->height = (type == LG_CURSOR_MONOCHROME ? height / 2 : height);
cursor->stride = stride;
const size_t size = height * stride;
if (size > cursor->dataSize)
{
if (cursor->data)
free(cursor->data);
cursor->data = (uint8_t *)malloc(size);
if (!cursor->data)
{
DEBUG_ERROR("Failed to malloc buffer for cursor shape");
return false;
}
cursor->dataSize = size;
}
memcpy(cursor->data, data, size);
cursor->update = true;
LG_UNLOCK(cursor->lock);
return true;
}
void egl_cursor_set_size(EGL_Cursor * cursor, const float w, const float h)
{
cursor->w = w;
cursor->h = h;
}
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y)
{
cursor->visible = visible;
cursor->x = x;
cursor->y = y;
}
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
{
if (!cursor->visible)
return;
if (cursor->update)
{
LG_LOCK(cursor->lock);
cursor->update = false;
uint8_t * data = cursor->data;
switch(cursor->type)
{
case LG_CURSOR_MASKED_COLOR:
// fall through
case LG_CURSOR_COLOR:
{
egl_texture_setup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false, false);
egl_texture_update(cursor->norm.texture, data);
egl_model_set_texture(cursor->model, cursor->norm.texture);
break;
}
case LG_CURSOR_MONOCHROME:
{
uint32_t and[cursor->width * cursor->height];
uint32_t xor[cursor->width * cursor->height];
for(int y = 0; y < cursor->height; ++y)
for(int x = 0; x < cursor->width; ++x)
{
const uint8_t * srcAnd = data + (cursor->stride * y) + (x / 8);
const uint8_t * srcXor = srcAnd + cursor->stride * cursor->height;
const uint8_t mask = 0x80 >> (x % 8);
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
and[y * cursor->width + x] = andMask;
xor[y * cursor->width + x] = xorMask;
}
egl_texture_setup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
egl_texture_setup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
egl_texture_update(cursor->norm.texture, (uint8_t *)and);
egl_texture_update(cursor->mono.texture, (uint8_t *)xor);
break;
}
}
LG_UNLOCK(cursor->lock);
}
cursor->rotate = rotate;
glEnable(GL_BLEND);
switch(cursor->type)
{
case LG_CURSOR_MONOCHROME:
{
egl_shader_use(cursor->norm.shader);
egl_cursor_tex_uniforms(cursor, &cursor->norm, true);;
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
egl_model_set_texture(cursor->model, cursor->norm.texture);
egl_model_render(cursor->model);
egl_shader_use(cursor->mono.shader);
egl_cursor_tex_uniforms(cursor, &cursor->mono, true);;
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_set_texture(cursor->model, cursor->mono.texture);
egl_model_render(cursor->model);
break;
}
case LG_CURSOR_COLOR:
{
egl_shader_use(cursor->norm.shader);
egl_cursor_tex_uniforms(cursor, &cursor->norm, false);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
egl_model_render(cursor->model);
break;
}
case LG_CURSOR_MASKED_COLOR:
{
egl_shader_use(cursor->mono.shader);
egl_cursor_tex_uniforms(cursor, &cursor->mono, false);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_render(cursor->model);
break;
}
}
glDisable(GL_BLEND);
}

View File

@@ -0,0 +1,44 @@
/*
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 <stdbool.h>
#include "interface/renderer.h"
typedef struct EGL_Cursor EGL_Cursor;
bool egl_cursor_init(EGL_Cursor ** cursor);
void egl_cursor_free(EGL_Cursor ** cursor);
bool egl_cursor_set_shape(
EGL_Cursor * cursor,
const LG_RendererCursor type,
const int width,
const int height,
const int stride,
const uint8_t * data);
void egl_cursor_set_size(EGL_Cursor * cursor, const float x, const float y);
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible,
const float x, const float y);
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate);

View File

@@ -0,0 +1,284 @@
/*
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
cahe 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 "desktop.h"
#include "common/debug.h"
#include "common/option.h"
#include "common/locking.h"
#include "app.h"
#include "texture.h"
#include "shader.h"
#include "model.h"
#include <stdlib.h>
#include <string.h>
// these headers are auto generated by cmake
#include "desktop.vert.h"
#include "desktop_rgb.frag.h"
struct DesktopShader
{
EGL_Shader * shader;
GLint uDesktopPos;
GLint uDesktopSize;
GLint uRotate;
GLint uNearest;
GLint uNV, uNVGain;
GLint uCBMode;
};
struct EGL_Desktop
{
EGLDisplay * display;
EGL_Texture * texture;
struct DesktopShader * shader; // the active shader
EGL_Model * model;
// internals
int width, height;
LG_RendererRotate rotate;
// shader instances
struct DesktopShader shader_generic;
// night vision
int nvMax;
int nvGain;
// colorblind mode
int cbMode;
};
// forwards
void egl_desktop_toggle_nv(int key, void * opaque);
static bool egl_init_desktop_shader(
struct DesktopShader * shader,
const char * vertex_code , size_t vertex_size,
const char * fragment_code, size_t fragment_size
)
{
if (!egl_shader_init(&shader->shader))
return false;
if (!egl_shader_compile(shader->shader,
vertex_code , vertex_size,
fragment_code, fragment_size))
{
return false;
}
shader->uDesktopPos = egl_shader_get_uniform_location(shader->shader, "position");
shader->uDesktopSize = egl_shader_get_uniform_location(shader->shader, "size" );
shader->uRotate = egl_shader_get_uniform_location(shader->shader, "rotate" );
shader->uNearest = egl_shader_get_uniform_location(shader->shader, "nearest" );
shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" );
shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" );
shader->uCBMode = egl_shader_get_uniform_location(shader->shader, "cbMode" );
return true;
}
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
{
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
if (!*desktop)
{
DEBUG_ERROR("Failed to malloc EGL_Desktop");
return false;
}
memset(*desktop, 0, sizeof(EGL_Desktop));
(*desktop)->display = display;
if (!egl_texture_init(&(*desktop)->texture, display))
{
DEBUG_ERROR("Failed to initialize the desktop texture");
return false;
}
if (!egl_init_desktop_shader(
&(*desktop)->shader_generic,
b_shader_desktop_vert , b_shader_desktop_vert_size,
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size))
{
DEBUG_ERROR("Failed to initialize the generic desktop shader");
return false;
}
if (!egl_model_init(&(*desktop)->model))
{
DEBUG_ERROR("Failed to initialize the desktop model");
return false;
}
egl_model_set_default((*desktop)->model);
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
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" );
(*desktop)->cbMode = option_get_int("egl", "cbMode" );
return true;
}
void egl_desktop_toggle_nv(int key, void * opaque)
{
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
if (desktop->nvGain++ == desktop->nvMax)
desktop->nvGain = 0;
if (desktop->nvGain == 0) app_alert(LG_ALERT_INFO, "NV Disabled");
else if (desktop->nvGain == 1) app_alert(LG_ALERT_INFO, "NV Enabled");
else app_alert(LG_ALERT_INFO, "NV Gain + %d", desktop->nvGain - 1);
}
void egl_desktop_free(EGL_Desktop ** desktop)
{
if (!*desktop)
return;
egl_texture_free(&(*desktop)->texture );
egl_shader_free (&(*desktop)->shader_generic.shader);
egl_model_free (&(*desktop)->model );
free(*desktop);
*desktop = NULL;
}
bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA)
{
enum EGL_PixelFormat pixFmt;
switch(format.type)
{
case FRAME_TYPE_BGRA:
pixFmt = EGL_PF_BGRA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA:
pixFmt = EGL_PF_RGBA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA10:
pixFmt = EGL_PF_RGBA10;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA16F:
pixFmt = EGL_PF_RGBA16F;
desktop->shader = &desktop->shader_generic;
break;
default:
DEBUG_ERROR("Unsupported frame format");
return false;
}
desktop->width = format.width;
desktop->height = format.height;
if (!egl_texture_setup(
desktop->texture,
pixFmt,
format.width,
format.height,
format.pitch,
true, // streaming texture
useDMA
))
{
DEBUG_ERROR("Failed to setup the desktop texture");
return false;
}
return true;
}
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd)
{
if (dmaFd >= 0)
{
if (!egl_texture_update_from_dma(desktop->texture, frame, dmaFd))
return false;
}
else
{
if (!egl_texture_update_from_frame(desktop->texture, frame))
return false;
}
enum EGL_TexStatus status;
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
{
if (status != EGL_TEX_STATUS_NOTREADY)
DEBUG_ERROR("Failed to process the desktop texture");
}
return true;
}
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
const float scaleX, const float scaleY, const bool nearest,
LG_RendererRotate rotate)
{
if (!desktop->shader)
return false;
bool useNearest = nearest;
if (!nearest)
{
switch(rotate)
{
case LG_ROTATE_90:
case LG_ROTATE_270:
if (scaleX < 1.0f || scaleY < 1.0f)
useNearest = true;
break;
default:
break;
}
}
const struct DesktopShader * shader = desktop->shader;
egl_shader_use(shader->shader);
glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY);
glUniform1i(shader->uRotate , rotate);
glUniform1i(shader->uNearest , useNearest ? 1 : 0);
glUniform2f(shader->uDesktopSize, desktop->width, desktop->height);
if (desktop->nvGain)
{
glUniform1i(shader->uNV, 1);
glUniform1f(shader->uNVGain, (float)desktop->nvGain);
}
else
glUniform1i(shader->uNV, 0);
glUniform1i(shader->uCBMode, desktop->cbMode);
egl_model_render(desktop->model);
return true;
}

View File

@@ -0,0 +1,35 @@
/*
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 <stdbool.h>
#include "interface/renderer.h"
typedef struct EGL_Desktop EGL_Desktop;
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display);
void egl_desktop_free(EGL_Desktop ** desktop);
bool egl_desktop_setup (EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA);
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd);
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
const float scaleX, const float scaleY, const bool nearest,
LG_RendererRotate rotate);

View File

@@ -0,0 +1,66 @@
/*
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 "draw.h"
#include <stdlib.h>
#include <math.h>
void egl_draw_torus(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer)
{
GLfloat * v = (GLfloat *)malloc(sizeof(GLfloat) * (pts + 1) * 6);
GLfloat * dst = v;
for(unsigned int i = 0; i <= pts; ++i)
{
const float angle = (i / (float)pts) * M_PI * 2.0f;
const float c = cos(angle);
const float s = sin(angle);
*dst = x + (inner * c); ++dst;
*dst = y + (inner * s); ++dst;
*dst = 0.0f; ++dst;
*dst = x + (outer * c); ++dst;
*dst = y + (outer * s); ++dst;
*dst = 0.0f; ++dst;
}
egl_model_add_verticies(model, v, NULL, (pts + 1) * 2);
free(v);
}
void egl_draw_torus_arc(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer, float s, float e)
{
GLfloat * v = (GLfloat *)malloc(sizeof(GLfloat) * (pts + 1) * 6);
GLfloat * dst = v;
for(unsigned int i = 0; i <= pts; ++i)
{
const float angle = s + ((i / (float)pts) * e);
const float c = cos(angle);
const float s = sin(angle);
*dst = x + (inner * c); ++dst;
*dst = y + (inner * s); ++dst;
*dst = 0.0f; ++dst;
*dst = x + (outer * c); ++dst;
*dst = y + (outer * s); ++dst;
*dst = 0.0f; ++dst;
}
egl_model_add_verticies(model, v, NULL, (pts + 1) * 2);
free(v);
}

View File

@@ -0,0 +1,25 @@
/*
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 "model.h"
void egl_draw_torus (EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer);
void egl_draw_torus_arc(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer, float s, float e);

766
client/renderers/EGL/egl.c Normal file
View File

@@ -0,0 +1,766 @@
/*
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 "interface/renderer.h"
#include "common/debug.h"
#include "common/option.h"
#include "common/sysinfo.h"
#include "common/time.h"
#include "common/locking.h"
#include "util.h"
#include "dynamic/fonts.h"
#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"
#include "cursor.h"
#include "fps.h"
#include "splash.h"
#include "alert.h"
#include "help.h"
#define SPLASH_FADE_TIME 1000000
#define ALERT_TIMEOUT 2000000
struct Options
{
bool vsync;
bool doubleBuffer;
};
struct Inst
{
bool dmaSupport;
LG_RendererParams params;
struct Options opt;
EGLNativeWindowType nativeWind;
EGLDisplay display;
EGLConfig configs;
EGLSurface surface;
EGLContext context, frameContext;
EGL_Desktop * desktop; // the desktop
EGL_Cursor * cursor; // the mouse cursor
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;
bool start;
uint64_t waitFadeTime;
bool waitDone;
bool showAlert;
uint64_t alertTimeout;
bool useCloseFlag;
bool closeFlag;
int width, height;
LG_RendererRect destRect;
LG_RendererRotate rotate; //client side rotation
float translateX , translateY;
float scaleX , scaleY;
float splashRatio;
float screenScaleX, screenScaleY;
bool useNearest;
bool cursorVisible;
int cursorX , cursorY;
float mouseWidth , mouseHeight;
float mouseScaleX, mouseScaleY;
const LG_Font * font;
LG_FontObj fontObj;
LG_FontObj helpFontObj;
};
static struct Option egl_options[] =
{
{
.module = "egl",
.name = "vsync",
.description = "Enable vsync",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "egl",
.name = "doubleBuffer",
.description = "Enable double buffering",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{
.module = "egl",
.name = "multisample",
.description = "Enable Multisampling",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "egl",
.name = "nvGainMax",
.description = "The maximum night vision gain",
.type = OPTION_TYPE_INT,
.value.x_int = 1
},
{
.module = "egl",
.name = "nvGain",
.description = "The initial night vision gain at startup",
.type = OPTION_TYPE_INT,
.value.x_int = 0
},
{
.module = "egl",
.name = "cbMode",
.description = "Color Blind Mode (0 = Off, 1 = Protanope, 2 = Deuteranope, 3 = Tritanope)",
.type = OPTION_TYPE_INT,
.value.x_int = 0
},
{0}
};
void update_mouse_shape(struct Inst * this);
const char * egl_get_name(void)
{
return "EGL";
}
void egl_setup(void)
{
option_register(egl_options);
}
bool egl_create(void ** opaque, const LG_RendererParams params, bool * needsOpenGL)
{
// check if EGL is even available
if (!eglQueryString(EGL_NO_DISPLAY, EGL_VERSION))
return false;
// create our local storage
*opaque = malloc(sizeof(struct Inst));
if (!*opaque)
{
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
return false;
}
memset(*opaque, 0, sizeof(struct Inst));
// safe off parameteres and init our default option values
struct Inst * this = (struct Inst *)*opaque;
memcpy(&this->params, &params, sizeof(LG_RendererParams));
this->opt.vsync = option_get_bool("egl", "vsync");
this->opt.doubleBuffer = option_get_bool("egl", "doubleBuffer");
this->translateX = 0;
this->translateY = 0;
this->scaleX = 1.0f;
this->scaleY = 1.0f;
this->screenScaleX = 1.0f;
this->screenScaleY = 1.0f;
this->font = LG_Fonts[0];
if (!this->font->create(&this->fontObj, NULL, 16))
{
DEBUG_ERROR("Failed to create a font instance");
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)
{
struct Inst * this = (struct Inst *)opaque;
DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off");
return true;
}
void egl_deinitialize(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
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);
}
bool egl_supports(void * opaque, LG_RendererSupport flag)
{
struct Inst * this = (struct Inst *)opaque;
switch(flag)
{
case LG_SUPPORTS_DMABUF:
return this->dmaSupport;
default:
return false;
}
}
void egl_on_restart(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
eglDestroyContext(this->display, this->frameContext);
this->frameContext = NULL;
this->start = false;
}
static void egl_calc_mouse_size(struct Inst * this)
{
if (!this->formatValid)
return;
int w, h;
switch(this->format.rotate)
{
case LG_ROTATE_0:
case LG_ROTATE_180:
this->mouseScaleX = 2.0f / this->format.width;
this->mouseScaleY = 2.0f / this->format.height;
w = this->format.width;
h = this->format.height;
break;
case LG_ROTATE_90:
case LG_ROTATE_270:
this->mouseScaleX = 2.0f / this->format.height;
this->mouseScaleY = 2.0f / this->format.width;
w = this->format.height;
h = this->format.width;
break;
default:
assert(!"unreachable");
}
switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX)
{
case LG_ROTATE_0:
case LG_ROTATE_180:
egl_cursor_set_size(this->cursor,
(this->mouseWidth * (1.0f / w)) * this->scaleX,
(this->mouseHeight * (1.0f / h)) * this->scaleY
);
break;
case LG_ROTATE_90:
case LG_ROTATE_270:
egl_cursor_set_size(this->cursor,
(this->mouseWidth * (1.0f / w)) * this->scaleY,
(this->mouseHeight * (1.0f / h)) * this->scaleX
);
break;
}
}
static void egl_calc_mouse_state(struct Inst * this)
{
if (!this->formatValid)
return;
switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX)
{
case LG_ROTATE_0:
case LG_ROTATE_180:
egl_cursor_set_state(
this->cursor,
this->cursorVisible,
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
);
break;
case LG_ROTATE_90:
case LG_ROTATE_270:
egl_cursor_set_state(
this->cursor,
this->cursorVisible,
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX
);
break;
}
}
void egl_on_resize(void * opaque, const int width, const int height,
const LG_RendererRect destRect, LG_RendererRotate rotate)
{
struct Inst * this = (struct Inst *)opaque;
this->width = width;
this->height = height;
this->rotate = rotate;
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
glViewport(0, 0, width, height);
if (destRect.valid)
{
this->translateX = 1.0f - (((destRect.w / 2) + destRect.x) * 2) / (float)width;
this->translateY = 1.0f - (((destRect.h / 2) + destRect.y) * 2) / (float)height;
this->scaleX = (float)destRect.w / (float)width;
this->scaleY = (float)destRect.h / (float)height;
}
egl_calc_mouse_size(this);
this->splashRatio = (float)width / (float)height;
this->screenScaleX = 1.0f / width;
this->screenScaleY = 1.0f / height;
egl_calc_mouse_state(this);
}
bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor,
const int width, const int height,
const int pitch, const uint8_t * data)
{
struct Inst * this = (struct Inst *)opaque;
if (!egl_cursor_set_shape(this->cursor, cursor, width, height, pitch, data))
{
DEBUG_ERROR("Failed to update the cursor shape");
return false;
}
this->mouseWidth = width;
this->mouseHeight = height;
egl_calc_mouse_size(this);
return true;
}
bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
{
struct Inst * this = (struct Inst *)opaque;
this->cursorVisible = visible;
this->cursorX = x;
this->cursorY = y;
egl_calc_mouse_state(this);
return true;
}
bool egl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
{
struct Inst * this = (struct Inst *)opaque;
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->formatValid = true;
/* this event runs in a second thread so we need to init it here */
if (!this->frameContext)
{
static EGLint attrs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
if (!(this->frameContext = eglCreateContext(this->display, this->configs, this->context, attrs)))
{
DEBUG_ERROR("Failed to create the frame context");
return false;
}
if (!eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->frameContext))
{
DEBUG_ERROR("Failed to make the frame context current");
return false;
}
}
this->useNearest = this->width < format.width || this->height < format.height;
return egl_desktop_setup(this->desktop, format, useDMA);
}
bool egl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd)
{
struct Inst * this = (struct Inst *)opaque;
if (!egl_desktop_update(this->desktop, frame, dmaFd))
{
DEBUG_INFO("Failed to to update the desktop");
return false;
}
this->start = true;
return true;
}
void egl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag)
{
struct Inst * this = (struct Inst *)opaque;
static const uint32_t colors[] =
{
0x0000CCCC, // LG_ALERT_INFO
0x00CC00CC, // LG_ALERT_SUCCESS
0xCC7F00CC, // LG_ALERT_WARNING
0xFF0000CC // LG_ALERT_ERROR
};
if (alert > LG_ALERT_ERROR || alert < 0)
{
DEBUG_ERROR("Invalid alert value");
return;
}
egl_alert_set_color(this->alert, colors[alert]);
egl_alert_set_text (this->alert, message );
if (closeFlag)
{
this->useCloseFlag = true;
*closeFlag = &this->closeFlag;
}
else
{
this->useCloseFlag = false;
this->alertTimeout = microtime() + ALERT_TIMEOUT;
}
this->showAlert = true;
}
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;
this->nativeWind = app_getEGLNativeWindow();
if (!this->nativeWind)
return false;
this->display = app_getEGLDisplay();
if (this->display == EGL_NO_DISPLAY)
return false;
int maj, min;
if (!eglInitialize(this->display, &maj, &min))
{
DEBUG_ERROR("Unable to initialize EGL");
return false;
}
int maxSamples = 1;
if (option_get_bool("egl", "multisample"))
{
if (app_getProp(LG_DS_MAX_MULTISAMPLE, &maxSamples) && maxSamples > 1)
{
if (maxSamples > 4)
maxSamples = 4;
DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples);
}
}
EGLint attr[] =
{
EGL_BUFFER_SIZE , 24,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0,
EGL_SAMPLES , maxSamples,
EGL_NONE
};
EGLint num_config;
if (!eglChooseConfig(this->display, attr, &this->configs, 1, &num_config))
{
DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError());
return false;
}
const EGLint surfattr[] =
{
EGL_RENDER_BUFFER, this->opt.doubleBuffer ? EGL_BACK_BUFFER : EGL_SINGLE_BUFFER,
EGL_NONE
};
this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, surfattr);
if (this->surface == EGL_NO_SURFACE)
{
DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError());
return false;
}
EGLint ctxattr[] =
{
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
this->context = eglCreateContext(this->display, this->configs, EGL_NO_CONTEXT, ctxattr);
if (this->context == EGL_NO_CONTEXT)
{
DEBUG_ERROR("Failed to create EGL context (eglError: 0x%x)", eglGetError());
return false;
}
EGLint rb = 0;
eglQuerySurface(this->display, this->surface, EGL_RENDER_BUFFER, &rb);
switch(rb)
{
case EGL_SINGLE_BUFFER:
DEBUG_INFO("Single buffer mode");
break;
case EGL_BACK_BUFFER:
DEBUG_INFO("Back buffer mode");
break;
default:
DEBUG_WARN("Unknown render buffer mode: %d", rb);
break;
}
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
const char *client_exts = eglQueryString(this->display, EGL_EXTENSIONS);
const char *vendor = (const char *)glGetString(GL_VENDOR);
DEBUG_INFO("EGL : %d.%d", maj, min);
DEBUG_INFO("Vendor : %s", vendor);
DEBUG_INFO("Renderer : %s", glGetString(GL_RENDERER));
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
DEBUG_INFO("EGL APIs : %s", eglQueryString(this->display, EGL_CLIENT_APIS));
DEBUG_INFO("Extensions: %s", client_exts);
if (g_egl_dynProcs.glEGLImageTargetTexture2DOES)
{
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);
if (!egl_desktop_init(&this->desktop, this->display))
{
DEBUG_ERROR("Failed to initialize the desktop");
return false;
}
if (!egl_cursor_init(&this->cursor))
{
DEBUG_ERROR("Failed to initialize the cursor");
return false;
}
if (!egl_fps_init(&this->fps, this->font, this->fontObj))
{
DEBUG_ERROR("Failed to initialize the FPS display");
return false;
}
if (!egl_splash_init(&this->splash))
{
DEBUG_ERROR("Failed to initialize the splash screen");
return false;
}
if (!egl_alert_init(&this->alert, this->font, this->fontObj))
{
DEBUG_ERROR("Failed to initialize the alert display");
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, LG_RendererRotate rotate)
{
struct Inst * this = (struct Inst *)opaque;
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (this->start && egl_desktop_render(this->desktop,
this->translateX, this->translateY,
this->scaleX , this->scaleY ,
this->useNearest,
rotate))
{
if (!this->waitFadeTime)
{
if (!this->params.quickSplash)
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
else
this->waitDone = true;
}
egl_cursor_render(this->cursor,
(this->format.rotate + rotate) % LG_ROTATE_MAX);
}
if (!this->waitDone)
{
float a = 1.0f;
if (!this->waitFadeTime)
a = 1.0f;
else
{
uint64_t t = microtime();
if (t > this->waitFadeTime)
this->waitDone = true;
else
{
uint64_t delta = this->waitFadeTime - t;
a = 1.0f / SPLASH_FADE_TIME * delta;
}
}
if (!this->waitDone)
egl_splash_render(this->splash, a, this->splashRatio);
}
else
{
if (!this->start)
egl_splash_render(this->splash, 1.0f, this->splashRatio);
}
if (this->showAlert)
{
bool close = false;
if (this->useCloseFlag)
close = this->closeFlag;
else if (this->alertTimeout < microtime())
close = true;
if (close)
this->showAlert = false;
else
egl_alert_render(this->alert, this->screenScaleX, this->screenScaleY);
}
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
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;
egl_fps_update(this->fps, avgUPS, avgFPS);
}
struct LG_Renderer LGR_EGL =
{
.get_name = egl_get_name,
.setup = egl_setup,
.create = egl_create,
.initialize = egl_initialize,
.deinitialize = egl_deinitialize,
.supports = egl_supports,
.on_restart = egl_on_restart,
.on_resize = egl_on_resize,
.on_mouse_shape = egl_on_mouse_shape,
.on_mouse_event = egl_on_mouse_event,
.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

@@ -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 "common/debug.h"
const char * egl_getErrorStr(void);
#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())

205
client/renderers/EGL/fps.c Normal file
View File

@@ -0,0 +1,205 @@
/*
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
cahe 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 "fps.h"
#include "common/debug.h"
#include "texture.h"
#include "shader.h"
#include "model.h"
#include <stdlib.h>
#include <string.h>
// these headers are auto generated by cmake
#include "fps.vert.h"
#include "fps.frag.h"
#include "fps_bg.frag.h"
struct EGL_FPS
{
const LG_Font * font;
LG_FontObj fontObj;
EGL_Texture * texture;
EGL_Shader * shader;
EGL_Shader * shaderBG;
EGL_Model * model;
bool display;
bool ready;
int iwidth, iheight;
float width, height;
// uniforms
GLint uScreen , uSize;
GLint uScreenBG, uSizeBG;
};
bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj)
{
*fps = (EGL_FPS *)malloc(sizeof(EGL_FPS));
if (!*fps)
{
DEBUG_ERROR("Failed to malloc EGL_FPS");
return false;
}
memset(*fps, 0, sizeof(EGL_FPS));
(*fps)->font = font;
(*fps)->fontObj = fontObj;
if (!egl_texture_init(&(*fps)->texture, NULL))
{
DEBUG_ERROR("Failed to initialize the fps texture");
return false;
}
if (!egl_shader_init(&(*fps)->shader))
{
DEBUG_ERROR("Failed to initialize the fps shader");
return false;
}
if (!egl_shader_init(&(*fps)->shaderBG))
{
DEBUG_ERROR("Failed to initialize the fps bg shader");
return false;
}
if (!egl_shader_compile((*fps)->shader,
b_shader_fps_vert, b_shader_fps_vert_size,
b_shader_fps_frag, b_shader_fps_frag_size))
{
DEBUG_ERROR("Failed to compile the fps shader");
return false;
}
if (!egl_shader_compile((*fps)->shaderBG,
b_shader_fps_vert , b_shader_fps_vert_size,
b_shader_fps_bg_frag, b_shader_fps_bg_frag_size))
{
DEBUG_ERROR("Failed to compile the fps shader");
return false;
}
(*fps)->uSize = egl_shader_get_uniform_location((*fps)->shader , "size" );
(*fps)->uScreen = egl_shader_get_uniform_location((*fps)->shader , "screen");
(*fps)->uSizeBG = egl_shader_get_uniform_location((*fps)->shaderBG, "size" );
(*fps)->uScreenBG = egl_shader_get_uniform_location((*fps)->shaderBG, "screen");
if (!egl_model_init(&(*fps)->model))
{
DEBUG_ERROR("Failed to initialize the fps model");
return false;
}
egl_model_set_default((*fps)->model);
egl_model_set_texture((*fps)->model, (*fps)->texture);
return true;
}
void egl_fps_free(EGL_FPS ** fps)
{
if (!*fps)
return;
egl_texture_free(&(*fps)->texture );
egl_shader_free (&(*fps)->shader );
egl_shader_free (&(*fps)->shaderBG);
egl_model_free (&(*fps)->model );
free(*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);
LG_FontBitmap * bmp = fps->font->render(fps->fontObj, 0xffffff00, str);
if (!bmp)
{
DEBUG_ERROR("Failed to render fps text");
return;
}
if (fps->iwidth != bmp->width || fps->iheight != bmp->height)
{
fps->iwidth = bmp->width;
fps->iheight = bmp->height;
fps->width = (float)bmp->width;
fps->height = (float)bmp->height;
egl_texture_setup(
fps->texture,
EGL_PF_BGRA,
bmp->width ,
bmp->height,
bmp->width * bmp->bpp,
false,
false
);
}
egl_texture_update
(
fps->texture,
bmp->pixels
);
fps->ready = true;
fps->font->release(fps->fontObj, bmp);
}
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
{
if (!fps->display || !fps->ready)
return;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// render the background first
egl_shader_use(fps->shaderBG);
glUniform2f(fps->uScreenBG, scaleX , scaleY );
glUniform2f(fps->uSizeBG , fps->width, fps->height);
egl_model_render(fps->model);
// render the texture over the background
egl_shader_use(fps->shader);
glUniform2f(fps->uScreen, scaleX , scaleY );
glUniform2f(fps->uSize , fps->width, fps->height);
egl_model_render(fps->model);
glDisable(GL_BLEND);
}

View File

@@ -0,0 +1,33 @@
/*
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 <stdbool.h>
#include "interface/font.h"
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

@@ -0,0 +1,216 @@
/*
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 "model.h"
#include "shader.h"
#include "texture.h"
#include "common/debug.h"
#include "ll.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
struct EGL_Model
{
bool rebuild;
struct ll * verticies;
size_t vertexCount;
bool finish;
bool hasBuffer;
GLuint buffer;
EGL_Shader * shader;
EGL_Texture * texture;
};
struct FloatList
{
GLfloat * v;
GLfloat * u;
size_t count;
};
void update_uniform_bindings(EGL_Model * model);
bool egl_model_init(EGL_Model ** model)
{
*model = (EGL_Model *)malloc(sizeof(EGL_Model));
if (!*model)
{
DEBUG_ERROR("Failed to malloc EGL_Model");
return false;
}
memset(*model, 0, sizeof(EGL_Model));
(*model)->verticies = ll_new();
return true;
}
void egl_model_free(EGL_Model ** model)
{
if (!*model)
return;
struct FloatList * fl;
while(ll_shift((*model)->verticies, (void **)&fl))
{
free(fl->u);
free(fl->v);
free(fl);
}
ll_free((*model)->verticies);
if ((*model)->hasBuffer)
glDeleteBuffers(1, &(*model)->buffer);
free(*model);
*model = NULL;
}
void egl_model_set_default(EGL_Model * model)
{
static const GLfloat square[] =
{
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f
};
static const GLfloat uvs[] =
{
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
};
egl_model_add_verticies(model, square, uvs, 4);
}
void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count)
{
struct FloatList * fl = (struct FloatList *)malloc(sizeof(struct FloatList));
fl->count = count;
fl->v = (GLfloat *)malloc(sizeof(GLfloat) * count * 3);
fl->u = (GLfloat *)malloc(sizeof(GLfloat) * count * 2);
memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3);
if (uvs)
memcpy(fl->u, uvs, sizeof(GLfloat) * count * 2);
else
memset(fl->u, 0 , sizeof(GLfloat) * count * 2);
ll_push(model->verticies, fl);
model->rebuild = true;
model->vertexCount += count;
}
void egl_model_render(EGL_Model * model)
{
if (!model->vertexCount)
return;
if (model->rebuild)
{
if (model->hasBuffer)
glDeleteBuffers(1, &model->buffer);
/* create a buffer large enough */
glGenBuffers(1, &model->buffer);
glBindBuffer(GL_ARRAY_BUFFER, model->buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * (model->vertexCount * 5), NULL, GL_STATIC_DRAW);
GLintptr offset = 0;
/* buffer the verticies */
struct FloatList * fl;
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
{
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 3, fl->v);
offset += sizeof(GLfloat) * fl->count * 3;
}
/* buffer the uvs */
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
{
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 2, fl->u);
offset += sizeof(GLfloat) * fl->count * 2;
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
model->rebuild = false;
}
/* bind the model buffer and setup the pointers */
glBindBuffer(GL_ARRAY_BUFFER, model->buffer);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat) * model->vertexCount * 3));
if (model->shader)
egl_shader_use(model->shader);
if (model->texture)
egl_texture_bind(model->texture);
/* draw the arrays */
GLint offset = 0;
struct FloatList * fl;
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
{
glDrawArrays(GL_TRIANGLE_STRIP, offset, fl->count);
offset += fl->count;
}
/* unbind and cleanup */
glBindTexture(GL_TEXTURE_2D, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glUseProgram(0);
}
void egl_model_set_shader(EGL_Model * model, EGL_Shader * shader)
{
model->shader = shader;
update_uniform_bindings(model);
}
void egl_model_set_texture(EGL_Model * model, EGL_Texture * texture)
{
model->texture = texture;
update_uniform_bindings(model);
}
void update_uniform_bindings(EGL_Model * model)
{
if (!model->shader || !model->texture)
return;
const int count = egl_texture_count(model->texture);
egl_shader_associate_textures(model->shader, count);
}

View File

@@ -0,0 +1,38 @@
/*
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 <stdbool.h>
#include "shader.h"
#include "texture.h"
#include <GL/gl.h>
typedef struct EGL_Model EGL_Model;
bool egl_model_init(EGL_Model ** model);
void egl_model_free(EGL_Model ** model);
void egl_model_set_default (EGL_Model * model);
void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count);
void egl_model_set_shader (EGL_Model * model, EGL_Shader * shader);
void egl_model_set_texture (EGL_Model * model, EGL_Texture * texture);
void egl_model_render(EGL_Model * model);

View File

@@ -0,0 +1,223 @@
/*
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 "shader.h"
#include "common/debug.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
struct EGL_Shader
{
bool hasShader;
GLuint shader;
};
bool egl_shader_init(EGL_Shader ** this)
{
*this = (EGL_Shader *)malloc(sizeof(EGL_Shader));
if (!*this)
{
DEBUG_ERROR("Failed to malloc EGL_Shader");
return false;
}
memset(*this, 0, sizeof(EGL_Shader));
return true;
}
void egl_shader_free(EGL_Shader ** this)
{
if (!*this)
return;
if ((*this)->hasShader)
glDeleteProgram((*this)->shader);
free(*this);
*this = NULL;
}
bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * fragment_file)
{
char * vertex_code, * fragment_code;
size_t vertex_size, fragment_size;
if (!util_fileGetContents(vertex_file, &vertex_code, &vertex_size))
{
DEBUG_ERROR("Failed to read vertex shader");
return false;
}
DEBUG_INFO("Loaded vertex shader: %s", vertex_file);
if (!util_fileGetContents(fragment_file, &fragment_code, &fragment_size))
{
DEBUG_ERROR("Failed to read fragment shader");
free(vertex_code);
return false;
}
DEBUG_INFO("Loaded fragment shader: %s", fragment_file);
bool ret = egl_shader_compile(this, vertex_code, vertex_size, fragment_code, fragment_size);
free(vertex_code);
free(fragment_code);
return ret;
}
bool egl_shader_compile(EGL_Shader * this, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size)
{
if (this->hasShader)
{
glDeleteProgram(this->shader);
this->hasShader = false;
}
GLint length;
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
length = vertex_size;
glShaderSource(vertexShader, 1, (const char**)&vertex_code, &length);
glCompileShader(vertexShader);
GLint result = GL_FALSE;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);
if (result == GL_FALSE)
{
DEBUG_ERROR("Failed to compile vertex shader");
int logLength;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength + 1);
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
log[logLength] = 0;
DEBUG_ERROR("%s", log);
free(log);
}
glDeleteShader(vertexShader);
return false;
}
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
length = fragment_size;
glShaderSource(fragmentShader, 1, (const char**)&fragment_code, &length);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);
if (result == GL_FALSE)
{
DEBUG_ERROR("Failed to compile fragment shader");
int logLength;
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength + 1);
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
log[logLength] = 0;
DEBUG_ERROR("%s", log);
free(log);
}
glDeleteShader(fragmentShader);
glDeleteShader(vertexShader );
return false;
}
this->shader = glCreateProgram();
glAttachShader(this->shader, vertexShader );
glAttachShader(this->shader, fragmentShader);
glLinkProgram(this->shader);
glGetProgramiv(this->shader, GL_LINK_STATUS, &result);
if (result == GL_FALSE)
{
DEBUG_ERROR("Failed to link shader program");
int logLength;
glGetProgramiv(this->shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
char *log = malloc(logLength + 1);
glGetProgramInfoLog(this->shader, logLength, NULL, log);
log[logLength] = 0;
DEBUG_ERROR("%s", log);
free(log);
}
glDetachShader(this->shader, vertexShader );
glDetachShader(this->shader, fragmentShader);
glDeleteShader(fragmentShader);
glDeleteShader(vertexShader );
glDeleteProgram(this->shader );
return false;
}
glDetachShader(this->shader, vertexShader );
glDetachShader(this->shader, fragmentShader);
glDeleteShader(fragmentShader);
glDeleteShader(vertexShader );
this->hasShader = true;
return true;
}
void egl_shader_use(EGL_Shader * this)
{
if (this->hasShader)
glUseProgram(this->shader);
else
DEBUG_ERROR("Shader program has not been compiled");
}
void egl_shader_associate_textures(EGL_Shader * this, const int count)
{
char name[] = "sampler1";
glUseProgram(this->shader);
for(int i = 0; i < count; ++i, name[7]++)
{
GLint loc = glGetUniformLocation(this->shader, name);
if (loc == -1)
{
DEBUG_WARN("Shader uniform location `%s` not found", name);
continue;
}
glUniform1i(loc, i);
}
glUseProgram(0);
}
GLint egl_shader_get_uniform_location(EGL_Shader * this, const char * name)
{
if (!this->shader)
{
DEBUG_ERROR("Shader program has not been compiled");
return 0;
}
return glGetUniformLocation(this->shader, name);
}

View File

@@ -0,0 +1,37 @@
/*
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 <stdbool.h>
#include <stddef.h>
#include <GL/gl.h>
typedef struct EGL_Shader EGL_Shader;
bool egl_shader_init(EGL_Shader ** shader);
void egl_shader_free(EGL_Shader ** shader);
bool egl_shader_load (EGL_Shader * model, const char * vertex_file, const char * fragment_file);
bool egl_shader_compile(EGL_Shader * model, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size);
void egl_shader_use (EGL_Shader * shader);
void egl_shader_associate_textures(EGL_Shader * shader, const int count);
GLint egl_shader_get_uniform_location(EGL_Shader * shader, const char * name);

View File

@@ -0,0 +1,12 @@
#version 300 es
in highp vec2 uv;
in highp vec2 sz;
out highp vec4 color;
uniform sampler2D sampler1;
void main()
{
color = texelFetch(sampler1, ivec2(uv * sz), 0);
}

View File

@@ -0,0 +1,24 @@
#version 300 es
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec2 vertexUV;
uniform vec2 screen;
uniform ivec2 size;
uniform vec4 color;
out highp vec2 uv;
out highp vec2 sz;
out highp vec4 c;
void main()
{
sz = vec2(size) + 0.5;
gl_Position.xyz = vertexPosition_modelspace;
gl_Position.w = 1.0;
gl_Position.xy *= screen.xy * sz;
uv = vertexUV;
c = color;
}

View File

@@ -0,0 +1,9 @@
#version 300 es
in highp vec4 c;
out highp vec4 color;
void main()
{
color = c;
}

View File

@@ -0,0 +1,43 @@
#version 300 es
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec2 vertexUV;
uniform vec4 mouse;
uniform lowp int rotate;
out highp vec2 uv;
void main()
{
vec2 muv = vertexPosition_modelspace.xy;
muv.x += 1.0f;
muv.y -= 1.0f;
muv.x *= mouse.z;
muv.y *= mouse.w;
muv.x += mouse.x;
muv.y -= mouse.y;
if (rotate == 0) // 0
{
gl_Position.xy = muv;
}
else if (rotate == 1) // 90
{
gl_Position.x = muv.y;
gl_Position.y = -muv.x;
}
else if (rotate == 2) // 180
{
gl_Position.x = -muv.x;
gl_Position.y = -muv.y;
}
else if (rotate == 3) // 270
{
gl_Position.x = -muv.y;
gl_Position.y = muv.x;
}
gl_Position.w = 1.0;
uv = vertexUV;
}

View File

@@ -0,0 +1,14 @@
#version 300 es
in highp vec2 uv;
out highp vec4 color;
uniform sampler2D sampler1;
void main()
{
highp vec4 tmp = texture(sampler1, uv);
if (tmp.rgb == vec3(0.0, 0.0, 0.0))
discard;
color = tmp;
}

View File

@@ -0,0 +1,51 @@
#version 300 es
in highp vec2 uv;
out highp vec4 color;
uniform sampler2D sampler1;
uniform lowp int rotate;
uniform int cbMode;
void main()
{
color = texture(sampler1, uv);
if (cbMode > 0)
{
highp float L = (17.8824000 * color.r) + (43.516100 * color.g) + (4.11935 * color.b);
highp float M = (03.4556500 * color.r) + (27.155400 * color.g) + (3.86714 * color.b);
highp float S = (00.0299566 * color.r) + (00.184309 * color.g) + (1.46709 * color.b);
highp float l, m, s;
if (cbMode == 1) // Protanope
{
l = 0.0f * L + 2.02344f * M + -2.52581f * S;
m = 0.0f * L + 1.0f * M + 0.0f * S;
s = 0.0f * L + 0.0f * M + 1.0f * S;
}
else if (cbMode == 2) // Deuteranope
{
l = 1.000000 * L + 0.0f * M + 0.00000 * S;
m = 0.494207 * L + 0.0f * M + 1.24827 * S;
s = 0.000000 * L + 0.0f * M + 1.00000 * S;
}
else if (cbMode == 3) // Tritanope
{
l = 1.000000 * L + 0.000000 * M + 0.0 * S;
m = 0.000000 * L + 1.000000 * M + 0.0 * S;
s = -0.395913 * L + 0.801109 * M + 0.0 * S;
}
highp vec4 error;
error.r = ( 0.080944447900 * l) + (-0.13050440900 * m) + ( 0.116721066 * s);
error.g = (-0.010248533500 * l) + ( 0.05401932660 * m) + (-0.113614708 * s);
error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + ( 0.693511405 * s);
error.a = 0.0;
error = color - error;
color.g += (error.r * 0.7) + (error.g * 1.0);
color.b += (error.r * 0.7) + (error.b * 1.0);
}
}

View File

@@ -0,0 +1,20 @@
#version 300 es
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec2 vertexUV;
uniform vec4 position;
out highp vec2 uv;
void main()
{
gl_Position.xyz = vertexPosition_modelspace;
gl_Position.w = 1.0;
gl_Position.x -= position.x;
gl_Position.y -= position.y;
gl_Position.x *= position.z;
gl_Position.y *= position.w;
uv = vertexUV;
}

View File

@@ -0,0 +1,89 @@
#version 300 es
in highp vec2 uv;
out highp vec4 color;
uniform sampler2D sampler1;
uniform int nearest;
uniform highp vec2 size;
uniform int rotate;
uniform int nv;
uniform highp float nvGain;
uniform int cbMode;
void main()
{
highp vec2 ruv;
if (rotate == 0) // 0
{
ruv = uv;
}
else if (rotate == 1) // 90
{
ruv.x = uv.y;
ruv.y = -uv.x + 1.0f;
}
else if (rotate == 2) // 180
{
ruv.x = -uv.x + 1.0f;
ruv.y = -uv.y + 1.0f;
}
else if (rotate == 3) // 270
{
ruv.x = -uv.y + 1.0f;
ruv.y = uv.x;
}
if(nearest == 1)
color = texture(sampler1, ruv);
else
color = texelFetch(sampler1, ivec2(ruv * size), 0);
if (cbMode > 0)
{
highp float L = (17.8824000 * color.r) + (43.516100 * color.g) + (4.11935 * color.b);
highp float M = (03.4556500 * color.r) + (27.155400 * color.g) + (3.86714 * color.b);
highp float S = (00.0299566 * color.r) + (00.184309 * color.g) + (1.46709 * color.b);
highp float l, m, s;
if (cbMode == 1) // Protanope
{
l = 0.0f * L + 2.02344f * M + -2.52581f * S;
m = 0.0f * L + 1.0f * M + 0.0f * S;
s = 0.0f * L + 0.0f * M + 1.0f * S;
}
else if (cbMode == 2) // Deuteranope
{
l = 1.000000 * L + 0.0f * M + 0.00000 * S;
m = 0.494207 * L + 0.0f * M + 1.24827 * S;
s = 0.000000 * L + 0.0f * M + 1.00000 * S;
}
else if (cbMode == 3) // Tritanope
{
l = 1.000000 * L + 0.000000 * M + 0.0 * S;
m = 0.000000 * L + 1.000000 * M + 0.0 * S;
s = -0.395913 * L + 0.801109 * M + 0.0 * S;
}
highp vec4 error;
error.r = ( 0.080944447900 * l) + (-0.13050440900 * m) + ( 0.116721066 * s);
error.g = (-0.010248533500 * l) + ( 0.05401932660 * m) + (-0.113614708 * s);
error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + ( 0.693511405 * s);
error.a = 0.0;
error = color - error;
color.g += (error.r * 0.7) + (error.g * 1.0);
color.b += (error.r * 0.7) + (error.b * 1.0);
}
if (nv == 1)
{
highp float lumi = 1.0 - (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
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

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

@@ -0,0 +1,12 @@
#version 300 es
in highp vec3 pos;
out highp vec4 color;
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, 1);
}

View File

@@ -0,0 +1,14 @@
#version 300 es
layout(location = 0) in vec3 vertexPosition_modelspace;
out highp vec3 pos;
out highp float a;
void main()
{
gl_Position.xyz = vertexPosition_modelspace;
gl_Position.w = 1.0;
pos = vertexPosition_modelspace;
}

View File

@@ -0,0 +1,10 @@
#version 300 es
out highp vec4 color;
uniform sampler2D sampler1;
void main()
{
color = vec4(1.0, 1.0, 1.0, 1.0);
}

View File

@@ -0,0 +1,12 @@
#version 300 es
layout(location = 0) in vec3 vertexPosition_modelspace;
uniform float scale;
void main()
{
gl_Position.xyz = vertexPosition_modelspace;
gl_Position.y *= scale;
gl_Position.w = 1.0;
}

View File

@@ -0,0 +1,177 @@
/*
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
cahe 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 "splash.h"
#include "common/debug.h"
#include "draw.h"
#include "texture.h"
#include "shader.h"
#include "model.h"
#include <GL/gl.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
// these headers are auto generated by cmake
#include "splash_bg.vert.h"
#include "splash_bg.frag.h"
#include "splash_logo.vert.h"
#include "splash_logo.frag.h"
struct EGL_Splash
{
EGL_Shader * bgShader;
EGL_Model * bg;
EGL_Shader * logoShader;
EGL_Model * logo;
// uniforms
GLint uScale;
};
bool egl_splash_init(EGL_Splash ** splash)
{
*splash = (EGL_Splash *)malloc(sizeof(EGL_Splash));
if (!*splash)
{
DEBUG_ERROR("Failed to malloc EGL_Splash");
return false;
}
memset(*splash, 0, sizeof(EGL_Splash));
if (!egl_shader_init(&(*splash)->bgShader))
{
DEBUG_ERROR("Failed to initialize the splash bgShader");
return false;
}
if (!egl_shader_compile((*splash)->bgShader,
b_shader_splash_bg_vert, b_shader_splash_bg_vert_size,
b_shader_splash_bg_frag, b_shader_splash_bg_frag_size))
{
DEBUG_ERROR("Failed to compile the splash bgShader");
return false;
}
if (!egl_model_init(&(*splash)->bg))
{
DEBUG_ERROR("Failed to intiailize the splash bg model");
return false;
}
egl_model_set_default((*splash)->bg);
if (!egl_shader_init(&(*splash)->logoShader))
{
DEBUG_ERROR("Failed to initialize the splash logoShader");
return false;
}
if (!egl_shader_compile((*splash)->logoShader,
b_shader_splash_logo_vert, b_shader_splash_logo_vert_size,
b_shader_splash_logo_frag, b_shader_splash_logo_frag_size))
{
DEBUG_ERROR("Failed to compile the splash logoShader");
return false;
}
(*splash)->uScale = egl_shader_get_uniform_location((*splash)->logoShader, "scale");
if (!egl_model_init(&(*splash)->logo))
{
DEBUG_ERROR("Failed to intiailize the splash model");
return false;
}
/* build the splash model */
#define P(x) ((1.0f/800.0f)*(float)(x))
egl_draw_torus_arc((*splash)->logo, 30, P( 0 ), P(0), P(102), P(98), 0.0f, -M_PI);
egl_draw_torus ((*splash)->logo, 30, P(-100), P(8), P(8 ), P(4 ));
egl_draw_torus ((*splash)->logo, 30, P( 100), P(8), P(8 ), P(4 ));
egl_draw_torus ((*splash)->logo, 60, P(0), P(0), P(83), P(79));
egl_draw_torus ((*splash)->logo, 60, P(0), P(0), P(67), P(63));
static const GLfloat lines[][12] =
{
{
P( -2), P(-140), 0.0f,
P( -2), P(-100), 0.0f,
P( 2), P(-140), 0.0f,
P( 2), P(-100), 0.0f
},
{
P(-26), P(-144), 0.0f,
P(-26), P(-140), 0.0f,
P( 26), P(-144), 0.0f,
P( 26), P(-140), 0.0f
},
{
P(-40), P(-156), 0.0f,
P(-40), P(-152), 0.0f,
P( 40), P(-156), 0.0f,
P( 40), P(-152), 0.0f
}
};
egl_model_add_verticies((*splash)->logo, lines[0], NULL, 4);
egl_model_add_verticies((*splash)->logo, lines[1], NULL, 4);
egl_model_add_verticies((*splash)->logo, lines[2], NULL, 4);
egl_draw_torus_arc((*splash)->logo, 10, P(-26), P(-154), P(10), P(14), M_PI , -M_PI / 2.0);
egl_draw_torus_arc((*splash)->logo, 10, P( 26), P(-154), P(10), P(14), M_PI / 2.0f, -M_PI / 2.0);
#undef P
return true;
}
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;
}
void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY)
{
glEnable(GL_BLEND);
glBlendColor(0, 0, 0, alpha);
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
egl_shader_use(splash->bgShader);
egl_model_render(splash->bg);
egl_shader_use(splash->logoShader);
glUniform1f(splash->uScale, scaleY);
egl_model_render(splash->logo);
glDisable(GL_BLEND);
}

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
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
@@ -18,16 +18,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "lg-renderer.h"
extern const LG_Renderer LGR_OpenGL;
//extern const LG_Renderer LGR_OpenGLBasic;
#include <stdbool.h>
const LG_Renderer * LG_Renderers[] =
{
&LGR_OpenGL,
// &LGR_OpenGLBasic,
NULL // end of array sentinal
};
typedef struct EGL_Splash EGL_Splash;
#define LG_RENDERER_COUNT ((sizeof(LG_Renderers) / sizeof(LG_Renderer *)) - 1)
bool egl_splash_init(EGL_Splash ** splash);
void egl_splash_free(EGL_Splash ** splash);
void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY);

View File

@@ -0,0 +1,578 @@
/*
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 "texture.h"
#include "common/debug.h"
#include "common/framebuffer.h"
#include "egl_dynprocs.h"
#include "egldebug.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdatomic.h>
/**
* the following comes from drm_fourcc.h and is included here to avoid the
* external dependency for the few simple defines we need
*/
#define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \
((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4')
#define DRM_FORMAT_ABGR8888 fourcc_code('A', 'B', '2', '4')
#define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0')
#define DRM_FORMAT_ABGR16161616F fourcc_code('A', 'B', '4', 'H')
/* this must be a multiple of 2 */
#define BUFFER_COUNT 4
struct Buffer
{
bool hasPBO;
GLuint pbo;
void * map;
GLsync sync;
};
struct BufferState
{
_Atomic(uint8_t) w, u, s, d;
};
struct EGL_Texture
{
EGLDisplay * display;
enum EGL_PixelFormat pixFmt;
size_t bpp;
bool streaming;
bool dma;
bool ready;
GLuint sampler;
size_t width, height, stride, pitch;
GLenum intFormat;
GLenum format;
GLenum dataType;
unsigned int fourcc;
size_t pboBufferSize;
struct BufferState state;
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)
{
*texture = (EGL_Texture *)malloc(sizeof(EGL_Texture));
if (!*texture)
{
DEBUG_ERROR("Failed to malloc EGL_Texture");
return false;
}
memset(*texture, 0, sizeof(EGL_Texture));
(*texture)->display = display;
return true;
}
void egl_texture_free(EGL_Texture ** texture)
{
if (!*texture)
return;
glDeleteSamplers(1, &(*texture)->sampler);
for(int i = 0; i < (*texture)->bufferCount; ++i)
{
struct Buffer * b = &(*texture)->buf[i];
if (b->hasPBO)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, b->pbo);
if ((*texture)->buf[i].map)
{
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
(*texture)->buf[i].map = NULL;
}
glDeleteBuffers(1, &b->pbo);
if (b->sync)
glDeleteSync(b->sync);
}
}
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;
}
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
texture->buf[i].map = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT |
GL_MAP_PERSISTENT_BIT
);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
if (!texture->buf[i].map)
{
DEBUG_EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i,
texture->pboBufferSize);
return false;
}
return true;
}
static void egl_texture_unmap(EGL_Texture * texture, uint8_t i)
{
if (!texture->buf[i].map)
return;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
texture->buf[i].map = NULL;
}
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming, bool useDMA)
{
if (texture->streaming && !useDMA)
{
for(int i = 0; i < texture->bufferCount; ++i)
{
egl_texture_unmap(texture, i);
if (texture->buf[i].hasPBO)
{
glDeleteBuffers(1, &texture->buf[i].pbo);
texture->buf[i].hasPBO = false;
}
}
}
texture->pixFmt = pixFmt;
texture->width = width;
texture->height = height;
texture->stride = stride;
texture->streaming = streaming;
texture->bufferCount = streaming ? BUFFER_COUNT : 1;
texture->dma = useDMA;
texture->ready = false;
atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed);
switch(pixFmt)
{
case EGL_PF_BGRA:
texture->bpp = 4;
texture->format = GL_BGRA;
texture->intFormat = GL_BGRA;
texture->dataType = GL_UNSIGNED_BYTE;
texture->fourcc = DRM_FORMAT_ARGB8888;
texture->pboBufferSize = height * stride;
break;
case EGL_PF_RGBA:
texture->bpp = 4;
texture->format = GL_RGBA;
texture->intFormat = GL_BGRA;
texture->dataType = GL_UNSIGNED_BYTE;
texture->fourcc = DRM_FORMAT_ABGR8888;
texture->pboBufferSize = height * stride;
break;
case EGL_PF_RGBA10:
texture->bpp = 4;
texture->format = GL_RGBA;
texture->intFormat = GL_RGB10_A2;
texture->dataType = GL_UNSIGNED_INT_2_10_10_10_REV;
texture->fourcc = DRM_FORMAT_BGRA1010102;
texture->pboBufferSize = height * stride;
break;
case EGL_PF_RGBA16F:
texture->bpp = 8;
texture->format = GL_RGBA;
texture->intFormat = GL_RGBA16F;
texture->dataType = GL_HALF_FLOAT;
texture->fourcc = DRM_FORMAT_ABGR16161616F;
texture->pboBufferSize = height * stride;
break;
default:
DEBUG_ERROR("Unsupported pixel format");
return false;
}
texture->pitch = stride / texture->bpp;
if (texture->tex)
glDeleteTextures(1, &texture->tex);
glGenTextures(1, &texture->tex);
if (!texture->sampler)
{
glGenSamplers(1, &texture->sampler);
glSamplerParameteri(texture->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(texture->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(texture->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glSamplerParameteri(texture->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
}
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,
texture->height, 0, texture->format, texture->dataType, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
if (!streaming)
return true;
for(int i = 0; i < texture->bufferCount; ++i)
{
glGenBuffers(1, &texture->buf[i].pbo);
texture->buf[i].hasPBO = true;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
glBufferStorage(
GL_PIXEL_UNPACK_BUFFER,
texture->pboBufferSize,
NULL,
GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT
);
if (!egl_texture_map(texture, i))
return false;
}
return true;
}
static void egl_warn_slow(void)
{
static bool warnDone = false;
if (!warnDone)
{
warnDone = true;
DEBUG_BREAK();
DEBUG_WARN("The guest is providing updates faster then your computer can display them");
DEBUG_WARN("This is a hardware limitation, expect microstutters & frame skips");
DEBUG_BREAK();
}
}
bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
{
if (texture->streaming)
{
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true;
}
const uint8_t b = sw % BUFFER_COUNT;
memcpy(texture->buf[b].map, buffer, texture->pboBufferSize);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
}
else
{
glBindTexture(GL_TEXTURE_2D, texture->tex);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->pitch);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height,
texture->format, texture->dataType, buffer);
glBindTexture(GL_TEXTURE_2D, 0);
}
return true;
}
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame)
{
if (!texture->streaming)
return false;
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true;
}
const uint8_t b = sw % BUFFER_COUNT;
framebuffer_read(
frame,
texture->buf[b].map,
texture->stride,
texture->height,
texture->width,
texture->bpp,
texture->stride
);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
return true;
}
bool egl_texture_update_from_dma(EGL_Texture * texture, const FrameBuffer * frame, const int dmaFd)
{
if (!texture->streaming)
return false;
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true;
}
EGLImage image = EGL_NO_IMAGE;
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)
{
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
};
/* 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);
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;
}
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
{
if (!texture->streaming)
return EGL_TEX_STATUS_OK;
const uint8_t su =
atomic_load_explicit(&texture->state.u, memory_order_acquire);
const uint8_t nextu = su + 1;
if (
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
const uint8_t b = su % BUFFER_COUNT;
/* update the texture */
if (!texture->dma)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[b].pbo);
glBindTexture(GL_TEXTURE_2D, texture->tex);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->pitch);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height,
texture->format, texture->dataType, (const void *)0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
/* create a fence to prevent usage before the update is complete */
texture->buf[b].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
/* we must flush to ensure the sync is in the command buffer */
glFlush();
}
texture->ready = true;
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
return EGL_TEX_STATUS_OK;
}
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
{
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
if (texture->streaming)
{
if (!texture->ready)
return EGL_TEX_STATUS_NOTREADY;
const uint8_t b = ss % BUFFER_COUNT;
if (texture->dma)
{
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
memory_order_release) + 1;
}
else if (texture->buf[b].sync != 0)
{
switch(glClientWaitSync(texture->buf[b].sync, 0, 20000000)) // 20ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
glDeleteSync(texture->buf[b].sync);
texture->buf[b].sync = 0;
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
memory_order_release) + 1;
break;
case GL_TIMEOUT_EXPIRED:
break;
case GL_WAIT_FAILED:
case GL_INVALID_VALUE:
glDeleteSync(texture->buf[b].sync);
texture->buf[b].sync = 0;
DEBUG_EGL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
}
if (ss != sd && ss != (uint8_t)(sd + 1))
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
memory_order_release) + 1;
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture->tex);
glBindSampler(0, texture->sampler);
return EGL_TEX_STATUS_OK;
}
int egl_texture_count(EGL_Texture * texture)
{
return 1;
}

View File

@@ -0,0 +1,57 @@
/*
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 <stdbool.h>
#include "shader.h"
#include "common/framebuffer.h"
#include <GL/gl.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
typedef struct EGL_Texture EGL_Texture;
enum EGL_PixelFormat
{
EGL_PF_RGBA,
EGL_PF_BGRA,
EGL_PF_RGBA10,
EGL_PF_RGBA16F,
EGL_PF_YUV420
};
enum EGL_TexStatus
{
EGL_TEX_STATUS_NOTREADY,
EGL_TEX_STATUS_OK,
EGL_TEX_STATUS_ERROR
};
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display);
void egl_texture_free(EGL_Texture ** tex);
bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming, bool useDMA);
bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame);
bool egl_texture_update_from_dma (EGL_Texture * texture, const FrameBuffer * frmame, const int dmaFd);
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture);
int egl_texture_count (EGL_Texture * texture);

View File

@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.0)
project(renderer_Opengl LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(RENDERER_OPENGL_PKGCONFIG REQUIRED
gl
)
add_library(renderer_OpenGL STATIC
opengl.c
)
target_link_libraries(renderer_OpenGL
${RENDERER_OPENGL_PKGCONFIG_LIBRARIES}
lg_common
fonts
)
target_include_directories(renderer_OpenGL
PRIVATE
src
${RENDERER_OPENGL_PKGCONFIG_INCLUDE_DIRS}
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,814 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 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 "lg-renderer.h"
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <SDL2/SDL_ttf.h>
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#include "debug.h"
#include "memcpySSE.h"
#include "utils.h"
#define FRAME_TEXTURE 0
#define FPS_TEXTURE 1
#define MOUSE_TEXTURE 2
#define TEXTURE_COUNT 3
static PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI = NULL;
static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI = NULL;
struct Options
{
bool mipmap;
bool vsync;
bool preventBuffer;
bool splitMouse;
};
static struct Options defaultOptions =
{
.mipmap = true,
.vsync = true,
.preventBuffer = true,
.splitMouse = false
};
struct LGR_OpenGLBasic
{
LG_RendererParams params;
struct Options opt;
bool configured;
SDL_Window * sdlWindow;
SDL_GLContext glContext;
bool doneInfo;
SDL_Point window;
bool resizeWindow;
bool frameUpdate;
LG_RendererFormat format;
GLuint intFormat;
GLuint vboFormat;
size_t texSize;
uint64_t drawStart;
int texList;
int fpsList;
int mouseList;
LG_RendererRect destRect;
bool hasTextures;
GLuint textures[TEXTURE_COUNT];
uint gpuFrameCount;
bool fpsTexture;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
SDL_Rect fpsRect;
bool mouseUpdate;
bool newShape;
uint64_t lastMouseDraw;
LG_RendererCursor mouseType;
bool mouseVisible;
SDL_Rect mousePos;
};
bool lgr_opengl_basic_check_error(const char * name)
{
GLenum error = glGetError();
if (error == GL_NO_ERROR)
return false;
const GLubyte * errStr = gluErrorString(error);
DEBUG_ERROR("%s = %d (%s)", name, error, errStr);
return true;
}
const char * lgr_opengl_basic_get_name()
{
return "OpenGL-Basic";
}
bool lgr_opengl_basic_create(void ** opaque, const LG_RendererParams params)
{
// create our local storage
*opaque = malloc(sizeof(struct LGR_OpenGLBasic));
if (!*opaque)
{
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct LGR_OpenGLBasic));
return false;
}
memset(*opaque, 0, sizeof(struct LGR_OpenGLBasic));
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)*opaque;
memcpy(&this->params, &params , sizeof(LG_RendererParams));
memcpy(&this->opt , &defaultOptions, sizeof(struct Options ));
return true;
}
bool lgr_opengl_basic_initialize(void * opaque, Uint32 * sdlFlags)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this)
return false;
if (!glXGetVideoSyncSGI)
{
glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC )glXGetProcAddress((const GLubyte *)"glXGetVideoSyncSGI" );
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddress((const GLubyte *)"glXWaitVideoSyncSGI");
if (!glXGetVideoSyncSGI || !glXWaitVideoSyncSGI)
{
glXGetVideoSyncSGI = NULL;
DEBUG_ERROR("Failed to get proc addresses");
return false;
}
}
*sdlFlags = SDL_WINDOW_OPENGL;
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
return true;
}
bool lgr_opengl_basic_configure(void * opaque, SDL_Window *window, const LG_RendererFormat format)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this)
return false;
if (this->configured)
{
DEBUG_ERROR("Renderer already configured, call deconfigure first");
return false;
}
this->sdlWindow = window;
this->glContext = SDL_GL_CreateContext(window);
if (!this->glContext)
{
DEBUG_ERROR("Failed to create the OpenGL context");
return false;
}
if (!this->doneInfo)
{
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
this->doneInfo = true;
}
if (SDL_GL_MakeCurrent(window, this->glContext) != 0)
{
DEBUG_ERROR("Failed to make the GL context current");
return false;
}
SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0);
// check if the GPU supports GL_ARB_buffer_storage first
// there is no advantage to this renderer if it is not present.
const GLubyte * extensions = glGetString(GL_EXTENSIONS);
if (!gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions))
{
DEBUG_INFO("The GPU doesn't support GL_ARB_buffer_storage");
return false;
}
// assume 24 and 32 bit formats are RGB and RGBA
switch(format.bpp)
{
case 24:
this->intFormat = GL_RGB8;
this->vboFormat = GL_BGR;
break;
case 32:
this->intFormat = GL_RGBA8;
this->vboFormat = GL_BGRA;
break;
default:
DEBUG_INFO("%d bpp not supported", format.bpp);
return false;
}
// calculate the texture size in bytes
this->texSize = format.height * format.pitch;
// generate lists for drawing
this->texList = glGenLists(1);
this->fpsList = glGenLists(1);
this->mouseList = glGenLists(1);
// create the textures
glGenTextures(TEXTURE_COUNT, this->textures);
if (lgr_opengl_basic_check_error("glGenTextures"))
return false;
this->hasTextures = true;
// create the frame texture
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
if (lgr_opengl_basic_check_error("glBindTexture"))
return false;
glTexImage2D(
GL_TEXTURE_2D,
0,
this->intFormat,
format.width,
format.height,
0,
this->vboFormat,
GL_UNSIGNED_BYTE,
(void*)0
);
if (lgr_opengl_basic_check_error("glTexImage2D"))
return false;
// configure the texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// create the display list
glNewList(this->texList, GL_COMPILE);
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, 0.0f); glVertex2i(format.width, 0 );
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , format.height);
glTexCoord2f(1.0f, 1.0f); glVertex2i(format.width, format.height);
glEnd();
glEndList();
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glEnable(GL_TEXTURE_2D);
glEnable(GL_COLOR_MATERIAL);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
glEnable(GL_SCISSOR_TEST);
this->resizeWindow = true;
this->drawStart = nanotime();
glXGetVideoSyncSGI(&this->gpuFrameCount);
// copy the format into the local storage
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->configured = true;
return true;
}
void lgr_opengl_basic_deconfigure(void * opaque)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this || !this->configured)
return;
if (this->hasTextures)
{
glDeleteTextures(TEXTURE_COUNT, this->textures);
this->hasTextures = false;
}
if (this->glContext)
{
SDL_GL_DeleteContext(this->glContext);
this->glContext = NULL;
}
this->configured = false;
}
void lgr_opengl_basic_deinitialize(void * opaque)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this)
return;
if (this->configured)
lgr_opengl_basic_deconfigure(opaque);
free(this);
}
bool lgr_opengl_basic_is_compatible(void * opaque, const LG_RendererFormat format)
{
const struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this || !this->configured)
return false;
return (memcmp(&this->format, &format, sizeof(LG_RendererFormat)) == 0);
}
void lgr_opengl_basic_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this || !this->configured)
return;
this->window.x = width;
this->window.y = height;
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
this->resizeWindow = true;
}
bool lgr_opengl_basic_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this || !this->configured)
return false;
this->mouseType = cursor;
switch(cursor)
{
case LG_CURSOR_COLOR:
{
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glTexImage2D
(
GL_TEXTURE_2D,
0 ,
GL_RGBA,
width ,
height ,
0 ,
GL_BGRA, // windows cursors are in BGRA format
GL_UNSIGNED_BYTE,
data
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
this->mousePos.w = width;
this->mousePos.h = height;
glNewList(this->mouseList, GL_COMPILE);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , height);
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, height);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_BLEND);
glEndList();
break;
}
case LG_CURSOR_MONOCHROME:
{
const int hheight = height / 2;
uint32_t d[width * height];
for(int y = 0; y < hheight; ++y)
for(int x = 0; x < width; ++x)
{
const uint8_t * srcAnd = data + (pitch * y) + (x / 8);
const uint8_t * srcXor = srcAnd + pitch * hheight;
const uint8_t mask = 0x80 >> (x % 8);
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
d[y * width + x ] = andMask;
d[y * width + x + width * hheight] = xorMask;
}
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glTexImage2D
(
GL_TEXTURE_2D,
0 ,
GL_RGBA,
width ,
height ,
0 ,
GL_RGBA,
GL_UNSIGNED_BYTE,
d
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
this->mousePos.w = width;
this->mousePos.h = hheight;
glNewList(this->mouseList, GL_COMPILE);
glEnable(GL_COLOR_LOGIC_OP);
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
glLogicOp(GL_AND);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , hheight);
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, hheight);
glEnd();
glLogicOp(GL_XOR);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, 0 );
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , hheight);
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, hheight);
glEnd();
glDisable(GL_COLOR_LOGIC_OP);
glEndList();
break;
}
case LG_CURSOR_MASKED_COLOR:
{
//TODO
break;
}
}
this->mouseUpdate = true;
this->newShape = true;
return true;
}
bool lgr_opengl_basic_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this || !this->configured)
return false;
if (this->mousePos.x == x && this->mousePos.y == y && this->mouseVisible == visible)
return true;
this->mouseVisible = visible;
this->mousePos.x = x;
this->mousePos.y = y;
this->mouseUpdate = true;
return false;
}
bool lgr_opengl_basic_on_frame_event(void * opaque, const uint8_t * data)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this)
{
DEBUG_ERROR("Invalid opaque pointer");
return false;
}
if (!this->configured)
{
DEBUG_ERROR("Not configured");
return false;
}
if (this->params.showFPS && this->renderTime > 1e9)
{
char str[128];
const float avgFPS = 1000.0f / (((float)this->renderTime / this->frameCount ) / 1e6f);
const float renderFPS = 1000.0f / (((float)this->renderTime / this->renderCount) / 1e6f);
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
SDL_Color color = {0xff, 0xff, 0xff};
SDL_Surface *textSurface = NULL;
if (!(textSurface = TTF_RenderText_Blended(this->params.font, str, color)))
{
DEBUG_ERROR("Failed to render text");
return false;
}
glBindTexture(GL_TEXTURE_2D , this->textures[FPS_TEXTURE]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH, textSurface->w );
glTexImage2D(
GL_TEXTURE_2D,
0,
textSurface->format->BytesPerPixel,
textSurface->w,
textSurface->h,
0,
GL_BGRA,
GL_UNSIGNED_BYTE,
textSurface->pixels
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
this->fpsRect.x = 5;
this->fpsRect.y = 5;
this->fpsRect.w = textSurface->w;
this->fpsRect.h = textSurface->h;
SDL_FreeSurface(textSurface);
this->renderTime = 0;
this->frameCount = 0;
this->renderCount = 0;
this->fpsTexture = true;
glNewList(this->fpsList, GL_COMPILE);
glEnable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
glBegin(GL_TRIANGLE_STRIP);
glVertex2i(this->fpsRect.x , this->fpsRect.y );
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
glEnd();
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, this->textures[FPS_TEXTURE]);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f , 0.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y );
glTexCoord2f(1.0f , 0.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
glTexCoord2f(0.0f , 1.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
glTexCoord2f(1.0f, 1.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
glEnd();
glDisable(GL_BLEND);
glEndList();
}
// bind the texture and update it
glBindTexture(GL_TEXTURE_2D , this->textures[FRAME_TEXTURE]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride );
// update the texture
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
this->format.width ,
this->format.height,
this->vboFormat,
GL_UNSIGNED_BYTE,
data
);
lgr_opengl_basic_check_error("glTexSubImage2D");
// unbind the buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
const bool mipmap = this->opt.mipmap && (
(this->format.width > this->destRect.w) ||
(this->format.height > this->destRect.h));
if (mipmap)
{
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
else
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
glBindTexture(GL_TEXTURE_2D, 0);
++this->frameCount;
this->frameUpdate = true;
return true;
}
static inline void lgr_opengl_basic_draw_mouse(struct LGR_OpenGLBasic * this)
{
if (!this->mouseVisible)
return;
glPushMatrix();
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
glCallList(this->mouseList);
glPopMatrix();
}
bool lgr_opengl_basic_render(void * opaque)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this || !this->configured)
return false;
if (this->resizeWindow)
{
// setup the projection matrix
glViewport(0, 0, this->window.x, this->window.y);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, this->window.x, this->window.y, 0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(this->destRect.x, this->destRect.y, 0.0f);
glScalef(
(float)this->destRect.w / (float)this->format.width,
(float)this->destRect.h / (float)this->format.height,
1.0f
);
// update the scissor rect to prevent drawing outside of the frame
glScissor(
this->destRect.x,
this->destRect.y,
this->destRect.w,
this->destRect.h
);
this->resizeWindow = false;
}
if (this->opt.splitMouse)
{
if (!this->frameUpdate)
{
if (!this->mouseUpdate)
return true;
if (!this->newShape)
{
// don't update the mouse too fast
const uint64_t delta = nanotime() - this->lastMouseDraw;
if (delta < 5e6)
return true;
}
this->newShape = false;
glDrawBuffer(GL_FRONT);
glCallList(this->texList);
lgr_opengl_basic_draw_mouse(this);
if (this->fpsTexture)
glCallList(this->fpsList);
glDrawBuffer(GL_BACK);
glFlush();
this->mouseUpdate = false;
this->lastMouseDraw = nanotime();
return true;
}
}
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glCallList(this->texList);
lgr_opengl_basic_draw_mouse(this);
if (this->fpsTexture)
glCallList(this->fpsList);
if (this->opt.preventBuffer)
{
unsigned int before, after;
glXGetVideoSyncSGI(&before);
SDL_GL_SwapWindow(this->sdlWindow);
// wait for the swap to happen to ensure we dont buffer frames
glXGetVideoSyncSGI(&after);
if (before == after)
glXWaitVideoSyncSGI(1, 0, &before);
}
else
SDL_GL_SwapWindow(this->sdlWindow);
const uint64_t t = nanotime();
this->renderTime += t - this->lastFrameTime;
this->lastFrameTime = t;
++this->renderCount;
this->frameUpdate = false;
this->mouseUpdate = false;
this->lastMouseDraw = t;
return true;
}
static void handle_opt_mipmap(void * opaque, const char *value)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this)
return;
this->opt.mipmap = LG_RendererValueToBool(value);
}
static void handle_opt_vsync(void * opaque, const char *value)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this)
return;
this->opt.vsync = LG_RendererValueToBool(value);
}
static void handle_opt_prevent_buffer(void * opaque, const char *value)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this)
return;
this->opt.preventBuffer = LG_RendererValueToBool(value);
}
static void handle_opt_split_mouse(void * opaque, const char *value)
{
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
if (!this)
return;
this->opt.splitMouse = LG_RendererValueToBool(value);
}
static LG_RendererOpt lgr_opengl_basic_options[] =
{
{
.name = "mipmap",
.desc = "Enable or disable mipmapping [default: enabled]",
.validator = LG_RendererValidatorBool,
.handler = handle_opt_mipmap
},
{
.name = "vsync",
.desc ="Enable or disable vsync [default: enabled]",
.validator = LG_RendererValidatorBool,
.handler = handle_opt_vsync
},
{
.name = "preventBuffer",
.desc = "Prevent the driver from buffering frames [default: enabled]",
.validator = LG_RendererValidatorBool,
.handler = handle_opt_prevent_buffer
},
{
.name = "splitMouse",
.desc = "Draw mouse updates directly to the front buffer [default: disabled]",
.validator = LG_RendererValidatorBool,
.handler = handle_opt_split_mouse
}
};
const LG_Renderer LGR_OpenGLBasic =
{
.get_name = lgr_opengl_basic_get_name,
.options = lgr_opengl_basic_options,
.option_count = LGR_OPTION_COUNT(lgr_opengl_basic_options),
.create = lgr_opengl_basic_create,
.initialize = lgr_opengl_basic_initialize,
.configure = lgr_opengl_basic_configure,
.deconfigure = lgr_opengl_basic_deconfigure,
.deinitialize = lgr_opengl_basic_deinitialize,
.is_compatible = lgr_opengl_basic_is_compatible,
.on_resize = lgr_opengl_basic_on_resize,
.on_mouse_shape = lgr_opengl_basic_on_mouse_shape,
.on_mouse_event = lgr_opengl_basic_on_mouse_event,
.on_frame_event = lgr_opengl_basic_on_frame_event,
.render = lgr_opengl_basic_render
};

View File

@@ -1,951 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 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 "lg-renderer.h"
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <SDL2/SDL_ttf.h>
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#include "debug.h"
#include "memcpySSE.h"
#include "utils.h"
#define BUFFER_COUNT 2
#define FRAME_TEXTURE 0
#define FPS_TEXTURE 1
#define MOUSE_TEXTURE 2
#define TEXTURE_COUNT 3
static PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI = NULL;
static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI = NULL;
struct Options
{
bool mipmap;
bool vsync;
bool preventBuffer;
};
static struct Options defaultOptions =
{
.mipmap = true,
.vsync = true,
.preventBuffer = true,
};
struct Inst
{
LG_RendererParams params;
struct Options opt;
bool configured;
bool reconfigure;
SDL_GLContext glContext;
bool doneInfo;
SDL_Point window;
bool resizeWindow;
bool frameUpdate;
LG_Lock formatLock;
LG_RendererFormat format;
GLuint intFormat;
GLuint vboFormat;
size_t texSize;
uint64_t drawStart;
bool hasBuffers;
GLuint vboID[1];
uint8_t * texPixels[BUFFER_COUNT];
LG_Lock syncLock;
int texIndex, wTexIndex;
int texList;
int fpsList;
int mouseList;
LG_RendererRect destRect;
bool hasTextures;
GLuint textures[TEXTURE_COUNT];
uint gpuFrameCount;
bool fpsTexture;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
SDL_Rect fpsRect;
LG_Lock mouseLock;
LG_RendererCursor mouseCursor;
int mouseWidth;
int mouseHeight;
int mousePitch;
uint8_t * mouseData;
size_t mouseDataSize;
bool mouseUpdate;
bool newShape;
uint64_t lastMouseDraw;
LG_RendererCursor mouseType;
bool mouseVisible;
SDL_Rect mousePos;
};
static bool _check_gl_error(unsigned int line, const char * name);
#define check_gl_error(name) _check_gl_error(__LINE__, name)
static void deconfigure(struct Inst * this);
static bool configure(struct Inst * this, SDL_Window *window);
static void update_mouse_shape(struct Inst * this, bool * newShape);
static bool draw_frame(struct Inst * this, bool * frameUpdate);
static void draw_mouse(struct Inst * this);
const char * opengl_get_name()
{
return "OpenGL";
}
bool opengl_create(void ** opaque, const LG_RendererParams params)
{
// create our local storage
*opaque = malloc(sizeof(struct Inst));
if (!*opaque)
{
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
return false;
}
memset(*opaque, 0, sizeof(struct Inst));
struct Inst * this = (struct Inst *)*opaque;
memcpy(&this->params, &params , sizeof(LG_RendererParams));
memcpy(&this->opt , &defaultOptions, sizeof(struct Options ));
LG_LOCK_INIT(this->formatLock);
LG_LOCK_INIT(this->syncLock );
LG_LOCK_INIT(this->mouseLock );
return true;
}
bool opengl_initialize(void * opaque, Uint32 * sdlFlags)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return false;
if (!glXGetVideoSyncSGI)
{
glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC )glXGetProcAddress((const GLubyte *)"glXGetVideoSyncSGI" );
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddress((const GLubyte *)"glXWaitVideoSyncSGI");
if (!glXGetVideoSyncSGI || !glXWaitVideoSyncSGI)
{
glXGetVideoSyncSGI = NULL;
DEBUG_ERROR("Failed to get proc addresses");
return false;
}
}
*sdlFlags = SDL_WINDOW_OPENGL;
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
return true;
}
void opengl_deinitialize(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return;
deconfigure(this);
if (this->mouseData)
free(this->mouseData);
LG_LOCK_FREE(this->formatLock);
LG_LOCK_FREE(this->syncLock );
LG_LOCK_FREE(this->mouseLock );
free(this);
}
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return;
this->window.x = width;
this->window.y = height;
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
this->resizeWindow = true;
}
bool opengl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return false;
LG_LOCK(this->mouseLock);
this->mouseCursor = cursor;
this->mouseWidth = width;
this->mouseHeight = height;
this->mousePitch = pitch;
const size_t size = height * pitch;
if (size > this->mouseDataSize)
{
if (this->mouseData)
free(this->mouseData);
this->mouseData = (uint8_t *)malloc(size);
this->mouseDataSize = size;
}
memcpy(this->mouseData, data, size);
this->newShape = true;
LG_UNLOCK(this->mouseLock);
return true;
}
bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return false;
if (this->mousePos.x == x && this->mousePos.y == y && this->mouseVisible == visible)
return true;
this->mouseVisible = visible;
this->mousePos.x = x;
this->mousePos.y = y;
this->mouseUpdate = true;
return false;
}
bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
{
DEBUG_ERROR("Invalid opaque pointer");
return false;
}
LG_LOCK(this->formatLock);
if (this->reconfigure)
{
LG_UNLOCK(this->formatLock);
return true;
}
if (!this->configured || memcmp(&this->format, &format, sizeof(LG_RendererFormat)) != 0)
{
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->reconfigure = true;
LG_UNLOCK(this->formatLock);
return true;
}
LG_UNLOCK(this->formatLock);
// lock, perform the update, then unlock
LG_LOCK(this->syncLock);
memcpySSE(this->texPixels[this->wTexIndex], data, this->texSize);
this->frameUpdate = true;
LG_UNLOCK(this->syncLock);
++this->frameCount;
return true;
}
bool opengl_render(void * opaque, SDL_Window * window)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return false;
if (!configure(this, window))
return true;
if (this->resizeWindow)
{
// setup the projection matrix
glViewport(0, 0, this->window.x, this->window.y);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, this->window.x, this->window.y, 0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(this->destRect.x, this->destRect.y, 0.0f);
glScalef(
(float)this->destRect.w / (float)this->format.width,
(float)this->destRect.h / (float)this->format.height,
1.0f
);
// update the scissor rect to prevent drawing outside of the frame
glScissor(
this->destRect.x,
this->destRect.y,
this->destRect.w,
this->destRect.h
);
this->resizeWindow = false;
}
if (this->params.showFPS && this->renderTime > 1e9)
{
char str[128];
const float avgFPS = 1000.0f / (((float)this->renderTime / this->frameCount ) / 1e6f);
const float renderFPS = 1000.0f / (((float)this->renderTime / this->renderCount) / 1e6f);
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
SDL_Color color = {0xff, 0xff, 0xff};
SDL_Surface *textSurface = NULL;
if (!(textSurface = TTF_RenderText_Blended(this->params.font, str, color)))
{
DEBUG_ERROR("Failed to render text");
LG_UNLOCK(this->formatLock);
return false;
}
glBindTexture(GL_TEXTURE_2D , this->textures[FPS_TEXTURE]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH, textSurface->w );
glTexImage2D(
GL_TEXTURE_2D,
0,
textSurface->format->BytesPerPixel,
textSurface->w,
textSurface->h,
0,
GL_BGRA,
GL_UNSIGNED_BYTE,
textSurface->pixels
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
this->fpsRect.x = 5;
this->fpsRect.y = 5;
this->fpsRect.w = textSurface->w;
this->fpsRect.h = textSurface->h;
SDL_FreeSurface(textSurface);
this->renderTime = 0;
this->frameCount = 0;
this->renderCount = 0;
this->fpsTexture = true;
glNewList(this->fpsList, GL_COMPILE);
glPushMatrix();
glLoadIdentity();
glEnable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
glBegin(GL_TRIANGLE_STRIP);
glVertex2i(this->fpsRect.x , this->fpsRect.y );
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
glEnd();
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, this->textures[FPS_TEXTURE]);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f , 0.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y );
glTexCoord2f(1.0f , 0.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
glTexCoord2f(0.0f , 1.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
glTexCoord2f(1.0f, 1.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
glEnd();
glDisable(GL_BLEND);
glPopMatrix();
glEndList();
}
bool frameUpdate;
if (!draw_frame(this, &frameUpdate))
return false;
bool newShape;
update_mouse_shape(this, &newShape);
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glCallList(this->texList + this->texIndex);
draw_mouse(this);
if (this->fpsTexture)
glCallList(this->fpsList);
if (this->opt.preventBuffer)
{
unsigned int before, after;
glXGetVideoSyncSGI(&before);
SDL_GL_SwapWindow(window);
// wait for the swap to happen to ensure we dont buffer frames //
glXGetVideoSyncSGI(&after);
if (before == after)
glXWaitVideoSyncSGI(1, 0, &before);
}
else
SDL_GL_SwapWindow(window);
const uint64_t t = nanotime();
this->renderTime += t - this->lastFrameTime;
this->lastFrameTime = t;
++this->renderCount;
this->mouseUpdate = false;
this->lastMouseDraw = t;
return true;
}
static void handle_opt_mipmap(void * opaque, const char *value)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return;
this->opt.mipmap = LG_RendererValueToBool(value);
}
static void handle_opt_vsync(void * opaque, const char *value)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return;
this->opt.vsync = LG_RendererValueToBool(value);
}
static void handle_opt_prevent_buffer(void * opaque, const char *value)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return;
this->opt.preventBuffer = LG_RendererValueToBool(value);
}
static LG_RendererOpt opengl_options[] =
{
{
.name = "mipmap",
.desc = "Enable or disable mipmapping [default: enabled]",
.validator = LG_RendererValidatorBool,
.handler = handle_opt_mipmap
},
{
.name = "vsync",
.desc ="Enable or disable vsync [default: enabled]",
.validator = LG_RendererValidatorBool,
.handler = handle_opt_vsync
},
{
.name = "preventBuffer",
.desc = "Prevent the driver from buffering frames [default: enabled]",
.validator = LG_RendererValidatorBool,
.handler = handle_opt_prevent_buffer
}
};
const LG_Renderer LGR_OpenGL =
{
.get_name = opengl_get_name,
.options = opengl_options,
.option_count = LGR_OPTION_COUNT(opengl_options),
.create = opengl_create,
.initialize = opengl_initialize,
.deinitialize = opengl_deinitialize,
.on_resize = opengl_on_resize,
.on_mouse_shape = opengl_on_mouse_shape,
.on_mouse_event = opengl_on_mouse_event,
.on_frame_event = opengl_on_frame_event,
.render = opengl_render
};
static bool _check_gl_error(unsigned int line, const char * name)
{
GLenum error = glGetError();
if (error == GL_NO_ERROR)
return false;
const GLubyte * errStr = gluErrorString(error);
DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr);
return true;
}
static bool configure(struct Inst * this, SDL_Window *window)
{
LG_LOCK(this->formatLock);
if (!this->reconfigure)
{
LG_UNLOCK(this->formatLock);
return this->configured;
}
if (this->configured)
deconfigure(this);
this->glContext = SDL_GL_CreateContext(window);
if (!this->glContext)
{
DEBUG_ERROR("Failed to create the OpenGL context");
LG_UNLOCK(this->formatLock);
return false;
}
if (!this->doneInfo)
{
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
this->doneInfo = true;
}
SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0);
// check if the GPU supports GL_ARB_buffer_storage first
// there is no advantage to this renderer if it is not present.
const GLubyte * extensions = glGetString(GL_EXTENSIONS);
if (!gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions))
{
DEBUG_INFO("The GPU doesn't support GL_ARB_buffer_storage");
LG_UNLOCK(this->formatLock);
return false;
}
// assume 24 and 32 bit formats are RGB and RGBA
switch(this->format.bpp)
{
case 24:
this->intFormat = GL_RGB8;
this->vboFormat = GL_BGR;
break;
case 32:
this->intFormat = GL_RGBA8;
this->vboFormat = GL_BGRA;
break;
default:
DEBUG_INFO("%d bpp not supported", this->format.bpp);
LG_UNLOCK(this->formatLock);
return false;
}
// calculate the texture size in bytes
this->texSize = this->format.height * this->format.pitch;
// generate lists for drawing
this->texList = glGenLists(BUFFER_COUNT);
this->fpsList = glGenLists(1);
this->mouseList = glGenLists(1);
// generate the pixel unpack buffers
glGenBuffers(1, this->vboID);
if (check_gl_error("glGenBuffers"))
{
LG_UNLOCK(this->formatLock);
return false;
}
this->hasBuffers = true;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[0]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return false;
}
glBufferStorage(
GL_PIXEL_UNPACK_BUFFER,
this->texSize * BUFFER_COUNT,
NULL,
GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT
);
if (check_gl_error("glBufferStorage"))
{
LG_UNLOCK(this->formatLock);
return false;
}
this->texPixels[0] = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
this->texSize * BUFFER_COUNT,
GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT |
GL_MAP_FLUSH_EXPLICIT_BIT
);
if (check_gl_error("glMapBufferRange"))
{
LG_UNLOCK(this->formatLock);
return false;
}
for(int i = 1; i < BUFFER_COUNT; ++i)
this->texPixels[i] = this->texPixels[i-1] + this->texSize;
// create the textures
glGenTextures(TEXTURE_COUNT, this->textures);
if (check_gl_error("glGenTextures"))
{
LG_UNLOCK(this->formatLock);
return false;
}
this->hasTextures = true;
// create the frame texture
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
if (check_gl_error("glBindTexture"))
{
LG_UNLOCK(this->formatLock);
return false;
}
glTexImage2D(
GL_TEXTURE_2D,
0,
this->intFormat,
this->format.width,
this->format.height * BUFFER_COUNT,
0,
this->vboFormat,
GL_UNSIGNED_BYTE,
(void*)0
);
if (check_gl_error("glTexImage2D"))
{
LG_UNLOCK(this->formatLock);
return false;
}
// configure the texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
for (int i = 0; i < BUFFER_COUNT; ++i)
{
const float ts = (1.0f / BUFFER_COUNT) * i;
const float te = (1.0f / BUFFER_COUNT) + ts;
// create the display lists
glNewList(this->texList + i, GL_COMPILE);
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, ts); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, ts); glVertex2i(this->format.width, 0 );
glTexCoord2f(0.0f, te); glVertex2i(0 , this->format.height);
glTexCoord2f(1.0f, te); glVertex2i(this->format.width, this->format.height);
glEnd();
glEndList();
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glEnable(GL_TEXTURE_2D);
glEnable(GL_COLOR_MATERIAL);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
glEnable(GL_SCISSOR_TEST);
this->resizeWindow = true;
this->drawStart = nanotime();
glXGetVideoSyncSGI(&this->gpuFrameCount);
this->configured = true;
this->reconfigure = false;
LG_UNLOCK(this->formatLock);
return true;
}
static void deconfigure(struct Inst * this)
{
if (!this->configured)
return;
if (this->hasTextures)
{
glDeleteTextures(TEXTURE_COUNT, this->textures);
this->hasTextures = false;
}
if (this->hasBuffers)
{
glDeleteBuffers(1, this->vboID);
this->hasBuffers = false;
}
if (this->glContext)
{
SDL_GL_DeleteContext(this->glContext);
this->glContext = NULL;
}
this->configured = false;
}
static void update_mouse_shape(struct Inst * this, bool * newShape)
{
LG_LOCK(this->mouseLock);
*newShape = this->newShape;
if (!this->newShape)
{
LG_UNLOCK(this->mouseLock);
return;
}
const LG_RendererCursor cursor = this->mouseCursor;
const int width = this->mouseWidth;
const int height = this->mouseHeight;
const int pitch = this->mousePitch;
const uint8_t * data = this->mouseData;
this->mouseType = cursor;
switch(cursor)
{
case LG_CURSOR_COLOR:
{
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glTexImage2D
(
GL_TEXTURE_2D,
0 ,
GL_RGBA,
width ,
height ,
0 ,
GL_BGRA, // windows cursors are in BGRA format
GL_UNSIGNED_BYTE,
data
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
this->mousePos.w = width;
this->mousePos.h = height;
glNewList(this->mouseList, GL_COMPILE);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , height);
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, height);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_BLEND);
glEndList();
break;
}
case LG_CURSOR_MONOCHROME:
{
const int hheight = height / 2;
uint32_t d[width * height];
for(int y = 0; y < hheight; ++y)
for(int x = 0; x < width; ++x)
{
const uint8_t * srcAnd = data + (pitch * y) + (x / 8);
const uint8_t * srcXor = srcAnd + pitch * hheight;
const uint8_t mask = 0x80 >> (x % 8);
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
d[y * width + x ] = andMask;
d[y * width + x + width * hheight] = xorMask;
}
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glTexImage2D
(
GL_TEXTURE_2D,
0 ,
GL_RGBA,
width ,
height ,
0 ,
GL_RGBA,
GL_UNSIGNED_BYTE,
d
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
this->mousePos.w = width;
this->mousePos.h = hheight;
glNewList(this->mouseList, GL_COMPILE);
glEnable(GL_COLOR_LOGIC_OP);
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
glLogicOp(GL_AND);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , hheight);
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, hheight);
glEnd();
glLogicOp(GL_XOR);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, 0 );
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , hheight);
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, hheight);
glEnd();
glDisable(GL_COLOR_LOGIC_OP);
glEndList();
break;
}
case LG_CURSOR_MASKED_COLOR:
{
//TODO
break;
}
}
this->mouseUpdate = true;
LG_UNLOCK(this->mouseLock);
}
static bool draw_frame(struct Inst * this, bool * frameUpdate)
{
LG_LOCK(this->syncLock);
*frameUpdate = this->frameUpdate;
if (!this->frameUpdate)
{
LG_UNLOCK(this->syncLock);
return true;
}
this->texIndex = this->wTexIndex;
if (++this->wTexIndex == BUFFER_COUNT)
this->wTexIndex = 0;
this->frameUpdate = false;
LG_UNLOCK(this->syncLock);
LG_LOCK(this->formatLock);
// bind the texture and update it
glBindTexture(GL_TEXTURE_2D , this->textures[FRAME_TEXTURE]);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[0] );
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride );
// copy the buffer to the texture
glFlushMappedBufferRange(
GL_PIXEL_UNPACK_BUFFER,
this->texSize * this->texIndex,
this->texSize
);
// update the texture
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
this->texIndex * this->format.height,
this->format.width ,
this->format.height,
this->vboFormat,
GL_UNSIGNED_BYTE,
(void*)(this->texIndex * this->texSize)
);
if (check_gl_error("glTexSubImage2D"))
DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
);
// unbind the buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
const bool mipmap = this->opt.mipmap && (
(this->format.width > this->destRect.w) ||
(this->format.height > this->destRect.h));
if (mipmap)
{
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
else
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
glBindTexture(GL_TEXTURE_2D, 0);
LG_UNLOCK(this->formatLock);
return true;
}
static void draw_mouse(struct Inst * this)
{
if (!this->mouseVisible)
return;
glPushMatrix();
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
glCallList(this->mouseList);
glPopMatrix();
}

View File

@@ -1,121 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 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 <stdint.h>
#pragma pack(push,1)
typedef struct SpicePoint16
{
int16_t x, y;
}
SpicePoint16;
typedef struct SpiceMsgMainInit
{
uint32_t session_id;
uint32_t display_channels_hint;
uint32_t supported_mouse_modes;
uint32_t current_mouse_mode;
uint32_t agent_connected;
uint32_t agent_tokens;
uint32_t multi_media_time;
uint32_t ram_hint;
}
SpiceMsgMainInit;
typedef struct SpiceMsgcMainMouseModeRequest
{
uint16_t mouse_mode;
}
SpiceMsgcMainMouseModeRequest;
typedef struct SpiceMsgPing
{
uint32_t id;
uint64_t timestamp;
}
SpiceMsgPing,
SpiceMsgcPong;
typedef struct SpiceMsgSetAck
{
uint32_t generation;
uint32_t window;
}
SpiceMsgSetAck;
typedef struct SpiceMsgcAckSync
{
uint32_t generation;
}
SpiceMsgcAckSync;
typedef struct SpiceMsgNotify
{
uint64_t time_stamp;
uint32_t severity;
uint32_t visibility;
uint32_t what;
uint32_t message_len;
//char message[message_len+1]
}
SpiceMsgNotify;
typedef struct SpiceMsgInputsInit
{
uint16_t modifiers;
}
SpiceMsgInputsInit,
SpiceMsgInputsKeyModifiers,
SpiceMsgcInputsKeyModifiers;
typedef struct SpiceMsgcKeyDown
{
uint32_t code;
}
SpiceMsgcKeyDown,
SpiceMsgcKeyUp;
typedef struct SpiceMsgcMousePosition
{
uint32_t x;
uint32_t y;
uint16_t button_state;
uint8_t display_id;
}
SpiceMsgcMousePosition;
typedef struct SpiceMsgcMouseMotion
{
int32_t x;
int32_t y;
uint16_t button_state;
}
SpiceMsgcMouseMotion;
typedef struct SpiceMsgcMousePress
{
uint8_t button;
uint16_t button_state;
}
SpiceMsgcMousePress,
SpiceMsgcMouseRelease;
#pragma pack(pop)

View File

@@ -1,900 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017 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.h"
#include "debug.h"
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <spice/protocol.h>
#include <spice/error_codes.h>
#include "messages.h"
#ifdef DEBUG_SPICE_MOUSE
#define DEBUG_MOUSE(fmt, args...) DEBUG_PRINT("[M]", fmt, ##args)
#else
#define DEBUG_MOUSE(fmt, args...) do {} while(0)
#endif
#ifdef DEBUG_SPICE_KEYBOARD
#define DEBUG_KEYBOARD(fmt, args...) DEBUG_PRINT("[K]", fmt, ##args)
#else
#define DEBUG_KEYBOARD(fmt, args...) do {} while(0)
#endif
// ============================================================================
// internal structures
struct SpiceChannel
{
bool connected;
bool initDone;
uint8_t channelType;
int socket;
uint32_t ackFrequency;
uint32_t ackCount;
uint32_t serial;
};
struct SpiceKeyboard
{
uint32_t modifiers;
};
#define SPICE_MOUSE_QUEUE_SIZE 32
struct SpiceMouse
{
uint32_t buttonState;
int sentCount;
SpiceMsgcMouseMotion queue[SPICE_MOUSE_QUEUE_SIZE];
int rpos, wpos;
int queueLen;
};
struct Spice
{
char password[32];
struct sockaddr_in addr;
uint32_t sessionID;
uint32_t channelID;
struct SpiceChannel scMain;
struct SpiceChannel scInputs;
struct SpiceKeyboard kb;
struct SpiceMouse mouse;
};
// globals
struct Spice spice =
{
.sessionID = 0,
.scMain .connected = false,
.scMain .channelType = SPICE_CHANNEL_MAIN,
.scInputs.connected = false,
.scInputs.channelType = SPICE_CHANNEL_INPUTS,
};
// internal forward decls
bool spice_connect_channel (struct SpiceChannel * channel);
void spice_disconnect_channel(struct SpiceChannel * channel);
bool spice_process_ack(struct SpiceChannel * channel);
bool spice_on_common_read (struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled);
bool spice_on_main_channel_read ();
bool spice_on_inputs_channel_read();
bool spice_read (const struct SpiceChannel * channel, void * buffer, const ssize_t size);
ssize_t spice_write (const struct SpiceChannel * channel, const void * buffer, const ssize_t size);
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size);
bool spice_discard (const struct SpiceChannel * channel, ssize_t size);
// ============================================================================
bool spice_connect(const char * host, const short port, const char * password)
{
strncpy(spice.password, password, sizeof(spice.password));
memset(&spice.addr, 0, sizeof(struct sockaddr_in));
inet_pton(AF_INET, host, &spice.addr.sin_addr);
spice.addr.sin_family = AF_INET;
spice.addr.sin_port = htons(port);
spice.channelID = 0;
if (!spice_connect_channel(&spice.scMain))
{
DEBUG_ERROR("connect main channel failed");
return false;
}
return true;
}
// ============================================================================
void spice_disconnect()
{
spice_disconnect_channel(&spice.scMain );
spice_disconnect_channel(&spice.scInputs);
spice.sessionID = 0;
}
// ============================================================================
bool spice_ready()
{
return spice.scMain.connected &&
spice.scInputs.connected;
}
// ============================================================================
bool spice_process()
{
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(spice.scMain.socket , &readSet);
FD_SET(spice.scInputs.socket, &readSet);
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int rc = select(FD_SETSIZE, &readSet, NULL, NULL, &timeout);
if (rc < 0)
{
DEBUG_ERROR("select failure");
return false;
}
for(int i = 0; i < FD_SETSIZE; ++i)
if (FD_ISSET(i, &readSet))
{
if (i == spice.scMain.socket)
{
if (spice_on_main_channel_read())
{
if (spice.scMain.connected && !spice_process_ack(&spice.scMain))
{
DEBUG_ERROR("failed to process ack on main channel");
return false;
}
continue;
}
else
{
DEBUG_ERROR("failed to perform read on main channel");
return false;
}
}
if (spice.scInputs.connected && i == spice.scInputs.socket)
{
if (spice_on_inputs_channel_read())
{
if (!spice_process_ack(&spice.scInputs))
{
DEBUG_ERROR("failed to process ack on inputs channel");
return false;
}
continue;
}
else
{
DEBUG_ERROR("failed to perform read on inputs channel");
return false;
}
}
}
return true;
}
// ============================================================================
bool spice_process_ack(struct SpiceChannel * channel)
{
if (channel->ackFrequency == 0)
return true;
if (channel->ackCount++ != channel->ackFrequency)
return true;
channel->ackCount = 0;
return spice_write_msg(channel, SPICE_MSGC_ACK, "\0", 1);
}
// ============================================================================
bool spice_on_common_read(struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled)
{
if (!spice_read(channel, header, sizeof(SpiceDataHeader)))
{
DEBUG_ERROR("read failure");
*handled = false;
return false;
}
#if 0
printf("socket: %d, serial: %6u, type: %2u, size %6u, sub_list %4u\n",
channel->socket,
header->serial, header->type, header->size, header->sub_list);
#endif
if (!channel->initDone)
{
*handled = false;
return true;
}
switch(header->type)
{
case SPICE_MSG_MIGRATE:
case SPICE_MSG_MIGRATE_DATA:
{
DEBUG_PROTO("SPICE_MSG_MIGRATE_DATA");
*handled = true;
DEBUG_WARN("migration is not supported");
return false;
}
case SPICE_MSG_SET_ACK:
{
DEBUG_INFO("SPICE_MSG_SET_ACK");
*handled = true;
SpiceMsgSetAck in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
channel->ackFrequency = in.window;
SpiceMsgcAckSync out;
out.generation = in.generation;
if (!spice_write_msg(channel, SPICE_MSGC_ACK_SYNC, &out, sizeof(out)))
return false;
return true;
}
case SPICE_MSG_PING:
{
DEBUG_PROTO("SPICE_MSG_PING");
*handled = true;
SpiceMsgPing in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
if (!spice_discard(channel, header->size - sizeof(in)))
{
DEBUG_ERROR("failed discarding enough bytes from the ping packet");
return false;
}
SpiceMsgcPong out;
out.id = in.id;
out.timestamp = in.timestamp;
if (!spice_write_msg(channel, SPICE_MSGC_PONG, &out, sizeof(out)))
return false;
return true;
}
case SPICE_MSG_WAIT_FOR_CHANNELS:
case SPICE_MSG_DISCONNECTING :
{
*handled = true;
DEBUG_FIXME("ignored wait-for-channels or disconnect message");
return false;
}
case SPICE_MSG_NOTIFY:
{
DEBUG_PROTO("SPICE_MSG_NOTIFY");
SpiceMsgNotify in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
char msg[in.message_len+1];
if (!spice_read(channel, msg, in.message_len+1))
return false;
DEBUG_INFO("notify message: %s", msg);
*handled = true;
return true;
}
}
*handled = false;
return true;
}
// ============================================================================
bool spice_on_main_channel_read()
{
struct SpiceChannel *channel = &spice.scMain;
SpiceDataHeader header;
bool handled;
if (!spice_on_common_read(channel, &header, &handled))
{
DEBUG_ERROR("read failure");
return false;
}
if (handled)
return true;
if (!channel->initDone)
{
if (header.type != SPICE_MSG_MAIN_INIT)
{
spice_disconnect();
DEBUG_ERROR("expected main init message but got type %u", header.type);
return false;
}
DEBUG_PROTO("SPICE_MSG_MAIN_INIT");
channel->initDone = true;
SpiceMsgMainInit msg;
if (!spice_read(channel, &msg, sizeof(msg)))
{
spice_disconnect();
return false;
}
spice.sessionID = msg.session_id;
if (!spice_connect_channel(&spice.scInputs))
{
DEBUG_ERROR("failed to connect inputs channel");
return false;
}
if (msg.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && !spice_mouse_mode(false))
{
DEBUG_ERROR("failed to set mouse mode");
return false;
}
return true;
}
DEBUG_WARN("main channel unhandled message type %u", header.type);
spice_discard(channel, header.size);
return true;
}
// ============================================================================
bool spice_on_inputs_channel_read()
{
struct SpiceChannel *channel = &spice.scInputs;
SpiceDataHeader header;
bool handled;
if (!spice_on_common_read(channel, &header, &handled))
{
DEBUG_ERROR("read failure");
return false;
}
if (handled)
return true;
switch(header.type)
{
case SPICE_MSG_INPUTS_INIT:
{
DEBUG_PROTO("SPICE_MSG_INPUTS_INIT");
if (channel->initDone)
{
DEBUG_ERROR("input init message already done");
return false;
}
channel->initDone = true;
SpiceMsgInputsInit in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
return true;
}
case SPICE_MSG_INPUTS_KEY_MODIFIERS:
{
DEBUG_PROTO("SPICE_MSG_INPUTS_KEY_MODIFIERS");
SpiceMsgInputsInit in;
if (!spice_read(channel, &in, sizeof(in)))
return false;
spice.kb.modifiers = in.modifiers;
return true;
}
case SPICE_MSG_INPUTS_MOUSE_MOTION_ACK:
{
DEBUG_PROTO("SPICE_MSG_INPUTS_MOUSE_MOTION_ACK");
int sent = 0;
while(spice.mouse.queueLen && sent < 4)
{
SpiceMsgcMouseMotion *msg = &spice.mouse.queue[spice.mouse.rpos];
if (!spice_write_msg(channel, SPICE_MSGC_INPUTS_MOUSE_MOTION, msg, sizeof(SpiceMsgcMouseMotion)))
{
DEBUG_ERROR("failed to send post ack");
spice.mouse.sentCount = sent;
return false;
}
if (++spice.mouse.rpos == SPICE_MOUSE_QUEUE_SIZE)
spice.mouse.rpos = 0;
++sent;
--spice.mouse.queueLen;
}
spice.mouse.sentCount = sent;
return true;
}
}
DEBUG_WARN("inputs channel unhandled message type %u", header.type);
spice_discard(channel, header.size);
return true;
}
// ============================================================================
bool spice_connect_channel(struct SpiceChannel * channel)
{
channel->initDone = false;
channel->ackFrequency = 0;
channel->ackCount = 0;
channel->serial = 0;
channel->socket = socket(AF_INET, SOCK_STREAM, 0);
if (channel->socket == -1)
return false;
int flag = 1;
setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
if (connect(channel->socket, (const struct sockaddr *)&spice.addr, sizeof(spice.addr)) == -1)
{
DEBUG_ERROR("socket connect failure");
close(channel->socket);
return false;
}
channel->connected = true;
SpiceLinkHeader header =
{
.magic = SPICE_MAGIC ,
.major_version = SPICE_VERSION_MAJOR,
.minor_version = SPICE_VERSION_MINOR,
.size = sizeof(SpiceLinkMess)
};
SpiceLinkMess message =
{
.connection_id = spice.sessionID,
.channel_type = channel->channelType,
.channel_id = spice.channelID,
.num_common_caps = 0,
.num_channel_caps = 0,
.caps_offset = sizeof(SpiceLinkMess)
};
spice_write(channel, &header , sizeof(header ));
spice_write(channel, &message, sizeof(message));
if (!spice_read(channel, &header, sizeof(header)))
{
DEBUG_ERROR("failed to read SpiceLinkHeader");
spice_disconnect_channel(channel);
return false;
}
if (header.magic != SPICE_MAGIC || header.major_version != SPICE_VERSION_MAJOR)
{
DEBUG_ERROR("invalid or unsupported protocol version");
spice_disconnect_channel(channel);
return false;
}
if (header.size < sizeof(SpiceLinkReply))
{
DEBUG_ERROR("reported data size too small");
spice_disconnect_channel(channel);
return false;
}
SpiceLinkReply reply;
if (!spice_read(channel, &reply, sizeof(reply)))
{
DEBUG_ERROR("failed to read SpiceLinkReply");
spice_disconnect_channel(channel);
return false;
}
if (reply.error != SPICEC_ERROR_CODE_SUCCESS)
{
DEBUG_ERROR("server replied with error %u", reply.error);
spice_disconnect_channel(channel);
return false;
}
uint32_t capsCommon [reply.num_common_caps ];
uint32_t capsChannel[reply.num_channel_caps];
spice_read(channel, &capsCommon , sizeof(capsCommon ));
spice_read(channel, &capsChannel, sizeof(capsChannel));
BIO *bioKey = BIO_new(BIO_s_mem());
if (!bioKey)
{
DEBUG_ERROR("failed to allocate bioKey");
spice_disconnect_channel(channel);
return false;
}
BIO_write(bioKey, reply.pub_key, SPICE_TICKET_PUBKEY_BYTES);
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
char enc[RSA_size(rsa)];
if (RSA_public_encrypt(
strlen(spice.password) + 1,
(uint8_t*)spice.password,
(uint8_t*)enc,
rsa,
RSA_PKCS1_OAEP_PADDING
) <= 0)
{
DEBUG_ERROR("rsa public encrypt failed");
spice_disconnect_channel(channel);
EVP_PKEY_free(rsaKey);
BIO_free(bioKey);
return false;
}
ssize_t rsaSize = RSA_size(rsa);
EVP_PKEY_free(rsaKey);
BIO_free(bioKey);
if (!spice_write(channel, enc, rsaSize))
{
DEBUG_ERROR("failed to write encrypted data");
spice_disconnect_channel(channel);
return false;
}
uint32_t linkResult;
if (!spice_read(channel, &linkResult, sizeof(linkResult)))
{
DEBUG_ERROR("failed to read SpiceLinkResult");
spice_disconnect_channel(channel);
return false;
}
if (linkResult != SPICE_LINK_ERR_OK)
{
DEBUG_ERROR("connect code error %u", linkResult);
spice_disconnect_channel(channel);
return false;
}
return true;
}
// ============================================================================
void spice_disconnect_channel(struct SpiceChannel * channel)
{
if (channel->connected)
close(channel->socket);
channel->connected = false;
}
// ============================================================================
ssize_t spice_write(const struct SpiceChannel * channel, const void * buffer, const ssize_t size)
{
if (!channel->connected)
{
DEBUG_ERROR("not connected");
return -1;
}
if (!buffer)
{
DEBUG_ERROR("invalid buffer argument supplied");
return -1;
}
ssize_t len = send(channel->socket, buffer, size, 0);
if (len != size)
DEBUG_WARN("incomplete write");
return len;
}
// ============================================================================
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size)
{
SpiceDataHeader header;
header.serial = channel->serial++;
header.type = type;
header.size = size;
header.sub_list = 0;
if (spice_write(channel, &header, sizeof(header)) != sizeof(header))
{
DEBUG_ERROR("failed to write message header");
return false;
}
if (spice_write(channel, buffer, size) != size)
{
DEBUG_ERROR("failed to write message body");
return false;
}
return true;
}
// ============================================================================
bool spice_read(const struct SpiceChannel * channel, void * buffer, const ssize_t size)
{
if (!channel->connected)
{
DEBUG_ERROR("not connected");
return false;
}
if (!buffer)
{
DEBUG_ERROR("invalid buffer argument supplied");
return false;
}
ssize_t len = read(channel->socket, buffer, size);
if (len != size)
{
DEBUG_ERROR("incomplete write");
return false;
}
return true;
}
// ============================================================================
bool spice_discard(const struct SpiceChannel * channel, ssize_t size)
{
while(size)
{
char c[8192];
size_t len = read(channel->socket, c, size > sizeof(c) ? sizeof(c) : size);
if (len <= 0)
return false;
size -= len;
}
return true;
}
// ============================================================================
bool spice_key_down(uint32_t code)
{
DEBUG_KEYBOARD("%u", code);
if (!spice.scInputs.connected)
{
DEBUG_ERROR("not connected");
return false;
}
if (code > 0x100)
code = 0xe0 | ((code - 0x100) << 8);
SpiceMsgcKeyDown msg;
msg.code = code;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_DOWN, &msg, sizeof(msg));
}
// ============================================================================
bool spice_key_up(uint32_t code)
{
DEBUG_KEYBOARD("%u", code);
if (!spice.scInputs.connected)
{
DEBUG_ERROR("not connected");
return false;
}
if (code < 0x100)
code |= 0x80;
else
code = 0x80e0 | ((code - 0x100) << 8);
SpiceMsgcKeyDown msg;
msg.code = code;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_UP, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_mode(bool server)
{
DEBUG_MOUSE("%s", server ? "server" : "client");
if (!spice.scInputs.connected)
{
DEBUG_ERROR("not connected");
return false;
}
SpiceMsgcMainMouseModeRequest msg;
msg.mouse_mode = server ? SPICE_MOUSE_MODE_SERVER : SPICE_MOUSE_MODE_CLIENT;
return spice_write_msg(&spice.scMain, SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_position(uint32_t x, uint32_t y)
{
DEBUG_MOUSE("x=%u, y=%u", x, y);
if (!spice.scInputs.connected)
{
DEBUG_ERROR("not connected");
return false;
}
SpiceMsgcMousePosition msg;
msg.x = x;
msg.y = y;
msg.button_state = spice.mouse.buttonState;
msg.display_id = 0;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_POSITION, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_motion(int32_t x, int32_t y)
{
DEBUG_MOUSE("x=%d, y=%d", x, y);
if (!spice.scInputs.connected)
{
DEBUG_ERROR("not connected");
return false;
}
if (spice.mouse.sentCount == 4)
{
if (spice.mouse.queueLen == SPICE_MOUSE_QUEUE_SIZE)
{
DEBUG_ERROR("mouse motion ringbuffer full!");
return false;
}
SpiceMsgcMouseMotion *msg =
&spice.mouse.queue[spice.mouse.wpos++];
msg->x = x;
msg->y = y;
msg->button_state = spice.mouse.buttonState;
if (spice.mouse.wpos == SPICE_MOUSE_QUEUE_SIZE)
spice.mouse.wpos = 0;
++spice.mouse.queueLen;
return true;
}
SpiceMsgcMouseMotion msg;
msg.x = x;
msg.y = y;
msg.button_state = spice.mouse.buttonState;
++spice.mouse.sentCount;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_MOTION, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_press(uint32_t button)
{
DEBUG_MOUSE("%u", button);
if (!spice.scInputs.connected)
{
DEBUG_ERROR("not connected");
return false;
}
switch(button)
{
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
}
SpiceMsgcMousePress msg;
msg.button = button;
msg.button_state = spice.mouse.buttonState;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_PRESS, &msg, sizeof(msg));
}
// ============================================================================
bool spice_mouse_release(uint32_t button)
{
DEBUG_MOUSE("%u", button);
if (!spice.scInputs.connected)
{
DEBUG_ERROR("not connected");
return false;
}
switch(button)
{
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
}
SpiceMsgcMouseRelease msg;
msg.button = button;
msg.button_state = spice.mouse.buttonState;
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_RELEASE, &msg, sizeof(msg));
}

567
client/src/app.c Normal file
View File

@@ -0,0 +1,567 @@
/*
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 "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 || !g_params.showAlerts)
return;
va_list args;
va_start(args, fmt);
const int length = vsnprintf(NULL, 0, fmt, args);
va_end(args);
char *buffer = malloc(length + 1);
va_start(args, fmt);
vsnprintf(buffer, length + 1, fmt, args);
va_end(args);
g_state.lgr->on_alert(
g_state.lgrData,
type,
buffer,
NULL
);
free(buffer);
}
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
{
// don't allow duplicate binds
if (g_state.bindings[sc])
{
DEBUG_INFO("Key already bound");
return NULL;
}
KeybindHandle handle = (KeybindHandle)malloc(sizeof(struct KeybindHandle));
handle->sc = sc;
handle->callback = callback;
handle->opaque = opaque;
g_state.bindings[sc] = handle;
g_state.keyDescription[sc] = description;
return handle;
}
void app_releaseKeybind(KeybindHandle * handle)
{
if (!*handle)
return;
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);

736
client/src/config.c Normal file
View File

@@ -0,0 +1,736 @@
/*
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 "main.h"
#include "config.h"
#include "kb.h"
#include "common/option.h"
#include "common/debug.h"
#include "common/stringutils.h"
#include <sys/stat.h>
#include <pwd.h>
#include <unistd.h>
#include <string.h>
// forwards
static bool optRendererParse (struct Option * opt, const char * str);
static StringList optRendererValues (struct Option * opt);
static char * optRendererToString(struct Option * opt);
static bool optPosParse (struct Option * opt, const char * str);
static StringList optPosValues (struct Option * opt);
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);
static void doLicense();
static struct Option options[] =
{
// app options
{
.module = "app",
.name = "configFile",
.description = "A file to read additional configuration from",
.shortopt = 'C',
.type = OPTION_TYPE_STRING,
.value.x_string = NULL,
},
{
.module = "app",
.name = "renderer",
.description = "Specify the renderer to use",
.shortopt = 'g',
.type = OPTION_TYPE_CUSTOM,
.parser = optRendererParse,
.getValues = optRendererValues,
.toString = optRendererToString
},
{
.module = "app",
.name = "license",
.description = "Show the license for this application and then terminate",
.shortopt = 'l',
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "app",
.name = "cursorPollInterval",
.description = "How often to check for a cursor update in microseconds",
.type = OPTION_TYPE_INT,
.value.x_int = 1000
},
{
.module = "app",
.name = "framePollInterval",
.description = "How often to check for a frame update in microseconds",
.type = OPTION_TYPE_INT,
.value.x_int = 1000
},
{
.module = "app",
.name = "allowDMA",
.description = "Allow direct DMA transfers if possible (VM-VM only for now)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
// window options
{
.module = "win",
.name = "title",
.description = "The window title",
.type = OPTION_TYPE_STRING,
.value.x_string = "Looking Glass (client)"
},
{
.module = "win",
.name = "position",
.description = "Initial window position at startup",
.type = OPTION_TYPE_CUSTOM,
.parser = optPosParse,
.getValues = optPosValues,
.toString = optPosToString
},
{
.module = "win",
.name = "size",
.description = "Initial window size at startup",
.type = OPTION_TYPE_CUSTOM,
.parser = optSizeParse,
.getValues = optSizeValues,
.toString = optSizeToString
},
{
.module = "win",
.name = "autoResize",
.description = "Auto resize the window to the guest",
.shortopt = 'a',
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "allowResize",
.description = "Allow the window to be manually resized",
.shortopt = 'n',
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "win",
.name = "keepAspect",
.description = "Maintain the correct aspect ratio",
.shortopt = 'r',
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "win",
.name = "forceAspect",
.description = "Force the window to maintain the aspect ratio",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "win",
.name = "dontUpscale",
.description = "Never try to upscale the window",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "borderless",
.description = "Borderless mode",
.shortopt = 'd',
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "fullScreen",
.description = "Launch in fullscreen borderless mode",
.shortopt = 'F',
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "maximize",
.description = "Launch window maximized",
.shortopt = 'T',
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "minimizeOnFocusLoss",
.description = "Minimize window on focus loss",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "win",
.name = "fpsMin",
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)",
.shortopt = 'K',
.type = OPTION_TYPE_INT,
.value.x_int = -1,
},
{
.module = "win",
.name = "showFPS",
.description = "Enable the FPS & UPS display",
.shortopt = 'k',
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "ignoreQuit",
.description = "Ignore requests to quit (ie: Alt+F4)",
.shortopt = 'Q',
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "noScreensaver",
.description = "Prevent the screensaver from starting",
.shortopt = 'S',
.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",
.description = "Show on screen alert messages",
.shortopt = 'q',
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "win",
.name = "quickSplash",
.description = "Skip fading out the splash screen when a connection is established",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "rotate",
.description = "Rotate the displayed image (0, 90, 180, 270)",
.type = OPTION_TYPE_INT,
.validator = optRotateValidate,
.value.x_int = 0,
},
// input options
{
.module = "input",
.name = "grabKeyboard",
.description = "Grab the keyboard in capture mode",
.shortopt = 'G',
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "input",
.name = "grabKeyboardOnFocus",
.description = "Grab the keyboard when focused",
.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 <linux/input-event-codes.h> for valid values",
.shortopt = 'm',
.type = OPTION_TYPE_INT,
.value.x_int = KEY_SCROLLLOCK,
.validator = optScancodeValidate,
.toString = optScancodeToString,
},
{
.module = "input",
.name = "ignoreWindowsKeys",
.description = "Do not pass events for the windows keys to the guest",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{
.module = "input",
.name = "hideCursor",
.description = "Hide the local mouse cursor",
.shortopt = 'M',
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "input",
.name = "mouseSens",
.description = "Initial mouse sensitivity when in capture mode (-9 to 9)",
.type = OPTION_TYPE_INT,
.value.x_int = 0,
},
{
.module = "input",
.name = "mouseSmoothing",
.description = "Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "input",
.name = "rawMouse",
.description = "Use RAW mouse input when in capture mode (good for gaming)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "input",
.name = "mouseRedraw",
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "input",
.name = "autoCapture",
.description = "Try to keep the mouse captured when needed",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{
.module = "input",
.name = "captureOnly",
.description = "Only enable input via SPICE if in capture mode",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
// spice options
{
.module = "spice",
.name = "enable",
.description = "Enable the built in SPICE client for input and/or clipboard support",
.shortopt = 's',
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "host",
.description = "The SPICE server host or UNIX socket",
.shortopt = 'c',
.type = OPTION_TYPE_STRING,
.value.x_string = "127.0.0.1"
},
{
.module = "spice",
.name = "port",
.description = "The SPICE server port (0 = unix socket)",
.shortopt = 'p',
.type = OPTION_TYPE_INT,
.value.x_int = 5900
},
{
.module = "spice",
.name = "input",
.description = "Use SPICE to send keyboard and mouse input events to the guest",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "clipboard",
.description = "Use SPICE to syncronize the clipboard contents with the guest",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "clipboardToVM",
.description = "Allow the clipboard to be syncronized TO the VM",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "clipboardToLocal",
.description = "Allow the clipboard to be syncronized FROM the VM",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "scaleCursor",
.description = "Scale cursor input position to screen size when up/down scaled",
.shortopt = 'j',
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "captureOnStart",
.description = "Capture mouse and keyboard on start",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{
.module = "spice",
.name = "alwaysShowCursor",
.description = "Always show host cursor",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{0}
};
void config_init(void)
{
g_params.center = true;
g_params.w = 1024;
g_params.h = 768;
option_register(options);
}
bool config_load(int argc, char * argv[])
{
// load any global options first
struct stat st;
if (stat("/etc/looking-glass-client.ini", &st) >= 0)
{
DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini");
if (!option_load("/etc/looking-glass-client.ini"))
return false;
}
// load user's local options
struct passwd * pw = getpwuid(getuid());
char * localFile;
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
if (stat(localFile, &st) >= 0)
{
DEBUG_INFO("Loading config from: %s", localFile);
if (!option_load(localFile))
{
free(localFile);
return false;
}
}
free(localFile);
// parse the command line arguments
if (!option_parse(argc, argv))
return false;
// if a file was specified to also load, do it
const char * configFile = option_get_string("app", "configFile");
if (configFile)
{
DEBUG_INFO("Loading config from: %s", configFile);
if (!option_load(configFile))
return false;
}
// validate the values are sane
if (!option_validate())
return false;
if (option_get_bool("app", "license"))
{
doLicense();
return false;
}
// setup the application params for the basic types
g_params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
g_params.framePollInterval = option_get_int ("app", "framePollInterval" );
g_params.allowDMA = option_get_bool ("app", "allowDMA" );
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 : 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;
}
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" );
g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
if (option_get_bool("spice", "enable"))
{
g_params.spiceHost = option_get_string("spice", "host");
g_params.spicePort = option_get_int ("spice", "port");
g_params.useSpiceInput = option_get_bool("spice", "input" );
g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
if (g_params.useSpiceClipboard)
{
g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
if (!g_params.clipboardToVM && !g_params.clipboardToLocal)
g_params.useSpiceClipboard = false;
}
g_params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
g_params.captureOnStart = option_get_bool("spice", "captureOnStart");
g_params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor");
}
return true;
}
void config_free(void)
{
option_free();
}
static void doLicense(void)
{
fprintf(stderr,
"\n"
"Looking Glass - KVM FrameRelay (KVMFR) Client\n"
"Copyright(C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>\n"
"https://looking-glass.hostfission.com\n"
"\n"
"This program is free software; you can redistribute it and / or modify it under\n"
"the terms of the GNU General Public License as published by the Free Software\n"
"Foundation; either version 2 of the License, or (at your option) any later\n"
"version.\n"
"\n"
"This program is distributed in the hope that it will be useful, but WITHOUT ANY\n"
"WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n"
"PARTICULAR PURPOSE.See the GNU General Public License for more details.\n"
"\n"
"You should have received a copy of the GNU General Public License along with\n"
"this program; if not, write to the Free Software Foundation, Inc., 59 Temple\n"
"Place, Suite 330, Boston, MA 02111 - 1307 USA\n"
"\n"
);
}
static bool optRendererParse(struct Option * opt, const char * str)
{
if (!str)
return false;
if (strcasecmp(str, "auto") == 0)
{
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)
{
g_params.forceRenderer = true;
g_params.forceRendererIndex = i;
return true;
}
return false;
}
static StringList optRendererValues(struct Option * opt)
{
StringList sl = stringlist_new(false);
// this typecast is safe as the stringlist doesn't own the values
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
stringlist_push(sl, (char *)LG_Renderers[i]->get_name());
return sl;
}
static char * optRendererToString(struct Option * opt)
{
if (!g_params.forceRenderer)
return strdup("auto");
if (g_params.forceRendererIndex >= LG_RENDERER_COUNT)
return NULL;
return strdup(LG_Renderers[g_params.forceRendererIndex]->get_name());
}
static bool optPosParse(struct Option * opt, const char * str)
{
if (!str)
return false;
if (strcmp(str, "center") == 0)
{
g_params.center = true;
return true;
}
if (sscanf(str, "%dx%d", &g_params.x, &g_params.y) == 2)
{
g_params.center = false;
return true;
}
return false;
}
static StringList optPosValues(struct Option * opt)
{
StringList sl = stringlist_new(false);
stringlist_push(sl, "center");
stringlist_push(sl, "<left>x<top>, ie: 100x100");
return sl;
}
static char * optPosToString(struct Option * opt)
{
if (g_params.center)
return strdup("center");
int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y);
char * str = malloc(len + 1);
sprintf(str, "%dx%d", g_params.x, g_params.y);
return str;
}
static bool optSizeParse(struct Option * opt, const char * str)
{
if (!str)
return false;
if (sscanf(str, "%dx%d", &g_params.w, &g_params.h) == 2)
{
if (g_params.w < 1 || g_params.h < 1)
return false;
return true;
}
return false;
}
static StringList optSizeValues(struct Option * opt)
{
StringList sl = stringlist_new(false);
stringlist_push(sl, "<left>x<top>, ie: 100x100");
return sl;
}
static char * optSizeToString(struct Option * opt)
{
int len = snprintf(NULL, 0, "%dx%d", g_params.w, g_params.h);
char * str = malloc(len + 1);
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,
xfree86_to_str[opt->value.x_int]);
return str;
}
static bool optRotateValidate(struct Option * opt, const char ** error)
{
switch(opt->value.x_int)
{
case 0:
case 90:
case 180:
case 270:
return true;
}
*error = "Rotation angle must be one of 0, 90, 180 or 270";
return false;
}

24
client/src/config.h Normal file
View File

@@ -0,0 +1,24 @@
/*
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 <stdbool.h>
void config_init();
bool config_load(int argc, char * argv[]);
void config_free();

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

Some files were not shown because too many files have changed in this diff Show More