Compare commits

..

710 Commits

Author SHA1 Message Date
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
168 changed files with 14287 additions and 9951 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)

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ module/modules.order
*.a
*.o
*.exe
*/build

6
.gitmodules vendored
View File

@@ -0,0 +1,6 @@
[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

@@ -1,14 +1,20 @@
# Looking Glass
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough.
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
VGA PCI Passthrough.
* Project Website: https://looking-glass.hostfission.com
* 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.
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)
@@ -16,32 +22,41 @@ If you like this project and find it useful and would like to help out you can s
## 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)
* [c-host/README.md](c-host/README.md)
* [host/README.md](host/README.md)
* [module/README.md](module/README.md)
## Obtaining and using Looking Glass
Please see https://looking-glass.hostfission.com/quickstart
## 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.
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.hostfission.com/downloads
https://looking-glass.io/downloads
# Help and support
## Web
https://forum.level1techs.com/t/looking-glass-triage/130952
https://forum.level1techs.com/c/software/lookingglass/142
## Discord
https://discord.gg/4ahCn4c
* Looking Glass: https://discord.gg/52SMupxkvt
* VFIO: https://discord.gg/4ahCn4c
## IRC

View File

@@ -1 +0,0 @@
B1-rc6-5-g8ad2d5f949+1

View File

@@ -1 +0,0 @@
theme: jekyll-theme-cayman

View File

@@ -1,54 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdbool.h>
int app_main(int argc, char * argv[]);
bool app_init();
void app_quit();
// these must be implemented for each OS
const char * os_getExecutable();
unsigned int os_shmemSize();
bool os_shmemMmap(void **ptr);
void os_shmemUnmap();
// os specific thread functions
typedef struct osThreadHandle osThreadHandle;
typedef int (*osThreadFunction)(void * opaque);
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle);
bool os_joinThread (osThreadHandle * handle, int * resultCode);
// os specific event functions
#define TIMEOUT_INFINITE ((unsigned int)~0)
typedef struct osEventHandle osEventHandle;
osEventHandle * os_createEvent(bool autoReset);
void os_freeEvent (osEventHandle * handle);
bool os_waitEvent (osEventHandle * handle, unsigned int timeout);
bool os_waitEvents (osEventHandle * handles[], int count, bool waitAll, unsigned int timeout);
bool os_signalEvent(osEventHandle * handle);
bool os_resetEvent (osEventHandle * handle);

View File

@@ -1,453 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/platform.h"
#include "common/debug.h"
#include "common/option.h"
#include <assert.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
struct app
{
const char * executable;
unsigned int shmSize;
int shmFD;
void * shmMap;
};
static struct app app;
struct osThreadHandle
{
const char * name;
osThreadFunction function;
void * opaque;
pthread_t handle;
int resultCode;
};
void sigHandler(int signo)
{
DEBUG_INFO("SIGINT");
app_quit();
}
static int uioOpenFile(const char * shmDevice, const char * file)
{
int len = snprintf(NULL, 0, "/sys/class/uio/%s/%s", shmDevice, file);
char * path = malloc(len + 1);
sprintf(path, "/sys/class/uio/%s/%s", shmDevice, file);
int fd = open(path, O_RDONLY);
if (fd < 0)
{
free(path);
return -1;
}
free(path);
return fd;
}
static char * uioGetName(const char * shmDevice)
{
int fd = uioOpenFile(shmDevice, "name");
if (fd < 0)
return NULL;
char * name = malloc(32);
int len = read(fd, name, 31);
if (len <= 0)
{
free(name);
close(fd);
return NULL;
}
name[len] = '\0';
close(fd);
while(len > 0 && name[len-1] == '\n')
{
--len;
name[len] = '\0';
}
return name;
}
static int shmOpenDev(const char * shmDevice)
{
int len = snprintf(NULL, 0, "/dev/%s", shmDevice);
char * path = malloc(len + 1);
sprintf(path, "/dev/%s", shmDevice);
int fd = open(path, O_RDWR, (mode_t)0600);
if (fd < 0)
{
DEBUG_ERROR("Failed to open: %s", path);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
free(path);
return -1;
}
free(path);
return fd;
}
static bool shmDeviceValidator(struct Option * opt, const char ** error)
{
char * name = uioGetName(opt->value.x_string);
if (!name)
{
*error = "Failed to get the uio device name";
return false;
}
if (strcmp(name, "KVMFR") != 0)
{
free(name);
*error = "Device is not a KVMFR device";
return false;
}
free(name);
return true;
}
static StringList shmDeviceGetValues(struct Option * option)
{
StringList sl = stringlist_new(true);
DIR * d = opendir("/sys/class/uio");
if (!d)
return sl;
struct dirent * dir;
while((dir = readdir(d)) != NULL)
{
if (dir->d_name[0] == '.')
continue;
char * name = uioGetName(dir->d_name);
if (!name)
continue;
if (strcmp(name, "KVMFR") == 0)
stringlist_push(sl, strdup(dir->d_name));
free(name);
}
closedir(d);
return sl;
}
int main(int argc, char * argv[])
{
app.executable = argv[0];
struct Option options[] =
{
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_STRING,
.value.x_string = "uio0",
.validator = shmDeviceValidator,
.getValues = shmDeviceGetValues
},
{0}
};
option_register(options);
int result = app_main(argc, argv);
os_shmemUnmap();
close(app.shmFD);
return result;
}
bool app_init()
{
const char * shmDevice = option_get_string("os", "shmDevice");
// get the device size
int fd = uioOpenFile(shmDevice, "maps/map0/size");
if (fd < 0)
{
DEBUG_ERROR("Failed to open %s/size", shmDevice);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
return false;
}
char size[32];
int len = read(fd, size, sizeof(size) - 1);
if (len <= 0)
{
DEBUG_ERROR("Failed to read the device size");
close(fd);
return false;
}
size[len] = '\0';
close(fd);
app.shmSize = strtoul(size, NULL, 16);
// open the device
app.shmFD = shmOpenDev(shmDevice);
app.shmMap = MAP_FAILED;
if (app.shmFD < 0)
return false;
DEBUG_INFO("KVMFR Device : %s", shmDevice);
signal(SIGINT, sigHandler);
return true;
}
const char * os_getExecutable()
{
return app.executable;
}
unsigned int os_shmemSize()
{
return app.shmSize;
}
bool os_shmemMmap(void **ptr)
{
if (app.shmMap == MAP_FAILED)
{
app.shmMap = mmap(0, app.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, app.shmFD, 0);
if (app.shmMap == MAP_FAILED)
{
const char * shmDevice = option_get_string("os", "shmDevice");
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
return false;
}
}
*ptr = app.shmMap;
return true;
}
void os_shmemUnmap()
{
if (app.shmMap == MAP_FAILED)
return;
munmap(app.shmMap, app.shmSize);
app.shmMap = MAP_FAILED;
}
static void * threadWrapper(void * opaque)
{
osThreadHandle * handle = (osThreadHandle *)opaque;
handle->resultCode = handle->function(handle->opaque);
return NULL;
}
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle)
{
*handle = (osThreadHandle*)malloc(sizeof(osThreadHandle));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0)
{
DEBUG_ERROR("pthread_create failed for thread: %s", name);
free(*handle);
*handle = NULL;
return false;
}
return true;
}
bool os_joinThread(osThreadHandle * handle, int * resultCode)
{
if (pthread_join(handle->handle, NULL) != 0)
{
DEBUG_ERROR("pthread_join failed for thread: %s", handle->name);
free(handle);
return false;
}
if (resultCode)
*resultCode = handle->resultCode;
free(handle);
return true;
}
struct osEventHandle
{
pthread_mutex_t mutex;
pthread_cond_t cond;
bool flag;
bool autoReset;
};
osEventHandle * os_createEvent(bool autoReset)
{
osEventHandle * handle = (osEventHandle *)calloc(sizeof(osEventHandle), 1);
if (!handle)
{
DEBUG_ERROR("Failed to allocate memory");
return NULL;
}
if (pthread_mutex_init(&handle->mutex, NULL) != 0)
{
DEBUG_ERROR("Failed to create the mutex");
free(handle);
return NULL;
}
if (pthread_cond_init(&handle->cond, NULL) != 0)
{
pthread_mutex_destroy(&handle->mutex);
free(handle);
return NULL;
}
handle->autoReset = autoReset;
return handle;
}
void os_freeEvent(osEventHandle * handle)
{
assert(handle);
pthread_cond_destroy (&handle->cond );
pthread_mutex_destroy(&handle->mutex);
free(handle);
}
bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
while(!handle->flag)
{
if (timeout == TIMEOUT_INFINITE)
{
if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0)
{
DEBUG_ERROR("Wait to wait on the condition");
return false;
}
}
else
{
struct timespec ts;
ts.tv_sec = timeout / 1000;
ts.tv_nsec = (timeout % 1000) * 1000000;
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, &ts))
{
case ETIMEDOUT:
return false;
default:
DEBUG_ERROR("Timed wait failed");
return false;
}
}
}
if (handle->autoReset)
handle->flag = false;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}
bool os_signalEvent(osEventHandle * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
handle->flag = true;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
if (pthread_cond_signal(&handle->cond) != 0)
{
DEBUG_ERROR("Failed to signal the condition");
return false;
}
return true;
}
bool os_resetEvent(osEventHandle * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
handle->flag = false;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}

View File

@@ -1,315 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/capture.h"
#include "interface/platform.h"
#include "windows/platform.h"
#include "windows/debug.h"
#include "windows/mousehook.h"
#include "common/option.h"
#include <assert.h>
#include <stdlib.h>
#include <windows.h>
#include <NvFBC/nvFBC.h>
#include "wrapper.h"
struct iface
{
bool stop;
NvFBCHandle nvfbc;
bool seperateCursor;
void * pointerShape;
unsigned int pointerSize;
unsigned int maxWidth, maxHeight;
unsigned int width , height;
uint8_t * frameBuffer;
NvFBCFrameGrabInfo grabInfo;
osEventHandle * frameEvent;
osEventHandle * cursorEvents[2];
int mouseX, mouseY, mouseHotX, mouseHotY;
bool mouseVisible;
};
static struct iface * this = NULL;
static void nvfbc_free();
static void getDesktopSize(unsigned int * width, unsigned int * height)
{
HMONITOR monitor = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorInfo = {
.cbSize = sizeof(MONITORINFO)
};
GetMonitorInfo(monitor, &monitorInfo);
CloseHandle(monitor);
*width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
*height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
}
static void on_mouseMove(int x, int y)
{
this->mouseX = x;
this->mouseY = y;
os_signalEvent(this->cursorEvents[0]);
}
static const char * nvfbc_getName()
{
return "NVFBC (NVidia Frame Buffer Capture)";
};
static void nvfbc_initOptions()
{
struct Option options[] =
{
{
.module = "nvfbc",
.name = "decoupleCursor",
.description = "Capture the cursor seperately",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{0}
};
option_register(options);
}
static bool nvfbc_create()
{
if (!NvFBCInit())
return false;
int bufferLen = GetEnvironmentVariable("NVFBC_PRIV_DATA", NULL, 0);
uint8_t * privData = NULL;
int privDataLen = 0;
if(bufferLen)
{
char * buffer = malloc(bufferLen);
GetEnvironmentVariable("NVFBC_PRIV_DATA", buffer, bufferLen);
privDataLen = (bufferLen - 1) / 2;
privData = (uint8_t *)malloc(privDataLen);
char hex[3] = {0};
for(int i = 0; i < privDataLen; ++i)
{
memcpy(hex, &buffer[i*2], 2);
privData[i] = (uint8_t)strtoul(hex, NULL, 16);
}
free(buffer);
}
this = (struct iface *)calloc(sizeof(struct iface), 1);
if (!NvFBCToSysCreate(privData, privDataLen, &this->nvfbc, &this->maxWidth, &this->maxHeight))
{
free(privData);
nvfbc_free();
return false;
}
free(privData);
this->frameEvent = os_createEvent(true);
if (!this->frameEvent)
{
DEBUG_ERROR("failed to create the frame event");
nvfbc_free();
return false;
}
this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor");
return true;
}
static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
{
this->stop = false;
this->pointerShape = pointerShape;
this->pointerSize = pointerSize;
getDesktopSize(&this->width, &this->height);
os_resetEvent(this->frameEvent);
HANDLE event;
if (!NvFBCToSysSetup(
this->nvfbc,
BUFFER_FMT_ARGB,
!this->seperateCursor,
this->seperateCursor,
false,
0,
(void **)&this->frameBuffer,
NULL,
&event
))
{
return false;
}
this->cursorEvents[0] = os_createEvent(true);
mouseHook_install(on_mouseMove);
if (this->seperateCursor)
this->cursorEvents[1] = os_wrapEvent(event);
DEBUG_INFO("Cursor mode : %s", this->seperateCursor ? "decoupled" : "integrated");
Sleep(100);
return true;
}
static void nvfbc_stop()
{
this->stop = true;
os_signalEvent(this->cursorEvents[0]);
os_signalEvent(this->frameEvent);
}
static bool nvfbc_deinit()
{
mouseHook_remove();
return true;
}
static void nvfbc_free()
{
NvFBCToSysRelease(&this->nvfbc);
if (this->frameEvent)
os_freeEvent(this->frameEvent);
free(this);
this = NULL;
NvFBCFree();
}
static unsigned int nvfbc_getMaxFrameSize()
{
return this->maxWidth * this->maxHeight * 4;
}
static CaptureResult nvfbc_capture()
{
getDesktopSize(&this->width, &this->height);
NvFBCFrameGrabInfo grabInfo;
CaptureResult result = NvFBCToSysCapture(
this->nvfbc,
1000,
0, 0,
this->width,
this->height,
&grabInfo
);
if (result != CAPTURE_RESULT_OK)
return result;
memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo));
os_signalEvent(this->frameEvent);
return CAPTURE_RESULT_OK;
}
static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
{
if (!os_waitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
return CAPTURE_RESULT_REINIT;
frame->width = this->grabInfo.dwWidth;
frame->height = this->grabInfo.dwHeight;
frame->pitch = this->grabInfo.dwBufferWidth * 4;
frame->stride = this->grabInfo.dwBufferWidth;
#if 0
//NvFBC never sets bIsHDR so instead we check for any data in the alpha channel
//If there is data, it's HDR. This is clearly suboptimal
if (!this->grabInfo.bIsHDR)
for(int y = 0; y < frame->height; ++y)
for(int x = 0; x < frame->width; ++x)
{
int offset = (y * frame->pitch) + (x * 4);
if (this->frameBuffer[offset + 3])
{
this->grabInfo.bIsHDR = 1;
break;
}
}
#endif
frame->format = this->grabInfo.bIsHDR ? CAPTURE_FMT_RGBA10 : CAPTURE_FMT_BGRA;
memcpy(frame->data, this->frameBuffer, frame->pitch * frame->height);
return CAPTURE_RESULT_OK;
}
static CaptureResult nvfbc_getPointer(CapturePointer * pointer)
{
osEventHandle * events[2];
memcpy(&events, &this->cursorEvents, sizeof(osEventHandle *) * 2);
if (!os_waitEvents(events, this->seperateCursor ? 2 : 1, false, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
return CAPTURE_RESULT_REINIT;
CaptureResult result;
pointer->shapeUpdate = false;
if (this->seperateCursor && events[1])
{
result = NvFBCToSysGetCursor(this->nvfbc, pointer, this->pointerShape, this->pointerSize);
this->mouseVisible = pointer->visible;
this->mouseHotX = pointer->x;
this->mouseHotY = pointer->y;
if (result != CAPTURE_RESULT_OK)
return result;
}
pointer->visible = this->mouseVisible;
pointer->x = this->mouseX - this->mouseHotX;
pointer->y = this->mouseY - this->mouseHotY;
return CAPTURE_RESULT_OK;
}
struct CaptureInterface Capture_NVFBC =
{
.getName = nvfbc_getName,
.initOptions = nvfbc_initOptions,
.create = nvfbc_create,
.init = nvfbc_init,
.stop = nvfbc_stop,
.deinit = nvfbc_deinit,
.free = nvfbc_free,
.getMaxFrameSize = nvfbc_getMaxFrameSize,
.capture = nvfbc_capture,
.getFrame = nvfbc_getFrame,
.getPointer = nvfbc_getPointer
};

View File

@@ -1,98 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "windows/mousehook.h"
#include "windows/debug.h"
#include "platform.h"
#include <windows.h>
#include <stdbool.h>
struct mouseHook
{
bool installed;
HHOOK hook;
MouseHookFn callback;
};
static struct mouseHook mouseHook = { 0 };
// forwards
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam);
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam);
void mouseHook_install(MouseHookFn callback)
{
struct MSG_CALL_FUNCTION cf;
cf.fn = msg_callback;
cf.wParam = 1;
cf.lParam = (LPARAM)callback;
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
}
void mouseHook_remove()
{
struct MSG_CALL_FUNCTION cf;
cf.fn = msg_callback;
cf.wParam = 0;
cf.lParam = 0;
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
}
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam)
{
if (wParam)
{
if (mouseHook.installed)
{
DEBUG_WARN("Mouse hook already installed");
return 0;
}
mouseHook.hook = SetWindowsHookEx(WH_MOUSE_LL, mouseHook_hook, NULL, 0);
if (!mouseHook.hook)
{
DEBUG_WINERROR("Failed to install the mouse hook", GetLastError());
return 0;
}
mouseHook.installed = true;
mouseHook.callback = (MouseHookFn)lParam;
}
else
{
if (!mouseHook.installed)
return 0;
UnhookWindowsHookEx(mouseHook.hook);
mouseHook.installed = false;
}
return 0;
}
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION && wParam == WM_MOUSEMOVE)
{
MSLLHOOKSTRUCT *msg = (MSLLHOOKSTRUCT *)lParam;
mouseHook.callback(msg->pt.x, msg->pt.y);
}
return CallNextHookEx(mouseHook.hook, nCode, wParam, lParam);
}

View File

@@ -1,551 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "platform.h"
#include "windows/platform.h"
#include "windows/mousehook.h"
#include <windows.h>
#include <setupapi.h>
#include <shellapi.h>
#include "interface/platform.h"
#include "common/debug.h"
#include "common/option.h"
#include "windows/debug.h"
#include "ivshmem.h"
#define ID_MENU_OPEN_LOG 3000
#define ID_MENU_EXIT 3001
struct AppState
{
HINSTANCE hInst;
int argc;
char ** argv;
char executable[MAX_PATH + 1];
HANDLE shmemHandle;
bool shmemOwned;
IVSHMEM_MMAP shmemMap;
HWND messageWnd;
HMENU trayMenu;
};
static struct AppState app =
{
.shmemHandle = INVALID_HANDLE_VALUE,
.shmemOwned = false,
.shmemMap = {0}
};
struct osThreadHandle
{
const char * name;
osThreadFunction function;
void * opaque;
HANDLE handle;
DWORD threadID;
int resultCode;
};
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CALL_FUNCTION:
{
struct MSG_CALL_FUNCTION * cf = (struct MSG_CALL_FUNCTION *)lParam;
return cf->fn(cf->wParam, cf->lParam);
}
case WM_TRAYICON:
{
if (lParam == WM_RBUTTONDOWN)
{
POINT curPoint;
GetCursorPos(&curPoint);
SetForegroundWindow(hwnd);
UINT clicked = TrackPopupMenu(
app.trayMenu,
TPM_RETURNCMD | TPM_NONOTIFY,
curPoint.x,
curPoint.y,
0,
hwnd,
NULL
);
if (clicked == ID_MENU_EXIT ) app_quit();
else if (clicked == ID_MENU_OPEN_LOG)
{
const char * logFile = option_get_string("os", "logFile");
if (strcmp(logFile, "stderr") == 0)
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
else
ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
}
}
break;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
static int appThread(void * opaque)
{
// register our TrayIcon
NOTIFYICONDATA iconData =
{
.cbSize = sizeof(NOTIFYICONDATA),
.hWnd = app.messageWnd,
.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP,
.uCallbackMessage = WM_TRAYICON,
.szTip = "Looking Glass (host)"
};
iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
Shell_NotifyIcon(NIM_ADD, &iconData);
int result = app_main(app.argc, app.argv);
Shell_NotifyIcon(NIM_DELETE, &iconData);
mouseHook_remove();
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
return result;
}
LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam)
{
return SendMessage(app.messageWnd, Msg, wParam, lParam);
}
static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
if (dwCtrlType == CTRL_C_EVENT)
{
SendMessage(app.messageWnd, WM_CLOSE, 0, 0);
return TRUE;
}
return FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
int result = 0;
app.hInst = hInstance;
char tempPath[MAX_PATH+1];
GetTempPathA(sizeof(tempPath), tempPath);
int len = snprintf(NULL, 0, "%slooking-glass-host.txt", tempPath);
char * logFilePath = malloc(len + 1);
sprintf(logFilePath, "%slooking-glass-host.txt", tempPath);
struct Option options[] =
{
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_INT,
.value.x_int = 0
},
{
.module = "os",
.name = "logFile",
.description = "The log file to write to",
.type = OPTION_TYPE_STRING,
.value.x_string = logFilePath
},
{0}
};
option_register(options);
free(logFilePath);
// convert the command line to the standard argc and argv
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
app.argv = malloc(sizeof(char *) * app.argc);
for(int i = 0; i < app.argc; ++i)
{
const size_t s = (wcslen(wargv[i])+1) * 2;
app.argv[i] = malloc(s);
wcstombs(app.argv[i], wargv[i], s);
}
LocalFree(wargv);
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
// setup a handler for ctrl+c
SetConsoleCtrlHandler(CtrlHandler, TRUE);
// create a message window so that our message pump works
WNDCLASSEX wx = {};
wx.cbSize = sizeof(WNDCLASSEX);
wx.lpfnWndProc = DummyWndProc;
wx.hInstance = hInstance;
wx.lpszClassName = "DUMMY_CLASS";
wx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
if (!RegisterClassEx(&wx))
{
DEBUG_ERROR("Failed to register message window class");
result = -1;
goto finish;
}
app.messageWnd = CreateWindowEx(0, "DUMMY_CLASS", "DUMMY_NAME", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
app.trayMenu = CreatePopupMenu();
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_OPEN_LOG, "Open Log File");
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
// create the application thread
osThreadHandle * thread;
if (!os_createThread("appThread", appThread, NULL, &thread))
{
DEBUG_ERROR("Failed to create the main application thread");
result = -1;
goto finish;
}
while(true)
{
MSG msg;
BOOL bRet = GetMessage(&msg, NULL, 0, 0);
if (bRet > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}
else if (bRet < 0)
{
DEBUG_ERROR("Unknown error from GetMessage");
result = -1;
goto shutdown;
}
break;
}
shutdown:
DestroyMenu(app.trayMenu);
app_quit();
if (!os_joinThread(thread, &result))
{
DEBUG_ERROR("Failed to join the main application thread");
result = -1;
}
finish:
os_shmemUnmap();
if (app.shmemHandle != INVALID_HANDLE_VALUE)
CloseHandle(app.shmemHandle);
for(int i = 0; i < app.argc; ++i)
free(app.argv[i]);
free(app.argv);
return result;
}
bool app_init()
{
const int shmDevice = option_get_int ("os", "shmDevice");
const char * logFile = option_get_string("os", "logFile" );
// redirect stderr to a file
if (logFile && strcmp(logFile, "stderr") != 0)
freopen(logFile, "a", stderr);
// always flush stderr
setbuf(stderr, NULL);
HDEVINFO deviceInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
deviceInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
memset(&deviceInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA));
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &deviceInterfaceData) == FALSE)
{
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_ITEMS)
{
DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error);
return false;
}
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
return false;
}
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &reqSize, NULL);
if (!reqSize)
{
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1);
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, infData, reqSize, NULL, NULL))
{
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
app.shmemHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (app.shmemHandle == INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
free(infData);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
return false;
}
free(infData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return true;
}
const char * os_getExecutable()
{
return app.executable;
}
unsigned int os_shmemSize()
{
IVSHMEM_SIZE size;
if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return 0;
}
return (unsigned int)size;
}
bool os_shmemMmap(void **ptr)
{
if (app.shmemOwned)
{
*ptr = app.shmemMap.ptr;
return true;
}
memset(&app.shmemMap, 0, sizeof(IVSHMEM_MMAP));
if (!DeviceIoControl(
app.shmemHandle,
IOCTL_IVSHMEM_REQUEST_MMAP,
NULL, 0,
&app.shmemMap, sizeof(IVSHMEM_MMAP),
NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return false;
}
*ptr = app.shmemMap.ptr;
app.shmemOwned = true;
return true;
}
void os_shmemUnmap()
{
if (!app.shmemOwned)
return;
if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL))
DEBUG_WINERROR("DeviceIoControl failed", GetLastError());
else
app.shmemOwned = false;
}
static DWORD WINAPI threadWrapper(LPVOID lpParameter)
{
osThreadHandle * handle = (osThreadHandle *)lpParameter;
handle->resultCode = handle->function(handle->opaque);
return 0;
}
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle)
{
*handle = (osThreadHandle *)malloc(sizeof(osThreadHandle));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
(*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID);
if (!(*handle)->handle)
{
free(*handle);
*handle = NULL;
DEBUG_WINERROR("CreateThread failed", GetLastError());
return false;
}
return true;
}
bool os_joinThread(osThreadHandle * handle, int * resultCode)
{
while(true)
{
switch(WaitForSingleObject(handle->handle, INFINITE))
{
case WAIT_OBJECT_0:
if (resultCode)
*resultCode = handle->resultCode;
CloseHandle(handle->handle);
free(handle);
return true;
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
continue;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for thread failed", GetLastError());
CloseHandle(handle->handle);
free(handle);
return false;
}
break;
}
DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError());
return false;
}
osEventHandle * os_createEvent(bool autoReset)
{
HANDLE event = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL);
if (!event)
{
DEBUG_WINERROR("Failed to create the event", GetLastError());
return NULL;
}
return (osEventHandle*)event;
}
osEventHandle * os_wrapEvent(HANDLE event)
{
return (osEventHandle*)event;
}
void os_freeEvent(osEventHandle * handle)
{
CloseHandle((HANDLE)handle);
}
bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
switch(WaitForSingleObject((HANDLE)handle, to))
{
case WAIT_OBJECT_0:
return true;
case WAIT_ABANDONED:
continue;
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool os_waitEvents(osEventHandle * handles[], int count, bool waitAll, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
DWORD result = WaitForMultipleObjects(count, (HANDLE*)handles, waitAll, to);
if (result >= WAIT_OBJECT_0 && result < count)
{
// null non signalled events from the handle list
for(int i = 0; i < count; ++i)
if (i != result && !os_waitEvent(handles[i], 0))
handles[i] = NULL;
return true;
}
if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count)
continue;
switch(result)
{
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool os_signalEvent(osEventHandle * handle)
{
return SetEvent((HANDLE)handle);
}
bool os_resetEvent(osEventHandle * handle)
{
return ResetEvent((HANDLE)handle);
}

View File

@@ -1,475 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/platform.h"
#include "interface/capture.h"
#include "dynamic/capture.h"
#include "common/debug.h"
#include "common/option.h"
#include "common/locking.h"
#include "common/KVMFR.h"
#include "common/crash.h"
#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
#define ALIGN_UP(x) ALIGN_DN(x + 0x7F)
#define MAX_FRAMES 2
struct app
{
unsigned int clientInstance;
KVMFRHeader * shmHeader;
uint8_t * pointerData;
unsigned int pointerDataSize;
unsigned int pointerOffset;
CaptureInterface * iface;
uint8_t * frames;
unsigned int frameSize;
uint8_t * frame[MAX_FRAMES];
unsigned int frameOffset[MAX_FRAMES];
bool running;
bool reinit;
osThreadHandle * pointerThread;
osThreadHandle * frameThread;
};
static struct app app;
static int pointerThread(void * opaque)
{
DEBUG_INFO("Pointer thread started");
volatile KVMFRCursor * ci = &(app.shmHeader->cursor);
uint8_t flags;
bool pointerValid = false;
bool shapeValid = false;
unsigned int clientInstance = 0;
CapturePointer pointer = { 0 };
while(app.running)
{
bool resend = false;
pointer.shapeUpdate = false;
switch(app.iface->getPointer(&pointer))
{
case CAPTURE_RESULT_OK:
{
pointerValid = true;
break;
}
case CAPTURE_RESULT_REINIT:
{
app.reinit = true;
DEBUG_INFO("Pointer thread reinit");
return 0;
}
case CAPTURE_RESULT_ERROR:
{
DEBUG_ERROR("Failed to get the pointer");
return 0;
}
case CAPTURE_RESULT_TIMEOUT:
{
// if the pointer is valid and the client has restarted, send it
if (pointerValid && clientInstance != app.clientInstance)
{
resend = true;
break;
}
continue;
}
}
clientInstance = app.clientInstance;
// wait for the client to finish with the previous update
while((ci->flags & ~KVMFR_CURSOR_FLAG_UPDATE) != 0 && app.running)
usleep(1000);
flags = KVMFR_CURSOR_FLAG_UPDATE;
ci->x = pointer.x;
ci->y = pointer.y;
flags |= KVMFR_CURSOR_FLAG_POS;
if (pointer.visible)
flags |= KVMFR_CURSOR_FLAG_VISIBLE;
// if we have shape data
if (pointer.shapeUpdate || (shapeValid && resend))
{
switch(pointer.format)
{
case CAPTURE_FMT_COLOR : ci->type = CURSOR_TYPE_COLOR ; break;
case CAPTURE_FMT_MONO : ci->type = CURSOR_TYPE_MONOCHROME ; break;
case CAPTURE_FMT_MASKED: ci->type = CURSOR_TYPE_MASKED_COLOR; break;
default:
DEBUG_ERROR("Invalid pointer format: %d", pointer.format);
continue;
}
ci->width = pointer.width;
ci->height = pointer.height;
ci->pitch = pointer.pitch;
ci->dataPos = app.pointerOffset;
++ci->version;
shapeValid = true;
flags |= KVMFR_CURSOR_FLAG_SHAPE;
}
// update the flags for the client
ci->flags = flags;
}
DEBUG_INFO("Pointer thread stopped");
return 0;
}
static int frameThread(void * opaque)
{
DEBUG_INFO("Frame thread started");
volatile KVMFRFrame * fi = &(app.shmHeader->frame);
bool frameValid = false;
int frameIndex = 0;
unsigned int clientInstance = 0;
CaptureFrame frame = { 0 };
while(app.running)
{
frame.data = app.frame[frameIndex];
switch(app.iface->getFrame(&frame))
{
case CAPTURE_RESULT_OK:
break;
case CAPTURE_RESULT_REINIT:
{
app.reinit = true;
DEBUG_INFO("Frame thread reinit");
return 0;
}
case CAPTURE_RESULT_ERROR:
{
DEBUG_ERROR("Failed to get the frame");
return 0;
}
case CAPTURE_RESULT_TIMEOUT:
{
if (frameValid && clientInstance != app.clientInstance)
{
// resend the last frame
if (--frameIndex < 0)
frameIndex = MAX_FRAMES - 1;
break;
}
continue;
}
}
clientInstance = app.clientInstance;
// wait for the client to finish with the previous frame
while(fi->flags & KVMFR_FRAME_FLAG_UPDATE && app.running)
usleep(1000);
switch(frame.format)
{
case CAPTURE_FMT_BGRA : fi->type = FRAME_TYPE_BGRA ; break;
case CAPTURE_FMT_RGBA : fi->type = FRAME_TYPE_RGBA ; break;
case CAPTURE_FMT_RGBA10: fi->type = FRAME_TYPE_RGBA10; break;
case CAPTURE_FMT_YUV420: fi->type = FRAME_TYPE_YUV420; break;
default:
DEBUG_ERROR("Unsupported frame format %d, skipping frame", frame.format);
continue;
}
fi->width = frame.width;
fi->height = frame.height;
fi->stride = frame.stride;
fi->pitch = frame.pitch;
fi->dataPos = app.frameOffset[frameIndex];
frameValid = true;
INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE);
if (++frameIndex == MAX_FRAMES)
frameIndex = 0;
}
DEBUG_INFO("Frame thread stopped");
return 0;
}
bool startThreads()
{
app.running = true;
if (!os_createThread("CursorThread", pointerThread, NULL, &app.pointerThread))
{
DEBUG_ERROR("Failed to create the pointer thread");
return false;
}
if (!os_createThread("FrameThread", frameThread, NULL, &app.frameThread))
{
DEBUG_ERROR("Failed to create the frame thread");
return false;
}
return true;
}
bool stopThreads()
{
bool ok = true;
app.running = false;
app.iface->stop();
if (app.frameThread && !os_joinThread(app.frameThread, NULL))
{
DEBUG_WARN("Failed to join the frame thread");
ok = false;
}
app.frameThread = NULL;
if (app.pointerThread && !os_joinThread(app.pointerThread, NULL))
{
DEBUG_WARN("Failed to join the pointer thread");
ok = false;
}
app.pointerThread = NULL;
return ok;
}
static bool captureStart()
{
DEBUG_INFO("Using : %s", app.iface->getName());
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
if (maxFrameSize > app.frameSize)
{
DEBUG_ERROR("Maximum frame size of %d bytes excceds maximum space available", maxFrameSize);
return false;
}
DEBUG_INFO("Capture Size : %u MiB (%u)", maxFrameSize / 1048576, maxFrameSize);
DEBUG_INFO("==== [ Capture Start ] ====");
return startThreads();
}
static bool captureRestart()
{
DEBUG_INFO("==== [ Capture Restart ] ====");
if (!stopThreads())
return false;
if (!app.iface->deinit() || !app.iface->init(app.pointerData, app.pointerDataSize))
{
DEBUG_ERROR("Failed to reinitialize the capture device");
return false;
}
if (!captureStart())
return false;
return true;
}
// this is called from the platform specific startup routine
int app_main(int argc, char * argv[])
{
if (!installCrashHandler(os_getExecutable()))
DEBUG_WARN("Failed to install the crash handler");
// register capture interface options
for(int i = 0; CaptureInterfaces[i]; ++i)
if (CaptureInterfaces[i]->initOptions)
CaptureInterfaces[i]->initOptions();
// try load values from a config file
option_load("looking-glass-host.ini");
// parse the command line arguments
if (!option_parse(argc, argv))
{
option_free();
DEBUG_ERROR("Failure to parse the command line");
return -1;
}
if (!option_validate())
{
option_free();
return -1;
}
// perform platform specific initialization
if (!app_init())
return -1;
unsigned int shmemSize = os_shmemSize();
uint8_t * shmemMap = NULL;
int exitcode = 0;
DEBUG_INFO("Looking Glass Host (" BUILD_VERSION ")");
DEBUG_INFO("IVSHMEM Size : %u MiB", shmemSize / 1048576);
if (!os_shmemMmap((void **)&shmemMap) || !shmemMap)
{
DEBUG_ERROR("Failed to map the shared memory");
return -1;
}
DEBUG_INFO("IVSHMEM Address : 0x%" PRIXPTR, (uintptr_t)shmemMap);
app.shmHeader = (KVMFRHeader *)shmemMap;
app.pointerData = (uint8_t *)ALIGN_UP(shmemMap + sizeof(KVMFRHeader));
app.pointerDataSize = 1048576; // 1MB fixed for pointer size, should be more then enough
app.pointerOffset = app.pointerData - shmemMap;
app.frames = (uint8_t *)ALIGN_UP(app.pointerData + app.pointerDataSize);
app.frameSize = ALIGN_DN((shmemSize - (app.frames - shmemMap)) / MAX_FRAMES);
DEBUG_INFO("Max Cursor Size : %u MiB" , app.pointerDataSize / 1048576);
DEBUG_INFO("Max Frame Size : %u MiB" , app.frameSize / 1048576);
DEBUG_INFO("Cursor : 0x%" PRIXPTR " (0x%08x)", (uintptr_t)app.pointerData, app.pointerOffset);
for (int i = 0; i < MAX_FRAMES; ++i)
{
app.frame [i] = app.frames + i * app.frameSize;
app.frameOffset[i] = app.frame[i] - shmemMap;
DEBUG_INFO("Frame %d : 0x%" PRIXPTR " (0x%08x)", i, (uintptr_t)app.frame[i], app.frameOffset[i]);
}
CaptureInterface * iface = NULL;
for(int i = 0; CaptureInterfaces[i]; ++i)
{
iface = CaptureInterfaces[i];
DEBUG_INFO("Trying : %s", iface->getName());
if (!iface->create())
{
iface = NULL;
continue;
}
if (iface->init(app.pointerData, app.pointerDataSize))
break;
iface->free();
iface = NULL;
}
if (!iface)
{
DEBUG_ERROR("Failed to find a supported capture interface");
exitcode = -1;
goto fail;
}
app.iface = iface;
// initialize the shared memory headers
memcpy(app.shmHeader->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC));
app.shmHeader->version = KVMFR_HEADER_VERSION;
// zero and notify the client we are starting
memset(&(app.shmHeader->frame ), 0, sizeof(KVMFRFrame ));
memset(&(app.shmHeader->cursor), 0, sizeof(KVMFRCursor));
app.shmHeader->flags &= ~KVMFR_HEADER_FLAG_RESTART;
if (!captureStart())
{
exitcode = -1;
goto exit;
}
volatile char * flags = (volatile char *)&(app.shmHeader->flags);
while(app.running)
{
if (INTERLOCKED_AND8(flags, ~(KVMFR_HEADER_FLAG_RESTART)) & KVMFR_HEADER_FLAG_RESTART)
{
DEBUG_INFO("Client restarted");
++app.clientInstance;
}
if (app.reinit && !captureRestart())
{
exitcode = -1;
goto exit;
}
app.reinit = false;
switch(iface->capture())
{
case CAPTURE_RESULT_OK:
break;
case CAPTURE_RESULT_TIMEOUT:
continue;
case CAPTURE_RESULT_REINIT:
if (!captureRestart())
{
exitcode = -1;
goto exit;
}
app.reinit = false;
continue;
case CAPTURE_RESULT_ERROR:
DEBUG_ERROR("Capture interface reported a fatal error");
exitcode = -1;
goto finish;
}
}
finish:
stopThreads();
exit:
iface->deinit();
iface->free();
fail:
os_shmemUnmap();
return exitcode;
}
void app_quit()
{
app.running = false;
}

View File

@@ -1,10 +0,0 @@
packadd termdebug
function Debug()
!cd build && make
if v:shell_error == 0
TermdebugCommand build/looking-glass-client
endif
endfunction
command Debug call Debug()

View File

@@ -21,14 +21,27 @@ 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_CB_X11 "Enable X11 clipboard integration" ON)
add_feature_info(ENABLE_CB_X11 ENABLE_CB_X11 "X11 Clipboard Integration.")
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"
@@ -37,44 +50,54 @@ add_compile_options(
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
)
set(EXE_FLAGS "-Wl,--gc-sections")
set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack")
set(CMAKE_C_STANDARD 11)
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
x11
)
execute_process(
COMMAND cat ../VERSION
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE BUILD_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
find_package(GMP)
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
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_INCLUDE_DIRS} ${PKGCONFIG_OPT_INCLUDE_DIRS}
${GMP_INCLUDE_DIR}
)
link_libraries(
${PKGCONFIG_LIBRARIES}
${PKGCONFIG_LIBRARIES} ${PKGCONFIG_OPT_LIBRARIES}
${GMP_LIBRARIES}
${CMAKE_DL_LIBS}
rt
m
)
set(SOURCES
${CMAKE_BINARY_DIR}/version.c
src/main.c
src/app.c
src/config.c
@@ -83,21 +106,23 @@ set(SOURCES
src/utils.c
)
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory(spice)
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(clipboards)
add_subdirectory(fonts)
add_subdirectory(decoders)
add_executable(looking-glass-client ${SOURCES})
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER} ${PKGCONFIG_OPT_CFLAGS_OTHER})
target_link_libraries(looking-glass-client
${EXE_FLAGS}
lg_common
spice
displayservers
lgmp
purespice
renderers
clipboards
fonts
)

View File

@@ -17,10 +17,12 @@ This is the Looking Glass client application that is designed to work in tandem
* 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
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
@@ -31,6 +33,19 @@ This is the Looking Glass client application that is designed to work in tandem
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
@@ -44,8 +59,11 @@ Below are a list of current key bindings:
|-|-|
| <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 |
@@ -60,6 +78,8 @@ Below are a list of current key bindings:
| <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 |
@@ -89,46 +109,57 @@ 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:shmFile | -f | /dev/shm/looking-glass | The path to the shared memory file |
| app:shmSize | -L | 0 | Specify the size in MB of the shared memory file (0 = detect) |
| 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 |
|-------------------------------------------------------------------------------------------------------------------------|
|--------------------------------------------------------------------------------------------------------------------------------------------------|
| 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 | Aallow the window to be manually resized |
| win:keepAspect | -r | yes | Maintain the correct aspect ratio |
| 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:fpsLimit | -K | 200 | Frame rate limit (0 = disable - not recommended) |
| 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 |
|-------------------------------------------------------------------------------------------------------------|
|---------------------------------------------------------------------------------------------------------------------------------|
| 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 | yes | Grab the keyboard in capture mode |
| input:escapeKey | -m | 71 = ScrollLock | Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values |
| input:hideCursor | -M | yes | Hide the local mouse cursor |
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
|---------------------------------------------------------------------------------------------------------------------------------------|
|----------------------------------------------------------------------------------------------------------------------------------------------|
| Long | Short | Value | Description |
|----------------------------------------------------------------------------------------------------------------------------------------------|
| input:grabKeyboard | -G | yes | Grab the keyboard in capture mode |
| input:grabKeyboardOnFocus | | yes | Grab the keyboard when focused |
| input:escapeKey | -m | 71 = ScrollLock | Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values |
| input:ignoreWindowsKeys | | no | Do not pass events for the windows keys to the guest |
| input:hideCursor | -M | yes | Hide the local mouse cursor |
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
| input:mouseSmoothing | | yes | Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing) |
| input:rawMouse | | no | Use RAW mouse input when in capture mode (good for gaming) |
| input:mouseRedraw | | yes | Mouse movements trigger redraws (ignores FPS minimum) |
| input:autoCapture | | no | Try to keep the mouse captured when needed |
| input:captureOnly | | no | Only enable input via SPICE if in capture mode |
|----------------------------------------------------------------------------------------------------------------------------------------------|
|------------------------------------------------------------------------------------------------------------------|
| Long | Short | Value | Description |
@@ -141,21 +172,26 @@ Command line arguments will override any options loaded from the config files.
| 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:nvGainMax | | 1 | The maximum night vision gain |
| egl:nvGain | | 0 | The initial night vision gain at startup |
|--------------------------------------------------------------------------|
|--------------------------------------------------------------------------------------------------------------|
| 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 | | yes | Enable vsync |
| 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 |
|------------------------------------------------------------------------------------|

View File

@@ -1,43 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(clipboards LANGUAGES C)
set(CLIPBOARD_H "${CMAKE_BINARY_DIR}/include/dynamic/clipboards.h")
set(CLIPBOARD_C "${CMAKE_BINARY_DIR}/src/clipboards.c")
file(WRITE ${CLIPBOARD_H} "#include \"interface/clipboard.h\"\n\n")
file(APPEND ${CLIPBOARD_H} "extern LG_Clipboard * LG_Clipboards[];\n\n")
file(WRITE ${CLIPBOARD_C} "#include \"interface/clipboard.h\"\n\n")
file(APPEND ${CLIPBOARD_C} "#include <stddef.h>\n\n")
set(CLIPBOARDS "_")
set(CLIPBOARDS_LINK "_")
function(add_clipboard name)
set(CLIPBOARDS "${CLIPBOARDS};${name}" PARENT_SCOPE)
set(CLIPBOARDS_LINK "${CLIPBOARDS_LINK};clipboard_${name}" PARENT_SCOPE)
add_subdirectory(${name})
endfunction()
# Add/remove clipboards here!
if (ENABLE_CB_X11)
add_clipboard(X11)
endif()
list(REMOVE_AT CLIPBOARDS 0)
list(REMOVE_AT CLIPBOARDS_LINK 0)
list(LENGTH CLIPBOARDS CLIPBOARD_COUNT)
file(APPEND ${CLIPBOARD_H} "#define LG_CLIPBOARD_COUNT ${CLIPBOARD_COUNT}\n")
foreach(clipboard ${CLIPBOARDS})
file(APPEND ${CLIPBOARD_C} "extern LG_Clipboard LGC_${clipboard};\n")
endforeach()
file(APPEND ${CLIPBOARD_C} "\nconst LG_Clipboard * LG_Clipboards[] =\n{\n")
foreach(clipboard ${CLIPBOARDS})
file(APPEND ${CLIPBOARD_C} " &LGC_${clipboard},\n")
endforeach()
file(APPEND ${CLIPBOARD_C} " NULL\n};\n\n")
add_library(clipboards STATIC ${CLIPBOARD_C})
target_link_libraries(clipboards ${CLIPBOARDS_LINK})

View File

@@ -1,26 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(clipboard_X11 LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(CLIPBOARD_PKGCONFIG REQUIRED
x11
xfixes
)
add_library(clipboard_X11 STATIC
src/x11.c
)
target_link_libraries(clipboard_X11
${CLIPBOARD_PKGCONFIG_LIBRARIES}
lg_common
)
target_include_directories(clipboard_X11
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src
${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS}
)

View File

@@ -1,359 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/clipboard.h"
#include "common/debug.h"
#include <X11/extensions/Xfixes.h>
struct state
{
Display * display;
Window window;
Atom aSelection;
Atom aCurSelection;
Atom aTargets;
Atom aSelData;
Atom aIncr;
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
LG_ClipboardReleaseFn releaseFn;
LG_ClipboardRequestFn requestFn;
LG_ClipboardNotifyFn notifyFn;
LG_ClipboardDataFn dataFn;
LG_ClipboardData type;
// XFixes vars
int eventBase;
int errorBase;
};
static struct state * this = NULL;
static const char * atomTypes[] =
{
"UTF8_STRING",
"image/png",
"image/bmp",
"image/tiff",
"image/jpeg"
};
static const char * x11_cb_getName()
{
return "X11";
}
static bool x11_cb_init(
SDL_SysWMinfo * wminfo,
LG_ClipboardReleaseFn releaseFn,
LG_ClipboardNotifyFn notifyFn,
LG_ClipboardDataFn dataFn)
{
// final sanity check
if (wminfo->subsystem != SDL_SYSWM_X11)
{
DEBUG_ERROR("wrong subsystem");
return false;
}
this = (struct state *)malloc(sizeof(struct state));
memset(this, 0, sizeof(struct state));
this->display = wminfo->info.x11.display;
this->window = wminfo->info.x11.window;
this->aSelection = XInternAtom(this->display, "CLIPBOARD", False);
this->aTargets = XInternAtom(this->display, "TARGETS" , False);
this->aSelData = XInternAtom(this->display, "SEL_DATA" , False);
this->aIncr = XInternAtom(this->display, "INCR" , False);
this->aCurSelection = BadValue;
this->releaseFn = releaseFn;
this->notifyFn = notifyFn;
this->dataFn = dataFn;
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
{
this->aTypes[i] = XInternAtom(this->display, atomTypes[i], False);
if (this->aTypes[i] == BadAlloc || this->aTypes[i] == BadValue)
{
DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]);
free(this);
this = NULL;
return false;
}
}
// we need the raw X events
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
// use xfixes to get clipboard change notifications
if (!XFixesQueryExtension(this->display, &this->eventBase, &this->errorBase))
{
DEBUG_ERROR("failed to initialize xfixes");
free(this);
this = NULL;
return false;
}
XFixesSelectSelectionInput(this->display, this->window, XA_PRIMARY , XFixesSetSelectionOwnerNotifyMask);
XFixesSelectSelectionInput(this->display, this->window, this->aSelection, XFixesSetSelectionOwnerNotifyMask);
return true;
}
static void x11_cb_free()
{
free(this);
this = NULL;
}
static void x11_cb_reply_fn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size)
{
XEvent *s = (XEvent *)opaque;
XChangeProperty(
this->display ,
s->xselection.requestor,
s->xselection.property ,
s->xselection.target ,
8,
PropModeReplace,
data,
size);
XSendEvent(this->display, s->xselection.requestor, 0, 0, s);
XFlush(this->display);
free(s);
}
static void x11_cb_wmevent(SDL_SysWMmsg * msg)
{
XEvent e = msg->msg.x11.event;
if (e.type == SelectionRequest)
{
XEvent * s = (XEvent *)malloc(sizeof(XEvent));
s->xselection.type = SelectionNotify;
s->xselection.requestor = e.xselectionrequest.requestor;
s->xselection.selection = e.xselectionrequest.selection;
s->xselection.target = e.xselectionrequest.target;
s->xselection.property = e.xselectionrequest.property;
s->xselection.time = e.xselectionrequest.time;
if (!this->requestFn)
{
s->xselection.property = None;
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
XFlush(this->display);
free(s);
return;
}
// target list requested
if (e.xselectionrequest.target == this->aTargets)
{
Atom targets[2];
targets[0] = this->aTargets;
targets[1] = this->aTypes[this->type];
XChangeProperty(
e.xselectionrequest.display,
e.xselectionrequest.requestor,
e.xselectionrequest.property,
XA_ATOM,
32,
PropModeReplace,
(unsigned char*)targets,
sizeof(targets) / sizeof(Atom));
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
XFlush(this->display);
free(s);
return;
}
// look to see if we can satisfy the data type
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
if (this->aTypes[i] == e.xselectionrequest.target && this->type == i)
{
// request the data
this->requestFn(x11_cb_reply_fn, s);
return;
}
// report no data
s->xselection.property = None;
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
XFlush(this->display);
}
if (e.type == SelectionClear && (
e.xselectionclear.selection == XA_PRIMARY ||
e.xselectionclear.selection == this->aSelection)
)
{
this->aCurSelection = BadValue;
this->releaseFn();
return;
}
// if someone selected data
if (e.type == this->eventBase + XFixesSelectionNotify)
{
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&e;
// check if the selection is valid and it isn't ourself
if (
(sne->selection != XA_PRIMARY && sne->selection != this->aSelection) ||
sne->owner == this->window ||
sne->owner == 0
)
{
return;
}
// remember which selection we are working with
this->aCurSelection = sne->selection;
XConvertSelection(
this->display,
sne->selection,
this->aTargets,
this->aTargets,
this->window,
CurrentTime);
return;
}
if (e.type == SelectionNotify)
{
if (e.xselection.property == None)
return;
Atom type;
int format;
unsigned long itemCount, after;
unsigned char *data;
XGetWindowProperty(
this->display,
this->window,
e.xselection.property,
0, ~0L, // start and length
True , // delete the property
AnyPropertyType,
&type,
&format,
&itemCount,
&after,
&data);
// the target list
if (e.xselection.property == this->aTargets)
{
// the format is 32-bit and we must have data
// this is technically incorrect however as it's
// an array of padded 64-bit values
if (!data || format != 32)
{
if (data)
XFree(data);
return;
}
// see if we support any of the targets listed
const uint64_t * targets = (const uint64_t *)data;
for(unsigned long i = 0; i < itemCount; ++i)
{
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
if (this->aTypes[n] == targets[i])
{
// we have a match, so send the notification
this->notifyFn(n);
XFree(data);
return;
}
}
// no matches
this->notifyFn(LG_CLIPBOARD_DATA_NONE);
XFree(data);
return;
}
if (format == this->aIncr)
{
DEBUG_WARN("fixme: large paste buffers are not yet supported");
XFree(data);
return;
}
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
if (this->aTypes[i] == type)
{
this->dataFn(i, data, itemCount);
XFree(data);
return;
}
DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(this->display, type));
XFree(data);
return;
}
}
static void x11_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type)
{
this->requestFn = requestFn;
this->type = type;
XSetSelectionOwner(this->display, XA_PRIMARY , this->window, CurrentTime);
XSetSelectionOwner(this->display, this->aSelection, this->window, CurrentTime);
XFlush(this->display);
}
static void x11_cb_release()
{
this->requestFn = NULL;
XSetSelectionOwner(this->display, XA_PRIMARY , None, CurrentTime);
XSetSelectionOwner(this->display, this->aSelection, None, CurrentTime);
XFlush(this->display);
}
static void x11_cb_request(LG_ClipboardData type)
{
if (this->aCurSelection == BadValue)
return;
XConvertSelection(
this->display,
this->aCurSelection,
this->aTypes[type],
this->aSelData,
this->window,
CurrentTime);
}
const LG_Clipboard LGC_X11 =
{
.getName = x11_cb_getName,
.init = x11_cb_init,
.free = x11_cb_free,
.wmevent = x11_cb_wmevent,
.notice = x11_cb_notice,
.release = x11_cb_release,
.request = x11_cb_request
};

View File

@@ -1,25 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(decoders LANGUAGES C)
#find_package(PkgConfig)
#pkg_check_modules(DECODERS_PKGCONFIG REQUIRED
#)
add_library(decoders STATIC
src/null.c
src/yuv420.c
)
target_link_libraries(decoders
lg_common
${DECODERS_PKGCONFIG_LIBRARIES}
)
target_include_directories(decoders
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src
${DECODERS_PKGCONFIG_INCLUDE_DIRS}
)

View File

@@ -1,981 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "lg-decoder.h"
#include "debug.h"
#include "memcpySSE.h"
#include "parsers/nal.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <SDL2/SDL_syswm.h>
#include <va/va_glx.h>
#define SURFACE_NUM 3
struct Inst
{
LG_RendererFormat format;
SDL_Window * window;
VADisplay vaDisplay;
int vaMajorVer, vaMinorVer;
VASurfaceID vaSurfaceID[SURFACE_NUM];
VAConfigID vaConfigID;
VAContextID vaContextID;
int lastSID;
int currentSID;
VAPictureH264 curPic;
VAPictureH264 oldPic;
int frameNum;
int fieldCount;
VABufferID picBufferID[SURFACE_NUM];
VABufferID matBufferID[SURFACE_NUM];
VABufferID sliBufferID[SURFACE_NUM];
VABufferID datBufferID[SURFACE_NUM];
bool t2First;
int sliceType;
NAL nal;
};
static const unsigned char MatrixBufferH264[] = {
//ScalingList4x4[6][16]
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
//ScalingList8x8[2][64]
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
static bool lgd_h264_create (void ** opaque);
static void lgd_h264_destroy (void * opaque);
static bool lgd_h264_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
static void lgd_h264_deinitialize (void * opaque);
static LG_OutFormat lgd_h264_get_out_format (void * opaque);
static unsigned int lgd_h264_get_frame_pitch (void * opaque);
static unsigned int lgd_h264_get_frame_stride(void * opaque);
static bool lgd_h264_decode (void * opaque, const uint8_t * src, size_t srcSize);
static bool lgd_h264_get_buffer (void * opaque, uint8_t * dst, size_t dstSize);
static bool lgd_h264_init_gl_texture (void * opaque, GLenum target, GLuint texture, void ** ref);
static void lgd_h264_free_gl_texture (void * opaque, void * ref);
static bool lgd_h264_update_gl_texture(void * opaque, void * ref);
#define check_surface(x, y, z) _check_surface(__LINE__, x, y, z)
static bool _check_surface(const unsigned int line, struct Inst * this, unsigned int sid, VASurfaceStatus *out)
{
VASurfaceStatus surfStatus;
VAStatus status = vaQuerySurfaceStatus(
this->vaDisplay,
this->vaSurfaceID[sid],
&surfStatus
);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaQuerySurfaceStatus: %s", vaErrorStr(status));
return false;
}
#if 0
DEBUG_INFO("L%d: surface %u status: %d", line, sid, surfStatus);
#endif
if (out)
*out = surfStatus;
return true;
}
static bool lgd_h264_create(void ** opaque)
{
// 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;
this->vaSurfaceID[0] = VA_INVALID_ID;
this->vaConfigID = VA_INVALID_ID;
this->vaContextID = VA_INVALID_ID;
for(int i = 0; i < SURFACE_NUM; ++i)
this->picBufferID[i] =
this->matBufferID[i] =
this->sliBufferID[i] =
this->datBufferID[i] = VA_INVALID_ID;
if (!nal_initialize(&this->nal))
{
DEBUG_INFO("Failed to initialize NAL parser");
free(this);
return false;
}
lgd_h264_deinitialize(this);
return true;
}
static void lgd_h264_destroy(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
nal_deinitialize(this->nal);
lgd_h264_deinitialize(this);
free(this);
}
static bool lgd_h264_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
{
struct Inst * this = (struct Inst *)opaque;
lgd_h264_deinitialize(this);
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->window = window;
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version);
if (!SDL_GetWindowWMInfo(window, &wminfo))
{
DEBUG_ERROR("Failed to get SDL window WM Info");
return false;
}
switch(wminfo.subsystem)
{
case SDL_SYSWM_X11:
this->vaDisplay = vaGetDisplayGLX(wminfo.info.x11.display);
break;
default:
DEBUG_ERROR("Unsupported window subsystem");
return false;
}
VAStatus status;
status = vaInitialize(this->vaDisplay, &this->vaMajorVer, &this->vaMinorVer);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaInitialize Failed");
return false;
}
DEBUG_INFO("Vendor: %s", vaQueryVendorString(this->vaDisplay));
VAEntrypoint entryPoints[5];
int entryPointCount;
status = vaQueryConfigEntrypoints(
this->vaDisplay,
VAProfileH264High,
entryPoints,
&entryPointCount
);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaQueryConfigEntrypoints Failed");
return false;
}
int ep;
for(ep = 0; ep < entryPointCount; ++ep)
if (entryPoints[ep] == VAEntrypointVLD)
break;
if (ep == entryPointCount)
{
DEBUG_ERROR("Failed to find VAEntrypointVLD index");
return false;
}
VAConfigAttrib attrib;
attrib.type = VAConfigAttribRTFormat;
vaGetConfigAttributes(
this->vaDisplay,
VAProfileH264High,
VAEntrypointVLD,
&attrib,
1);
if (!(attrib.value & VA_RT_FORMAT_YUV420))
{
DEBUG_ERROR("Failed to find desired YUV420 RT format");
return false;
}
status = vaCreateConfig(
this->vaDisplay,
VAProfileH264High,
VAEntrypointVLD,
&attrib,
1,
&this->vaConfigID);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaCreateConfig");
return false;
}
status = vaCreateSurfaces(
this->vaDisplay,
VA_RT_FORMAT_YUV420,
this->format.width,
this->format.height,
this->vaSurfaceID,
SURFACE_NUM,
NULL,
0
);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaCreateSurfaces");
return false;
}
for(int i = 0; i < SURFACE_NUM; ++i)
if (!check_surface(this, i, NULL))
return false;
status = vaCreateContext(
this->vaDisplay,
this->vaConfigID,
this->format.width,
this->format.height,
VA_PROGRESSIVE,
this->vaSurfaceID,
SURFACE_NUM,
&this->vaContextID
);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaCreateContext");
return false;
}
this->currentSID = 0;
this->sliceType = 2;
this->t2First = true;
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[0]);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaBeginPicture");
return false;
}
return true;
}
static void lgd_h264_deinitialize(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
for(int i = 0; i < SURFACE_NUM; ++i)
{
if (this->picBufferID[i] != VA_INVALID_ID)
vaDestroyBuffer(this->vaDisplay, this->picBufferID[i]);
if (this->matBufferID[i] != VA_INVALID_ID)
vaDestroyBuffer(this->vaDisplay, this->matBufferID[i]);
if (this->sliBufferID[i] != VA_INVALID_ID)
vaDestroyBuffer(this->vaDisplay, this->sliBufferID[i]);
if (this->datBufferID[i] != VA_INVALID_ID)
vaDestroyBuffer(this->vaDisplay, this->datBufferID[i]);
this->picBufferID[i] =
this->matBufferID[i] =
this->sliBufferID[i] =
this->datBufferID[i] = VA_INVALID_ID;
}
if (this->vaSurfaceID[0] != VA_INVALID_ID)
vaDestroySurfaces(this->vaDisplay, this->vaSurfaceID, SURFACE_NUM);
this->vaSurfaceID[0] = VA_INVALID_ID;
if (this->vaContextID != VA_INVALID_ID)
vaDestroyContext(this->vaDisplay, this->vaContextID);
this->vaContextID = VA_INVALID_ID;
if (this->vaConfigID != VA_INVALID_ID)
vaDestroyConfig(this->vaDisplay, this->vaConfigID);
this->vaConfigID = VA_INVALID_ID;
if (this->vaDisplay)
vaTerminate(this->vaDisplay);
this->vaDisplay = NULL;
}
static LG_OutFormat lgd_h264_get_out_format(void * opaque)
{
return LG_OUTPUT_YUV420;
}
static unsigned int lgd_h264_get_frame_pitch(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
return this->format.width * 4;
}
static unsigned int lgd_h264_get_frame_stride(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
return this->format.width;
}
static bool get_buffer(struct Inst * this, const VABufferType type, const unsigned int size, VABufferID * buf_id)
{
if (*buf_id != VA_INVALID_ID)
return true;
VAStatus status = vaCreateBuffer(this->vaDisplay, this->vaContextID, type, size, 1, NULL, buf_id);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("Failed to create buffer: %s", vaErrorStr(status));
return false;
}
if (!check_surface(this, this->currentSID, NULL))
return false;
return true;
}
static bool setup_pic_buffer(struct Inst * this, const NAL_SLICE * slice)
{
VAStatus status;
VABufferID * picBufferID = &this->picBufferID[this->currentSID];
if (!get_buffer(this, VAPictureParameterBufferType, sizeof(VAPictureParameterBufferH264), picBufferID))
{
DEBUG_ERROR("get picBuffer failed");
return false;
}
VAPictureParameterBufferH264 *p;
status = vaMapBuffer(this->vaDisplay, *picBufferID, (void **)&p);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
return false;
}
const NAL_SPS * sps;
if (!nal_get_sps(this->nal, &sps))
{
DEBUG_ERROR("nal_get_sps");
return false;
}
const NAL_PPS * pps;
if (!nal_get_pps(this->nal, &pps))
{
DEBUG_ERROR("nal_get_pps");
return false;
}
memset(p, 0, sizeof(VAPictureParameterBufferH264));
p->picture_width_in_mbs_minus1 = sps->pic_width_in_mbs_minus1;
p->picture_height_in_mbs_minus1 = sps->pic_height_in_map_units_minus1;
p->bit_depth_luma_minus8 = sps->bit_depth_luma_minus8;
p->bit_depth_chroma_minus8 = sps->bit_depth_chroma_minus8;
p->num_ref_frames = sps->num_ref_frames;
p->seq_fields.value = 0;
p->seq_fields.bits.chroma_format_idc = sps->chroma_format_idc;
p->seq_fields.bits.residual_colour_transform_flag = sps->gaps_in_frame_num_value_allowed_flag;
p->seq_fields.bits.frame_mbs_only_flag = sps->frame_mbs_only_flag;
p->seq_fields.bits.mb_adaptive_frame_field_flag = sps->mb_adaptive_frame_field_flag;
p->seq_fields.bits.direct_8x8_inference_flag = sps->direct_8x8_inference_flag;
p->seq_fields.bits.MinLumaBiPredSize8x8 = sps->level_idc >= 31;
p->seq_fields.bits.log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4;
p->seq_fields.bits.pic_order_cnt_type = sps->pic_order_cnt_type;
p->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = sps->log2_max_pic_order_cnt_lsb_minus4;
p->seq_fields.bits.delta_pic_order_always_zero_flag = sps->delta_pic_order_always_zero_flag;
#if 0
// these are deprecated, FMO is not supported
p->num_slice_groups_minus1 = pps->num_slice_groups_minus1;
p->slice_group_map_type = pps->slice_group_map_type;
p->slice_group_change_rate_minus1 = pps->slice_group_change_rate_minus1;
#endif
p->pic_init_qp_minus26 = pps->pic_init_qp_minus26;
p->pic_init_qs_minus26 = pps->pic_init_qs_minus26;
p->chroma_qp_index_offset = pps->chroma_qp_index_offset;
p->second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset;
p->pic_fields.value = 0;
p->pic_fields.bits.entropy_coding_mode_flag = pps->entropy_coding_mode_flag;
p->pic_fields.bits.weighted_pred_flag = pps->weighted_pred_flag;
p->pic_fields.bits.weighted_bipred_idc = pps->weighted_bipred_idc;
p->pic_fields.bits.transform_8x8_mode_flag = pps->transform_8x8_mode_flag;
p->pic_fields.bits.field_pic_flag = slice->field_pic_flag;
p->pic_fields.bits.constrained_intra_pred_flag = pps->constrained_intra_pred_flag;
p->pic_fields.bits.pic_order_present_flag = pps->pic_order_present_flag;
p->pic_fields.bits.deblocking_filter_control_present_flag = pps->deblocking_filter_control_present_flag;
p->pic_fields.bits.redundant_pic_cnt_present_flag = pps->redundant_pic_cnt_present_flag;
p->pic_fields.bits.reference_pic_flag = slice->nal_ref_idc != 0;
p->frame_num = slice->frame_num;
for(int i = 0; i < 16; ++i)
{
p->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
p->ReferenceFrames[i].picture_id = 0xFFFFFFFF;
}
this->curPic.picture_id = this->vaSurfaceID[this->currentSID];
this->curPic.frame_idx = p->frame_num;
this->curPic.flags = 0;
this->curPic.BottomFieldOrderCnt = this->fieldCount;
this->curPic.TopFieldOrderCnt = this->fieldCount;
memcpy(&p->CurrPic, &this->curPic, sizeof(VAPictureH264));
if (this->sliceType != 2)
{
memcpy(&p->ReferenceFrames[0], &this->oldPic, sizeof(VAPictureH264));
p->ReferenceFrames[0].flags = 0;
}
status = vaUnmapBuffer(this->vaDisplay, *picBufferID);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
return false;
}
return true;
}
static bool setup_mat_buffer(struct Inst * this)
{
VAStatus status;
VABufferID * matBufferID = &this->matBufferID[this->currentSID];
if (!get_buffer(this, VAIQMatrixBufferType, sizeof(VAIQMatrixBufferH264), matBufferID))
{
DEBUG_ERROR("get matBuffer failed");
return false;
}
VAIQMatrixBufferH264 * m;
status = vaMapBuffer(this->vaDisplay, *matBufferID, (void **)&m);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
return false;
}
memcpy(m, MatrixBufferH264, sizeof(MatrixBufferH264));
status = vaUnmapBuffer(this->vaDisplay, *matBufferID);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
return false;
}
return true;
}
static void fill_pred_weight_table(
NAL_PW_TABLE_L * list,
uint32_t active,
uint32_t luma_log2_weight_denom,
uint8_t luma_weight_flag,
short luma_weight[32],
short luma_offset[32],
uint32_t chroma_log2_weight_denom,
uint8_t chroma_weight_flag,
short chroma_weight[32][2],
short chroma_offset[32][2]
)
{
assert(active < 32);
for(uint32_t i = 0; i <= active; ++i)
{
NAL_PW_TABLE_L * l = &list[i];
if (luma_weight_flag)
{
luma_weight[i] = l->luma_weight;
luma_offset[i] = l->luma_offset;
}
else
{
luma_weight[i] = 1 << luma_log2_weight_denom;
luma_weight[i] = 0;
}
if (chroma_weight_flag)
{
chroma_weight[i][0] = l->chroma_weight[0];
chroma_offset[i][0] = l->chroma_offset[0];
chroma_weight[i][1] = l->chroma_weight[1];
chroma_offset[i][1] = l->chroma_offset[1];
}
else
{
chroma_weight[i][0] = 1 << chroma_log2_weight_denom;
chroma_weight[i][0] = 0;
chroma_weight[i][1] = 1 << chroma_log2_weight_denom;
chroma_weight[i][1] = 0;
}
}
}
static bool setup_sli_buffer(struct Inst * this, size_t srcSize, const NAL_SLICE * slice, const size_t seek)
{
VAStatus status;
VABufferID * sliBufferID = &this->sliBufferID[this->currentSID];
if (!get_buffer(this, VASliceParameterBufferType, sizeof(VASliceParameterBufferH264), sliBufferID))
{
DEBUG_ERROR("get sliBuffer failed");
return false;
}
VASliceParameterBufferH264 * s;
status = vaMapBuffer(this->vaDisplay, *sliBufferID, (void **)&s);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
return false;
}
memset(s, 0, sizeof(VASliceParameterBufferH264));
s->slice_data_size = srcSize;
s->slice_data_bit_offset = seek << 3;
s->slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
s->first_mb_in_slice = slice->first_mb_in_slice;
s->slice_type = slice->slice_type;
s->direct_spatial_mv_pred_flag = slice->direct_spatial_mv_pred_flag;
s->num_ref_idx_l0_active_minus1 = slice->num_ref_idx_l0_active_minus1;
s->num_ref_idx_l1_active_minus1 = slice->num_ref_idx_l1_active_minus1;
s->cabac_init_idc = slice->cabac_init_idc;
s->slice_qp_delta = slice->slice_qp_delta;
s->disable_deblocking_filter_idc = slice->disable_deblocking_filter_idc;
s->slice_alpha_c0_offset_div2 = slice->slice_alpha_c0_offset_div2;
s->slice_beta_offset_div2 = slice->slice_beta_offset_div2;
s->luma_log2_weight_denom = slice->pred_weight_table.luma_log2_weight_denom;
s->chroma_log2_weight_denom = slice->pred_weight_table.chroma_log2_weight_denom;
s->luma_weight_l0_flag = slice->pred_weight_table.luma_weight_flag [0];
s->chroma_weight_l0_flag = slice->pred_weight_table.chroma_weight_flag[0];
s->luma_weight_l1_flag = slice->pred_weight_table.luma_weight_flag [1];
s->chroma_weight_l1_flag = slice->pred_weight_table.chroma_weight_flag[1];
//RefPicList0/1
fill_pred_weight_table(
slice->pred_weight_table.l0,
s->num_ref_idx_l0_active_minus1,
s->luma_log2_weight_denom,
s->luma_weight_l0_flag,
s->luma_weight_l0,
s->luma_offset_l0,
s->chroma_log2_weight_denom,
s->chroma_weight_l0_flag,
s->chroma_weight_l0,
s->chroma_weight_l0
);
fill_pred_weight_table(
slice->pred_weight_table.l1,
s->num_ref_idx_l1_active_minus1,
s->luma_log2_weight_denom,
s->luma_weight_l1_flag,
s->luma_weight_l1,
s->luma_offset_l1,
s->chroma_log2_weight_denom,
s->chroma_weight_l1_flag,
s->chroma_weight_l1,
s->chroma_weight_l1
);
#if 0
if (this->sliceType == 2)
{
set_slice_parameter_buffer_t2(s, this->t2First);
this->t2First = false;
}
else
{
set_slice_parameter_buffer(s);
memcpy(&s->RefPicList0[0], &this->oldPic, sizeof(VAPictureH264));
s->RefPicList0[0].flags = 0;
}
#endif
status = vaUnmapBuffer(this->vaDisplay, *sliBufferID);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
return false;
}
return true;
}
static bool setup_dat_buffer(struct Inst * this, const uint8_t * src, size_t srcSize)
{
VAStatus status;
VABufferID * datBufferID = &this->datBufferID[this->currentSID];
if (!get_buffer(this, VASliceDataBufferType, srcSize, datBufferID))
{
DEBUG_ERROR("get datBuffer failed");
return false;
}
uint8_t * d;
status = vaMapBuffer(this->vaDisplay, *datBufferID, (void **)&d);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
return false;
}
memcpySSE(d, src, srcSize);
status = vaUnmapBuffer(this->vaDisplay, *datBufferID);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
return false;
}
return true;
}
static bool lgd_h264_decode(void * opaque, const uint8_t * src, size_t srcSize)
{
VAStatus status;
struct Inst * this = (struct Inst *)opaque;
size_t seek;
if (!nal_parse(this->nal, src, srcSize, &seek))
{
DEBUG_WARN("nal_parse, perhaps mid stream");
return true;
}
const NAL_SLICE * slice;
if (!nal_get_slice(this->nal, &slice))
{
DEBUG_WARN("nal_get_slice failed");
return true;
}
assert(seek < srcSize);
this->sliceType = slice->slice_type;
// don't start until we have an I-FRAME
if (this->frameNum == 0 && this->sliceType != NAL_SLICE_TYPE_I)
return true;
{
if (!setup_pic_buffer(this, slice)) return false;
if (!setup_mat_buffer(this)) return false;
VABufferID bufferIDs[] =
{
this->picBufferID[this->currentSID],
this->matBufferID[this->currentSID]
};
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
return false;
}
// intel broke the ABI here, see:
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
// the buffers are released by vaRenderPicture in old versions
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
{
this->picBufferID[this->currentSID] =
this->matBufferID[this->currentSID] = VA_INVALID_ID;
}
}
{
if (!setup_sli_buffer(this, srcSize, slice, seek)) return false;
if (!setup_dat_buffer(this, src, srcSize )) return false;
VABufferID bufferIDs[] =
{
this->sliBufferID[this->currentSID],
this->datBufferID[this->currentSID]
};
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
return false;
}
// intel broke the ABI here, see:
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
// the buffers are released by vaRenderPicture in old versions
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
{
this->sliBufferID[this->currentSID] =
this->datBufferID[this->currentSID] = VA_INVALID_ID;
}
}
status = vaEndPicture(this->vaDisplay, this->vaContextID);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaEndPicture: %s", vaErrorStr(status));
return false;
}
// advance to the next surface and save the old picture info
this->lastSID = this->currentSID;
if (++this->currentSID == SURFACE_NUM)
this->currentSID = 0;
this->frameNum += 1;
this->fieldCount += 2;
memcpy(&this->oldPic, &this->curPic, sizeof(VAPictureH264));
// prepare the next surface
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[this->currentSID]);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaBeginPicture: %s", vaErrorStr(status));
return false;
}
return true;
}
static bool lgd_h264_get_buffer(void * opaque, uint8_t * dst, size_t dstSize)
{
struct Inst * this = (struct Inst *)opaque;
VAStatus status;
// don't return anything until we have some data
if (this->frameNum == 0)
return true;
// ensure the surface is ready
status = vaSyncSurface(this->vaDisplay, this->vaSurfaceID[this->lastSID]);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaSyncSurface: %s", vaErrorStr(status));
return false;
}
#if 0
// this doesn't work on my system, seems the vdpau va driver is bugged
VASurfaceStatus surfStatus;
if (!check_surface(this, this->lastSID, &surfStatus))
return false;
if (surfStatus != VASurfaceReady)
{
DEBUG_ERROR("vaSyncSurface didn't block, the surface is not ready!");
return false;
}
#endif
// get the decoded data
VAImage decoded =
{
.image_id = VA_INVALID_ID,
.buf = VA_INVALID_ID
};
status = vaDeriveImage(this->vaDisplay, this->vaSurfaceID[this->lastSID], &decoded);
if (status == VA_STATUS_ERROR_OPERATION_FAILED)
{
VAImageFormat format =
{
.fourcc = VA_FOURCC_NV12,
.byte_order = VA_LSB_FIRST,
.bits_per_pixel = 12
};
status = vaCreateImage(
this->vaDisplay,
&format,
this->format.width,
this->format.height,
&decoded
);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaCreateImage: %s", vaErrorStr(status));
return false;
}
status = vaPutImage(
this->vaDisplay,
this->vaSurfaceID[this->lastSID],
decoded.image_id,
0 , 0 ,
this->format.width, this->format.height,
0 , 0 ,
this->format.width, this->format.height
);
if (status != VA_STATUS_SUCCESS)
{
vaDestroyImage(this->vaDisplay, decoded.image_id);
DEBUG_ERROR("vaPutImage: %s", vaErrorStr(status));
return false;
}
}
else
{
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaDeriveImage: %s", vaErrorStr(status));
return false;
}
}
uint8_t * d;
status = vaMapBuffer(this->vaDisplay, decoded.buf, (void **)&d);
if (status != VA_STATUS_SUCCESS)
{
vaDestroyImage(this->vaDisplay, decoded.image_id);
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
return false;
}
memcpySSE(dst, d, decoded.data_size);
status = vaUnmapBuffer(this->vaDisplay, decoded.buf);
if (status != VA_STATUS_SUCCESS)
{
vaDestroyImage(this->vaDisplay, decoded.image_id);
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
return false;
}
status = vaDestroyImage(this->vaDisplay, decoded.image_id);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaDestroyImage: %s", vaErrorStr(status));
return false;
}
return true;
}
static bool lgd_h264_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
{
struct Inst * this = (struct Inst *)opaque;
VAStatus status;
status = vaCreateSurfaceGLX(this->vaDisplay, target, texture, ref);
if (status != VA_STATUS_SUCCESS)
{
*ref = NULL;
DEBUG_ERROR("vaCreateSurfaceGLX: %s", vaErrorStr(status));
return false;
}
return true;
}
static void lgd_h264_free_gl_texture(void * opaque, void * ref)
{
struct Inst * this = (struct Inst *)opaque;
VAStatus status;
status = vaDestroySurfaceGLX(this->vaDisplay, ref);
if (status != VA_STATUS_SUCCESS)
DEBUG_ERROR("vaDestroySurfaceGLX: %s", vaErrorStr(status));
}
static bool lgd_h264_update_gl_texture(void * opaque, void * ref)
{
struct Inst * this = (struct Inst *)opaque;
VAStatus status;
// don't return anything until we have some data
if (this->frameNum == 0)
return true;
status = vaCopySurfaceGLX(
this->vaDisplay,
ref,
this->vaSurfaceID[this->lastSID],
0
);
if (status != VA_STATUS_SUCCESS)
{
DEBUG_ERROR("vaCopySurfaceGLX: %s", vaErrorStr(status));
return false;
}
return true;
}
const LG_Decoder LGD_H264 =
{
.name = "H.264",
.create = lgd_h264_create,
.destroy = lgd_h264_destroy,
.initialize = lgd_h264_initialize,
.deinitialize = lgd_h264_deinitialize,
.get_out_format = lgd_h264_get_out_format,
.get_frame_pitch = lgd_h264_get_frame_pitch,
.get_frame_stride = lgd_h264_get_frame_stride,
.decode = lgd_h264_decode,
.get_buffer = lgd_h264_get_buffer,
.has_gl = true,
.init_gl_texture = lgd_h264_init_gl_texture,
.free_gl_texture = lgd_h264_free_gl_texture,
.update_gl_texture = lgd_h264_update_gl_texture
};

View File

@@ -1,130 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/decoder.h"
#include "common/debug.h"
#include "common/memcpySSE.h"
#include <stdlib.h>
#include <string.h>
struct Inst
{
LG_RendererFormat format;
const uint8_t * src;
};
static bool lgd_null_create (void ** opaque);
static void lgd_null_destroy (void * opaque);
static bool lgd_null_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
static void lgd_null_deinitialize (void * opaque);
static LG_OutFormat lgd_null_get_out_format (void * opaque);
static unsigned int lgd_null_get_frame_pitch (void * opaque);
static unsigned int lgd_null_get_frame_stride(void * opaque);
static bool lgd_null_decode (void * opaque, const uint8_t * src, size_t srcSize);
static const uint8_t * lgd_null_get_buffer (void * opaque);
static bool lgd_null_create(void ** opaque)
{
// 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));
return true;
}
static void lgd_null_destroy(void * opaque)
{
free(opaque);
}
static bool lgd_null_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
{
struct Inst * this = (struct Inst *)opaque;
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
return true;
}
static void lgd_null_deinitialize(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
memset(this, 0, sizeof(struct Inst));
}
static LG_OutFormat lgd_null_get_out_format(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
switch(this->format.type)
{
case FRAME_TYPE_BGRA : return LG_OUTPUT_BGRA;
case FRAME_TYPE_RGBA : return LG_OUTPUT_RGBA;
case FRAME_TYPE_RGBA10: return LG_OUTPUT_RGBA10;
default:
DEBUG_ERROR("Unknown frame type");
return LG_OUTPUT_INVALID;
}
}
static unsigned int lgd_null_get_frame_pitch(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
return this->format.pitch;
}
static unsigned int lgd_null_get_frame_stride(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
return this->format.stride;
}
static bool lgd_null_decode(void * opaque, const uint8_t * src, size_t srcSize)
{
struct Inst * this = (struct Inst *)opaque;
this->src = src;
return true;
}
static const uint8_t * lgd_null_get_buffer(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
if (!this->src)
return NULL;
return this->src;
}
const LG_Decoder LGD_NULL =
{
.name = "NULL",
.create = lgd_null_create,
.destroy = lgd_null_destroy,
.initialize = lgd_null_initialize,
.deinitialize = lgd_null_deinitialize,
.get_out_format = lgd_null_get_out_format,
.get_frame_pitch = lgd_null_get_frame_pitch,
.get_frame_stride = lgd_null_get_frame_stride,
.decode = lgd_null_decode,
.get_buffer = lgd_null_get_buffer
};

View File

@@ -1,169 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/decoder.h"
#include "common/debug.h"
#include "common/memcpySSE.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <GL/gl.h>
struct Pixel
{
uint8_t b, g, r, a;
};
struct Inst
{
LG_RendererFormat format;
struct Pixel * pixels;
unsigned int yBytes;
};
static bool lgd_yuv420_create (void ** opaque);
static void lgd_yuv420_destroy (void * opaque);
static bool lgd_yuv420_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
static void lgd_yuv420_deinitialize (void * opaque);
static LG_OutFormat lgd_yuv420_get_out_format (void * opaque);
static unsigned int lgd_yuv420_get_frame_pitch (void * opaque);
static unsigned int lgd_yuv420_get_frame_stride(void * opaque);
static bool lgd_yuv420_decode (void * opaque, const uint8_t * src, size_t srcSize);
static const uint8_t * lgd_yuv420_get_buffer (void * opaque);
static bool lgd_yuv420_create(void ** opaque)
{
// 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));
return true;
}
static void lgd_yuv420_destroy(void * opaque)
{
free(opaque);
}
static bool lgd_yuv420_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
{
struct Inst * this = (struct Inst *)opaque;
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->yBytes = format.width * format.height;
this->pixels = malloc(sizeof(struct Pixel) * (format.width * format.height));
return true;
}
static void lgd_yuv420_deinitialize(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
free(this->pixels);
}
static LG_OutFormat lgd_yuv420_get_out_format(void * opaque)
{
return LG_OUTPUT_BGRA;
}
static unsigned int lgd_yuv420_get_frame_pitch(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
return this->format.width * 4;
}
static unsigned int lgd_yuv420_get_frame_stride(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
return this->format.width;
}
static bool lgd_yuv420_decode(void * opaque, const uint8_t * src, size_t srcSize)
{
//FIXME: implement this properly using GLSL
struct Inst * this = (struct Inst *)opaque;
const unsigned int hw = this->format.width / 2;
const unsigned int hp = this->yBytes / 4;
for(size_t y = 0; y < this->format.height; ++y)
for(size_t x = 0; x < this->format.width; ++x)
{
const unsigned int yoff = y * this->format.width + x;
const unsigned int uoff = this->yBytes + ((y / 2) * hw + x / 2);
const unsigned int voff = uoff + hp;
float b = 1.164f * ((float)src[yoff] - 16.0f) + 2.018f * ((float)src[uoff] - 128.0f);
float g = 1.164f * ((float)src[yoff] - 16.0f) - 0.813f * ((float)src[voff] - 128.0f) - 0.391f * ((float)src[uoff] - 128.0f);
float r = 1.164f * ((float)src[yoff] - 16.0f) + 1.596f * ((float)src[voff] - 128.0f);
#define CLAMP(x) (x < 0 ? 0 : (x > 255 ? 255 : x))
this->pixels[yoff].b = CLAMP(b);
this->pixels[yoff].g = CLAMP(g);
this->pixels[yoff].r = CLAMP(r);
}
return true;
}
static const uint8_t * lgd_yuv420_get_buffer(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
return (uint8_t *)this->pixels;
}
bool lgd_yuv420_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
{
return false;
}
void lgd_yuv420_free_gl_texture(void * opaque, void * ref)
{
}
bool lgd_yuv420_update_gl_texture(void * opaque, void * ref)
{
return false;
}
const LG_Decoder LGD_YUV420 =
{
.name = "YUV420",
.create = lgd_yuv420_create,
.destroy = lgd_yuv420_destroy,
.initialize = lgd_yuv420_initialize,
.deinitialize = lgd_yuv420_deinitialize,
.get_out_format = lgd_yuv420_get_out_format,
.get_frame_pitch = lgd_yuv420_get_frame_pitch,
.get_frame_stride = lgd_yuv420_get_frame_stride,
.decode = lgd_yuv420_decode,
.get_buffer = lgd_yuv420_get_buffer,
.has_gl = false, //FIXME: Implement this
.init_gl_texture = lgd_yuv420_init_gl_texture,
.free_gl_texture = lgd_yuv420_free_gl_texture,
.update_gl_texture = lgd_yuv420_update_gl_texture
};

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()
# SDL must be first as it's the default implementation!
add_displayserver(SDL)
# Add/remove displayservers here!
if (ENABLE_X11)
add_displayserver(X11)
endif()
if (ENABLE_WAYLAND)
add_displayserver(Wayland)
endif()
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,256 @@
/*
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 "app.h"
#include "kb.h"
#include "common/debug.h"
struct SDLDSState
{
bool keyboardGrabbed;
bool pointerGrabbed;
bool exiting;
};
static struct SDLDSState sdl;
static bool sdlEarlyInit(void)
{
return true;
}
static bool sdlInit(SDL_SysWMinfo * info)
{
memset(&sdl, 0, sizeof(sdl));
return true;
}
static void sdlStartup(void)
{
}
static void sdlShutdown(void)
{
}
static void sdlFree(void)
{
}
static bool sdlGetProp(LG_DSProperty prop, void * ret)
{
return false;
}
static bool sdlEventFilter(SDL_Event * event)
{
switch(event->type)
{
case SDL_QUIT:
app_handleCloseEvent();
return true;
case SDL_MOUSEMOTION:
// stop motion events during the warp out of the window
if (sdl.exiting)
return true;
app_updateCursorPos(event->motion.x, event->motion.y);
if (app_cursorIsGrabbed())
app_handleMouseGrabbed(event->motion.xrel, event->motion.yrel);
else
app_handleMouseNormal(event->motion.xrel, event->motion.yrel);
return true;
case SDL_MOUSEBUTTONDOWN:
{
int button = event->button.button;
if (button > 3)
button += 2;
app_handleButtonPress(button);
return true;
}
case SDL_MOUSEBUTTONUP:
{
int button = event->button.button;
if (button > 3)
button += 2;
app_handleButtonRelease(button);
return true;
}
case SDL_MOUSEWHEEL:
{
int button = event->wheel.y > 0 ? 4 : 5;
app_handleButtonPress(button);
app_handleButtonRelease(button);
return true;
}
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_handleWindowEnter();
return true;
case SDL_WINDOWEVENT_LEAVE:
sdl.exiting = false;
app_handleWindowLeave();
return true;
case SDL_WINDOWEVENT_FOCUS_GAINED:
app_handleFocusEvent(true);
return true;
case SDL_WINDOWEVENT_FOCUS_LOST:
app_handleFocusEvent(false);
return true;
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
app_handleResizeEvent(event->window.data1, event->window.data2);
return true;
case SDL_WINDOWEVENT_MOVED:
app_updateWindowPos(event->window.data1, event->window.data2);
return true;
case SDL_WINDOWEVENT_CLOSE:
app_handleCloseEvent();
return true;
}
break;
}
return false;
}
static void sdlGrabPointer(void)
{
SDL_SetWindowGrab(app_getWindow(), SDL_TRUE);
SDL_SetRelativeMouseMode(SDL_TRUE);
sdl.pointerGrabbed = true;
}
static void sdlUngrabPointer(void)
{
SDL_SetWindowGrab(app_getWindow(), SDL_FALSE);
SDL_SetRelativeMouseMode(SDL_FALSE);
sdl.pointerGrabbed = false;
}
static void sdlGrabKeyboard(void)
{
if (sdl.pointerGrabbed)
SDL_SetWindowGrab(app_getWindow(), 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(app_getWindow(), SDL_TRUE);
sdl.keyboardGrabbed = true;
}
static void sdlUngrabKeyboard(void)
{
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0");
SDL_SetWindowGrab(app_getWindow(), SDL_FALSE);
if (sdl.pointerGrabbed)
SDL_SetWindowGrab(app_getWindow(), 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(app_getWindow(), x, y);
}
static void sdlRealignPointer(void)
{
// no need to care about grab, realign only happens in normal mode
app_handleMouseNormal(0, 0);
}
static void sdlInhibitIdle(void)
{
SDL_DisableScreenSaver();
}
static void sdlUninhibitIdle(void)
{
SDL_EnableScreenSaver();
}
struct LG_DisplayServerOps LGDS_SDL =
{
.subsystem = SDL_SYSWM_UNKNOWN,
.earlyInit = sdlEarlyInit,
.init = sdlInit,
.startup = sdlStartup,
.shutdown = sdlShutdown,
.free = sdlFree,
.getProp = sdlGetProp,
.eventFilter = sdlEventFilter,
.grabPointer = sdlGrabPointer,
.ungrabPointer = sdlUngrabPointer,
.grabKeyboard = sdlGrabKeyboard,
.ungrabKeyboard = sdlUngrabKeyboard,
.warpPointer = sdlWarpPointer,
.realignPointer = sdlRealignPointer,
.inhibitIdle = sdlInhibitIdle,
.uninhibitIdle = sdlUninhibitIdle,
/* SDL does not have clipboard support */
.cbInit = NULL,
};

View File

@@ -0,0 +1,61 @@
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
wayland.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}/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,880 @@
/*
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 <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 <SDL2/SDL.h>
#include <wayland-client.h>
#include "app.h"
#include "common/debug.h"
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
struct WaylandDSState
{
bool pointerGrabbed;
bool keyboardGrabbed;
struct wl_display * display;
struct wl_surface * surface;
struct wl_registry * registry;
struct wl_seat * seat;
struct wl_data_device_manager * dataDeviceManager;
struct wl_data_device * dataDevice;
uint32_t capabilities;
struct wl_keyboard * keyboard;
struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager;
struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor;
uint32_t keyboardEnterSerial;
struct wl_pointer * pointer;
struct zwp_relative_pointer_manager_v1 * relativePointerManager;
struct zwp_pointer_constraints_v1 * pointerConstraints;
struct zwp_relative_pointer_v1 * relativePointer;
struct zwp_confined_pointer_v1 * confinedPointer;
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
struct zwp_idle_inhibitor_v1 * idleInhibitor;
};
struct WCBTransfer
{
void * data;
size_t size;
const char ** mimetypes;
};
struct WCBState
{
enum LG_ClipboardData stashedType;
char * stashedMimetype;
uint8_t * stashedContents;
ssize_t stashedSize;
bool isReceiving;
bool isSelfCopy;
bool haveRequest;
LG_ClipboardData type;
};
static struct WaylandDSState wm;
static struct WCBState wcb;
// Registry-handling listeners.
static void registryGlobalHandler(void * data, struct wl_registry * registry,
uint32_t name, const char * interface, uint32_t version)
{
if (!strcmp(interface, wl_seat_interface.name) && !wm.seat)
wm.seat = wl_registry_bind(wm.registry, name, &wl_seat_interface, 1);
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name))
wm.relativePointerManager = wl_registry_bind(wm.registry, name,
&zwp_relative_pointer_manager_v1_interface, 1);
else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name))
wm.pointerConstraints = wl_registry_bind(wm.registry, name,
&zwp_pointer_constraints_v1_interface, 1);
else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name))
wm.keyboardInhibitManager = wl_registry_bind(wm.registry, name,
&zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
else if (!strcmp(interface, wl_data_device_manager_interface.name))
wm.dataDeviceManager = wl_registry_bind(wm.registry, name,
&wl_data_device_manager_interface, 3);
else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name))
wm.idleInhibitManager = wl_registry_bind(wm.registry, name,
&zwp_idle_inhibit_manager_v1_interface, 1);
}
static void registryGlobalRemoveHandler(void * data,
struct wl_registry * registry, uint32_t name)
{
// Do nothing.
}
static const struct wl_registry_listener registryListener = {
.global = registryGlobalHandler,
.global_remove = registryGlobalRemoveHandler,
};
// Mouse-handling listeners.
static void pointerMotionHandler(void * data, struct wl_pointer * pointer,
uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW)
{
if (wm.relativePointer)
return;
int sx = wl_fixed_to_int(sxW);
int sy = wl_fixed_to_int(syW);
app_updateCursorPos(sx, sy);
app_handleMouseBasic();
}
static void pointerEnterHandler(void * data, struct wl_pointer * pointer,
uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW,
wl_fixed_t syW)
{
if (wm.relativePointer)
return;
int sx = wl_fixed_to_int(sxW);
int sy = wl_fixed_to_int(syW);
app_updateCursorPos(sx, sy);
app_handleMouseBasic();
}
static void pointerLeaveHandler(void * data, struct wl_pointer * pointer,
uint32_t serial, struct wl_surface * surface)
{
// Do nothing.
}
static void pointerAxisHandler(void * data, struct wl_pointer * pointer,
uint32_t serial, uint32_t axis, wl_fixed_t value)
{
int button = value > 0 ?
5 /* SPICE_MOUSE_BUTTON_DOWN */ :
4 /* SPICE_MOUSE_BUTTON_UP */;
app_handleButtonPress(button);
app_handleButtonRelease(button);
}
static int mapWaylandToSpiceButton(uint32_t button)
{
switch (button)
{
case BTN_LEFT:
return 1; // SPICE_MOUSE_BUTTON_LEFT
case BTN_MIDDLE:
return 2; // SPICE_MOUSE_BUTTON_MIDDLE
case BTN_RIGHT:
return 3; // SPICE_MOUSE_BUTTON_RIGHT
case BTN_SIDE:
return 6; // SPICE_MOUSE_BUTTON_SIDE
case BTN_EXTRA:
return 7; // SPICE_MOUSE_BUTTON_EXTRA
}
return 0; // SPICE_MOUSE_BUTTON_INVALID
}
static void pointerButtonHandler(void *data, struct wl_pointer *pointer,
uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW)
{
button = mapWaylandToSpiceButton(button);
if (stateW == WL_POINTER_BUTTON_STATE_PRESSED)
app_handleButtonPress(button);
else
app_handleButtonRelease(button);
}
static const struct wl_pointer_listener pointerListener = {
.enter = pointerEnterHandler,
.leave = pointerLeaveHandler,
.motion = pointerMotionHandler,
.button = pointerButtonHandler,
.axis = pointerAxisHandler,
};
static void waylandInhibitIdle(void)
{
if (wm.idleInhibitManager && !wm.idleInhibitor)
wm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
wm.idleInhibitManager, wm.surface);
}
static void waylandUninhibitIdle(void)
{
if (wm.idleInhibitor)
{
zwp_idle_inhibitor_v1_destroy(wm.idleInhibitor);
wm.idleInhibitor = NULL;
}
}
// Keyboard-handling listeners.
static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard,
uint32_t format, int fd, uint32_t size)
{
close(fd);
}
static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
{
wm.keyboardEnterSerial = serial;
uint32_t * key;
wl_array_for_each(key, keys)
app_handleKeyPress(*key);
}
static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard,
uint32_t serial, struct wl_surface * surface)
{
// Do nothing.
}
static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard,
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
{
if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
app_handleKeyPress(key);
else
app_handleKeyRelease(key);
}
static void keyboardModifiersHandler(void * data,
struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed,
uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
{
// Do nothing.
}
static const struct wl_keyboard_listener keyboardListener = {
.keymap = keyboardKeymapHandler,
.enter = keyboardEnterHandler,
.leave = keyboardLeaveHandler,
.key = keyboardKeyHandler,
.modifiers = keyboardModifiersHandler,
};
// Seat-handling listeners.
static void handlePointerCapability(uint32_t capabilities)
{
bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
if (!hasPointer && wm.pointer)
{
wl_pointer_destroy(wm.pointer);
wm.pointer = NULL;
}
else if (hasPointer && !wm.pointer)
{
wm.pointer = wl_seat_get_pointer(wm.seat);
wl_pointer_add_listener(wm.pointer, &pointerListener, NULL);
}
}
static void handleKeyboardCapability(uint32_t capabilities)
{
bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
if (!hasKeyboard && wm.keyboard)
{
wl_keyboard_destroy(wm.keyboard);
wm.keyboard = NULL;
}
else if (hasKeyboard && !wm.keyboard)
{
wm.keyboard = wl_seat_get_keyboard(wm.seat);
wl_keyboard_add_listener(wm.keyboard, &keyboardListener, NULL);
}
}
static void seatCapabilitiesHandler(void * data, struct wl_seat * seat,
uint32_t capabilities)
{
wm.capabilities = capabilities;
handlePointerCapability(capabilities);
handleKeyboardCapability(capabilities);
}
static void seatNameHandler(void * data, struct wl_seat * seat,
const char * name)
{
// Do nothing.
}
static const struct wl_seat_listener seatListener = {
.capabilities = seatCapabilitiesHandler,
.name = seatNameHandler,
};
static bool waylandEarlyInit(void)
{
if (!getenv("SDL_VIDEODRIVER"))
{
int err = setenv("SDL_VIDEODRIVER", "wayland", 1);
if (err < 0)
{
DEBUG_ERROR("Unable to set the env variable SDL_VIDEODRIVER: %d", err);
return false;
}
DEBUG_INFO("SDL_VIDEODRIVER has been set to wayland");
}
// Request to receive EPIPE instead of SIGPIPE when one end of a pipe
// disconnects while a write is pending. This is useful to the Wayland
// clipboard backend, where an arbitrary application is on the other end of
// that pipe.
signal(SIGPIPE, SIG_IGN);
return true;
}
static bool waylandInit(SDL_SysWMinfo * info)
{
memset(&wm, 0, sizeof(wm));
wm.display = info->info.wl.display;
wm.surface = info->info.wl.surface;
wm.registry = wl_display_get_registry(wm.display);
wl_registry_add_listener(wm.registry, &registryListener, NULL);
wl_display_roundtrip(wm.display);
if (!wm.seat || !wm.dataDeviceManager)
{
DEBUG_ERROR("Compositor missing a required interface, will not proceed");
return false;
}
if (!wm.relativePointerManager)
DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, "
"mouse will not be captured");
if (!wm.pointerConstraints)
DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse "
"will not be captured");
if (!wm.keyboardInhibitManager)
DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by "
"compositor, keyboard will not be grabbed");
if (!wm.idleInhibitManager)
DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will "
"not be able to suppress idle states");
wl_seat_add_listener(wm.seat, &seatListener, NULL);
wl_display_roundtrip(wm.display);
wm.dataDevice = wl_data_device_manager_get_data_device(
wm.dataDeviceManager, wm.seat);
return true;
}
static void waylandStartup(void)
{
}
static void relativePointerMotionHandler(void * data,
struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo,
wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW,
wl_fixed_t dyUnaccelW)
{
double dx, dy;
if (app_cursorWantsRaw())
{
dx = wl_fixed_to_double(dxUnaccelW);
dy = wl_fixed_to_double(dyUnaccelW);
}
else
{
dx = wl_fixed_to_double(dxW);
dy = wl_fixed_to_double(dyW);
}
app_handleMouseGrabbed(dx, dy);
}
static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
.relative_motion = relativePointerMotionHandler,
};
static void waylandGrabPointer(void)
{
if (!wm.relativePointerManager || !wm.pointerConstraints)
return;
if (!wm.relativePointer)
{
wm.relativePointer =
zwp_relative_pointer_manager_v1_get_relative_pointer(
wm.relativePointerManager, wm.pointer);
zwp_relative_pointer_v1_add_listener(wm.relativePointer,
&relativePointerListener, NULL);
}
if (!wm.confinedPointer)
{
wm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
wm.pointerConstraints, wm.surface, wm.pointer, NULL,
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
}
}
static void waylandUngrabPointer(void)
{
if (wm.relativePointer)
{
zwp_relative_pointer_v1_destroy(wm.relativePointer);
wm.relativePointer = NULL;
}
if (wm.confinedPointer)
{
zwp_confined_pointer_v1_destroy(wm.confinedPointer);
wm.confinedPointer = NULL;
}
}
static void waylandGrabKeyboard(void)
{
if (wm.keyboardInhibitManager && !wm.keyboardInhibitor)
{
wm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
wm.keyboardInhibitManager, wm.surface, wm.seat);
}
}
static void waylandUngrabKeyboard(void)
{
if (wm.keyboardInhibitor)
{
zwp_keyboard_shortcuts_inhibitor_v1_destroy(wm.keyboardInhibitor);
wm.keyboardInhibitor = NULL;
}
}
static void waylandWarpPointer(int x, int y, bool exiting)
{
// This is an unsupported operation on Wayland.
}
static void waylandRealignPointer(void)
{
app_handleMouseBasic();
}
static void waylandFree(void)
{
waylandUngrabPointer();
if (wm.idleInhibitManager)
{
waylandUninhibitIdle();
zwp_idle_inhibit_manager_v1_destroy(wm.idleInhibitManager);
}
// TODO: these also need to be freed, but are currently owned by SDL.
// wl_display_destroy(wm.display);
// wl_surface_destroy(wm.surface);
wl_pointer_destroy(wm.pointer);
wl_seat_destroy(wm.seat);
wl_registry_destroy(wm.registry);
}
static bool waylandGetProp(LG_DSProperty prop, void * ret)
{
if (prop == LG_DS_WARP_SUPPORT)
{
*(bool*)ret = false;
return true;
}
return false;
}
static bool waylandEventFilter(SDL_Event * event)
{
/* prevent the default processing for the following events */
switch(event->type)
{
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEWHEEL:
case SDL_KEYDOWN:
case SDL_KEYUP:
return true;
}
return false;
}
static const char * textMimetypes[] =
{
"text/plain",
"text/plain;charset=utf-8",
"TEXT",
"STRING",
"UTF8_STRING",
NULL,
};
static const char * pngMimetypes[] =
{
"image/png",
NULL,
};
static const char * bmpMimetypes[] =
{
"image/bmp",
"image/x-bmp",
"image/x-MS-bmp",
"image/x-win-bitmap",
NULL,
};
static const char * tiffMimetypes[] =
{
"image/tiff",
NULL,
};
static const char * jpegMimetypes[] =
{
"image/jpeg",
NULL,
};
static const char ** cbTypeToMimetypes(enum LG_ClipboardData type)
{
switch (type)
{
case LG_CLIPBOARD_DATA_TEXT:
return textMimetypes;
case LG_CLIPBOARD_DATA_PNG:
return pngMimetypes;
case LG_CLIPBOARD_DATA_BMP:
return bmpMimetypes;
case LG_CLIPBOARD_DATA_TIFF:
return tiffMimetypes;
case LG_CLIPBOARD_DATA_JPEG:
return jpegMimetypes;
default:
DEBUG_ERROR("invalid clipboard type");
abort();
}
}
static bool containsMimetype(const char ** mimetypes,
const char * needle)
{
for (const char ** mimetype = mimetypes; *mimetype; mimetype++)
if (!strcmp(needle, *mimetype))
return true;
return false;
}
static bool mimetypeEndswith(const char * mimetype, const char * what)
{
size_t mimetypeLen = strlen(mimetype);
size_t whatLen = strlen(what);
if (mimetypeLen < whatLen)
return false;
return !strcmp(mimetype + mimetypeLen - whatLen, what);
}
static bool isTextMimetype(const char * mimetype)
{
if (containsMimetype(textMimetypes, mimetype))
return true;
char * text = "text/";
if (!strncmp(mimetype, text, strlen(text)))
return true;
if (mimetypeEndswith(mimetype, "script") ||
mimetypeEndswith(mimetype, "xml") ||
mimetypeEndswith(mimetype, "yaml"))
return true;
if (strstr(mimetype, "json"))
return true;
return false;
}
static enum LG_ClipboardData mimetypeToCbType(const char * mimetype)
{
if (isTextMimetype(mimetype))
return LG_CLIPBOARD_DATA_TEXT;
if (containsMimetype(pngMimetypes, mimetype))
return LG_CLIPBOARD_DATA_PNG;
if (containsMimetype(bmpMimetypes, mimetype))
return LG_CLIPBOARD_DATA_BMP;
if (containsMimetype(tiffMimetypes, mimetype))
return LG_CLIPBOARD_DATA_TIFF;
if (containsMimetype(jpegMimetypes, mimetype))
return LG_CLIPBOARD_DATA_JPEG;
return LG_CLIPBOARD_DATA_NONE;
}
// Destination client handlers.
static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer,
const char * mimetype)
{
enum LG_ClipboardData type = mimetypeToCbType(mimetype);
// 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 &&
(wcb.stashedType == LG_CLIPBOARD_DATA_NONE ||
strstr(wcb.stashedMimetype, "html")))
{
wcb.stashedType = type;
if (wcb.stashedMimetype)
free(wcb.stashedMimetype);
wcb.stashedMimetype = strdup(mimetype);
}
}
static void dataOfferHandleSourceActions(void * data,
struct wl_data_offer * offer, uint32_t sourceActions)
{
// Do nothing.
}
static void dataOfferHandleAction(void * data, struct wl_data_offer * offer,
uint32_t dndAction)
{
// Do nothing.
}
static const struct wl_data_offer_listener dataOfferListener = {
.offer = dataOfferHandleOffer,
.source_actions = dataOfferHandleSourceActions,
.action = dataOfferHandleAction,
};
static void dataDeviceHandleDataOffer(void * data,
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
{
wcb.stashedType = LG_CLIPBOARD_DATA_NONE;
wl_data_offer_add_listener(offer, &dataOfferListener, NULL);
}
static void dataDeviceHandleSelection(void * data,
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
{
if (wcb.stashedType == LG_CLIPBOARD_DATA_NONE || !offer)
return;
int fds[2];
if (pipe(fds) < 0)
{
DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno));
abort();
}
wcb.isReceiving = true;
wcb.isSelfCopy = false;
wl_data_offer_receive(offer, wcb.stashedMimetype, fds[1]);
close(fds[1]);
free(wcb.stashedMimetype);
wcb.stashedMimetype = NULL;
wl_display_roundtrip(wm.display);
if (wcb.stashedContents)
{
free(wcb.stashedContents);
wcb.stashedContents = NULL;
}
size_t size = 4096, numRead = 0;
uint8_t * buf = (uint8_t *) malloc(size);
while (true)
{
ssize_t result = read(fds[0], buf + numRead, size - numRead);
if (result < 0)
{
DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno));
abort();
}
if (result == 0)
{
buf[numRead] = 0;
break;
}
numRead += result;
if (numRead >= size)
{
size *= 2;
void * nbuf = realloc(buf, size);
if (!nbuf) {
DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno));
abort();
}
buf = nbuf;
}
}
wcb.stashedSize = numRead;
wcb.stashedContents = buf;
wcb.isReceiving = false;
close(fds[0]);
wl_data_offer_destroy(offer);
if (!wcb.isSelfCopy)
app_clipboardNotify(wcb.stashedType, 0);
}
static const struct wl_data_device_listener dataDeviceListener = {
.data_offer = dataDeviceHandleDataOffer,
.selection = dataDeviceHandleSelection,
};
static void waylandCBRequest(LG_ClipboardData type)
{
// We only notified once, so it must be this.
assert(type == wcb.stashedType);
app_clipboardData(wcb.stashedType, wcb.stashedContents, wcb.stashedSize);
}
static bool waylandCBInit(void)
{
memset(&wcb, 0, sizeof(wcb));
wcb.stashedType = LG_CLIPBOARD_DATA_NONE;
wl_data_device_add_listener(wm.dataDevice, &dataDeviceListener, NULL);
return true;
}
static void dataSourceHandleSend(void * data, struct wl_data_source * source,
const char * mimetype, int fd)
{
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
if (wcb.isReceiving)
wcb.isSelfCopy = true;
else if (containsMimetype(transfer->mimetypes, mimetype))
{
// Consider making this do non-blocking sends to not stall the Wayland
// event loop if it becomes a problem. This is "fine" in the sense that
// wl-copy also stalls like this, but it's not necessary.
fcntl(fd, F_SETFL, 0);
size_t pos = 0;
while (pos < transfer->size)
{
ssize_t written = write(fd, transfer->data + pos, transfer->size - pos);
if (written < 0)
{
if (errno != EPIPE)
DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno));
goto error;
}
pos += written;
}
}
error:
close(fd);
}
static void dataSourceHandleCancelled(void * data,
struct wl_data_source * source)
{
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
free(transfer->data);
free(transfer);
wl_data_source_destroy(source);
}
static const struct wl_data_source_listener dataSourceListener = {
.send = dataSourceHandleSend,
.cancelled = dataSourceHandleCancelled,
};
static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
uint8_t * data, uint32_t size)
{
struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer));
void * dataCopy = malloc(size);
memcpy(dataCopy, data, size);
*transfer = (struct WCBTransfer) {
.data = dataCopy,
.size = size,
.mimetypes = cbTypeToMimetypes(type),
};
struct wl_data_source * source =
wl_data_device_manager_create_data_source(wm.dataDeviceManager);
wl_data_source_add_listener(source, &dataSourceListener, transfer);
for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++)
wl_data_source_offer(source, *mimetype);
wl_data_device_set_selection(wm.dataDevice, source,
wm.keyboardEnterSerial);
}
static void waylandCBNotice(LG_ClipboardData type)
{
wcb.haveRequest = true;
wcb.type = type;
app_clipboardRequest(waylandCBReplyFn, NULL);
}
static void waylandCBRelease(void)
{
wcb.haveRequest = false;
}
struct LG_DisplayServerOps LGDS_Wayland =
{
.subsystem = SDL_SYSWM_WAYLAND,
.earlyInit = waylandEarlyInit,
.init = waylandInit,
.startup = waylandStartup,
.free = waylandFree,
.getProp = waylandGetProp,
.eventFilter = waylandEventFilter,
.grabPointer = waylandGrabPointer,
.ungrabPointer = waylandUngrabPointer,
.grabKeyboard = waylandGrabKeyboard,
.ungrabKeyboard = waylandUngrabKeyboard,
.warpPointer = waylandWarpPointer,
.realignPointer = waylandRealignPointer,
.inhibitIdle = waylandInhibitIdle,
.uninhibitIdle = waylandUninhibitIdle,
.cbInit = waylandCBInit,
.cbNotice = waylandCBNotice,
.cbRelease = waylandCBRelease,
.cbRequest = waylandCBRequest
};

View File

@@ -0,0 +1,25 @@
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
)
add_library(displayserver_X11 STATIC
x11.c
)
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

@@ -36,19 +36,21 @@ struct Inst
static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
{
if (g_initCount++ == 0)
bool ret = false;
if (g_initCount == 0)
{
if (TTF_Init() < 0)
{
DEBUG_ERROR("TTF_Init Failed");
return false;
goto fail;
}
g_fontConfig = FcInitLoadConfigAndFonts();
if (!g_fontConfig)
{
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
return false;
goto fail_init;
}
}
@@ -56,50 +58,95 @@ static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned
if (!*opaque)
{
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
return false;
goto fail_config;
}
memset(*opaque, 0, sizeof(struct Inst));
memset(*opaque, 0, sizeof(struct Inst));
struct Inst * this = (struct Inst *)*opaque;
if (!font_name)
if (!font_name)
font_name = "FreeMono";
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
FcConfigSubstitute (g_fontConfig, pat, FcMatchPattern);
if (!pat)
{
DEBUG_ERROR("FCNameParse failed");
goto fail_opaque;
}
FcConfigSubstitute(g_fontConfig, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
FcResult result;
FcChar8 * file = NULL;
FcPattern * font = FcFontMatch(g_fontConfig, pat, &result);
FcPattern * match = FcFontMatch(g_fontConfig, pat, &result);
if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch))
if (!match)
{
DEBUG_ERROR("FcFontMatch Failed");
goto fail_parse;
}
if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch)
{
this->font = TTF_OpenFont((char *)file, size);
if (!this->font)
{
DEBUG_ERROR("TTL_OpenFont Failed");
return false;
goto fail_match;
}
}
else
{
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
return false;
goto fail_match;
}
++g_initCount;
ret = true;
fail_match:
FcPatternDestroy(match);
fail_parse:
FcPatternDestroy(pat);
return true;
if (ret)
return true;
fail_opaque:
free(this);
*opaque = NULL;
fail_config:
if (g_initCount == 0)
{
FcConfigDestroy(g_fontConfig);
g_fontConfig = NULL;
}
fail_init:
if (g_initCount == 0)
TTF_Quit();
fail:
return false;
}
static void lgf_sdl_destroy(LG_FontObj opaque)
{
struct Inst * this = (struct Inst *)opaque;
if (this->font)
TTF_CloseFont(this->font);
free(this);
if (--g_initCount == 0)
{
FcConfigDestroy(g_fontConfig);
g_fontConfig = NULL;
TTF_Quit();
}
}
static LG_FontBitmap * lgf_sdl_render(LG_FontObj opaque, unsigned int fg_color, const char * text)
@@ -150,4 +197,4 @@ struct LG_Font LGF_SDL =
.destroy = lgf_sdl_destroy,
.render = lgf_sdl_render,
.release = lgf_sdl_release
};
};

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

@@ -0,0 +1,50 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 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>
#include "interface/displayserver.h"
SDL_Window * app_getWindow(void);
bool app_getProp(LG_DSProperty prop, void * ret);
bool app_inputEnabled(void);
bool app_cursorIsGrabbed(void);
bool app_cursorWantsRaw(void);
bool app_cursorInWindow(void);
void app_updateCursorPos(double x, double y);
void app_updateWindowPos(int x, int y);
void app_handleResizeEvent(int w, int h);
void app_handleMouseGrabbed(double ex, double ey);
void app_handleMouseNormal(double ex, double ey);
void app_handleMouseBasic(void);
void app_handleButtonPress(int button);
void app_handleButtonRelease(int button);
void app_handleKeyPress(int scancode);
void app_handleKeyRelease(int scancode);
void app_handleWindowEnter(void);
void app_handleWindowLeave(void);
void app_handleFocusEvent(bool focused);
void app_handleCloseEvent(void);
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);

View File

@@ -20,6 +20,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include <SDL2/SDL.h>
#include <linux/input.h>
typedef enum LG_MsgAlert
{
@@ -31,7 +32,7 @@ typedef enum LG_MsgAlert
LG_MsgAlert;
typedef struct KeybindHandle * KeybindHandle;
typedef void (*SuperEventFn)(SDL_Scancode key, void * opaque);
typedef void (*SuperEventFn)(uint32_t sc, void * opaque);
/**
* Show an alert on screen
@@ -43,16 +44,16 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...);
/**
* Register a handler for the <super>+<key> combination
* @param key The scancode to register
* @param sc The scancode to register
* @param callback The function to be called when the combination is pressed
* @param opaque A pointer to be passed to the callback, may be NULL
* @retval A handle for the binding or NULL on failure.
* The caller is required to release the handle via `app_release_keybind` when it is no longer required
*/
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque);
KeybindHandle app_register_keybind(uint32_t sc, SuperEventFn callback, void * opaque);
/**
* Release an existing key binding
* @param handle A pointer to the keybind handle to release, may be NULL
*/
void app_release_keybind(KeybindHandle * handle);
void app_release_keybind(KeybindHandle * handle);

View File

@@ -1,62 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.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 void (* LG_ClipboardReplyFn )(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size);
typedef void (* LG_ClipboardRequestFn)(LG_ClipboardReplyFn replyFn, void * opaque);
typedef void (* LG_ClipboardReleaseFn)();
typedef void (* LG_ClipboardNotifyFn)(LG_ClipboardData type);
typedef void (* LG_ClipboardDataFn )(const LG_ClipboardData type, uint8_t * data, size_t size);
typedef const char * (* LG_ClipboardGetName)();
typedef bool (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo, LG_ClipboardReleaseFn releaseFn, LG_ClipboardNotifyFn notifyFn, LG_ClipboardDataFn dataFn);
typedef void (* LG_ClipboardFree)();
typedef void (* LG_ClipboardWMEvent)(SDL_SysWMmsg * msg);
typedef void (* LG_ClipboardNotice)(LG_ClipboardRequestFn requestFn, LG_ClipboardData type);
typedef void (* LG_ClipboardRelease)();
typedef void (* LG_ClipboardRequest)(LG_ClipboardData type);
typedef struct LG_Clipboard
{
LG_ClipboardGetName getName;
LG_ClipboardInit init;
LG_ClipboardFree free;
LG_ClipboardWMEvent wmevent;
LG_ClipboardNotice notice;
LG_ClipboardRelease release;
LG_ClipboardRequest request;
}
LG_Clipboard;

View File

@@ -0,0 +1,114 @@
/*
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 <SDL2/SDL.h>
#include <SDL2/SDL_syswm.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;
typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type,
uint8_t * data, uint32_t size);
struct LG_DisplayServerOps
{
const SDL_SYSWM_TYPE subsystem;
/* called before SDL has been initialized */
bool (*earlyInit)(void);
/* called after SDL has been initialized */
bool (*init)(SDL_SysWMinfo * info);
/* called at startup after window creation, renderer and/or 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);
/* event filter, return true if the event has been handled */
bool (*eventFilter)(SDL_Event * event);
/* dm specific cursor implementations */
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)();
/* called to disable/enable the screensaver */
void (*inhibitIdle)();
void (*uninhibitIdle)();
/* clipboard support */
bool (* cbInit)(void);
void (* cbNotice)(LG_ClipboardData type);
void (* cbRelease)(void);
void (* cbRequest)(LG_ClipboardData type);
};
#endif

View File

@@ -26,12 +26,14 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#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 && \
@@ -45,27 +47,48 @@ typedef struct LG_RendererParams
// TTF_Font * font;
// TTF_Font * alertFont;
bool showFPS;
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)
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;
unsigned int w;
unsigned int h;
bool valid;
int x;
int y;
int w;
int h;
}
LG_RendererRect;
@@ -83,32 +106,39 @@ typedef const char * (* LG_RendererGetName)();
// called pre-creation to allow the renderer to register any options it might have
typedef void (* LG_RendererSetup)();
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
typedef 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 void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
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 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 bool (* LG_RendererRenderStartup)(void * opaque, SDL_Window *window);
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window, 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_RendererOnResize on_resize;
LG_RendererOnMouseShape on_mouse_shape;
LG_RendererOnMouseEvent on_mouse_event;
LG_RendererOnFrameEvent on_frame_event;
LG_RendererOnAlert on_alert;
LG_RendererRender render_startup;
LG_RendererRender render;
LG_RendererUpdateFPS update_fps;
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_RendererRenderStartup render_startup;
LG_RendererRender render;
LG_RendererUpdateFPS update_fps;
}
LG_Renderer;
LG_Renderer;

View File

@@ -25,7 +25,8 @@ 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);
bool ll_walk (struct ll * list, void ** data);

View File

@@ -19,81 +19,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include <time.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
static inline uint64_t microtime()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
return ((uint64_t)time.tv_sec * 1000000) + (time.tv_nsec / 1000);
}
static inline uint64_t nanotime()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
return ((uint64_t)time.tv_sec * 1e9) + time.tv_nsec;
}
static inline void nsleep(uint64_t ns)
{
const struct timespec ts =
{
.tv_sec = ns / 1e9,
.tv_nsec = ns - ((ns / 1e9) * 1e9)
};
nanosleep(&ts, NULL);
}
#ifdef ATOMIC_LOCKING
#define LG_LOCK_MODE "Atomic"
typedef volatile int LG_Lock;
#define LG_LOCK_INIT(x) (x) = 0
#define LG_LOCK(x) while(__sync_lock_test_and_set(&(x), 1)) {nsleep(100);}
#define LG_UNLOCK(x) __sync_lock_release(&x)
#define LG_LOCK_FREE(x)
#else
#include <SDL2/SDL.h>
#define LG_LOCK_MODE "Mutex"
typedef SDL_mutex * LG_Lock;
#define LG_LOCK_INIT(x) (x = SDL_CreateMutex())
#define LG_LOCK(x) SDL_LockMutex(x)
#define LG_UNLOCK(x) SDL_UnlockMutex(x)
#define LG_LOCK_FREE(x) SDL_DestroyMutex(x)
#endif
static inline uint32_t get_bit(const uint8_t * const base, size_t * const offset)
{
uint32_t out = ((*(base + (*offset >> 0x3))) >> (0x7 - (*offset & 0x7))) & 0x1;
++*offset;
return out;
}
static inline uint32_t get_bits(const uint8_t * const base, size_t * const offset, const uint8_t bits)
{
uint32_t value = 0;
for (int i = 0; i < bits; ++i)
value |= (get_bit(base, offset) ? 1 : 0) << (bits - i - 1);
return value;
}
static inline uint32_t decode_u_golomb(const uint8_t * const base, size_t * const offset)
{
uint32_t i = 0;
while(get_bit(base, offset) == 0)
++i;
return ((1 << i) - 1 + get_bits(base, offset, i));
}
static inline int32_t decode_s_golomb(const uint8_t * const base, size_t * const offset)
{
const uint32_t g = decode_u_golomb(base, offset);
return (g & 0x1) ? (g + 1) / 2 : -(g / 2);
}
// reads the specified file into a new buffer
// the callee must free the buffer
bool file_get_contents(const char * filename, char ** buffer, size_t * length);
bool file_get_contents(const char * filename, char ** buffer, size_t * length);

File diff suppressed because it is too large Load Diff

View File

@@ -1,305 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#define NAL_TYPE_CODED_SLICE_NON_IDR 1
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_A 2
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_B 3
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_C 4
#define NAL_TYPE_CODED_SLICE_IDR 5
#define NAL_TYPE_SPS 7
#define NAL_TYPE_PPS 8
#define NAL_TYPE_AUD 9
#define NAL_TYPE_END_OF_SEQUENCE 10
#define NAL_TYPE_END_OF_STREAM 11
#define NAL_TYPE_CODED_SLICE_AUX 19
#define IDC_PROFILE_BASELINE 66
#define IDC_PROFILE_MAIN 77
#define IDC_PROFILE_EXTENDED 88
#define IDC_PROFILE_HP 100
#define IDC_PROFILE_Hi10P 110
#define IDC_PROFILE_Hi422 122
#define IDC_PROFILE_Hi444 244
#define IDC_PROFILE_CAVLC444 44
#define IDC_CHROMA_FORMAT_YUV400 0
#define IDC_CHROMA_FORMAT_YUV420 1
#define IDC_CHROMA_FORMAT_YVU422 2
#define IDC_CHROMA_FORMAT_YUV444 3
#define IDC_VUI_ASPECT_RATIO_EXTENDED_SAR 0xFF
#define NAL_PICTURE_TYPE_I 0
#define NAL_PICTURE_TYPE_P 1
#define NAL_PICTURE_TYPE_B 2
#define NAL_SLICE_TYPE_P 0
#define NAL_SLICE_TYPE_B 1
#define NAL_SLICE_TYPE_I 2
#define NAL_SLICE_TYPE_SP 3
#define NAL_SLICE_TYPE_SI 4
typedef struct NAL_SPS
{
uint8_t profile_idc;
uint8_t constraint_set_flags[3];
uint8_t level_idc;
uint32_t seq_parameter_set_id;
uint32_t chroma_format_idc;
uint8_t seperate_colour_plane_flag;
uint32_t bit_depth_luma_minus8;
uint32_t bit_depth_chroma_minus8;
uint8_t lossless_qpprime_y_zero_flag;
uint8_t seq_scaling_matrix_present_flag;
uint8_t seq_scaling_list_present_flag[12];
uint32_t log2_max_frame_num_minus4;
uint32_t pic_order_cnt_type;
uint32_t log2_max_pic_order_cnt_lsb_minus4;
uint8_t delta_pic_order_always_zero_flag;
int32_t offset_for_non_ref_pic;
int32_t offset_for_top_to_bottom_field;
uint32_t num_ref_frames_in_pic_order_cnt_cycle;
int32_t * offset_for_ref_frame;
uint32_t num_ref_frames;
uint8_t gaps_in_frame_num_value_allowed_flag;
uint32_t pic_width_in_mbs_minus1;
uint32_t pic_height_in_map_units_minus1;
uint8_t frame_mbs_only_flag;
uint8_t mb_adaptive_frame_field_flag;
uint8_t direct_8x8_inference_flag;
uint8_t frame_cropping_flag;
uint32_t frame_crop_left_offset;
uint32_t frame_crop_right_offset;
uint32_t frame_crop_top_offset;
uint32_t frame_crop_bottom_offset;
uint8_t vui_parameters_present_flag;
}
NAL_SPS;
typedef struct NAL_CPB
{
uint32_t bit_rate_value_minus1;
uint32_t cpb_size_value_minus1;
uint8_t cbr_flag;
}
NAL_CPB;
typedef struct NAL_HRD
{
uint32_t cpb_cnt_minus1;
uint8_t bit_rate_scale;
uint8_t cpb_size_scale;
uint8_t cpb_size_count;
NAL_CPB * cpb;
uint8_t initial_cpb_removal_delay_length_minus1;
uint8_t cpb_removal_delay_length_minus1;
uint8_t dpb_output_delay_length_minus1;
uint8_t time_offset_length;
}
NAL_HRD;
typedef struct NAL_VUI
{
uint8_t aspect_ratio_info_present_flag;
uint8_t aspect_ratio_idc;
uint16_t sar_width;
uint16_t sar_height;
uint8_t overscan_info_present_flag;
uint8_t overscan_appropriate_flag;
uint8_t video_signal_type_present_flag;
uint8_t video_format;
uint8_t video_full_range_flag;
uint8_t colour_description_present_flag;
uint8_t colour_primaries;
uint8_t transfer_characteristics;
uint8_t matrix_coefficients;
uint8_t chroma_loc_info_present_flag;
uint32_t chroma_sample_loc_type_top_field;
uint32_t chroma_sample_loc_type_bottom_field;
uint8_t timing_info_present_flag;
uint32_t num_units_in_tick;
uint32_t time_scale;
uint8_t fixed_frame_rate_flag;
uint8_t nal_hrd_parameters_present_flag;
NAL_HRD nal_hrd_parameters;
uint8_t vcl_hrd_parameters_present_flag;
NAL_HRD vcl_hrd_parameters;
uint8_t low_delay_hrd_flag;
uint8_t pic_struct_present_flag;
uint8_t bitstream_restriction_flag;
uint8_t motion_vectors_over_pic_boundaries_flag;
uint32_t max_bytes_per_pic_denom;
uint32_t max_bits_per_mb_denom;
uint32_t log2_max_mv_length_horizontal;
uint32_t log2_max_mv_length_vertical;
uint32_t num_reorder_frames;
uint32_t max_dec_frame_buffering;
}
NAL_VUI;
typedef struct NAL_SLICE_GROUP_T0
{
uint32_t run_length_minus1;
}
NAL_SLICE_GROUP_T0;
typedef struct NAL_SLICE_GROUP_T2
{
uint32_t top_left;
uint32_t bottom_right;
}
NAL_SLICE_GROUP_T2;
typedef union NAL_SLICE_GROUP
{
NAL_SLICE_GROUP_T0 t0;
NAL_SLICE_GROUP_T2 t2;
}
NAL_SLICE_GROUP;
typedef struct NAL_PPS
{
uint32_t pic_parameter_set_id;
uint32_t seq_parameter_set_id;
uint8_t entropy_coding_mode_flag;
uint8_t pic_order_present_flag;
uint32_t num_slice_groups_minus1;
NAL_SLICE_GROUP * slice_groups;
uint32_t slice_group_map_type;
uint8_t slice_group_change_direction_flag;
uint32_t slice_group_change_rate_minus1;
uint32_t pic_size_in_map_units_minus1;
uint32_t * slice_group_id;
uint32_t num_ref_idx_l0_active_minus1;
uint32_t num_ref_idx_l1_active_minus1;
uint8_t weighted_pred_flag;
uint8_t weighted_bipred_idc;
int32_t pic_init_qp_minus26;
int32_t pic_init_qs_minus26;
int32_t chroma_qp_index_offset;
uint8_t deblocking_filter_control_present_flag;
uint8_t constrained_intra_pred_flag;
uint8_t redundant_pic_cnt_present_flag;
uint8_t transform_8x8_mode_flag;
uint8_t pic_scaling_matrix_present_flag;
uint8_t pic_scaling_list_present_flag[6];
int32_t scaling_list_4x4[6];
int32_t scaling_list_8x8[2];
int32_t second_chroma_qp_index_offset;
}
NAL_PPS;
typedef struct NAL_RPL_REORDER_L
{
bool valid;
uint32_t reordering_of_pic_nums_idc;
uint32_t abs_diff_pic_num_minus1;
uint32_t long_term_pic_num;
}
NAL_RPL_REORDER_L;
typedef struct NAL_RPL_REORDER
{
uint8_t ref_pic_list_reordering_flag_l0;
NAL_RPL_REORDER_L l0[3];
uint8_t ref_pic_list_reordering_flag_l1;
NAL_RPL_REORDER_L l1[3];
}
NAL_RPL_REORDER;
typedef struct NAL_PW_TABLE_L
{
int32_t luma_weight;
int32_t luma_offset;
int32_t chroma_weight[2];
int32_t chroma_offset[2];
}
NAL_PW_TABLE_L;
typedef struct NAL_PW_TABLE
{
uint32_t luma_log2_weight_denom;
uint32_t chroma_log2_weight_denom;
uint8_t luma_weight_flag[2];
uint8_t chroma_weight_flag[2];
NAL_PW_TABLE_L * l0;
NAL_PW_TABLE_L * l1;
}
NAL_PW_TABLE;
typedef struct NAL_RP_MARKING
{
uint8_t no_output_of_prior_pics_flag;
uint8_t long_term_reference_flag;
uint8_t adaptive_ref_pic_marking_mode_flag;
uint32_t memory_management_control_operation;
uint32_t difference_of_pic_nums_minus1;
uint32_t long_term_pic_num;
uint32_t long_term_frame_idx;
uint32_t max_long_term_frame_idx_plus1;
}
NAL_RP_MARKING;
typedef struct NAL_SLICE
{
uint8_t nal_ref_idc;
uint32_t first_mb_in_slice;
uint32_t slice_type;
uint32_t pic_parameter_set_id;
uint32_t frame_num;
uint8_t field_pic_flag;
uint8_t bottom_field_flag;
uint32_t idr_pic_id;
uint32_t pic_order_cnt_lsb;
int32_t delta_pic_order_cnt_bottom;
int32_t delta_pic_order_cnt[2];
uint32_t redundant_pic_cnt;
uint8_t direct_spatial_mv_pred_flag;
uint8_t num_ref_idx_active_override_flag;
uint32_t num_ref_idx_l0_active_minus1;
uint32_t num_ref_idx_l1_active_minus1;
NAL_RPL_REORDER ref_pic_list_reordering;
NAL_PW_TABLE pred_weight_table;
NAL_RP_MARKING dec_ref_pic_marking;
uint32_t cabac_init_idc;
int32_t slice_qp_delta;
uint8_t sp_for_switch_flag;
int32_t slice_qs_delta;
uint32_t disable_deblocking_filter_idc;
int32_t slice_alpha_c0_offset_div2;
int32_t slice_beta_offset_div2;
uint32_t slice_group_change_cycle;
}
NAL_SLICE;
typedef struct NAL * NAL;
bool nal_initialize (NAL * ptr);
void nal_deinitialize(NAL this );
bool nal_parse (NAL this, const uint8_t * src, size_t size, size_t * seek);
bool nal_get_primary_picture_type(NAL this, uint8_t * pic_type);
bool nal_get_sps (NAL this, const NAL_SPS ** sps );
bool nal_get_pps (NAL this, const NAL_PPS ** pps );
bool nal_get_slice(NAL this, const NAL_SLICE ** slice);

View File

@@ -4,16 +4,18 @@ project(renderer_EGL LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(RENDERER_EGL_PKGCONFIG REQUIRED
egl
wayland-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/desktop_yuv.frag
shader/cursor.vert
shader/cursor_rgb.frag
shader/cursor_mono.frag
@@ -31,6 +33,8 @@ make_object(
add_library(renderer_EGL STATIC
egl.c
dynprocs.c
egldebug.c
shader.c
texture.c
model.c
@@ -45,6 +49,7 @@ add_library(renderer_EGL STATIC
target_link_libraries(renderer_EGL
${RENDERER_EGL_PKGCONFIG_LIBRARIES}
${RENDERER_EGL_OPT_PKGCONFIG_LIBRARIES}
lg_common
fonts
)
@@ -54,4 +59,5 @@ target_include_directories(renderer_EGL
src
${EGL_SHADER_INCS}
${RENDERER_EGL_PKGCONFIG_INCLUDE_DIRS}
${RENDERER_EGL_OPT_PKGCONFIG_INCLUDE_DIRS}
)

View File

@@ -19,7 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "alert.h"
#include "common/debug.h"
#include "utils.h"
#include "common/locking.h"
#include "texture.h"
#include "shader.h"
@@ -72,7 +72,7 @@ bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj
(*alert)->fontObj = fontObj;
LG_LOCK_INIT((*alert)->lock);
if (!egl_texture_init(&(*alert)->texture))
if (!egl_texture_init(&(*alert)->texture, NULL))
{
DEBUG_ERROR("Failed to initialize the alert texture");
return false;
@@ -175,6 +175,7 @@ void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY)
alert->bmp->width ,
alert->bmp->height,
alert->bmp->width * alert->bmp->bpp,
false,
false
);
@@ -215,4 +216,4 @@ void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY)
egl_model_render(alert->model);
glDisable(GL_BLEND);
}
}

View File

@@ -19,7 +19,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "cursor.h"
#include "common/debug.h"
#include "utils.h"
#include "common/locking.h"
#include "common/option.h"
#include "texture.h"
#include "shader.h"
@@ -33,6 +34,15 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#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;
@@ -47,21 +57,64 @@ struct EGL_Cursor
// cursor state
bool visible;
float x, y, w, h;
LG_RendererRotate rotate;
int cbMode;
// textures
struct EGL_Texture * texture;
struct EGL_Texture * textureMono;
struct CursorTex norm;
struct CursorTex mono;
struct EGL_Model * model;
};
// shaders
struct EGL_Shader * shader;
struct EGL_Shader * shaderMono;
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;
}
// uniforms
GLuint uMousePos;
GLuint uMousePosMono;
if (!egl_shader_init(&t->shader))
{
DEBUG_ERROR("Failed to initialize the cursor shader");
return false;
}
// model
struct EGL_Model * model;
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)
@@ -76,50 +129,15 @@ bool egl_cursor_init(EGL_Cursor ** cursor)
memset(*cursor, 0, sizeof(EGL_Cursor));
LG_LOCK_INIT((*cursor)->lock);
if (!egl_texture_init(&(*cursor)->texture))
{
DEBUG_ERROR("Failed to initialize the cursor texture");
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_texture_init(&(*cursor)->textureMono))
{
DEBUG_ERROR("Failed to initialize the cursor mono texture");
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_shader_init(&(*cursor)->shader))
{
DEBUG_ERROR("Failed to initialize the cursor shader");
return false;
}
if (!egl_shader_init(&(*cursor)->shaderMono))
{
DEBUG_ERROR("Failed to initialize the cursor mono shader");
return false;
}
if (!egl_shader_compile(
(*cursor)->shader,
b_shader_cursor_vert , b_shader_cursor_vert_size,
b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size))
{
DEBUG_ERROR("Failed to compile the cursor shader");
return false;
}
if (!egl_shader_compile(
(*cursor)->shaderMono,
b_shader_cursor_vert , b_shader_cursor_vert_size,
b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size))
{
DEBUG_ERROR("Failed to compile the cursor mono shader");
return false;
}
(*cursor)->uMousePos = egl_shader_get_uniform_location((*cursor)->shader , "mouse");
(*cursor)->uMousePosMono = egl_shader_get_uniform_location((*cursor)->shaderMono, "mouse");
if (!egl_model_init(&(*cursor)->model))
{
@@ -128,6 +146,9 @@ bool egl_cursor_init(EGL_Cursor ** cursor)
}
egl_model_set_default((*cursor)->model);
(*cursor)->cbMode = option_get_int("egl", "cbMode");
return true;
}
@@ -140,24 +161,23 @@ void egl_cursor_free(EGL_Cursor ** cursor)
if ((*cursor)->data)
free((*cursor)->data);
egl_texture_free(&(*cursor)->texture );
egl_texture_free(&(*cursor)->textureMono);
egl_shader_free (&(*cursor)->shader );
egl_shader_free (&(*cursor)->shaderMono );
egl_model_free (&(*cursor)->model );
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)
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;
cursor->stride = stride;
const size_t size = height * stride;
if (size > cursor->dataSize)
@@ -195,7 +215,7 @@ void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x
cursor->y = y;
}
void egl_cursor_render(EGL_Cursor * cursor)
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
{
if (!cursor->visible)
return;
@@ -207,30 +227,16 @@ void egl_cursor_render(EGL_Cursor * cursor)
uint8_t * data = cursor->data;
// tmp buffer for masked colour
uint32_t tmp[cursor->width * cursor->height];
switch(cursor->type)
{
case LG_CURSOR_MASKED_COLOR:
{
for(int i = 0; i < cursor->width * cursor->height; ++i)
{
const uint32_t c = ((uint32_t *)data)[i];
tmp[i] = (c & ~0xFF000000) | (c & 0xFF000000 ? 0x0 : 0xFF000000);
}
data = (uint8_t *)tmp;
// fall through to LG_CURSOR_COLOR
//
// technically we should also create an XOR texture from the data but this
// usage seems very rare in modern software.
}
// fall through
case LG_CURSOR_COLOR:
{
egl_texture_setup(cursor->texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false);
egl_texture_update(cursor->texture, data);
egl_model_set_texture(cursor->model, cursor->texture);
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;
}
@@ -252,43 +258,54 @@ void egl_cursor_render(EGL_Cursor * cursor)
xor[y * cursor->width + x] = xorMask;
}
egl_texture_setup (cursor->texture , EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false);
egl_texture_setup (cursor->textureMono, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false);
egl_texture_update(cursor->texture , (uint8_t *)and);
egl_texture_update(cursor->textureMono, (uint8_t *)xor);
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);
}
if (cursor->type == LG_CURSOR_MONOCHROME)
cursor->rotate = rotate;
glEnable(GL_BLEND);
switch(cursor->type)
{
glEnable(GL_BLEND);
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->shader);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
egl_model_set_texture(cursor->model, cursor->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;
}
egl_shader_use(cursor->shaderMono);
glUniform4f(cursor->uMousePosMono, cursor->x, cursor->y, cursor->w, cursor->h / 2);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_set_texture(cursor->model, cursor->textureMono);
egl_model_render(cursor->model);
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;
}
glDisable(GL_BLEND);
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;
}
}
else
{
glEnable(GL_BLEND);
egl_shader_use(cursor->shader);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
egl_model_render(cursor->model);
glDisable(GL_BLEND);
}
}
glDisable(GL_BLEND);
}

View File

@@ -28,7 +28,17 @@ 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);
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

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -20,7 +20,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "desktop.h"
#include "common/debug.h"
#include "common/option.h"
#include "utils.h"
#include "common/locking.h"
#include "texture.h"
#include "shader.h"
@@ -34,39 +34,40 @@ Place, Suite 330, Boston, MA 02111-1307 USA
// these headers are auto generated by cmake
#include "desktop.vert.h"
#include "desktop_rgb.frag.h"
#include "desktop_yuv.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;
struct DesktopShader shader_yuv;
// internals
LG_Lock updateLock;
enum EGL_PixelFormat pixFmt;
unsigned int width, height;
unsigned int pitch;
const uint8_t * data;
bool update;
// night vision
KeybindHandle kbNV;
int nvMax;
int nvGain;
// colorblind mode
int cbMode;
};
// forwards
@@ -90,14 +91,16 @@ static bool egl_init_desktop_shader(
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)
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
{
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
if (!*desktop)
@@ -107,8 +110,9 @@ bool egl_desktop_init(EGL_Desktop ** desktop)
}
memset(*desktop, 0, sizeof(EGL_Desktop));
(*desktop)->display = display;
if (!egl_texture_init(&(*desktop)->texture))
if (!egl_texture_init(&(*desktop)->texture, display))
{
DEBUG_ERROR("Failed to initialize the desktop texture");
return false;
@@ -123,15 +127,6 @@ bool egl_desktop_init(EGL_Desktop ** desktop)
return false;
}
if (!egl_init_desktop_shader(
&(*desktop)->shader_yuv,
b_shader_desktop_vert , b_shader_desktop_vert_size,
b_shader_desktop_yuv_frag, b_shader_desktop_yuv_frag_size))
{
DEBUG_ERROR("Failed to initialize the yuv desktop shader");
return false;
}
if (!egl_model_init(&(*desktop)->model))
{
DEBUG_ERROR("Failed to initialize the desktop model");
@@ -141,12 +136,11 @@ bool egl_desktop_init(EGL_Desktop ** desktop)
egl_model_set_default((*desktop)->model);
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
LG_LOCK_INIT((*desktop)->updateLock);
(*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop);
(*desktop)->kbNV = app_register_keybind(KEY_N, egl_desktop_toggle_nv, *desktop);
(*desktop)->nvMax = option_get_int("egl", "nvGainMax");
(*desktop)->nvGain = option_get_int("egl", "nvGain" );
(*desktop)->cbMode = option_get_int("egl", "cbMode" );
return true;
}
@@ -168,11 +162,8 @@ void egl_desktop_free(EGL_Desktop ** desktop)
if (!*desktop)
return;
LG_LOCK_FREE((*desktop)->updateLock);
egl_texture_free(&(*desktop)->texture );
egl_shader_free (&(*desktop)->shader_generic.shader);
egl_shader_free (&(*desktop)->shader_yuv.shader );
egl_model_free (&(*desktop)->model );
app_release_keybind(&(*desktop)->kbNV);
@@ -181,94 +172,107 @@ void egl_desktop_free(EGL_Desktop ** desktop)
*desktop = NULL;
}
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const uint8_t * data)
bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA)
{
if (sourceChanged)
enum EGL_PixelFormat pixFmt;
switch(format.type)
{
LG_LOCK(desktop->updateLock);
switch(format.type)
{
case FRAME_TYPE_BGRA:
desktop->pixFmt = EGL_PF_BGRA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_BGRA:
pixFmt = EGL_PF_BGRA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA:
desktop->pixFmt = EGL_PF_RGBA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA:
pixFmt = EGL_PF_RGBA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA10:
desktop->pixFmt = EGL_PF_RGBA10;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA10:
pixFmt = EGL_PF_RGBA10;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_YUV420:
desktop->pixFmt = EGL_PF_YUV420;
desktop->shader = &desktop->shader_yuv;
break;
case FRAME_TYPE_RGBA16F:
pixFmt = EGL_PF_RGBA16F;
desktop->shader = &desktop->shader_generic;
break;
default:
DEBUG_ERROR("Unsupported frame format");
LG_UNLOCK(desktop->updateLock);
return false;
}
desktop->width = format.width;
desktop->height = format.height;
desktop->pitch = format.pitch;
desktop->data = data;
desktop->update = true;
/* defer the actual update as the format has changed and we need to issue GL commands first */
LG_UNLOCK(desktop->updateLock);
return true;
default:
DEBUG_ERROR("Unsupported frame format");
return false;
}
/* update the texture now */
return egl_texture_update(desktop->texture, data);
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;
}
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd)
{
if (sourceChanged)
if (dmaFd >= 0)
{
LG_LOCK(desktop->updateLock);
if (!egl_texture_setup(
desktop->texture,
desktop->pixFmt,
desktop->width,
desktop->height,
desktop->pitch,
true // streaming texture
))
{
DEBUG_ERROR("Failed to setup the desktop texture");
LG_UNLOCK(desktop->updateLock);
return;
}
LG_UNLOCK(desktop->updateLock);
if (!egl_texture_update_from_dma(desktop->texture, frame, dmaFd))
return false;
}
else
{
if (!egl_texture_update_from_frame(desktop->texture, frame))
return false;
}
if (desktop->update)
enum EGL_TexStatus status;
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
{
desktop->update = false;
egl_texture_update(desktop->texture, desktop->data);
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)
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;
if (egl_texture_process(desktop->texture) != EGL_TEX_STATUS_OK)
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->uNearest , nearest ? 1 : 0);
glUniform1i(shader->uRotate , rotate);
glUniform1i(shader->uNearest , useNearest ? 1 : 0);
glUniform2f(shader->uDesktopSize, desktop->width, desktop->height);
if (desktop->nvGain)
@@ -279,6 +283,7 @@ bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, con
else
glUniform1i(shader->uNV, 0);
glUniform1i(shader->uCBMode, desktop->cbMode);
egl_model_render(desktop->model);
return true;
}
}

View File

@@ -20,14 +20,17 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include <stdbool.h>
#include <SDL2/SDL_egl.h>
#include "interface/renderer.h"
typedef struct EGL_Desktop EGL_Desktop;
bool egl_desktop_init(EGL_Desktop ** desktop);
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display);
void egl_desktop_free(EGL_Desktop ** desktop);
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const uint8_t * data);
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged);
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);
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,32 @@
/*
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 "dynprocs.h"
struct EGLDynProcs g_dynprocs = {0};
void egl_dynProcsInit(void)
{
g_dynprocs.eglGetPlatformDisplay = (eglGetPlatformDisplayEXT_t)
eglGetProcAddress("eglGetPlatformDisplay");
g_dynprocs.eglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_t)
eglGetProcAddress("eglGetPlatformDisplayEXT");
g_dynprocs.glEGLImageTargetTexture2DOES = (glEGLImageTargetTexture2DOES_t)
eglGetProcAddress("glEGLImageTargetTexture2DOES");
};

View File

@@ -0,0 +1,37 @@
/*
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 <SDL2/SDL_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_dynprocs;
void egl_dynProcsInit(void);

View File

@@ -22,6 +22,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/debug.h"
#include "common/option.h"
#include "common/sysinfo.h"
#include "common/time.h"
#include "common/locking.h"
#include "utils.h"
#include "dynamic/fonts.h"
@@ -32,6 +34,10 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <wayland-egl.h>
#endif
#include <assert.h>
#include "app.h"
#include "dynprocs.h"
#include "model.h"
#include "shader.h"
#include "desktop.h"
@@ -46,19 +52,20 @@ Place, Suite 330, Boston, MA 02111-1307 USA
struct Options
{
bool vsync;
bool doubleBuffer;
};
struct Inst
{
bool dmaSupport;
LG_RendererParams params;
struct Options opt;
EGLNativeDisplayType nativeDisp;
EGLNativeWindowType nativeWind;
EGLDisplay display;
EGLConfig configs;
EGLSurface surface;
EGLContext context;
EGLContext context, frameContext;
EGL_Desktop * desktop; // the desktop
EGL_Cursor * cursor; // the mouse cursor
@@ -67,7 +74,8 @@ struct Inst
EGL_Alert * alert; // the alert display
LG_RendererFormat format;
bool sourceChanged;
bool formatValid;
bool start;
uint64_t waitFadeTime;
bool waitDone;
@@ -76,8 +84,9 @@ struct Inst
bool useCloseFlag;
bool closeFlag;
int width, height;
LG_RendererRect destRect;
int width, height;
LG_RendererRect destRect;
LG_RendererRotate rotate; //client side rotation
float translateX , translateY;
float scaleX , scaleY;
@@ -94,7 +103,6 @@ struct Inst
LG_FontObj fontObj;
};
static struct Option egl_options[] =
{
{
@@ -102,14 +110,14 @@ static struct Option egl_options[] =
.name = "vsync",
.description = "Enable vsync",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
.value.x_bool = false,
},
{
.module = "egl",
.name = "doubleBuffer",
.description = "Enable double buffering",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
.value.x_bool = false
},
{
.module = "egl",
@@ -132,23 +140,41 @@ static struct Option egl_options[] =
.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()
const char * egl_get_name(void)
{
return "EGL";
}
void egl_setup()
void egl_setup(void)
{
option_register(egl_options);
}
bool egl_create(void ** opaque, const LG_RendererParams params)
{
// Fail if running on Wayland so that OpenGL is used instead. Wayland-EGL
// is broken (https://github.com/gnif/LookingGlass/issues/306) and isn't
// fixable until SDL is dropped entirely. Until then, the OpenGL renderer
// "mostly works".
if (getenv("WAYLAND_DISPLAY"))
return false;
// check if EGL is even available
if (!eglQueryString(EGL_NO_DISPLAY, EGL_VERSION))
return false;
// create our local storage
*opaque = malloc(sizeof(struct Inst));
if (!*opaque)
@@ -162,7 +188,8 @@ bool egl_create(void ** opaque, const LG_RendererParams params)
struct Inst * this = (struct Inst *)*opaque;
memcpy(&this->params, &params, sizeof(LG_RendererParams));
this->opt.vsync = option_get_bool("egl", "vsync");
this->opt.vsync = option_get_bool("egl", "vsync");
this->opt.doubleBuffer = option_get_bool("egl", "doubleBuffer");
this->translateX = 0;
this->translateY = 0;
@@ -183,27 +210,8 @@ bool egl_create(void ** opaque, const LG_RendererParams params)
bool egl_initialize(void * opaque, Uint32 * sdlFlags)
{
const bool doubleBuffer = option_get_bool("egl", "doubleBuffer");
DEBUG_INFO("Double buffering is %s", doubleBuffer ? "on" : "off");
*sdlFlags = SDL_WINDOW_OPENGL;
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , doubleBuffer ? 1 : 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
if (option_get_bool("egl", "multisample"))
{
int maxSamples = sysinfo_gfx_max_multisample();
if (maxSamples > 1)
{
if (maxSamples > 4)
maxSamples = 4;
DEBUG_INFO("Multsampling enabled, max samples: %d", maxSamples);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples);
}
}
struct Inst * this = (struct Inst *)opaque;
DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off");
return true;
}
@@ -220,15 +228,130 @@ void egl_deinitialize(void * opaque)
egl_splash_free (&this->splash);
egl_alert_free (&this->alert );
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);
}
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
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);
@@ -241,28 +364,21 @@ void egl_on_resize(void * opaque, const int width, const int height, const LG_Re
this->scaleY = (float)destRect.h / (float)height;
}
this->mouseScaleX = 2.0f / this->format.width ;
this->mouseScaleY = 2.0f / this->format.height;
egl_cursor_set_size(this->cursor,
(this->mouseWidth * (1.0f / this->format.width )) * this->scaleX,
(this->mouseHeight * (1.0f / this->format.height)) * this->scaleY
);
egl_calc_mouse_size(this);
this->splashRatio = (float)width / (float)height;
this->screenScaleX = 1.0f / width;
this->screenScaleY = 1.0f / height;
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
);
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)
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");
@@ -271,10 +387,7 @@ bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int
this->mouseWidth = width;
this->mouseHeight = height;
egl_cursor_set_size(this->cursor,
(this->mouseWidth * (1.0f / this->format.width )) * this->scaleX,
(this->mouseHeight * (1.0f / this->format.height)) * this->scaleY
);
egl_calc_mouse_size(this);
return true;
}
@@ -285,39 +398,52 @@ bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const in
this->cursorVisible = visible;
this->cursorX = x;
this->cursorY = y;
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
);
egl_calc_mouse_state(this);
return true;
}
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
bool egl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
{
struct Inst * this = (struct Inst *)opaque;
this->sourceChanged = (
this->sourceChanged ||
this->format.type != format.type ||
this->format.width != format.width ||
this->format.height != format.height ||
this->format.pitch != format.pitch
);
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->formatValid = true;
if (this->sourceChanged)
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
/* 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);
}
if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, data))
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 prepare to update the desktop");
DEBUG_INFO("Failed to to update the desktop");
return false;
}
this->start = true;
return true;
}
@@ -368,22 +494,28 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false;
}
egl_dynProcsInit();
EGLNativeDisplayType native;
EGLenum platform;
switch(wminfo.subsystem)
{
case SDL_SYSWM_X11:
{
this->nativeDisp = (EGLNativeDisplayType)wminfo.info.x11.display;
native = (EGLNativeDisplayType)wminfo.info.x11.display;
platform = EGL_PLATFORM_X11_KHR;
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
break;
}
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
case SDL_SYSWM_WAYLAND:
{
int width, height;
SDL_GetWindowSize(window, &width, &height);
this->nativeDisp = (EGLNativeDisplayType)wminfo.info.wl.display;
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height);
native = (EGLNativeDisplayType)wminfo.info.wl.display;
platform = EGL_PLATFORM_WAYLAND_KHR;
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(
wminfo.info.wl.surface, width, height);
break;
}
#endif
@@ -393,25 +525,56 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false;
}
this->display = eglGetDisplay(this->nativeDisp);
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
if (strstr(early_exts, "EGL_KHR_platform_base") != NULL &&
g_dynprocs.eglGetPlatformDisplay)
{
DEBUG_INFO("Using eglGetPlatformDisplay");
this->display = g_dynprocs.eglGetPlatformDisplay(platform, native, NULL);
}
else if (strstr(early_exts, "EGL_EXT_platform_base") != NULL &&
g_dynprocs.eglGetPlatformDisplayEXT)
{
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
this->display = g_dynprocs.eglGetPlatformDisplayEXT(platform, native, NULL);
}
else
{
DEBUG_INFO("Using eglGetDisplay");
this->display = eglGetDisplay(native);
}
if (this->display == EGL_NO_DISPLAY)
{
DEBUG_ERROR("eglGetDisplay failed");
return false;
}
if (!eglInitialize(this->display, NULL, NULL))
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 , 32,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SAMPLE_BUFFERS , 1,
EGL_SAMPLES , 4,
EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0,
EGL_SAMPLES , maxSamples,
EGL_NONE
};
@@ -422,7 +585,13 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false;
}
this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, NULL);
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());
@@ -442,15 +611,60 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false;
}
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
EGLint rb = 0;
eglQuerySurface(this->display, this->surface, EGL_RENDER_BUFFER, &rb);
switch(rb)
{
case EGL_SINGLE_BUFFER:
DEBUG_INFO("Single buffer mode");
break;
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
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_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))
if (!egl_desktop_init(&this->desktop, this->display))
{
DEBUG_ERROR("Failed to initialize the desktop");
return false;
@@ -483,18 +697,29 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return true;
}
bool egl_render(void * opaque, SDL_Window * window)
bool egl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
{
struct Inst * this = (struct Inst *)opaque;
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX, this->scaleY, this->useNearest))
if (this->start && egl_desktop_render(this->desktop,
this->translateX, this->translateY,
this->scaleX , this->scaleY ,
this->useNearest,
rotate))
{
if (!this->waitFadeTime)
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
egl_cursor_render(this->cursor);
{
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)
@@ -517,6 +742,11 @@ bool egl_render(void * opaque, SDL_Window * window)
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)
{
@@ -534,11 +764,6 @@ bool egl_render(void * opaque, SDL_Window * window)
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
eglSwapBuffers(this->display, this->surface);
// defer texture uploads until after the flip to avoid stalling
egl_desktop_perform_update(this->desktop, this->sourceChanged);
this->sourceChanged = false;
return true;
}
@@ -553,17 +778,20 @@ void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
struct LG_Renderer LGR_EGL =
{
.get_name = egl_get_name,
.setup = egl_setup,
.create = egl_create,
.initialize = egl_initialize,
.deinitialize = egl_deinitialize,
.on_resize = egl_on_resize,
.on_mouse_shape = egl_on_mouse_shape,
.on_mouse_event = egl_on_mouse_event,
.on_frame_event = egl_on_frame_event,
.on_alert = egl_on_alert,
.render_startup = egl_render_startup,
.render = egl_render,
.update_fps = egl_update_fps
};
.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,
.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 <SDL2/SDL_egl.h>
#include <GL/gl.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())

View File

@@ -44,6 +44,7 @@ struct EGL_FPS
EGL_Model * model;
bool ready;
int iwidth, iheight;
float width, height;
// uniforms
@@ -65,7 +66,7 @@ bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj)
(*fps)->font = font;
(*fps)->fontObj = fontObj;
if (!egl_texture_init(&(*fps)->texture))
if (!egl_texture_init(&(*fps)->texture, NULL))
{
DEBUG_ERROR("Failed to initialize the fps texture");
return false;
@@ -144,14 +145,23 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
return;
}
egl_texture_setup(
fps->texture,
EGL_PF_BGRA,
bmp->width ,
bmp->height,
bmp->width * bmp->bpp,
false
);
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
(
@@ -159,10 +169,7 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
bmp->pixels
);
fps->width = bmp->width;
fps->height = bmp->height;
fps->ready = true;
fps->font->release(fps->fontObj, bmp);
}
@@ -187,4 +194,4 @@ void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
egl_model_render(fps->model);
glDisable(GL_BLEND);
}
}

View File

@@ -4,22 +4,40 @@ 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()
{
gl_Position.xyz = vertexPosition_modelspace;
gl_Position.w = 1.0;
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;
gl_Position.x += 1.0f;
gl_Position.y -= 1.0f;
gl_Position.x *= mouse.z;
gl_Position.y *= mouse.w;
gl_Position.x += mouse.x;
gl_Position.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

@@ -5,7 +5,47 @@ 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

@@ -7,16 +7,76 @@ 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, uv);
color = texture(sampler1, ruv);
else
color = texelFetch(sampler1, ivec2(uv * size), 0);
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)
{

View File

@@ -1,53 +0,0 @@
#version 300 es
in highp vec2 uv;
out highp vec4 color;
uniform int nearest;
uniform highp vec2 size;
uniform int nv;
uniform highp float nvGain;
uniform sampler2D sampler1;
uniform sampler2D sampler2;
uniform sampler2D sampler3;
void main()
{
highp vec4 yuv;
if(nearest == 1)
{
yuv = vec4(
texture(sampler1, uv).r,
texture(sampler2, uv).r,
texture(sampler3, uv).r,
1.0
);
}
else
{
highp ivec2 px = ivec2(uv * size);
yuv = vec4(
texelFetch(sampler1, px, 0).r,
texelFetch(sampler2, px, 0).r,
texelFetch(sampler3, px, 0).r,
1.0
);
}
highp mat4 yuv_to_rgb = mat4(
1.0, 0.0 , 1.402, -0.701,
1.0, -0.344, -0.714, 0.529,
1.0, 1.772, 0.0 , -0.886,
1.0, 1.0 , 1.0 , 1.0
);
color = yuv * yuv_to_rgb;
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;
}
}

View File

@@ -154,8 +154,12 @@ void egl_splash_free(EGL_Splash ** splash)
if (!*splash)
return;
egl_model_free(&(*splash)->bg );
egl_model_free(&(*splash)->logo);
egl_shader_free(&(*splash)->bgShader );
egl_shader_free(&(*splash)->logoShader);
free(*splash);
*splash = NULL;
}
@@ -174,4 +178,4 @@ void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY)
egl_model_render(splash->logo);
glDisable(GL_BLEND);
}
}

View File

@@ -19,41 +19,70 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "texture.h"
#include "common/debug.h"
#include "common/framebuffer.h"
#include "dynprocs.h"
#include "utils.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')
#include <SDL2/SDL_egl.h>
struct EGL_Texture
/* this must be a multiple of 2 */
#define BUFFER_COUNT 4
struct Buffer
{
enum EGL_PixelFormat pixFmt;
size_t width, height;
bool streaming;
bool ready;
int textureCount;
GLuint textures[3];
GLuint samplers[3];
size_t planes[3][3];
GLintptr offsets[3];
GLenum intFormat;
GLenum format;
GLenum dataType;
bool hasPBO;
GLuint pbo[2];
int pboRIndex;
int pboWIndex;
int pboCount;
size_t pboBufferSize;
void * pboMap[2];
GLsync pboSync[2];
bool hasPBO;
GLuint pbo;
void * map;
GLsync sync;
};
bool egl_texture_init(EGL_Texture ** texture)
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];
};
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display)
{
*texture = (EGL_Texture *)malloc(sizeof(EGL_Texture));
if (!*texture)
@@ -63,7 +92,7 @@ bool egl_texture_init(EGL_Texture ** texture)
}
memset(*texture, 0, sizeof(EGL_Texture));
(*texture)->display = display;
return true;
}
@@ -72,95 +101,131 @@ void egl_texture_free(EGL_Texture ** texture)
if (!*texture)
return;
if ((*texture)->textureCount > 0)
{
glDeleteTextures((*texture)->textureCount, (*texture)->textures);
glDeleteSamplers((*texture)->textureCount, (*texture)->samplers);
}
glDeleteSamplers(1, &(*texture)->sampler);
if ((*texture)->hasPBO)
for(int i = 0; i < (*texture)->bufferCount; ++i)
{
for(int i = 0; i < 2; ++i)
struct Buffer * b = &(*texture)->buf[i];
if (b->hasPBO)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, (*texture)->pbo[i]);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
if ((*texture)->pboSync[i])
glDeleteSync((*texture)->pboSync[i]);
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);
glDeleteBuffers(2, (*texture)->pbo);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glDeleteTextures(1, &(*texture)->tex);
free(*texture);
*texture = NULL;
}
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming)
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
{
int textureCount;
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);
texture->pixFmt = pixFmt;
texture->width = width;
texture->height = height;
texture->streaming = streaming;
texture->ready = false;
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:
textureCount = 1;
texture->bpp = 4;
texture->format = GL_BGRA;
texture->planes[0][0] = width;
texture->planes[0][1] = height;
texture->planes[0][2] = stride / 4;
texture->offsets[0] = 0;
texture->intFormat = GL_BGRA;
texture->dataType = GL_UNSIGNED_BYTE;
texture->fourcc = DRM_FORMAT_ARGB8888;
texture->pboBufferSize = height * stride;
break;
case EGL_PF_RGBA:
textureCount = 1;
texture->bpp = 4;
texture->format = GL_RGBA;
texture->planes[0][0] = width;
texture->planes[0][1] = height;
texture->planes[0][2] = stride / 4;
texture->offsets[0] = 0;
texture->intFormat = GL_BGRA;
texture->dataType = GL_UNSIGNED_BYTE;
texture->fourcc = DRM_FORMAT_ABGR8888;
texture->pboBufferSize = height * stride;
break;
case EGL_PF_RGBA10:
textureCount = 1;
texture->bpp = 4;
texture->format = GL_RGBA;
texture->planes[0][0] = width;
texture->planes[0][1] = height;
texture->planes[0][2] = stride / 4;
texture->offsets[0] = 0;
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_YUV420:
textureCount = 3;
texture->format = GL_RED;
texture->planes[0][0] = width;
texture->planes[0][1] = height;
texture->planes[0][2] = stride;
texture->planes[1][0] = width / 2;
texture->planes[1][1] = height / 2;
texture->planes[1][2] = stride / 2;
texture->planes[2][0] = width / 2;
texture->planes[2][1] = height / 2;
texture->planes[2][2] = stride / 2;
texture->offsets[0] = 0;
texture->offsets[1] = stride * height;
texture->offsets[2] = texture->offsets[1] + (texture->offsets[1] / 4);
texture->dataType = GL_UNSIGNED_BYTE;
texture->pboBufferSize = texture->offsets[2] + (texture->offsets[1] / 4);
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:
@@ -168,198 +233,273 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
return false;
}
if (textureCount > texture->textureCount)
{
if (texture->textureCount > 0)
{
glDeleteTextures(texture->textureCount, texture->textures);
glDeleteSamplers(texture->textureCount, texture->samplers);
}
texture->pitch = stride / texture->bpp;
texture->textureCount = textureCount;
glGenTextures(texture->textureCount, texture->textures);
glGenSamplers(texture->textureCount, texture->samplers);
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);
}
for(int i = 0; i < textureCount; ++i)
{
glSamplerParameteri(texture->samplers[i], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(texture->samplers[i], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(texture->samplers[i], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glSamplerParameteri(texture->samplers[i], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
if (useDMA)
return true;
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->planes[i][0], texture->planes[i][1],
0, texture->format, texture->dataType, NULL);
}
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)
if (!streaming)
return true;
for(int i = 0; i < texture->bufferCount; ++i)
{
if (texture->hasPBO)
{
// release old PBOs and delete the buffers
for(int i = 0; i < 2; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
glDeleteBuffers(2, texture->pbo);
}
glGenBuffers(1, &texture->buf[i].pbo);
texture->buf[i].hasPBO = true;
glGenBuffers(2, texture->pbo);
texture->hasPBO = true;
for(int i = 0; i < 2; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]);
glBufferStorage(
GL_PIXEL_UNPACK_BUFFER,
texture->pboBufferSize,
NULL,
GL_MAP_PERSISTENT_BIT |
GL_MAP_WRITE_BIT
);
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
);
texture->pboMap[i] = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_PERSISTENT_BIT |
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT |
GL_MAP_FLUSH_EXPLICIT_BIT
);
if (!texture->pboMap[i])
{
DEBUG_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
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)
{
/* NOTE: DO NOT use any gl commands here as streaming must be thread safe */
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
if (texture->pboCount == 2)
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true;
}
/* update the GPU buffer */
memcpy(texture->pboMap[texture->pboWIndex], buffer, texture->pboBufferSize);
texture->pboSync[texture->pboWIndex] = 0;
if (++texture->pboWIndex == 2)
texture->pboWIndex = 0;
++texture->pboCount;
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
{
/* Non streaming, this is NOT thread safe */
for(int i = 0; i < texture->textureCount; ++i)
{
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[i][0]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[i][0], texture->planes[i][1],
texture->format, texture->dataType, buffer + texture->offsets[i]);
}
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;
}
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 */
EGLImage 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;
}
/* bind the texture and initiate the transfer */
glBindTexture(GL_TEXTURE_2D, texture->tex);
g_dynprocs.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
/* wait for completion */
framebuffer_wait(frame, texture->height * texture->stride);
/* destroy the image to prevent future writes corrupting the display image */
eglDestroyImage(texture->display, image);
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;
if (texture->pboCount == 0)
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;
/* process any buffers that have not yet been flushed */
int pos = texture->pboRIndex;
for(int i = 0; i < texture->pboCount; ++i)
const uint8_t b = su % BUFFER_COUNT;
/* update the texture */
if (!texture->dma)
{
if (texture->pboSync[pos] == 0)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[pos]);
glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texture->pboBufferSize);
texture->pboSync[pos] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
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);
if (++pos == 2)
pos = 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();
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
/* wait for the buffer to be ready */
pos = texture->pboRIndex;
switch(glClientWaitSync(texture->pboSync[pos], GL_SYNC_FLUSH_COMMANDS_BIT, 0))
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
break;
case GL_TIMEOUT_EXPIRED:
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
case GL_WAIT_FAILED:
glDeleteSync(texture->pboSync[pos]);
DEBUG_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
/* delete the sync and bind the buffer */
glDeleteSync(texture->pboSync[pos]);
texture->pboSync[pos] = 0;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[pos]);
/* update the textures */
for(int i = 0; i < texture->textureCount; ++i)
{
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[i][2]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[i][0], texture->planes[i][1],
texture->format, texture->dataType, (const void *)texture->offsets[i]);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
/* advance the read index */
if (++texture->pboRIndex == 2)
texture->pboRIndex = 0;
--texture->pboCount;
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)
{
/* if there are no new buffers ready, then just bind the textures */
if (texture->streaming && !texture->ready)
return EGL_TEX_STATUS_NOTREADY;
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
for(int i = 0; i < texture->textureCount; ++i)
if (texture->streaming)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glBindSampler(i, texture->samplers[i]);
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 texture->textureCount;
}
return 1;
}

View File

@@ -21,7 +21,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h>
#include "shader.h"
#include "common/framebuffer.h"
#include <SDL2/SDL_egl.h>
#include <GL/gl.h>
typedef struct EGL_Texture EGL_Texture;
@@ -31,6 +33,7 @@ enum EGL_PixelFormat
EGL_PF_RGBA,
EGL_PF_BGRA,
EGL_PF_RGBA10,
EGL_PF_RGBA16F,
EGL_PF_YUV420
};
@@ -41,11 +44,13 @@ enum EGL_TexStatus
EGL_TEX_STATUS_ERROR
};
bool egl_texture_init(EGL_Texture ** tex);
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 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);
int egl_texture_count (EGL_Texture * texture);

View File

@@ -4,7 +4,6 @@ project(renderer_Opengl LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(RENDERER_OPENGL_PKGCONFIG REQUIRED
gl
glu
)
add_library(renderer_OpenGL STATIC
@@ -14,7 +13,6 @@ add_library(renderer_OpenGL STATIC
target_link_libraries(renderer_OpenGL
${RENDERER_OPENGL_PKGCONFIG_LIBRARIES}
lg_common
decoders
fonts
)

View File

@@ -27,13 +27,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <SDL2/SDL_ttf.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#include "common/debug.h"
#include "common/option.h"
#include "utils.h"
#include "lg-decoders.h"
#include "common/framebuffer.h"
#include "common/locking.h"
#include "dynamic/fonts.h"
#include "ll.h"
@@ -62,7 +61,7 @@ static struct Option opengl_options[] =
.name = "vsync",
.description = "Enable vsync",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
.value.x_bool = false,
},
{
.module = "opengl",
@@ -77,7 +76,8 @@ static struct Option opengl_options[] =
.description = "Use GL_AMD_pinned_memory if it is available",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
}
},
{0}
};
struct OpenGL_Options
@@ -116,14 +116,14 @@ struct Inst
const LG_Font * font;
LG_FontObj fontObj, alertFontObj;
LG_Lock formatLock;
LG_RendererFormat format;
GLuint intFormat;
GLuint vboFormat;
GLuint dataFormat;
size_t texSize;
const LG_Decoder* decoder;
void * decoderData;
LG_Lock formatLock;
LG_RendererFormat format;
GLuint intFormat;
GLuint vboFormat;
GLuint dataFormat;
size_t texSize;
size_t texPos;
const FrameBuffer * frame;
uint64_t drawStart;
bool hasBuffers;
@@ -140,7 +140,6 @@ struct Inst
bool hasTextures, hasFrames;
GLuint frames[BUFFER_COUNT];
GLsync fences[BUFFER_COUNT];
void * decoderFrames[BUFFER_COUNT];
GLuint textures[TEXTURE_COUNT];
struct ll * alerts;
int alertList;
@@ -170,19 +169,26 @@ struct Inst
static bool _check_gl_error(unsigned int line, const char * name);
#define check_gl_error(name) _check_gl_error(__LINE__, name)
enum ConfigStatus
{
CONFIG_STATUS_OK,
CONFIG_STATUS_ERROR,
CONFIG_STATUS_NOOP
};
static void deconfigure(struct Inst * this);
static bool configure(struct Inst * this, SDL_Window *window);
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window);
static void update_mouse_shape(struct Inst * this, bool * newShape);
static bool draw_frame(struct Inst * this);
static void draw_mouse(struct Inst * this);
static void render_wait(struct Inst * this);
const char * opengl_get_name()
const char * opengl_get_name(void)
{
return "OpenGL";
}
static void opengl_setup()
static void opengl_setup(void)
{
option_register(opengl_options);
}
@@ -242,6 +248,9 @@ bool opengl_initialize(void * opaque, Uint32 * sdlFlags)
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE , 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8);
return true;
}
@@ -288,7 +297,14 @@ void opengl_deinitialize(void * opaque)
free(this);
}
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
void opengl_on_restart(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
this->waiting = true;
}
void opengl_on_resize(void * opaque, const int width, const int height,
const LG_RendererRect destRect, LG_RendererRotate rotate)
{
struct Inst * this = (struct Inst *)opaque;
@@ -302,7 +318,7 @@ void opengl_on_resize(void * opaque, const int width, const int height, const LG
glViewport(0, 0, this->window.x, this->window.y);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, this->window.x, this->window.y, 0);
glOrtho(0, this->window.x, this->window.y, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
@@ -318,7 +334,8 @@ void opengl_on_resize(void * opaque, const int width, const int height, const LG
}
}
bool opengl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
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)
@@ -362,51 +379,36 @@ bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const
return false;
}
bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
bool opengl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
{
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 ||
this->format.type != format.type ||
this->format.width != format.width ||
this->format.height != format.height ||
this->format.stride != format.stride ||
this->format.bpp != format.bpp
)
{
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->reconfigure = true;
LG_UNLOCK(this->formatLock);
return true;
}
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->reconfigure = true;
LG_UNLOCK(this->formatLock);
return true;
}
bool opengl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd)
{
struct Inst * this = (struct Inst *)opaque;
LG_LOCK(this->syncLock);
if (!this->decoder->decode(this->decoderData, data, format.pitch))
{
DEBUG_ERROR("decode returned failure");
LG_UNLOCK(this->syncLock);
return false;
}
this->frame = frame;
this->frameUpdate = true;
LG_UNLOCK(this->syncLock);
if (this->waiting)
{
this->waiting = false;
this->waitFadeTime = microtime() + FADE_TIME;
this->waiting = false;
if (!this->params.quickSplash)
this->waitFadeTime = microtime() + FADE_TIME;
else
{
glDisable(GL_MULTISAMPLE);
this->waitDone = true;
}
}
return true;
@@ -549,16 +551,24 @@ bool opengl_render_startup(void * opaque, SDL_Window * window)
return true;
}
bool opengl_render(void * opaque, SDL_Window * window)
bool opengl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
return false;
if (configure(this, window))
if (!draw_frame(this))
switch(configure(this, window))
{
case CONFIG_STATUS_ERROR:
DEBUG_ERROR("configure failed");
return false;
case CONFIG_STATUS_NOOP :
case CONFIG_STATUS_OK :
if (!draw_frame(this))
return false;
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
@@ -807,20 +817,22 @@ static void render_wait(struct Inst * this)
const LG_Renderer LGR_OpenGL =
{
.get_name = opengl_get_name,
.setup = opengl_setup,
.get_name = opengl_get_name,
.setup = opengl_setup,
.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,
.on_alert = opengl_on_alert,
.render_startup = opengl_render_startup,
.render = opengl_render,
.update_fps = opengl_update_fps
.create = opengl_create,
.initialize = opengl_initialize,
.deinitialize = opengl_deinitialize,
.on_restart = opengl_on_restart,
.on_resize = opengl_on_resize,
.on_mouse_shape = opengl_on_mouse_shape,
.on_mouse_event = opengl_on_mouse_event,
.on_frame_format = opengl_on_frame_format,
.on_frame = opengl_on_frame,
.on_alert = opengl_on_alert,
.render_startup = opengl_render_startup,
.render = opengl_render,
.update_fps = opengl_update_fps
};
static bool _check_gl_error(unsigned int line, const char * name)
@@ -829,18 +841,51 @@ static bool _check_gl_error(unsigned int line, const char * name)
if (error == GL_NO_ERROR)
return false;
const GLubyte * errStr = gluErrorString(error);
const char * errStr;
switch (error)
{
case GL_INVALID_ENUM:
errStr = "GL_INVALID_ENUM";
break;
case GL_INVALID_VALUE:
errStr = "GL_INVALID_VALUE";
break;
case GL_INVALID_OPERATION:
errStr = "GL_INVALID_OPERATION";
break;
case GL_STACK_OVERFLOW:
errStr = "GL_STACK_OVERFLOW";
break;
case GL_STACK_UNDERFLOW:
errStr = "GL_STACK_UNDERFLOW";
break;
case GL_OUT_OF_MEMORY:
errStr = "GL_OUT_OF_MEMORY";
break;
case GL_TABLE_TOO_LARGE:
errStr = "GL_TABLE_TOO_LARGE";
break;
default:
errStr = "unknown error";
}
DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr);
return true;
}
static bool configure(struct Inst * this, SDL_Window *window)
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
{
LG_LOCK(this->formatLock);
if (!this->reconfigure)
{
LG_UNLOCK(this->formatLock);
return this->configured;
return CONFIG_STATUS_NOOP;
}
if (this->configured)
@@ -849,142 +894,107 @@ static bool configure(struct Inst * this, SDL_Window *window)
switch(this->format.type)
{
case FRAME_TYPE_BGRA:
case FRAME_TYPE_RGBA:
case FRAME_TYPE_RGBA10:
this->decoder = &LGD_NULL;
break;
case FRAME_TYPE_YUV420:
this->decoder = &LGD_YUV420;
break;
default:
DEBUG_ERROR("Unknown/unsupported compression type");
return false;
}
DEBUG_INFO("Using decoder: %s", this->decoder->name);
if (!this->decoder->create(&this->decoderData))
{
DEBUG_ERROR("Failed to create the decoder");
return false;
}
if (!this->decoder->initialize(
this->decoderData,
this->format,
window))
{
DEBUG_ERROR("Failed to initialize decoder");
return false;
}
switch(this->decoder->get_out_format(this->decoderData))
{
case LG_OUTPUT_BGRA:
this->intFormat = GL_RGBA8;
this->vboFormat = GL_BGRA;
this->dataFormat = GL_UNSIGNED_BYTE;
break;
case LG_OUTPUT_RGBA:
case FRAME_TYPE_RGBA:
this->intFormat = GL_RGBA8;
this->vboFormat = GL_RGBA;
this->dataFormat = GL_UNSIGNED_BYTE;
break;
case LG_OUTPUT_RGBA10:
case FRAME_TYPE_RGBA10:
this->intFormat = GL_RGB10_A2;
this->vboFormat = GL_RGBA;
this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV;
break;
case LG_OUTPUT_YUV420:
// fixme
this->intFormat = GL_RGBA8;
this->vboFormat = GL_BGRA;
this->dataFormat = GL_UNSIGNED_BYTE;
case FRAME_TYPE_RGBA16F:
this->intFormat = GL_RGB16F;
this->vboFormat = GL_RGBA;
this->dataFormat = GL_HALF_FLOAT;
break;
default:
DEBUG_ERROR("Format not supported");
LG_UNLOCK(this->formatLock);
return false;
DEBUG_ERROR("Unknown/unsupported compression type");
return CONFIG_STATUS_ERROR;
}
// calculate the texture size in bytes
this->texSize =
this->format.height *
this->decoder->get_frame_pitch(this->decoderData);
this->texSize = this->format.height * this->format.pitch;
this->texPos = 0;
// generate the pixel unpack buffers if the decoder isn't going to do it for us
if (!this->decoder->has_gl)
glGenBuffers(BUFFER_COUNT, this->vboID);
if (check_gl_error("glGenBuffers"))
{
glGenBuffers(BUFFER_COUNT, this->vboID);
if (check_gl_error("glGenBuffers"))
{
LG_UNLOCK(this->formatLock);
return false;
}
this->hasBuffers = true;
LG_UNLOCK(this->formatLock);
return false;
}
this->hasBuffers = true;
if (this->amdPinnedMemSupport)
{
const int pagesize = getpagesize();
this->texPixels[0] = memalign(pagesize, this->texSize * BUFFER_COUNT);
memset(this->texPixels[0], 0, this->texSize * BUFFER_COUNT);
for(int i = 1; i < BUFFER_COUNT; ++i)
this->texPixels[i] = this->texPixels[0] + this->texSize;
if (this->amdPinnedMemSupport)
{
const int pagesize = getpagesize();
for(int i = 0; i < BUFFER_COUNT; ++i)
for(int i = 0; i < BUFFER_COUNT; ++i)
{
this->texPixels[i] = aligned_alloc(pagesize, this->texSize);
if (!this->texPixels[i])
{
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return false;
}
glBufferData(
GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
this->texSize,
this->texPixels[i],
GL_STREAM_DRAW);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return false;
}
DEBUG_ERROR("Failed to allocate memory for texture");
return CONFIG_STATUS_ERROR;
}
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
}
else
{
for(int i = 0; i < BUFFER_COUNT; ++i)
memset(this->texPixels[i], 0, this->texSize);
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return false;
}
glBufferData(
GL_PIXEL_UNPACK_BUFFER,
this->texSize,
NULL,
GL_STREAM_DRAW
);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return false;
}
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
}
glBufferData(
GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
this->texSize,
this->texPixels[i],
GL_STREAM_DRAW
);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
}
else
{
for(int i = 0; i < BUFFER_COUNT; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
}
glBufferData(
GL_PIXEL_UNPACK_BUFFER,
this->texSize,
NULL,
GL_STREAM_DRAW
);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR;
}
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
// create the frame textures
@@ -992,7 +1002,7 @@ static bool configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glGenTextures"))
{
LG_UNLOCK(this->formatLock);
return false;
return CONFIG_STATUS_ERROR;
}
this->hasFrames = true;
@@ -1003,7 +1013,7 @@ static bool configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBindTexture"))
{
LG_UNLOCK(this->formatLock);
return false;
return CONFIG_STATUS_ERROR;
}
glTexImage2D(
@@ -1020,29 +1030,14 @@ static bool configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glTexImage2D"))
{
LG_UNLOCK(this->formatLock);
return false;
return CONFIG_STATUS_ERROR;
}
if (this->decoder->has_gl)
{
if (!this->decoder->init_gl_texture(
this->decoderData,
GL_TEXTURE_2D,
this->frames[i],
&this->decoderFrames[i]))
{
LG_UNLOCK(this->formatLock);
return false;
}
}
else
{
// 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);
}
// 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 lists
glNewList(this->texList + i, GL_COMPILE);
@@ -1066,7 +1061,7 @@ static bool configure(struct Inst * this, SDL_Window *window)
this->reconfigure = false;
LG_UNLOCK(this->formatLock);
return true;
return CONFIG_STATUS_OK;
}
static void deconfigure(struct Inst * this)
@@ -1082,19 +1077,6 @@ static void deconfigure(struct Inst * this)
if (this->hasFrames)
{
if (this->decoder->has_gl)
{
for(int i = 0; i < BUFFER_COUNT; ++i)
{
if (this->decoderFrames[i])
this->decoder->free_gl_texture(
this->decoderData,
this->decoderFrames[i]
);
this->decoderFrames[i] = NULL;
}
}
glDeleteTextures(BUFFER_COUNT, this->frames);
this->hasFrames = false;
}
@@ -1107,9 +1089,6 @@ static void deconfigure(struct Inst * this)
if (this->amdPinnedMemSupport)
{
if (this->texPixels[0])
free(this->texPixels[0]);
for(int i = 0; i < BUFFER_COUNT; ++i)
{
if (this->fences[i])
@@ -1117,14 +1096,13 @@ static void deconfigure(struct Inst * this)
glDeleteSync(this->fences[i]);
this->fences[i] = NULL;
}
this->texPixels[i] = NULL;
}
}
if (this->decoderData)
{
this->decoder->destroy(this->decoderData);
this->decoderData = NULL;
if (this->texPixels[i])
{
free(this->texPixels[i]);
this->texPixels[i] = NULL;
}
}
}
this->configured = false;
@@ -1153,7 +1131,6 @@ static void update_mouse_shape(struct Inst * this, bool * newShape)
switch(cursor)
{
case LG_CURSOR_MASKED_COLOR:
{
for(int i = 0; i < width * height; ++i)
{
const uint32_t c = ((uint32_t *)data)[i];
@@ -1164,7 +1141,6 @@ static void update_mouse_shape(struct Inst * this, bool * newShape)
//
// technically we should also create an XOR texture from the data but this
// usage seems very rare in modern software.
}
case LG_CURSOR_COLOR:
{
@@ -1277,6 +1253,23 @@ static void update_mouse_shape(struct Inst * this, bool * newShape)
LG_UNLOCK(this->mouseLock);
}
static bool opengl_buffer_fn(void * opaque, const void * data, size_t size)
{
struct Inst * this = (struct Inst *)opaque;
// update the buffer, this performs a DMA transfer if possible
glBufferSubData(
GL_PIXEL_UNPACK_BUFFER,
this->texPos,
size,
data
);
check_gl_error("glBufferSubData");
this->texPos += size;
return true;
}
static bool draw_frame(struct Inst * this)
{
LG_LOCK(this->syncLock);
@@ -1293,96 +1286,75 @@ static bool draw_frame(struct Inst * this)
LG_UNLOCK(this->syncLock);
LG_LOCK(this->formatLock);
if (this->decoder->has_gl)
if (glIsSync(this->fences[this->texIndex]))
{
if (!this->decoder->update_gl_texture(
this->decoderData,
this->decoderFrames[this->texIndex]
))
switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED))
{
LG_UNLOCK(this->formatLock);
DEBUG_ERROR("Failed to update the texture from the decoder");
return false;
case GL_ALREADY_SIGNALED:
break;
case GL_CONDITION_SATISFIED:
DEBUG_WARN("Had to wait for the sync");
break;
case GL_TIMEOUT_EXPIRED:
DEBUG_WARN("Timeout expired, DMA transfers are too slow!");
break;
case GL_WAIT_FAILED:
DEBUG_ERROR("Wait failed %d", glGetError());
break;
}
glDeleteSync(this->fences[this->texIndex]);
this->fences[this->texIndex] = NULL;
}
else
glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]);
const int bpp = this->format.bpp / 8;
glPixelStorei(GL_UNPACK_ALIGNMENT , bpp);
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.width);
this->texPos = 0;
framebuffer_read_fn(
this->frame,
this->format.height,
this->format.width,
bpp,
this->format.pitch,
opengl_buffer_fn,
this
);
// update the texture
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
this->format.width ,
this->format.height,
this->vboFormat,
this->dataFormat,
(void*)0
);
if (check_gl_error("glTexSubImage2D"))
{
if (glIsSync(this->fences[this->texIndex]))
{
switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED))
{
case GL_ALREADY_SIGNALED:
break;
case GL_CONDITION_SATISFIED:
DEBUG_WARN("Had to wait for the sync");
break;
case GL_TIMEOUT_EXPIRED:
DEBUG_WARN("Timeout expired, DMA transfers are too slow!");
break;
case GL_WAIT_FAILED:
DEBUG_ERROR("Wait failed %s", gluErrorString(glGetError()));
break;
}
glDeleteSync(this->fences[this->texIndex]);
this->fences[this->texIndex] = NULL;
}
const uint8_t * data = this->decoder->get_buffer(this->decoderData);
if (!data)
{
LG_UNLOCK(this->formatLock);
DEBUG_ERROR("Failed to get the buffer from the decoder");
return false;
}
glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]);
glPixelStorei(GL_UNPACK_ALIGNMENT , 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH ,
this->decoder->get_frame_stride(this->decoderData)
DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
);
// update the buffer, this performs a DMA transfer if possible
glBufferSubData(
GL_PIXEL_UNPACK_BUFFER,
0,
this->texSize,
data
);
check_gl_error("glBufferSubData");
// update the texture
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
this->format.width ,
this->format.height,
this->vboFormat,
this->dataFormat,
(void*)0
);
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
);
}
// set a fence so we don't overwrite a buffer in use
this->fences[this->texIndex] =
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// unbind the buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
// set a fence so we don't overwrite a buffer in use
this->fences[this->texIndex] =
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// 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));
@@ -1415,4 +1387,4 @@ static void draw_mouse(struct Inst * this)
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
glCallList(this->mouseList);
glPopMatrix();
}
}

View File

@@ -1,30 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(spice LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(SPICE_PKGCONFIG REQUIRED
spice-protocol
nettle
hogweed
)
add_definitions(-D USE_NETTLE)
add_library(spice STATIC
src/spice.c
src/rsa.c
)
target_link_libraries(spice
lg_common
${SPICE_PKGCONFIG_LIBRARIES}
)
target_include_directories(spice
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src
${SPICE_PKGCONFIG_INCLUDE_DIRS}
)

View File

@@ -1,146 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <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 SpiceChannelID
{
uint8_t type;
uint8_t channel_id;
}
SpiceChannelID;
typedef struct SpiceMsgMainChannelsList
{
uint32_t num_of_channels;
//SpiceChannelID channels[num_of_channels]
}
SpiceMainChannelsList;
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;
// spice is missing these defines, the offical reference library incorrectly uses the VD defines
#define COMMON_CAPS_BYTES (((SPICE_COMMON_CAP_MINI_HEADER + 32) / 8) & ~3)
#define COMMON_SET_CAPABILITY(caps, index) \
{ (caps)[(index) / 32] |= (1 << ((index) % 32)); }
#define MAIN_CAPS_BYTES (((SPICE_MAIN_CAP_SEAMLESS_MIGRATE + 32) / 8) & ~3)
#define MAIN_SET_CAPABILITY(caps, index) \
{ (caps)[(index) / 32] |= (1 << ((index) % 32)); }
#pragma pack(pop)

View File

@@ -1 +0,0 @@
.

View File

@@ -1,64 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sys/types.h>
#include <stdbool.h>
#include <stdint.h>
typedef enum SpiceDataType
{
SPICE_DATA_TEXT,
SPICE_DATA_PNG,
SPICE_DATA_BMP,
SPICE_DATA_TIFF,
SPICE_DATA_JPEG,
SPICE_DATA_NONE
}
SpiceDataType;
typedef void (*SpiceClipboardNotice )(const SpiceDataType type);
typedef void (*SpiceClipboardData )(const SpiceDataType type, uint8_t * buffer, uint32_t size);
typedef void (*SpiceClipboardRelease)();
typedef void (*SpiceClipboardRequest)(const SpiceDataType type);
bool spice_connect(const char * host, const unsigned short port, const char * password);
void spice_disconnect();
bool spice_process();
bool spice_ready();
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);
bool spice_clipboard_request(SpiceDataType type);
bool spice_clipboard_grab (SpiceDataType type);
bool spice_clipboard_release();
bool spice_clipboard_data (SpiceDataType type, uint8_t * data, size_t size);
/* events */
bool spice_set_clipboard_cb(
SpiceClipboardNotice cbNoticeFn,
SpiceClipboardData cbDataFn,
SpiceClipboardRelease cbReleaseFn,
SpiceClipboardRequest cbRequestFn);

View File

@@ -1,227 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "rsa.h"
#include "common/debug.h"
#include <spice/protocol.h>
#include <malloc.h>
#include <string.h>
#if defined(USE_OPENSSL) && defined(USE_NETTLE)
#error "USE_OPENSSL and USE_NETTLE are both defined"
#elif !defined(USE_OPENSSL) && !defined(USE_NETTLE)
#error "One of USE_OPENSSL or USE_NETTLE must be defined"
#endif
#if defined(USE_OPENSSL)
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#endif
#if defined(USE_NETTLE)
#include <stdlib.h>
#include <nettle/asn1.h>
#include <nettle/sha1.h>
#include <nettle/rsa.h>
#include <nettle/bignum.h>
#include <gmp.h>
#define SHA1_HASH_LEN 20
#endif
#if defined(USE_NETTLE)
/* the below OAEP implementation is derived from the FreeTDS project */
static void memxor(uint8_t * a, const uint8_t * b, const unsigned int len)
{
for(unsigned int i = 0; i < len; ++i)
a[i] = a[i] ^ b[i];
}
static void sha1(uint8_t * hash, const uint8_t *data, unsigned int len)
{
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, len, data);
sha1_digest(&ctx, SHA1_HASH_LEN, hash);
}
static void oaep_mask(uint8_t * dest, size_t dest_len, const uint8_t * mask, size_t mask_len)
{
uint8_t hash[SHA1_HASH_LEN];
uint8_t seed[mask_len + 4 ];
memcpy(seed, mask, mask_len);
for(unsigned int n = 0;; ++n)
{
(seed+mask_len)[0] = n >> 24;
(seed+mask_len)[1] = n >> 16;
(seed+mask_len)[2] = n >> 8;
(seed+mask_len)[3] = n >> 0;
sha1(hash, seed, sizeof(seed));
if (dest_len <= SHA1_HASH_LEN)
{
memxor(dest, hash, dest_len);
break;
}
memxor(dest, hash, SHA1_HASH_LEN);
dest += SHA1_HASH_LEN;
dest_len -= SHA1_HASH_LEN;
}
}
static bool oaep_pad(mpz_t m, unsigned int key_size, const uint8_t * message, unsigned int len)
{
if (len + SHA1_HASH_LEN * 2 + 2 > key_size)
{
DEBUG_ERROR("Message too long");
return false;
}
struct
{
uint8_t all[1];
uint8_t ros[SHA1_HASH_LEN];
uint8_t db [key_size - SHA1_HASH_LEN - 1];
} em;
memset(&em, 0, sizeof(em));
sha1(em.db, (uint8_t *)"", 0);
em.all[key_size - len - 1] = 0x1;
memcpy(em.all + (key_size - len), message, len);
/* we are not too worried about randomness since we are just making a local
* connection, should anyone use this code outside of LookingGlass please be
* sure to use something better such as `gnutls_rnd` */
for(int i = 0; i < SHA1_HASH_LEN; ++i)
em.ros[i] = rand() % 255;
const int db_len = key_size - SHA1_HASH_LEN - 1;
oaep_mask(em.db , db_len , em.ros, SHA1_HASH_LEN);
oaep_mask(em.ros, SHA1_HASH_LEN, em.db , db_len );
nettle_mpz_set_str_256_u(m, key_size, em.all);
return true;
}
#endif
bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result)
{
result->size = 0;
result->data = NULL;
#if defined(USE_OPENSSL)
BIO *bioKey = BIO_new(BIO_s_mem());
if (!bioKey)
{
DEBUG_ERROR("failed to allocate bioKey");
return false;
}
BIO_write(bioKey, pub_key, SPICE_TICKET_PUBKEY_BYTES);
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
result->size = RSA_size(rsa);
result->data = (char *)malloc(result->size);
if (RSA_public_encrypt(
strlen(password) + 1,
(uint8_t*)password,
(uint8_t*)result->data,
rsa,
RSA_PKCS1_OAEP_PADDING
) <= 0)
{
free(result->data);
result->size = 0;
result->data = NULL;
DEBUG_ERROR("rsa public encrypt failed");
EVP_PKEY_free(rsaKey);
BIO_free(bioKey);
return false;
}
EVP_PKEY_free(rsaKey);
BIO_free(bioKey);
return true;
#endif
#if defined(USE_NETTLE)
struct asn1_der_iterator der;
struct asn1_der_iterator j;
struct rsa_public_key pub;
if (asn1_der_iterator_first(&der, SPICE_TICKET_PUBKEY_BYTES, pub_key) == ASN1_ITERATOR_CONSTRUCTED
&& der.type == ASN1_SEQUENCE
&& asn1_der_decode_constructed_last(&der) == ASN1_ITERATOR_CONSTRUCTED
&& der.type == ASN1_SEQUENCE
&& asn1_der_decode_constructed(&der, &j) == ASN1_ITERATOR_PRIMITIVE
&& j.type == ASN1_IDENTIFIER
&& asn1_der_iterator_next(&der) == ASN1_ITERATOR_PRIMITIVE
&& der.type == ASN1_BITSTRING
&& asn1_der_decode_bitstring_last(&der))
{
if (j.length != 9)
{
DEBUG_ERROR("Invalid key, not RSA");
return false;
}
if (asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE
&& j.type == ASN1_NULL
&& j.length == 0
&& asn1_der_iterator_next(&j) == ASN1_ITERATOR_END)
{
rsa_public_key_init(&pub);
if (!rsa_public_key_from_der_iterator(&pub, 0, &der))
{
DEBUG_ERROR("Unable to load public key from DER iterator");
rsa_public_key_clear(&pub);
return false;
}
}
}
mpz_t p;
mpz_init(p);
oaep_pad(p, pub.size, (uint8_t *)password, strlen(password)+1);
mpz_powm(p, p, pub.e, pub.n);
result->size = pub.size;
result->data = malloc(pub.size);
nettle_mpz_get_str_256(pub.size, (uint8_t *)result->data, p);
rsa_public_key_clear(&pub);
mpz_clear(p);
return true;
#endif
}
void spice_rsa_free_password(struct spice_password * pass)
{
free(pass->data);
pass->size = 0;
pass->data = NULL;
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
void app_alert(LG_MsgAlert type, const char * fmt, ...)
{
if (!state.lgr || !params.showAlerts)
if (!g_state.lgr || !params.showAlerts)
return;
va_list args;
@@ -36,8 +36,8 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...)
vsnprintf(buffer, length + 1, fmt, args);
va_end(args);
state.lgr->on_alert(
state.lgrData,
g_state.lgr->on_alert(
g_state.lgrData,
type,
buffer,
NULL
@@ -49,7 +49,7 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...)
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque)
{
// don't allow duplicate binds
if (state.bindings[key])
if (g_state.bindings[key])
{
DEBUG_INFO("Key already bound");
return NULL;
@@ -60,7 +60,7 @@ KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void
handle->callback = callback;
handle->opaque = opaque;
state.bindings[key] = handle;
g_state.bindings[key] = handle;
return handle;
}
@@ -69,7 +69,7 @@ void app_release_keybind(KeybindHandle * handle)
if (!*handle)
return;
state.bindings[(*handle)->key] = NULL;
g_state.bindings[(*handle)->key] = NULL;
free(*handle);
*handle = NULL;
}
}

View File

@@ -27,17 +27,22 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <pwd.h>
#include <unistd.h>
//FIXME: this should really not be included here and is an ugly hack to retain
//backwards compatibility with the escape key scancode
extern uint32_t sdl_to_xfree86[];
// 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 char * optScancodeToString (struct Option * opt);
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 char * optScancodeToString(struct Option * opt);
static bool optRotateValidate (struct Option * opt, const char ** error);
static void doLicense();
@@ -52,22 +57,6 @@ static struct Option options[] =
.type = OPTION_TYPE_STRING,
.value.x_string = NULL,
},
{
.module = "app",
.name = "shmFile",
.description = "The path to the shared memory file",
.shortopt = 'f',
.type = OPTION_TYPE_STRING,
.value.x_string = "/dev/shm/looking-glass",
},
{
.module = "app",
.name = "shmSize",
.description = "Specify the size in MB of the shared memory file (0 = detect)",
.shortopt = 'L',
.type = OPTION_TYPE_INT,
.value.x_int = 0,
},
{
.module = "app",
.name = "renderer",
@@ -100,6 +89,13 @@ static struct Option options[] =
.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
{
@@ -151,6 +147,20 @@ static struct Option options[] =
.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",
@@ -184,8 +194,8 @@ static struct Option options[] =
},
{
.module = "win",
.name = "fpsLimit",
.description = "Frame rate limit (0 = disable - not recommended, -1 = auto detect)",
.name = "fpsMin",
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)",
.shortopt = 'K',
.type = OPTION_TYPE_INT,
.value.x_int = -1,
@@ -222,6 +232,21 @@ static struct Option options[] =
.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
{
@@ -232,6 +257,13 @@ static struct Option options[] =
.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 = "escapeKey",
@@ -241,6 +273,13 @@ static struct Option options[] =
.value.x_int = SDL_SCANCODE_SCROLLLOCK,
.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",
@@ -256,6 +295,41 @@ static struct Option options[] =
.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
{
@@ -318,10 +392,24 @@ static struct Option options[] =
.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 config_init(void)
{
params.center = true;
params.w = 1024;
@@ -380,28 +468,45 @@ bool config_load(int argc, char * argv[])
}
// setup the application params for the basic types
params.shmFile = option_get_string("app", "shmFile" );
params.shmSize = option_get_int ("app", "shmSize" ) * 1048576;
params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
params.framePollInterval = option_get_int ("app", "framePollInterval" );
params.allowDMA = option_get_bool ("app", "allowDMA" );
params.windowTitle = option_get_string("win", "title" );
params.autoResize = option_get_bool ("win", "autoResize" );
params.allowResize = option_get_bool ("win", "allowResize" );
params.keepAspect = option_get_bool ("win", "keepAspect" );
params.forceAspect = option_get_bool ("win", "forceAspect" );
params.dontUpscale = option_get_bool ("win", "dontUpscale" );
params.borderless = option_get_bool ("win", "borderless" );
params.fullscreen = option_get_bool ("win", "fullScreen" );
params.maximize = option_get_bool ("win", "maximize" );
params.fpsLimit = option_get_int ("win", "fpsLimit" );
params.fpsMin = option_get_int ("win", "fpsMin" );
params.showFPS = option_get_bool ("win", "showFPS" );
params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
params.noScreensaver = option_get_bool ("win", "noScreensaver");
params.showAlerts = option_get_bool ("win", "alerts" );
params.quickSplash = option_get_bool ("win", "quickSplash" );
params.grabKeyboard = option_get_bool ("input", "grabKeyboard");
params.escapeKey = option_get_int ("input", "escapeKey" );
params.hideMouse = option_get_bool ("input", "hideCursor" );
params.mouseSens = option_get_int ("input", "mouseSens" );
switch(option_get_int("win", "rotate"))
{
case 0 : params.winRotate = LG_ROTATE_0 ; break;
case 90 : params.winRotate = LG_ROTATE_90 ; break;
case 180: params.winRotate = LG_ROTATE_180; break;
case 270: params.winRotate = LG_ROTATE_270; break;
}
params.grabKeyboard = option_get_bool("input", "grabKeyboard" );
params.grabKeyboardOnFocus = option_get_bool("input", "grabKeyboardOnFocus");
params.escapeKey = option_get_int ("input", "escapeKey" );
params.ignoreWindowsKeys = option_get_bool("input", "ignoreWindowsKeys" );
params.hideMouse = option_get_bool("input", "hideCursor" );
params.mouseSens = option_get_int ("input", "mouseSens" );
params.mouseSmoothing = option_get_bool("input", "mouseSmoothing" );
params.rawMouse = option_get_bool("input", "rawMouse" );
params.mouseRedraw = option_get_bool("input", "mouseRedraw" );
params.autoCapture = option_get_bool("input", "autoCapture" );
params.captureInputOnly = option_get_bool("input", "captureOnly" );
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
@@ -423,17 +528,22 @@ bool config_load(int argc, char * argv[])
}
params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
params.captureOnStart = option_get_bool("spice", "captureOnStart");
params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor");
}
//FIXME, this should be using linux keycodes
params.escapeKey = sdl_to_xfree86[params.escapeKey];
return true;
}
void config_free()
void config_free(void)
{
option_free();
}
static void doLicense()
static void doLicense(void)
{
fprintf(stderr,
"\n"
@@ -459,6 +569,9 @@ static void doLicense()
static bool optRendererParse(struct Option * opt, const char * str)
{
if (!str)
return false;
if (strcasecmp(str, "auto") == 0)
{
params.forceRenderer = false;
@@ -500,6 +613,9 @@ static char * optRendererToString(struct Option * opt)
static bool optPosParse(struct Option * opt, const char * str)
{
if (!str)
return false;
if (strcmp(str, "center") == 0)
{
params.center = true;
@@ -537,6 +653,9 @@ static char * optPosToString(struct Option * opt)
static bool optSizeParse(struct Option * opt, const char * str)
{
if (!str)
return false;
if (sscanf(str, "%dx%d", &params.w, &params.h) == 2)
{
if (params.w < 1 || params.h < 1)
@@ -566,6 +685,22 @@ static char * optSizeToString(struct Option * opt)
static char * optScancodeToString(struct Option * opt)
{
char * str;
alloc_sprintf(&str, "%d = %s", opt->value.x_int, SDL_GetScancodeName(opt->value.x_int));
alloc_sprintf(&str, "%d = %s", opt->value.x_int,
SDL_GetScancodeName(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;
}

View File

@@ -21,4 +21,4 @@ Place, Suite 330, Boston, MA 02111-1307 USA
void config_init();
bool config_load(int argc, char * argv[]);
void config_free();
void config_free();

View File

@@ -17,40 +17,127 @@ 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[] =
#include <linux/input.h>
const uint32_t xfree86_to_ps2[KEY_MAX] =
{
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
};
[KEY_RESERVED] /* = USB 0 */ = 0x000000,
[KEY_ESC] /* = USB 41 */ = 0x000001,
[KEY_1] /* = USB 30 */ = 0x000002,
[KEY_2] /* = USB 31 */ = 0x000003,
[KEY_3] /* = USB 32 */ = 0x000004,
[KEY_4] /* = USB 33 */ = 0x000005,
[KEY_5] /* = USB 34 */ = 0x000006,
[KEY_6] /* = USB 35 */ = 0x000007,
[KEY_7] /* = USB 36 */ = 0x000008,
[KEY_8] /* = USB 37 */ = 0x000009,
[KEY_9] /* = USB 38 */ = 0x00000A,
[KEY_0] /* = USB 39 */ = 0x00000B,
[KEY_MINUS] /* = USB 45 */ = 0x00000C,
[KEY_EQUAL] /* = USB 46 */ = 0x00000D,
[KEY_BACKSPACE] /* = USB 42 */ = 0x00000E,
[KEY_TAB] /* = USB 43 */ = 0x00000F,
[KEY_Q] /* = USB 20 */ = 0x000010,
[KEY_W] /* = USB 26 */ = 0x000011,
[KEY_E] /* = USB 8 */ = 0x000012,
[KEY_R] /* = USB 21 */ = 0x000013,
[KEY_T] /* = USB 23 */ = 0x000014,
[KEY_Y] /* = USB 28 */ = 0x000015,
[KEY_U] /* = USB 24 */ = 0x000016,
[KEY_I] /* = USB 12 */ = 0x000017,
[KEY_O] /* = USB 18 */ = 0x000018,
[KEY_P] /* = USB 19 */ = 0x000019,
[KEY_LEFTBRACE] /* = USB 47 */ = 0x00001A,
[KEY_RIGHTBRACE] /* = USB 48 */ = 0x00001B,
[KEY_ENTER] /* = USB 40 */ = 0x00001C,
[KEY_LEFTCTRL] /* = USB 224 */ = 0x00001D,
[KEY_A] /* = USB 4 */ = 0x00001E,
[KEY_S] /* = USB 22 */ = 0x00001F,
[KEY_D] /* = USB 7 */ = 0x000020,
[KEY_F] /* = USB 9 */ = 0x000021,
[KEY_G] /* = USB 10 */ = 0x000022,
[KEY_H] /* = USB 11 */ = 0x000023,
[KEY_J] /* = USB 13 */ = 0x000024,
[KEY_K] /* = USB 14 */ = 0x000025,
[KEY_L] /* = USB 15 */ = 0x000026,
[KEY_SEMICOLON] /* = USB 51 */ = 0x000027,
[KEY_APOSTROPHE] /* = USB 52 */ = 0x000028,
[KEY_GRAVE] /* = USB 53 */ = 0x000029,
[KEY_LEFTSHIFT] /* = USB 225 */ = 0x00002A,
[KEY_BACKSLASH] /* = USB 49 */ = 0x00002B,
[KEY_Z] /* = USB 29 */ = 0x00002C,
[KEY_X] /* = USB 27 */ = 0x00002D,
[KEY_C] /* = USB 6 */ = 0x00002E,
[KEY_V] /* = USB 25 */ = 0x00002F,
[KEY_B] /* = USB 5 */ = 0x000030,
[KEY_N] /* = USB 17 */ = 0x000031,
[KEY_M] /* = USB 16 */ = 0x000032,
[KEY_COMMA] /* = USB 54 */ = 0x000033,
[KEY_DOT] /* = USB 55 */ = 0x000034,
[KEY_SLASH] /* = USB 56 */ = 0x000035,
[KEY_RIGHTSHIFT] /* = USB 229 */ = 0x000036,
[KEY_KPASTERISK] /* = USB 85 */ = 0x000037,
[KEY_LEFTALT] /* = USB 226 */ = 0x000038,
[KEY_SPACE] /* = USB 44 */ = 0x000039,
[KEY_CAPSLOCK] /* = USB 57 */ = 0x00003A,
[KEY_F1] /* = USB 58 */ = 0x00003B,
[KEY_F2] /* = USB 59 */ = 0x00003C,
[KEY_F3] /* = USB 60 */ = 0x00003D,
[KEY_F4] /* = USB 61 */ = 0x00003E,
[KEY_F5] /* = USB 62 */ = 0x00003F,
[KEY_F6] /* = USB 63 */ = 0x000040,
[KEY_F7] /* = USB 64 */ = 0x000041,
[KEY_F8] /* = USB 65 */ = 0x000042,
[KEY_F9] /* = USB 66 */ = 0x000043,
[KEY_F10] /* = USB 67 */ = 0x000044,
[KEY_NUMLOCK] /* = USB 83 */ = 0x000045,
[KEY_SCROLLLOCK] /* = USB 71 */ = 0x000046,
[KEY_KP7] /* = USB 95 */ = 0x000047,
[KEY_KP8] /* = USB 96 */ = 0x000048,
[KEY_KP9] /* = USB 97 */ = 0x000049,
[KEY_KPMINUS] /* = USB 86 */ = 0x00004A,
[KEY_KP4] /* = USB 92 */ = 0x00004B,
[KEY_KP5] /* = USB 93 */ = 0x00004C,
[KEY_KP6] /* = USB 94 */ = 0x00004D,
[KEY_KPPLUS] /* = USB 87 */ = 0x00004E,
[KEY_KP1] /* = USB 89 */ = 0x00004F,
[KEY_KP2] /* = USB 90 */ = 0x000050,
[KEY_KP3] /* = USB 91 */ = 0x000051,
[KEY_KP0] /* = USB 98 */ = 0x000052,
[KEY_KPDOT] /* = USB 99 */ = 0x000053,
[KEY_102ND] /* = USB 100 */ = 0x000056,
[KEY_F11] /* = USB 68 */ = 0x000057,
[KEY_F12] /* = USB 69 */ = 0x000058,
[KEY_RO] /* = USB 135 */ = 0x000073,
[KEY_HENKAN] /* = USB 138 */ = 0x000079,
[KEY_KATAKANAHIRAGANA] /* = USB 136 */ = 0x000070,
[KEY_MUHENKAN] /* = USB 139 */ = 0x00007B,
[KEY_KPENTER] /* = USB 88 */ = 0x00E01C,
[KEY_RIGHTCTRL] /* = USB 228 */ = 0x00E01D,
[KEY_KPSLASH] /* = USB 84 */ = 0x00E035,
[KEY_SYSRQ] /* = USB 70 */ = 0x00E037,
[KEY_RIGHTALT] /* = USB 230 */ = 0x00E038,
[KEY_HOME] /* = USB 74 */ = 0x00E047,
[KEY_UP] /* = USB 82 */ = 0x00E048,
[KEY_PAGEUP] /* = USB 75 */ = 0x00E049,
[KEY_LEFT] /* = USB 80 */ = 0x00E04B,
[KEY_RIGHT] /* = USB 79 */ = 0x00E04D,
[KEY_END] /* = USB 77 */ = 0x00E04F,
[KEY_DOWN] /* = USB 81 */ = 0x00E050,
[KEY_PAGEDOWN] /* = USB 78 */ = 0x00E051,
[KEY_INSERT] /* = USB 73 */ = 0x00E052,
[KEY_DELETE] /* = USB 76 */ = 0x00E053,
[KEY_KPEQUAL] /* = USB 103 */ = 0x000059,
[KEY_PAUSE] /* = USB 72 */ = 0x00E046,
[KEY_KPCOMMA] /* = USB 133 */ = 0x00007E,
[KEY_HANGEUL] /* = USB 144 */ = 0x0000F2,
[KEY_HANJA] /* = USB 145 */ = 0x0000F1,
[KEY_YEN] /* = USB 137 */ = 0x00007D,
[KEY_LEFTMETA] /* = USB 227 */ = 0x00E05B,
[KEY_RIGHTMETA] /* = USB 231 */ = 0x00E05C,
[KEY_COMPOSE] /* = USB 101 */ = 0x00E05D,
[KEY_F13] /* = USB 104 */ = 0x00005D,
[KEY_F14] /* = USB 105 */ = 0x00005E,
[KEY_F15] /* = USB 106 */ = 0x00005F,
[KEY_PRINT] /* = USB 70 */ = 0x00E037,
};

View File

@@ -19,7 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "ll.h"
#include "utils.h"
#include "common/locking.h"
#include <stdlib.h>
#include <assert.h>
@@ -38,12 +38,13 @@ struct ll
LG_Lock lock;
};
struct ll * ll_new()
struct ll * ll_new(void)
{
struct ll * list = malloc(sizeof(struct ll));
list->head = NULL;
list->tail = NULL;
list->pos = NULL;
list->head = NULL;
list->tail = NULL;
list->pos = NULL;
list->count = 0;
LG_LOCK_INIT(list->lock);
return list;
}
@@ -64,6 +65,8 @@ void ll_push(struct ll * list, void * data)
item->next = NULL;
LG_LOCK(list->lock);
++list->count;
if (!list->head)
{
list->head = item;
@@ -72,7 +75,6 @@ void ll_push(struct ll * list, void * data)
return;
}
++list->count;
list->tail->next = item;
list->tail = item;
LG_UNLOCK(list->lock);
@@ -90,8 +92,10 @@ bool ll_shift(struct ll * list, void ** data)
--list->count;
struct ll_item * item = list->head;
list->head = item->next;
list->pos = NULL;
if (list->tail == item)
list->tail = NULL;
list->pos = NULL;
LG_UNLOCK(list->lock);
if (data)
@@ -116,6 +120,21 @@ bool ll_peek_head(struct ll * list, void ** data)
return true;
}
bool ll_peek_tail(struct ll * list, void ** data)
{
LG_LOCK(list->lock);
if (!list->tail)
{
LG_UNLOCK(list->lock);
return false;
}
*data = list->tail->data;
LG_UNLOCK(list->lock);
return true;
}
unsigned int ll_count(struct ll * list)
{
return list->count;
@@ -157,4 +176,4 @@ bool ll_walk(struct ll * list, void ** data)
LG_UNLOCK(list->lock);
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,100 +18,140 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdbool.h>
#include <stdatomic.h>
#include <SDL2/SDL.h>
#include <linux/input.h>
#include "interface/app.h"
#include "dynamic/displayservers.h"
#include "dynamic/renderers.h"
#include "dynamic/clipboards.h"
#include "common/ivshmem.h"
#include "spice/spice.h"
#include <lgmp/client.h>
enum RunState
{
APP_STATE_RUNNING,
APP_STATE_RESTART,
APP_STATE_SHUTDOWN
};
struct AppState
{
bool running;
enum RunState state;
struct LG_DisplayServerOps * ds;
bool stopVideo;
bool ignoreInput;
bool escapeActive;
SDL_Scancode escapeAction;
KeybindHandle bindings[SDL_NUM_SCANCODES];
bool keyDown[SDL_NUM_SCANCODES];
KeybindHandle bindings[KEY_MAX];
bool keyDown[KEY_MAX];
bool haveSrcSize;
SDL_Point windowPos;
int windowW, windowH;
int windowCX, windowCY;
LG_RendererRotate rotate;
bool focused;
SDL_Rect border;
SDL_Point srcSize;
LG_RendererRect dstRect;
SDL_Point cursor;
bool cursorVisible;
bool haveCursorPos;
float scaleX, scaleY;
float accX, accY;
bool posInfoValid;
bool alignToGuest;
const LG_Renderer * lgr;
void * lgrData;
bool lgrResize;
atomic_int lgrResize;
const LG_Clipboard * lgc;
bool cbAvailable;
SpiceDataType cbType;
bool cbChunked;
size_t cbXfer;
struct ll * cbRequestList;
SDL_SysWMinfo wminfo;
SDL_Window * window;
int shmFD;
struct KVMFRHeader * shm;
unsigned int shmSize;
uint64_t frameTime;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
struct IVSHMEM shm;
PLGMPClient lgmp;
PLGMPClientQueue frameQueue;
PLGMPClientQueue pointerQueue;
bool formatValid;
atomic_uint_least64_t frameTime;
uint64_t lastFrameTime;
uint64_t renderTime;
atomic_uint_least64_t frameCount;
uint64_t renderCount;
uint64_t resizeTimeout;
bool resizeDone;
KeybindHandle kbFS;
KeybindHandle kbVideo;
KeybindHandle kbRotate;
KeybindHandle kbInput;
KeybindHandle kbQuit;
KeybindHandle kbMouseSensInc;
KeybindHandle kbMouseSensDec;
KeybindHandle kbCtrlAltFn[12];
int mouseSens;
float sensX, sensY;
KeybindHandle kbPass[2];
};
struct AppParams
{
bool autoResize;
bool allowResize;
bool keepAspect;
bool borderless;
bool fullscreen;
bool maximize;
bool minimizeOnFocusLoss;
bool center;
int x, y;
unsigned int w, h;
const char * shmFile;
unsigned int shmSize;
unsigned int fpsLimit;
bool showFPS;
bool useSpiceInput;
bool useSpiceClipboard;
const char * spiceHost;
unsigned int spicePort;
bool clipboardToVM;
bool clipboardToLocal;
bool scaleMouseInput;
bool hideMouse;
bool ignoreQuit;
bool noScreensaver;
bool grabKeyboard;
SDL_Scancode escapeKey;
bool showAlerts;
bool autoResize;
bool allowResize;
bool keepAspect;
bool forceAspect;
bool dontUpscale;
bool borderless;
bool fullscreen;
bool maximize;
bool minimizeOnFocusLoss;
bool center;
int x, y;
unsigned int w, h;
int fpsMin;
bool showFPS;
LG_RendererRotate winRotate;
bool useSpiceInput;
bool useSpiceClipboard;
const char * spiceHost;
unsigned int spicePort;
bool clipboardToVM;
bool clipboardToLocal;
bool scaleMouseInput;
bool hideMouse;
bool ignoreQuit;
bool noScreensaver;
bool grabKeyboard;
bool grabKeyboardOnFocus;
SDL_Scancode escapeKey;
bool ignoreWindowsKeys;
bool showAlerts;
bool captureOnStart;
bool quickSplash;
bool alwaysShowCursor;
unsigned int cursorPollInterval;
unsigned int framePollInterval;
unsigned int cursorPollInterval;
unsigned int framePollInterval;
bool allowDMA;
bool forceRenderer;
unsigned int forceRendererIndex;
bool forceRenderer;
unsigned int forceRendererIndex;
const char * windowTitle;
int mouseSens;
const char * windowTitle;
bool mouseRedraw;
int mouseSens;
bool mouseSmoothing;
bool rawMouse;
bool autoCapture;
bool captureInputOnly;
};
struct CBRequest
@@ -128,6 +168,89 @@ struct KeybindHandle
void * opaque;
};
enum WarpState
{
WARP_STATE_ON,
WARP_STATE_OFF
};
struct CursorInfo
{
/* x & y postiion */
int x , y;
/* pointer hotspot offsets */
int hx, hy;
/* true if the pointer is visible on the guest */
bool visible;
/* true if the details in this struct are valid */
bool valid;
/* the DPI scaling of the guest */
uint32_t dpiScale;
};
struct DoublePoint
{
double x, y;
};
struct CursorState
{
/* cursor is in grab mode */
bool grab;
/* true if we are to draw the cursor on screen */
bool draw;
/* true if the cursor is currently in our window */
bool inWindow;
/* true if the cursor is currently in the guest view area */
bool inView;
/* true if the guest should be realigned to the host when next drawn */
bool realign;
/* true if the cursor needs re-drawing/updating */
bool redraw;
/* true if the cursor movements should be scaled */
bool useScale;
/* the amount to scale the X & Y movements by */
struct DoublePoint scale;
/* the dpi scale factor from the guest as a fraction */
double dpiScale;
/* the error accumulator */
struct DoublePoint acc;
/* the local position */
struct DoublePoint pos;
/* true if the position is valid */
bool valid;
/* the button state */
unsigned int buttons;
/* the delta since last warp when in auto capture mode */
struct DoublePoint delta;
/* the scale factor for the mouse sensitiviy */
int sens;
/* the mouse warp state */
enum WarpState warpState;
/* the guest's cursor position */
struct CursorInfo guest;
};
// forwards
extern struct AppState state;
extern struct AppState g_state;
extern struct AppParams params;

View File

@@ -5,36 +5,24 @@ include_directories(
${PROJECT_SOURCE_DIR}/include
)
add_definitions(-D_GNU_SOURCE)
if(ENABLE_BACKTRACE)
add_definitions(-DENABLE_BACKTRACE)
endif()
add_subdirectory(src/platform)
set(COMMON_SOURCES
src/stringutils.c
src/stringlist.c
src/option.c
src/framebuffer.c
src/KVMFR.c
)
set(LINUX_SOURCES
src/crash.linux.c
src/sysinfo.linux.c
)
set(WINDOWS_SOURCES
src/crash.windows.c
src/sysinfo.windows.c
)
if(WIN32)
set(SOURCES ${COMMON_SOURCES} ${WINDOWS_SOURCES})
add_library(lg_common STATIC ${SOURCES})
else()
set(SOURCES ${COMMON_SOURCES} ${LINUX_SOURCES})
add_library(lg_common STATIC ${SOURCES})
if(ENABLE_BACKTRACE)
target_link_libraries(lg_common bfd)
endif()
endif()
add_library(lg_common STATIC ${COMMON_SOURCES})
target_link_libraries(lg_common lg_common_platform)
target_include_directories(lg_common
INTERFACE

View File

@@ -20,8 +20,11 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdint.h>
#define KVMFR_HEADER_MAGIC "[[KVMFR]]"
#define KVMFR_HEADER_VERSION 8
#define LGMP_Q_POINTER 1
#define LGMP_Q_FRAME 2
#define LGMP_Q_FRAME_LEN 2
#define LGMP_Q_POINTER_LEN 20
typedef enum FrameType
{
@@ -29,11 +32,30 @@ typedef enum FrameType
FRAME_TYPE_BGRA , // BGRA interleaved: B,G,R,A 32bpp
FRAME_TYPE_RGBA , // RGBA interleaved: R,G,B,A 32bpp
FRAME_TYPE_RGBA10 , // RGBA interleaved: R,G,B,A 10,10,10,2 bpp
FRAME_TYPE_YUV420 , // YUV420
FRAME_TYPE_RGBA16F , // RGBA interleaved: R,G,B,A 16,16,16,16 bpp float
FRAME_TYPE_MAX , // sentinel value
}
FrameType;
typedef enum FrameRotation
{
FRAME_ROT_0,
FRAME_ROT_90,
FRAME_ROT_180,
FRAME_ROT_270
}
FrameRotation;
extern const char * FrameTypeStr[FRAME_TYPE_MAX];
enum
{
CURSOR_FLAG_POSITION = 0x1,
CURSOR_FLAG_VISIBLE = 0x2,
CURSOR_FLAG_SHAPE = 0x4
};
typedef uint32_t KVMFRCursorFlags;
typedef enum CursorType
{
CURSOR_TYPE_COLOR ,
@@ -42,49 +64,38 @@ typedef enum CursorType
}
CursorType;
#define KVMFR_CURSOR_FLAG_UPDATE 1 // cursor update available
#define KVMFR_CURSOR_FLAG_VISIBLE 2 // cursor is visible
#define KVMFR_CURSOR_FLAG_SHAPE 4 // shape updated
#define KVMFR_CURSOR_FLAG_POS 8 // position updated
#define KVMFR_MAGIC "KVMFR---"
#define KVMFR_VERSION 8
typedef struct KVMFR
{
char magic[8];
uint32_t version;
char hostver[32];
}
KVMFR;
typedef struct KVMFRCursor
{
uint8_t flags; // KVMFR_CURSOR_FLAGS
int16_t x, y; // cursor x & y position
uint32_t version; // shape version
CursorType type; // shape buffer data type
int8_t hx, hy; // shape hotspot x & y
uint32_t width; // width of the shape
uint32_t height; // height of the shape
uint32_t pitch; // row length in bytes of the shape
uint64_t dataPos; // offset to the shape data
}
KVMFRCursor;
#define KVMFR_FRAME_FLAG_UPDATE 1 // frame update available
typedef struct KVMFRFrame
{
uint8_t flags; // KVMFR_FRAME_FLAGS
FrameType type; // the frame data type
uint32_t width; // the width
uint32_t height; // the height
uint32_t stride; // the row stride (zero if compressed data)
uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size)
uint64_t dataPos; // offset to the frame
uint32_t formatVer; // the frame format version number
FrameType type; // the frame data type
uint32_t width; // the width
uint32_t height; // the height
FrameRotation rotation; // the frame rotation
uint32_t stride; // the row stride (zero if compressed data)
uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size)
uint32_t offset; // offset from the start of this header to the FrameBuffer header
uint32_t mouseScalePercent; // movement scale factor of the mouse (relates to DPI of display, 100 = no scale)
}
KVMFRFrame;
#define KVMFR_HEADER_FLAG_RESTART 1 // restart signal from client
#define KVMFR_HEADER_FLAG_READY 2 // ready signal from client
#define KVMFR_HEADER_FLAG_PAUSED 4 // capture has been paused by the host
typedef struct KVMFRHeader
{
char magic[sizeof(KVMFR_HEADER_MAGIC)];
uint32_t version; // version of this structure
uint8_t flags; // KVMFR_HEADER_FLAGS
KVMFRFrame frame; // the frame information
KVMFRCursor cursor; // the cursor information
}
KVMFRHeader;

View File

@@ -17,7 +17,13 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <stdio.h>
#include <inttypes.h>
#include "time.h"
#if defined(_WIN32) && !defined(__GNUC__)
#define DIRECTORY_SEPARATOR '\\'
@@ -47,8 +53,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \
sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s))
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, type " %20s:%-4u | %-30s | " fmt "\n", STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%12" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define DEBUG_BREAK() DEBUG_PRINT("[ ]", "%s", "================================================================================")
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)
#define DEBUG_WARN(fmt, ...) DEBUG_PRINT("[W]", fmt, ##__VA_ARGS__)
#define DEBUG_ERROR(fmt, ...) DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
@@ -58,4 +65,4 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args)
#else
#define DEBUG_PROTO(fmt, ...) do {} while(0)
#endif
#endif

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -17,7 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/platform.h"
#include <windows.h>
osEventHandle * os_wrapEvent(HANDLE event);
// At 100% scaling, Windows reports 96 DPI.
#define DPI_100_PERCENT 96
UINT monitor_dpi(HMONITOR hMonitor);

View File

@@ -0,0 +1,42 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 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 <time.h>
#define TIMEOUT_INFINITE ((unsigned int)~0)
typedef struct LGEvent LGEvent;
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime);
void lgFreeEvent (LGEvent * handle);
bool lgWaitEvent (LGEvent * handle, unsigned int timeout);
bool lgWaitEvents (LGEvent * handles[], int count, bool waitAll, unsigned int timeout);
bool lgSignalEvent(LGEvent * handle);
bool lgResetEvent (LGEvent * handle);
// os specific method to wrap/convert a native event into a LGEvent
// for windows this is an event HANDLE
LGEvent * lgWrapEvent(void * handle);
// Posix specific, not implmented/possible in windows
bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts);
bool lgWaitEventNS (LGEvent * handle, unsigned int timeout);

View File

@@ -0,0 +1,60 @@
/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
typedef struct stFrameBuffer FrameBuffer;
typedef bool (*FrameBufferReadFn)(void * opaque, const void * src, size_t size);
/**
* The size of the FrameBuffer struct
*/
extern const size_t FrameBufferStructSize;
/**
* Wait for the framebuffer to fill to the specified size
*/
void framebuffer_wait(const FrameBuffer * frame, size_t size);
/**
* Read data from the KVMFRFrame into the dst buffer
*/
bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch,
size_t height, size_t width, size_t bpp, size_t pitch);
/**
* Read data from the KVMFRFrame using a callback
*/
bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque);
/**
* Prepare the framebuffer for writing
*/
void framebuffer_prepare(FrameBuffer * frame);
/**
* Write data from the src buffer into the KVMFRFrame
*/
bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size);

View File

@@ -0,0 +1,43 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
struct IVSHMEM
{
unsigned int size;
void * mem;
// internal use
void * opaque;
};
void ivshmemOptionsInit();
bool ivshmemInit(struct IVSHMEM * dev);
bool ivshmemOpen(struct IVSHMEM * dev);
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev);
void ivshmemClose(struct IVSHMEM * dev);
void ivshmemFree(struct IVSHMEM * dev);
/* Linux KVMFR support only for now (VM->VM) */
bool ivshmemHasDMA (struct IVSHMEM * dev);
int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size);

View File

@@ -18,10 +18,23 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#if defined(__GCC__) || defined(__GNUC__)
#define INTERLOCKED_AND8 __sync_fetch_and_and
#define INTERLOCKED_OR8 __sync_fetch_and_or
#else
#define INTERLOCKED_OR8 InterlockedOr8
#define INTERLOCKED_AND8 InterlockedAnd8
#endif
#include "time.h"
#include <stdatomic.h>
#define LG_LOCK_MODE "Atomic"
typedef atomic_flag LG_Lock;
#define LG_LOCK_INIT(x) atomic_flag_clear(&(x))
#define LG_LOCK(x) \
while(atomic_flag_test_and_set_explicit(&(x), memory_order_acquire)) { ; }
#define LG_UNLOCK(x) \
atomic_flag_clear_explicit(&(x), memory_order_release);
#define LG_LOCK_FREE(x)
#define INTERLOCKED_INC(x) atomic_fetch_add((x), 1)
#define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1)
#define INTERLOCKED_SECTION(lock, x) \
LG_LOCK(lock) \
x \
LG_UNLOCK(lock)

View File

@@ -18,4 +18,5 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
// sprintf but with buffer allocation
int alloc_sprintf(char ** str, const char * format, ...);
int alloc_sprintf(char ** str, const char * format, ...)
__attribute__ ((format (printf, 2, 3)));

View File

@@ -17,5 +17,5 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
// returns the maximum number of multisamples supported by the system
int sysinfo_gfx_max_multisample();
// returns the page size
long sysinfo_getPageSize();

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2020 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,14 +17,12 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
struct spice_password
{
char * data;
unsigned int size;
};
typedef struct LGThread LGThread;
typedef int (*LGThreadFunction)(void * opaque);
bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result);
void spice_rsa_free_password(struct spice_password * pass);
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle);
bool lgJoinThread (LGThread * handle, int * resultCode);

View File

@@ -0,0 +1,112 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 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>
#if defined(_WIN32)
#include <windows.h>
#else
#include <time.h>
#include <stdint.h>
#endif
typedef struct LGTimer LGTimer;
static inline uint64_t microtime(void)
{
#if defined(_WIN32)
static LARGE_INTEGER freq = { 0 };
if (!freq.QuadPart)
QueryPerformanceFrequency(&freq);
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
return time.QuadPart / (freq.QuadPart / 1000000LL);
#else
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return (uint64_t)time.tv_sec * 1000000LL + time.tv_nsec / 1000LL;
#endif
}
#if !defined(_WIN32)
//FIXME: make win32 versions
static inline uint64_t nanotime(void)
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
return ((uint64_t)time.tv_sec * 1000000000LL) + time.tv_nsec;
}
static inline void nsleep(uint64_t ns)
{
const struct timespec ts =
{
.tv_sec = ns / 1e9,
.tv_nsec = ns - ((ns / 1e9) * 1e9)
};
nanosleep(&ts, NULL);
}
static inline void tsDiff(struct timespec *diff, const struct timespec *left,
const struct timespec *right)
{
diff->tv_sec = left->tv_sec - right->tv_sec;
diff->tv_nsec = left->tv_nsec - right->tv_nsec;
if (diff->tv_nsec < 0)
{
--diff->tv_sec;
diff->tv_nsec += 1000000000;
}
}
static inline uint32_t __iter_div_u64_rem(uint64_t dividend, uint32_t divisor, uint64_t *remainder)
{
uint32_t ret = 0;
while (dividend >= divisor) {
/* The following asm() prevents the compiler from
optimising this loop into a modulo operation. */
asm("" : "+rm"(dividend));
dividend -= divisor;
ret++;
}
*remainder = dividend;
return ret;
}
static inline void tsAdd(struct timespec *a, uint64_t ns)
{
a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, 1000000000L, &ns);
a->tv_nsec = ns;
}
#endif
typedef bool (*LGTimerFn)(void * udata);
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result);
void lgTimerDestroy(LGTimer * timer);

View File

@@ -0,0 +1 @@
extern char * BUILD_VERSION;

View File

@@ -1,6 +1,6 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
@@ -19,8 +19,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include "common/debug.h"
#include "debug.h"
#include <windows.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
@@ -30,6 +31,8 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
#define DEBUG_WINERROR(x, y) DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y)
bool IsWindows8();
#ifdef __cplusplus
}
#endif

29
common/src/KVMFR.c Normal file
View File

@@ -0,0 +1,29 @@
/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2020 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/KVMFR.h"
const char * FrameTypeStr[FRAME_TYPE_MAX] =
{
"FRAME_TYPE_INVALID",
"FRAME_TYPE_BGRA",
"FRAME_TYPE_RGBA",
"FRAME_TYPE_RGBA10",
"FRAME_TYPE_RGBA16F"
};

206
common/src/framebuffer.c Normal file
View File

@@ -0,0 +1,206 @@
/*
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 "common/framebuffer.h"
#include "common/debug.h"
#include <string.h>
#include <stdatomic.h>
#include <emmintrin.h>
#include <smmintrin.h>
#include <unistd.h>
#define FB_CHUNK_SIZE 1048576 // 1MB
#define FB_SPIN_LIMIT 10000 // 10ms
struct stFrameBuffer
{
atomic_uint_least32_t wp;
uint8_t data[0];
};
const size_t FrameBufferStructSize = sizeof(FrameBuffer);
void framebuffer_wait(const FrameBuffer * frame, size_t size)
{
while(atomic_load_explicit(&frame->wp, memory_order_acquire) < size)
{
int spinCount = 0;
while(frame->wp < size)
{
if (++spinCount == FB_SPIN_LIMIT)
return;
usleep(1);
}
}
}
bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
{
uint8_t * restrict d = (uint8_t*)dst;
uint_least32_t rp = 0;
size_t y = 0;
const size_t linewidth = width * bpp;
while(y < height)
{
uint_least32_t wp;
int spinCount = 0;
/* spinlock */
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < linewidth)
{
if (++spinCount == FB_SPIN_LIMIT)
return false;
usleep(1);
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
}
/* copy any unaligned bytes */
const uint8_t * src = frame->data + rp;
const size_t unaligned = (uintptr_t)src & 0xF;
if (unaligned)
{
memcpy(d, src, unaligned);
src += unaligned;
d += unaligned;
}
const size_t blocks = (linewidth - unaligned) / 64;
const size_t left = (linewidth - unaligned) % 64;
_mm_mfence();
__m128i * restrict s = (__m128i *)src;
for(int i = 0; i < blocks; ++i)
{
__m128i *_d = (__m128i *)d;
__m128i *_s = (__m128i *)s;
__m128i v1 = _mm_stream_load_si128(_s + 0);
__m128i v2 = _mm_stream_load_si128(_s + 1);
__m128i v3 = _mm_stream_load_si128(_s + 2);
__m128i v4 = _mm_stream_load_si128(_s + 3);
_mm_storeu_si128(_d + 0, v1);
_mm_storeu_si128(_d + 1, v2);
_mm_storeu_si128(_d + 2, v3);
_mm_storeu_si128(_d + 3, v4);
d += 64;
s += 4;
}
if (left)
{
memcpy(d, s, left);
d += left;
}
rp += pitch;
d += dstpitch - linewidth;
++y;
}
return true;
}
bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque)
{
uint_least32_t rp = 0;
size_t y = 0;
const size_t linewidth = width * bpp;
while(y < height)
{
uint_least32_t wp;
int spinCount = 0;
/* spinlock */
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < linewidth)
{
if (++spinCount == FB_SPIN_LIMIT)
return false;
usleep(1);
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
}
if (!fn(opaque, frame->data + rp, linewidth))
return false;
rp += pitch;
++y;
}
return true;
}
/**
* Prepare the framebuffer for writing
*/
void framebuffer_prepare(FrameBuffer * frame)
{
atomic_store_explicit(&frame->wp, 0, memory_order_release);
}
bool framebuffer_write(FrameBuffer * frame, const void * restrict src, size_t size)
{
__m128i * restrict s = (__m128i *)src;
__m128i * restrict d = (__m128i *)frame->data;
size_t wp = 0;
_mm_mfence();
/* copy in chunks */
while(size > 63)
{
__m128i *_d = (__m128i *)d;
__m128i *_s = (__m128i *)s;
__m128i v1 = _mm_stream_load_si128(_s + 0);
__m128i v2 = _mm_stream_load_si128(_s + 1);
__m128i v3 = _mm_stream_load_si128(_s + 2);
__m128i v4 = _mm_stream_load_si128(_s + 3);
_mm_store_si128(_d + 0, v1);
_mm_store_si128(_d + 1, v2);
_mm_store_si128(_d + 2, v3);
_mm_store_si128(_d + 3, v4);
s += 4;
d += 4;
size -= 64;
wp += 64;
if (wp % FB_CHUNK_SIZE == 0)
atomic_store_explicit(&frame->wp, wp, memory_order_release);
}
if(size)
{
memcpy(frame->data + wp, s, size);
wp += size;
}
atomic_store_explicit(&frame->wp, wp, memory_order_release);
return true;
}

View File

@@ -210,7 +210,7 @@ bool option_register(struct Option options[])
return true;
};
void option_free()
void option_free(void)
{
for(int i = 0; i < state.oCount; ++i)
{
@@ -223,6 +223,12 @@ void option_free()
state.options = NULL;
state.oCount = 0;
for(int g = 0; g < state.gCount; ++g)
{
struct OptionGroup * group = &state.groups[g];
if (group->options)
free(group->options);
}
free(state.groups);
state.groups = NULL;
state.gCount = 0;
@@ -367,6 +373,7 @@ bool option_load(const char * filename)
int lineno = 1;
char * module = NULL;
bool line = true;
bool comment = false;
bool expectLine = false;
bool expectValue = false;
char * name = NULL;
@@ -379,6 +386,10 @@ bool option_load(const char * filename)
for(int c = fgetc(fp); !feof(fp); c = fgetc(fp))
{
if (comment && c != '\n')
continue;
comment = false;
switch(c)
{
case '[':
@@ -477,6 +488,14 @@ bool option_load(const char * filename)
(*p)[(*len)++] = c;
break;
case ';':
if (line)
{
comment = true;
break;
}
// fallthrough
default:
if (expectLine)
{
@@ -507,7 +526,7 @@ exit:
return result;
}
bool option_validate()
bool option_validate(void)
{
if (state.doHelp)
{
@@ -558,7 +577,7 @@ bool option_validate()
return ok;
}
void option_print()
void option_print(void)
{
printf(
"The following is a complete list of options accepted by this application\n\n"
@@ -600,7 +619,7 @@ void option_print()
maxLen = alloc_sprintf(
&line,
"%-*s | Short | %-*s | Description",
strlen(state.groups[g].module) + state.groups[g].pad + 1,
(int)(strlen(state.groups[g].module) + state.groups[g].pad + 1),
"Long",
valueLen,
"Value"
@@ -717,4 +736,4 @@ bool option_get_bool(const char * module, const char * name)
}
assert(o->type == OPTION_TYPE_BOOL);
return o->value.x_bool;
}
}

View File

@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform LANGUAGES C)
if (UNIX)
add_subdirectory("linux")
elseif(WIN32)
add_subdirectory("windows")
endif()
add_library(lg_common_platform INTERFACE)
target_link_libraries(lg_common_platform INTERFACE lg_common_platform_code)

View File

@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform_code LANGUAGES C)
include_directories(
${PROJECT_SOURCE_DIR}/include
${PROJECT_TOP}
)
add_library(lg_common_platform_code STATIC
crash.c
sysinfo.c
thread.c
event.c
ivshmem.c
time.c
)
if(ENABLE_BACKTRACE)
target_link_libraries(lg_common_platform_code bfd)
endif()
target_link_libraries(lg_common_platform_code
lg_common
pthread
rt
)

View File

@@ -17,9 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define _GNU_SOURCE
#include "common/crash.h"
#include "common/debug.h"
#include "common/version.h"
#if defined(ENABLE_BACKTRACE)
@@ -54,7 +54,7 @@ struct crash
static struct crash crash = {0};
static void load_symbols()
static void load_symbols(void)
{
bfd_init();
crash.fd = bfd_openr(crash.exe, NULL);
@@ -98,10 +98,18 @@ static void load_symbols()
static bool lookup_address(bfd_vma pc, const char ** filename, const char ** function, unsigned int * line, unsigned int * discriminator)
{
#ifdef bfd_get_section_flags
if ((bfd_get_section_flags(crash.fd, crash.section) & SEC_ALLOC) == 0)
#else
if ((bfd_section_flags(crash.section) & SEC_ALLOC) == 0)
#endif
return false;
#ifdef bfd_get_section_size
bfd_size_type size = bfd_get_section_size(crash.section);
#else
bfd_size_type size = bfd_section_size(crash.section);
#endif
if (pc >= size)
return false;
@@ -123,7 +131,7 @@ static bool lookup_address(bfd_vma pc, const char ** filename, const char ** fun
return true;
}
static void cleanup()
static void cleanup(void)
{
if (crash.syms)
free(crash.syms);
@@ -169,7 +177,7 @@ static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
dl_iterate_phdr(dl_iterate_phdr_callback, NULL);
load_symbols();
DEBUG_ERROR("==== FATAL CRASH (" BUILD_VERSION ") ====");
DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION);
DEBUG_ERROR("signal %d (%s), address is %p", sig_num, strsignal(sig_num), info->si_addr);
size = backtrace(array, 50);
@@ -230,4 +238,4 @@ bool installCrashHandler(const char * exe)
return true;
}
#endif
#endif

View File

@@ -0,0 +1,192 @@
/*
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 "common/event.h"
#include "common/debug.h"
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>
#include <errno.h>
#include <stdatomic.h>
#include <stdint.h>
struct LGEvent
{
pthread_mutex_t mutex;
pthread_cond_t cond;
atomic_uint count;
bool autoReset;
};
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime)
{
LGEvent * handle = (LGEvent *)calloc(sizeof(LGEvent), 1);
if (!handle)
{
DEBUG_ERROR("Failed to allocate memory");
return NULL;
}
if (pthread_mutex_init(&handle->mutex, NULL) != 0)
{
DEBUG_ERROR("Failed to create the mutex");
free(handle);
return NULL;
}
pthread_condattr_t cattr;
pthread_condattr_init(&cattr);
if (pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC) != 0)
{
DEBUG_ERROR("Failed to set the condition clock to realtime");
pthread_mutex_destroy(&handle->mutex);
free(handle);
return NULL;
}
if (pthread_cond_init(&handle->cond, &cattr) != 0)
{
pthread_mutex_destroy(&handle->mutex);
free(handle);
return NULL;
}
handle->autoReset = autoReset;
return handle;
}
void lgFreeEvent(LGEvent * handle)
{
assert(handle);
pthread_cond_destroy (&handle->cond );
pthread_mutex_destroy(&handle->mutex);
free(handle);
}
bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
{
assert(handle);
bool ret = true;
int count = 0;
int res;
while(ret && (count = atomic_load(&handle->count)) == 0)
{
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
if (!ts)
{
if ((res = pthread_cond_wait(&handle->cond, &handle->mutex)) != 0)
{
DEBUG_ERROR("Failed to wait on the condition (err: %d)", res);
ret = false;
}
}
else
{
switch((res = pthread_cond_timedwait(&handle->cond, &handle->mutex, ts)))
{
case 0:
break;
case ETIMEDOUT:
ret = false;
break;
default:
ret = false;
DEBUG_ERROR("Timed wait failed (err: %d)", res);
break;
}
}
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
}
if (ret && handle->autoReset)
atomic_fetch_sub(&handle->count, count);
return ret;
}
bool lgWaitEventNS(LGEvent * handle, unsigned int timeout)
{
if (timeout == TIMEOUT_INFINITE)
return lgWaitEventAbs(handle, NULL);
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t nsec = ts.tv_nsec + timeout;
if(nsec > 1000000000UL)
{
ts.tv_nsec = nsec - 1000000000UL;
++ts.tv_sec;
}
else
ts.tv_nsec = nsec;
return lgWaitEventAbs(handle, &ts);
}
bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
{
if (timeout == TIMEOUT_INFINITE)
return lgWaitEventAbs(handle, NULL);
return lgWaitEventNS(handle, timeout * 1000000U);
}
bool lgSignalEvent(LGEvent * handle)
{
assert(handle);
const bool signalled = atomic_fetch_add_explicit(&handle->count, 1,
memory_order_acquire) > 0;
if (signalled)
return true;
if (pthread_cond_broadcast(&handle->cond) != 0)
{
DEBUG_ERROR("Failed to signal the condition");
return false;
}
return true;
}
bool lgResetEvent(LGEvent * handle)
{
assert(handle);
atomic_store(&handle->count, 0);
return true;
}

View File

@@ -0,0 +1,262 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 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/ivshmem.h"
#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "common/debug.h"
#include "common/option.h"
#include "common/stringutils.h"
#include "module/kvmfr.h"
struct IVSHMEMInfo
{
int devFd;
int dmaFd;
int size;
};
static bool ivshmemDeviceValidator(struct Option * opt, const char ** error)
{
// if it's not a kvmfr device, it must be a file on disk
if (strlen(opt->value.x_string) > 3 && memcmp(opt->value.x_string, "kvmfr", 5) != 0)
{
struct stat st;
if (stat(opt->value.x_string, &st) != 0)
{
*error = "Invalid path to the ivshmem file specified";
return false;
}
return true;
}
return true;
}
static StringList ivshmemDeviceGetValues(struct Option * option)
{
StringList sl = stringlist_new(true);
DIR * d = opendir("/sys/class/kvmfr");
if (!d)
return sl;
struct dirent * dir;
while((dir = readdir(d)) != NULL)
{
if (dir->d_name[0] == '.')
continue;
char * devName;
alloc_sprintf(&devName, "/dev/%s", dir->d_name);
stringlist_push(sl, devName);
}
closedir(d);
return sl;
}
void ivshmemOptionsInit(void)
{
struct Option options[] =
{
{
.module = "app",
.name = "shmFile",
.shortopt = 'f',
.description = "The path to the shared memory file, or the name of the kvmfr device to use, ie: kvmfr0",
.type = OPTION_TYPE_STRING,
.value.x_string = "/dev/shm/looking-glass",
.validator = ivshmemDeviceValidator,
.getValues = ivshmemDeviceGetValues
},
{0}
};
option_register(options);
}
bool ivshmemInit(struct IVSHMEM * dev)
{
// FIXME: split code from ivshmemOpen
return true;
}
bool ivshmemOpen(struct IVSHMEM * dev)
{
return ivshmemOpenDev(dev, option_get_string("app", "shmFile"));
}
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
{
assert(dev);
unsigned int devSize;
int devFd = -1;
int dmaFd = -1;
int mapFd = -1;
dev->opaque = NULL;
DEBUG_INFO("KVMFR Device : %s", shmDevice);
if (strlen(shmDevice) > 8 && memcmp(shmDevice, "/dev/kvmfr", 10) == 0)
{
devFd = open(shmDevice, O_RDWR, (mode_t)0600);
if (devFd < 0)
{
DEBUG_ERROR("Failed to open: %s", shmDevice);
DEBUG_ERROR("%s", strerror(errno));
return false;
}
// get the device size
devSize = ioctl(devFd, KVMFR_DMABUF_GETSIZE, 0);
const struct kvmfr_dmabuf_create create =
{
.flags = KVMFR_DMABUF_FLAG_CLOEXEC,
.offset = 0x0,
.size = devSize
};
dmaFd = ioctl(devFd, KVMFR_DMABUF_CREATE, &create);
if (dmaFd < 0)
{
DEBUG_ERROR("Failed to create the dma buffer");
close(devFd);
return false;
}
mapFd = dmaFd;
}
else
{
struct stat st;
if (stat(shmDevice, &st) != 0)
{
DEBUG_ERROR("Failed to stat: %s", shmDevice);
DEBUG_ERROR("%s", strerror(errno));
return false;
}
devSize = st.st_size;
devFd = open(shmDevice, O_RDWR, (mode_t)0600);
if (devFd < 0)
{
DEBUG_ERROR("Failed to open: %s", shmDevice);
DEBUG_ERROR("%s", strerror(errno));
return false;
}
mapFd = devFd;
}
void * map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, mapFd, 0);
if (map == MAP_FAILED)
{
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
DEBUG_ERROR("%s", strerror(errno));
return false;
}
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo));
info->size = devSize;
info->devFd = devFd;
info->dmaFd = dmaFd;
dev->opaque = info;
dev->size = devSize;
dev->mem = map;
return true;
}
void ivshmemClose(struct IVSHMEM * dev)
{
assert(dev);
if (!dev->opaque)
return;
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)dev->opaque;
munmap(dev->mem, info->size);
if (info->dmaFd >= 0)
close(info->dmaFd);
close(info->devFd);
free(info);
dev->mem = NULL;
dev->size = 0;
dev->opaque = NULL;
}
void ivshmemFree(struct IVSHMEM * dev)
{
// FIXME: split code from ivshmemClose
}
bool ivshmemHasDMA(struct IVSHMEM * dev)
{
assert(dev && dev->opaque);
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)dev->opaque;
return info->dmaFd >= 0;
}
int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size)
{
assert(ivshmemHasDMA(dev));
assert(dev && dev->opaque);
assert(offset + size <= dev->size);
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)dev->opaque;
// align to the page size
size = (size & ~(0x1000-1)) + 0x1000;
const struct kvmfr_dmabuf_create create =
{
.flags = KVMFR_DMABUF_FLAG_CLOEXEC,
.offset = offset,
.size = size
};
int fd = ioctl(info->devFd, KVMFR_DMABUF_CREATE, &create);
if (fd < 0)
DEBUG_ERROR("Failed to create the dma buffer");
return fd;
}

View File

@@ -17,8 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
int sysinfo_gfx_max_multisample()
#include <unistd.h>
long sysinfo_getPageSize(void)
{
//FIXME: Implement this
return 4;
}
return sysconf(_SC_PAGESIZE);
}

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