Compare commits

...

320 Commits

Author SHA1 Message Date
Geoffrey McRae
76710ef201 [all] updated issue template and readme in preperation for B2 2020-10-08 20:04:52 +11:00
Geoffrey McRae
e20c8a5cc7 [host] dxgi: don't try to get the hotspot of a null cursor 2020-10-06 23:24:01 +11:00
Geoffrey McRae
4f4d2dbf42 [host] dxgi: fix memory leak if an error occurs 2020-10-06 22:32:10 +11:00
Geoffrey McRae
8692e9af80 [client] don't hide the cursor when SPICE is disabled
Fixes #304
2020-08-21 15:40:22 +10:00
Geoffrey McRae
7d2b39058c [client] ensure the cursor is updated when the window looses/gains focus 2020-08-20 16:05:55 +10:00
Geoffrey McRae
6927dbecd2 [client] added new input:mouseRedraw option
This new option, when enabled (the default) enables cursor movements to
trigger frame updates in the client, improving responsiveness at the
cost of increased FPS while the mouse is moving around.
2020-08-20 15:50:33 +10:00
Geoffrey McRae
f9b6dcc986 [client] only resync the timer if we got an early frame
This prevents a slow update (ie, 30ups) from pulling the refresh rate
below the minimum (ie, 60fps).
2020-08-20 15:18:45 +10:00
Geoffrey McRae
5c912e3c27 [client] spice: improve mouse syncronization with the host 2020-08-20 14:52:24 +10:00
Geoffrey McRae
7e362050f7 [all] update KVMFR to provide cursor hotspot information
This commit bumps the KVMFR protocol version as it adds additional
hotspot x & y fields to the KVMFRCursor struct. This corrects the issue
of invalid alignment of the local mouse when the shape has an offset
such as the 'I' beam.
2020-08-20 13:51:01 +10:00
Ash
10fbdeb294 update client/README.md: spice:captureOnStart from #278 2020-08-19 23:08:34 +10:00
camr0
72d70e8322 Update host/README.md: c-host -> host 2020-08-17 11:44:52 +10:00
Geoffrey McRae
c66a339bbc [client] egl: ensure overflow occurs for state value checks 2020-08-15 22:39:10 +10:00
Geoffrey McRae
1c7961daeb [host] dxgi: rework locking and retry logic for lower latency 2020-08-15 20:49:49 +10:00
Geoffrey McRae
cdc3384883 [host] dxgi: improve frame signaling mechanics 2020-08-15 18:16:11 +10:00
Geoffrey McRae
969effedde [host] update information about PsExec now LG can run as a service 2020-08-13 11:41:16 +10:00
Geoffrey McRae
dc4d1d49fa [host] updated the readme with regards to log file location 2020-08-12 22:15:22 +10:00
Geoffrey McRae
4e1f947a09 [host] Windows: fix uninstaller product name 2020-08-12 22:03:10 +10:00
Geoffrey McRae
15d1a74291 [host] Windows: multiple fixes to the installer 2020-08-12 21:50:48 +10:00
TheCakeIsNaOH
7dba6b9b08 [Host] Convert installer to setup service instead of scheduled task 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
a5ad531004 [Host] Change default install dir "Looking-Glass" to "Looking Glass" 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
c119b3dcca [Host] Correct installer and shortcut names 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
e2f2437ef4 [Host] Installer command line options and install location selection add 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
b2980fea63 [Host] Add instructions on how to build NSIS installer. 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
2b518690b8 [Host] NSIS script change names from C-Host to Host 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
92aca75792 [c-host] Add NSIS installer script 2020-08-12 21:32:15 +10:00
Geoffrey McRae
64fdb8b7bb [host] Windows: service (un)install now starts/stops the service
In addition to starting and stopping the service, it now also stops the
LG process if the service started it.
2020-08-12 20:56:02 +10:00
Geoffrey McRae
431ae3fc55 [common] linux: fix issue with infinite timeout events 2020-08-11 19:31:11 +10:00
Geoffrey McRae
380b5df9f9 [host] increase sleep timeout to 100ms 2020-08-11 19:11:17 +10:00
Geoffrey McRae
c7330167cf [host] shutdown capture if there are no subscribers
Fixes #33
2020-08-11 18:30:47 +10:00
Geoffrey McRae
ca02e1aba9 [host] Windows: change "Open Log File" to "Log File Location" 2020-08-11 17:45:00 +10:00
Geoffrey McRae
ca4b1f5592 [host] Windows: don't open the log file, instead show it's location
Now that it's recommended to run LG as the `SYSTEM` user, launching an
application to read the log file is dangerous as it will be launched
with the same access rights (`SYSTEM`). Instead so as Microsoft
recommends and only present a message box with the information.
2020-08-11 17:42:00 +10:00
Geoffrey McRae
0cf1e27709 [host] Windows: run with HIGH priority if started by the service 2020-08-11 17:37:40 +10:00
Geoffrey McRae
045932ce77 [host] send the correct cursor shape on client connection 2020-08-11 17:16:54 +10:00
Geoffrey McRae
bf5481446b [host] Windows: poll more freqently for a stopped LG process 2020-08-11 15:22:29 +10:00
Geoffrey McRae
e3f97e384b [client] rework the start/restart logic to use an enum 2020-08-11 15:14:58 +10:00
Geoffrey McRae
76e119f8ad [client] egl: don't fade the splash when restarting 2020-08-11 14:54:48 +10:00
Geoffrey McRae
bfb12c74fb [client] be quicker at detecting restart and quieter about it 2020-08-11 14:52:22 +10:00
Geoffrey McRae
fa50b7824c [client] fix crash on shutdown while waiting for a restart 2020-08-11 14:45:43 +10:00
Geoffrey McRae
da8b2d0cec [client] egl: properly wait for a new frame on restart 2020-08-11 14:45:08 +10:00
Geoffrey McRae
74649ddb96 [client] gracefully restart if the host application restarts 2020-08-11 14:30:44 +10:00
Geoffrey McRae
4619ddef5d [host] Windows: added missing linker library 2020-08-11 13:15:18 +10:00
Geoffrey McRae
ea74ee6e25 [host] windows: fix crosscompile take 2 2020-08-11 13:11:42 +10:00
Geoffrey McRae
ecd73aa670 [host] windows: fix linux crosscompile 2020-08-11 13:07:23 +10:00
Geoffrey McRae
10d9678b3d [host] Windows: improved service restart detection 2020-08-11 12:47:50 +10:00
Geoffrey McRae
e08d3afdbc [host] Windows: added missing service files 2020-08-11 12:27:04 +10:00
Geoffrey McRae
9a6b598438 [host] Windows: Implemented service to launch LG as the SYSTEM user
Experimental, use at your own peril!

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

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

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

This reverts commit 3feed7ba07.

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

This reverts commit 57f1f2d1fe.

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

This reverts commit b0f9f15a60.

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

This reverts commit dc4d820666.

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

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

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

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

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

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

Fixes #171
2019-06-12 15:31:18 +10:00
Omar Pakker
e3343cbd01 Rewrite dkms.conf
1) With the change to the Makefile, this update allows dkms to build and install the module for different kernels.
2) As per dkms documentation, no use of ${dkms_tree}.
3) Removed the use of REMAKE_INITRD as this module is not needed that early in the boot process.
4) Updated version to match what's defined in the module
2019-06-06 13:40:06 +10:00
Omar Pakker
71ffa0a137 Update makefile to allow kernel override 2019-06-06 13:40:06 +10:00
Geoffrey McRae
2b4f8091f9 [client] README.md cosmetics 2019-05-31 16:45:55 +10:00
Geoffrey McRae
113da121e9 [client] updated documentation for new keybinds 2019-05-31 16:44:08 +10:00
Geoffrey McRae
dd7413f973 [client] added keybinds to send Ctrl+Alt+Fn
Fixes #165
2019-05-31 16:39:55 +10:00
Geoffrey McRae
0851fd13e6 [all] made a nicer icon, hopefully just a placeholder for now 2019-05-30 22:21:53 +10:00
Geoffrey McRae
97024041f3 [client] allow the screensaver to run 2019-05-30 20:54:39 +10:00
Geoffrey McRae
22238c3200 [client] fix invalid access on early termination 2019-05-30 20:24:51 +10:00
Geoffrey McRae
780bb248f7 [c-host] dxgi: fix invalid cursor type define 2019-05-28 15:17:11 +10:00
125 changed files with 7543 additions and 5294 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,69 @@
### 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 #looking-glass in the VFIO discord server
* https://discord.gg/4ahCn4c
*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).
To obtain this locate the log file on your system, it will be in one of the
following two locations depending on how you are launching the Looking Glass Host
application:
* C:\Windows\Temp\looking-glass.txt
* C:\Users\YOUR_USER\AppData\Local\Temp\looking-glass.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 unexpetedly 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
```

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
* Getting Started: https://looking-glass.hostfission.com/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,19 +22,27 @@ 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.hostfission.com/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.hostfission.com/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

View File

@@ -1 +1 @@
B1-rc3-8-g373d4ac932+1
B2-rc4-11-g8692e9af80+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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,552 +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;
size_t unused;
app.argv[i] = malloc(s);
wcstombs_s(&unused, app.argv[i], s, wargv[i], _TRUNCATE);
}
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,472 +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;
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

@@ -24,6 +24,9 @@ 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.")
add_compile_options(
"-Wall"
"-Werror"
@@ -43,6 +46,10 @@ pkg_check_modules(PKGCONFIG REQUIRED
x11
)
pkg_check_modules(PKGCONFIG_OPT
xi
)
execute_process(
COMMAND cat ../VERSION
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
@@ -60,13 +67,14 @@ get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
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
)
@@ -80,19 +88,22 @@ 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(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
lgmp
purespice
renderers
clipboards
fonts

View File

@@ -46,8 +46,23 @@ Below are a list of current key bindings:
| <kbd>ScrLk</kbd>+<kbd>F</kbd> | Full Screen 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>Q</kbd> | Quit |
| <kbd>ScrLk</kbd>+<kbd>Insert</kbd> | Increase mouse sensitivity (in capture mode only) |
| <kbd>ScrLk</kbd>+<kbd>Del</kbd> | Decrease mouse sensitivity (in capture mode only) |
| <kbd>ScrLk</kbd>+<kbd>F1</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F1</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F2</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F2</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F3</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F3</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F4</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F4</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F5</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F5</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F6</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F6</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F7</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F7</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F8</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F8</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F9</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F9</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F10</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F10</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F11</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F11</kbd> to the guest |
| <kbd>ScrLk</kbd>+<kbd>F12</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F12</kbd> to the guest |
### Setting options via command line arguments
@@ -87,9 +102,9 @@ Command line arguments will override any options loaded from the config files.
| app:framePollInterval | | 1000 | How often to check for a frame update in microseconds |
|-------------------------------------------------------------------------------------------------------------------------|
|-------------------------------------------------------------------------------------------------------|
|-------------------------------------------------------------------------------------------------------------|
| 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 |
@@ -99,12 +114,13 @@ Command line arguments will override any options loaded from the config files.
| 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 |
@@ -126,6 +142,7 @@ 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 |
|------------------------------------------------------------------------------------------------------------------|
|--------------------------------------------------------------------------|

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 && \
@@ -86,10 +88,11 @@ 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_RendererOnRestart )(void * opaque);
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const uint8_t * data);
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const FrameBuffer * frame);
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);
@@ -102,6 +105,7 @@ typedef struct LG_Renderer
LG_RendererCreate create;
LG_RendererInitialize initialize;
LG_RendererDeInitialize deinitialize;
LG_RendererOnRestart on_restart;
LG_RendererOnResize on_resize;
LG_RendererOnMouseShape on_mouse_shape;
LG_RendererOnMouseEvent on_mouse_event;

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

View File

@@ -4,10 +4,13 @@ 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
@@ -31,6 +34,7 @@ make_object(
add_library(renderer_EGL STATIC
egl.c
debug.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"

View File

@@ -19,7 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "cursor.h"
#include "common/debug.h"
#include "utils.h"
#include "common/locking.h"
#include "texture.h"
#include "shader.h"
@@ -207,24 +207,10 @@ 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:
{
@@ -262,10 +248,11 @@ void egl_cursor_render(EGL_Cursor * cursor)
LG_UNLOCK(cursor->lock);
}
if (cursor->type == LG_CURSOR_MONOCHROME)
{
glEnable(GL_BLEND);
switch(cursor->type)
{
case LG_CURSOR_MONOCHROME:
{
egl_shader_use(cursor->shader);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
@@ -277,18 +264,26 @@ void egl_cursor_render(EGL_Cursor * cursor)
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_set_texture(cursor->model, cursor->textureMono);
egl_model_render(cursor->model);
glDisable(GL_BLEND);
break;
}
else
{
glEnable(GL_BLEND);
case LG_CURSOR_COLOR:
{
egl_shader_use(cursor->shader);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
egl_model_render(cursor->model);
glDisable(GL_BLEND);
break;
}
case LG_CURSOR_MASKED_COLOR:
{
egl_shader_use(cursor->shaderMono);
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_render(cursor->model);
break;
}
}
glDisable(GL_BLEND);
}

View File

@@ -0,0 +1,58 @@
/*
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 <GL/gl.h>
#include <stdarg.h>
#include <stdio.h>
void egl_debug_printf(char * format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
GLenum error = glGetError();
switch(error)
{
case GL_NO_ERROR:
fprintf(stderr, " (GL_NO_ERROR)\n");
break;
case GL_INVALID_ENUM:
fprintf(stderr, " (GL_INVALID_ENUM)\n");
break;
case GL_INVALID_VALUE:
fprintf(stderr, " (GL_INVALID_VALUE)\n");
break;
case GL_INVALID_OPERATION:
fprintf(stderr, " (GL_INVALID_OPERATION)\n");
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
fprintf(stderr, " (GL_INVALID_FRAMEBUFFER_OPERATION)\n");
break;
case GL_OUT_OF_MEMORY:
fprintf(stderr, " (GL_OUT_OF_MEMORY)\n");
break;
}
}

View File

@@ -17,14 +17,11 @@ 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 <stdint.h>
#pragma once
struct spice_password
{
char * data;
unsigned int size;
};
#include <common/debug.h>
bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result);
void spice_rsa_free_password(struct spice_password * pass);
#define EGL_DEBUG_PRINT(type, fmt, ...) do {egl_debug_printf(type " %20s:%-4u | %-30s | " fmt, STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define EGL_ERROR(fmt, ...) EGL_DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
void egl_debug_printf(char * format, ...);

View File

@@ -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"
@@ -47,21 +47,19 @@ struct DesktopShader
struct EGL_Desktop
{
void * egl;
EGL_Texture * texture;
struct DesktopShader * shader; // the active shader
EGL_Model * model;
// internals
int width, height;
// shader instances
struct DesktopShader shader_generic;
struct DesktopShader shader_yuv;
// internals
enum EGL_PixelFormat pixFmt;
unsigned int width, height;
unsigned int pitch;
const uint8_t * data;
bool update;
// night vision
KeybindHandle kbNV;
int nvMax;
@@ -96,7 +94,7 @@ static bool egl_init_desktop_shader(
return true;
}
bool egl_desktop_init(EGL_Desktop ** desktop)
bool egl_desktop_init(void * egl, EGL_Desktop ** desktop)
{
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
if (!*desktop)
@@ -140,6 +138,7 @@ bool egl_desktop_init(EGL_Desktop ** desktop)
egl_model_set_default((*desktop)->model);
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
(*desktop)->egl = egl;
(*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop);
(*desktop)->nvMax = option_get_int("egl", "nvGainMax");
@@ -176,29 +175,30 @@ 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_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer * frame)
{
if (sourceChanged)
{
enum EGL_PixelFormat pixFmt;
switch(format.type)
{
case FRAME_TYPE_BGRA:
desktop->pixFmt = EGL_PF_BGRA;
pixFmt = EGL_PF_BGRA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA:
desktop->pixFmt = EGL_PF_RGBA;
pixFmt = EGL_PF_RGBA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA10:
desktop->pixFmt = EGL_PF_RGBA10;
pixFmt = EGL_PF_RGBA10;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_YUV420:
desktop->pixFmt = EGL_PF_YUV420;
pixFmt = EGL_PF_YUV420;
desktop->shader = &desktop->shader_yuv;
break;
@@ -209,25 +209,13 @@ bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged,
desktop->width = format.width;
desktop->height = format.height;
desktop->pitch = format.pitch;
}
desktop->data = data;
desktop->update = true;
return true;
}
bool egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
{
if (sourceChanged)
{
if (!egl_texture_setup(
desktop->texture,
desktop->pixFmt,
desktop->width,
desktop->height,
desktop->pitch,
pixFmt,
format.width,
format.height,
format.pitch,
true // streaming texture
))
{
@@ -236,23 +224,23 @@ bool egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
}
}
if (!desktop->update)
return true;
if (!egl_texture_update(desktop->texture, desktop->data))
{
DEBUG_ERROR("Failed to update the desktop texture");
if (!egl_texture_update_from_frame(desktop->texture, frame))
return false;
enum EGL_TexStatus status;
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
{
if (status != EGL_TEX_STATUS_NOTREADY)
DEBUG_ERROR("Failed to process the desktop texture");
}
desktop->update = false;
return true;
}
void 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)
{
if (!desktop->shader)
return;
return false;
const struct DesktopShader * shader = desktop->shader;
egl_shader_use(shader->shader);
@@ -269,4 +257,5 @@ void egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, con
glUniform1i(shader->uNV, 0);
egl_model_render(desktop->model);
return true;
}

View File

@@ -25,9 +25,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
typedef struct EGL_Desktop EGL_Desktop;
bool egl_desktop_init(EGL_Desktop ** desktop);
bool egl_desktop_init(void * egl, EGL_Desktop ** desktop);
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);
bool egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged);
void egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);
bool egl_desktop_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer * frame);
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);

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"
@@ -53,12 +55,11 @@ struct Inst
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 +68,7 @@ struct Inst
EGL_Alert * alert; // the alert display
LG_RendererFormat format;
bool sourceChanged;
bool start;
uint64_t waitFadeTime;
bool waitDone;
@@ -109,7 +110,7 @@ static struct Option egl_options[] =
.name = "doubleBuffer",
.description = "Enable double buffering",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
.value.x_bool = false
},
{
.module = "egl",
@@ -198,7 +199,7 @@ bool egl_initialize(void * opaque, Uint32 * sdlFlags)
if (maxSamples > 4)
maxSamples = 4;
DEBUG_INFO("Multsampling enabled, max samples: %d", maxSamples);
DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples);
}
@@ -220,9 +221,20 @@ void egl_deinitialize(void * opaque)
egl_splash_free (&this->splash);
egl_alert_free (&this->alert );
LG_LOCK_FREE(this->lock);
free(this);
}
void egl_on_restart(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
eglDestroyContext(this->display, this->frameContext);
this->frameContext = NULL;
this->start = false;
}
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
{
struct Inst * this = (struct Inst *)opaque;
@@ -296,31 +308,49 @@ bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const in
return true;
}
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer * frame)
{
struct Inst * this = (struct Inst *)opaque;
this->sourceChanged = (
this->sourceChanged ||
const bool sourceChanged = (
this->format.type != format.type ||
this->format.width != format.width ||
this->format.height != format.height ||
this->format.pitch != format.pitch
);
if (this->sourceChanged)
if (sourceChanged)
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->useNearest = this->width < format.width || this->height < format.height;
if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, data))
/* this event runs in a second thread so we need to init it here */
if (!this->frameContext)
{
DEBUG_INFO("Failed to prepare to update the desktop");
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 (!this->waitFadeTime)
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
if (!eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->frameContext))
{
DEBUG_ERROR("Failed to make the frame context current");
return false;
}
}
if (!egl_desktop_update(this->desktop, sourceChanged, format, frame))
{
DEBUG_INFO("Failed to to update the desktop");
return false;
}
this->start = true;
return true;
}
@@ -371,11 +401,26 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false;
}
const char *client_exts = eglQueryString(NULL, EGL_EXTENSIONS);
DEBUG_INFO("Supported extensions: %s", client_exts);
bool useNative = false;
if (strstr(client_exts, "EGL_KHR_platform_base") != NULL)
useNative = true;
DEBUG_INFO("use native: %s", useNative ? "true" : "false");
switch(wminfo.subsystem)
{
case SDL_SYSWM_X11:
{
this->nativeDisp = (EGLNativeDisplayType)wminfo.info.x11.display;
if (!useNative)
this->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, wminfo.info.x11.display, NULL);
else
{
EGLNativeDisplayType native = (EGLNativeDisplayType)wminfo.info.x11.display;
this->display = eglGetDisplay(native);
}
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
break;
}
@@ -385,7 +430,13 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
{
int width, height;
SDL_GetWindowSize(window, &width, &height);
this->nativeDisp = (EGLNativeDisplayType)wminfo.info.wl.display;
if (!useNative)
this->display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wminfo.info.wl.display, NULL);
else
{
EGLNativeDisplayType native = (EGLNativeDisplayType)wminfo.info.wl.display;
this->display = eglGetDisplay(native);
}
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height);
break;
}
@@ -396,7 +447,6 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false;
}
this->display = eglGetDisplay(this->nativeDisp);
if (this->display == EGL_NO_DISPLAY)
{
DEBUG_ERROR("eglGetDisplay failed");
@@ -453,7 +503,7 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
if (!egl_desktop_init(&this->desktop))
if (!egl_desktop_init(this, &this->desktop))
{
DEBUG_ERROR("Failed to initialize the desktop");
return false;
@@ -493,8 +543,15 @@ bool egl_render(void * opaque, SDL_Window * window)
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
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))
{
if (!this->waitFadeTime)
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
egl_cursor_render(this->cursor);
}
if (!this->waitDone)
{
@@ -516,6 +573,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)
{
@@ -533,15 +595,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
if (!egl_desktop_perform_update(this->desktop, this->sourceChanged))
{
DEBUG_ERROR("Failed to perform the desktop update");
return false;
}
this->sourceChanged = false;
return true;
}
@@ -561,6 +614,7 @@ struct LG_Renderer LGR_EGL =
.create = egl_create,
.initialize = egl_initialize,
.deinitialize = egl_deinitialize,
.on_restart = egl_on_restart,
.on_resize = egl_on_resize,
.on_mouse_shape = egl_on_mouse_shape,
.on_mouse_event = egl_on_mouse_event,

View File

@@ -44,6 +44,7 @@ struct EGL_FPS
EGL_Model * model;
bool ready;
int iwidth, iheight;
float width, height;
// uniforms
@@ -144,6 +145,13 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
return;
}
if (fps->iwidth != bmp->width || fps->iheight != bmp->height)
{
fps->iwidth = bmp->width;
fps->iheight = bmp->height;
fps->width = (float)bmp->width;
fps->height = (float)bmp->height;
egl_texture_setup(
fps->texture,
EGL_PF_BGRA,
@@ -152,6 +160,7 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
bmp->width * bmp->bpp,
false
);
}
egl_texture_update
(
@@ -159,10 +168,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);
}

View File

@@ -20,6 +20,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <GL/gl.h>

View File

@@ -19,35 +19,54 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "texture.h"
#include "common/debug.h"
#include "common/framebuffer.h"
#include "debug.h"
#include "utils.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdatomic.h>
#include <SDL2/SDL_egl.h>
/* this must be a multiple of 2 */
#define TEXTURE_COUNT 2
struct Tex
{
GLuint t[3];
bool hasPBO;
GLuint pbo;
void * map;
GLsync sync;
};
struct TexState
{
_Atomic(uint8_t) w, u, s, d;
};
struct EGL_Texture
{
enum EGL_PixelFormat pixFmt;
size_t width, height;
size_t width, height, stride;
size_t bpp;
bool streaming;
bool ready;
int textureCount;
GLuint textures[3];
int planeCount;
GLuint samplers[3];
size_t planes[3][3];
GLintptr offsets[3];
size_t planes [3][3];
GLintptr offsets [3];
GLenum intFormat;
GLenum format;
GLenum dataType;
bool hasPBO;
GLuint pbo[2];
int pboIndex;
bool needsUpdate;
size_t pboBufferSize;
void * pboMap[2];
struct TexState state;
int textureCount;
struct Tex tex[TEXTURE_COUNT];
};
bool egl_texture_init(EGL_Texture ** texture)
@@ -60,7 +79,6 @@ bool egl_texture_init(EGL_Texture ** texture)
}
memset(*texture, 0, sizeof(EGL_Texture));
return true;
}
@@ -69,40 +87,102 @@ void egl_texture_free(EGL_Texture ** texture)
if (!*texture)
return;
if ((*texture)->textureCount > 0)
if ((*texture)->planeCount > 0)
glDeleteSamplers((*texture)->planeCount, (*texture)->samplers);
for(int i = 0; i < (*texture)->textureCount; ++i)
{
glDeleteTextures((*texture)->textureCount, (*texture)->textures);
glDeleteSamplers((*texture)->textureCount, (*texture)->samplers);
struct Tex * t = &(*texture)->tex[i];
if (t->hasPBO)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, t->pbo);
if ((*texture)->tex[i].map)
{
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
(*texture)->tex[i].map = NULL;
}
glDeleteBuffers(1, &t->pbo);
if (t->sync)
glDeleteSync(t->sync);
}
if ((*texture)->hasPBO)
{
for(int i = 0; i < 2; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, (*texture)->pbo[i]);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
if ((*texture)->planeCount > 0)
glDeleteTextures((*texture)->planeCount, t->t);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glDeleteBuffers(2, (*texture)->pbo);
}
free(*texture);
*texture = NULL;
}
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
texture->tex[i].map = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT
);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
if (!texture->tex[i].map)
{
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->tex[i].map)
return;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
texture->tex[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)
{
int textureCount;
int planeCount;
if (texture->streaming)
{
for(int i = 0; i < texture->textureCount; ++i)
{
egl_texture_unmap(texture, i);
if (texture->tex[i].hasPBO)
{
glDeleteBuffers(1, &texture->tex[i].pbo);
texture->tex[i].hasPBO = false;
}
}
}
texture->pixFmt = pixFmt;
texture->width = width;
texture->height = height;
texture->stride = stride;
texture->streaming = streaming;
texture->textureCount = streaming ? TEXTURE_COUNT : 1;
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;
planeCount = 1;
texture->bpp = 4;
texture->format = GL_BGRA;
texture->planes[0][0] = width;
texture->planes[0][1] = height;
@@ -114,7 +194,8 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
break;
case EGL_PF_RGBA:
textureCount = 1;
planeCount = 1;
texture->bpp = 4;
texture->format = GL_RGBA;
texture->planes[0][0] = width;
texture->planes[0][1] = height;
@@ -126,7 +207,8 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
break;
case EGL_PF_RGBA10:
textureCount = 1;
planeCount = 1;
texture->bpp = 4;
texture->format = GL_RGBA;
texture->planes[0][0] = width;
texture->planes[0][1] = height;
@@ -138,7 +220,8 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
break;
case EGL_PF_YUV420:
textureCount = 3;
planeCount = 3;
texture->bpp = 4;
texture->format = GL_RED;
texture->planes[0][0] = width;
texture->planes[0][1] = height;
@@ -161,140 +244,236 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
return false;
}
if (textureCount > texture->textureCount)
if (planeCount > texture->planeCount)
{
if (texture->textureCount > 0)
if (texture->planeCount > 0)
glDeleteSamplers(texture->planeCount, texture->samplers);
for(int i = 0; i < texture->textureCount; ++i)
{
glDeleteTextures(texture->textureCount, texture->textures);
glDeleteSamplers(texture->textureCount, texture->samplers);
if (texture->planeCount > 0)
glDeleteTextures(texture->planeCount, texture->tex[i].t);
glGenTextures(planeCount, texture->tex[i].t);
}
texture->textureCount = textureCount;
glGenTextures(texture->textureCount, texture->textures);
glGenSamplers(texture->textureCount, texture->samplers);
glGenSamplers(planeCount, texture->samplers);
for(int p = 0; p < planeCount; ++p)
{
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glSamplerParameteri(texture->samplers[p], 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);
texture->planeCount = planeCount;
}
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);
for(int i = 0; i < texture->textureCount; ++i)
{
for(int p = 0; p < planeCount; ++p)
{
glBindTexture(GL_TEXTURE_2D, texture->tex[i].t[p]);
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->planes[p][0],
texture->planes[p][1], 0, texture->format, texture->dataType, NULL);
}
}
glBindTexture(GL_TEXTURE_2D, 0);
if (streaming)
{
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);
}
if (!streaming)
return true;
glGenBuffers(2, texture->pbo);
texture->hasPBO = true;
for(int i = 0; i < 2; ++i)
for(int i = 0; i < texture->textureCount; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]);
glGenBuffers(1, &texture->tex[i].pbo);
texture->tex[i].hasPBO = true;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
glBufferStorage(
GL_PIXEL_UNPACK_BUFFER,
texture->pboBufferSize,
NULL,
GL_MAP_PERSISTENT_BIT |
GL_MAP_WRITE_BIT |
GL_MAP_COHERENT_BIT
GL_MAP_WRITE_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
);
if (!texture->pboMap[i])
{
DEBUG_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
return true;
}
static void egl_warn_slow()
{
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)
{
if (texture->needsUpdate)
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))
{
DEBUG_ERROR("Previous frame was not consumed");
return false;
egl_warn_slow();
return true;
}
if (++texture->pboIndex == 2)
texture->pboIndex = 0;
const uint8_t t = sw % TEXTURE_COUNT;
if (!egl_texture_map(texture, t))
return EGL_TEX_STATUS_ERROR;
/* update the GPU buffer */
memcpy(texture->pboMap[texture->pboIndex], buffer, texture->pboBufferSize);
texture->needsUpdate = true;
memcpy(texture->tex[t].map, buffer, texture->pboBufferSize);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
egl_texture_unmap(texture, t);
}
else
{
for(int i = 0; i < texture->textureCount; ++i)
for(int p = 0; p < texture->planeCount; ++p)
{
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[0].t[p]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][0]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
texture->format, texture->dataType, buffer + texture->offsets[p]);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
return true;
}
void egl_texture_bind(EGL_Texture * texture)
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame)
{
if (texture->streaming && texture->needsUpdate)
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))
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[texture->pboIndex]);
for(int i = 0; i < texture->textureCount; ++i)
egl_warn_slow();
return true;
}
const uint8_t t = sw % TEXTURE_COUNT;
if (!egl_texture_map(texture, t))
return EGL_TEX_STATUS_ERROR;
framebuffer_read(
frame,
texture->tex[t].map,
texture->stride,
texture->height,
texture->width,
texture->bpp,
texture->stride
);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
egl_texture_unmap(texture, t);
return true;
}
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
{
if (!texture->streaming)
return EGL_TEX_STATUS_OK;
const uint8_t su =
atomic_load_explicit(&texture->state.u, memory_order_acquire);
const uint8_t nextu = su + 1;
if (
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
/* update the texture */
const uint8_t t = su % TEXTURE_COUNT;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[t].pbo);
for(int p = 0; p < texture->planeCount; ++p)
{
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]);
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[p]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][2]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
texture->format, texture->dataType, (const void *)texture->offsets[p]);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
texture->needsUpdate = false;
/* create a fence to prevent usage before the update is complete */
texture->tex[t].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
/* we must flush to ensure the sync is in the command buffer */
glFlush();
texture->ready = true;
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
return EGL_TEX_STATUS_OK;
}
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
{
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
if (texture->streaming)
{
if (!texture->ready)
return EGL_TEX_STATUS_NOTREADY;
const uint8_t t = ss % TEXTURE_COUNT;
if (texture->tex[t].sync != 0)
{
switch(glClientWaitSync(texture->tex[t].sync, 0, 20000000)) // 20ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
glDeleteSync(texture->tex[t].sync);
texture->tex[t].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->tex[t].sync);
texture->tex[t].sync = 0;
EGL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
}
for(int i = 0; i < texture->textureCount; ++i)
if (ss != sd && ss != (uint8_t)(sd + 1))
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
memory_order_release) + 1;
}
const uint8_t t = sd % TEXTURE_COUNT;
for(int i = 0; i < texture->planeCount; ++i)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[i]);
glBindSampler(i, texture->samplers[i]);
}
return EGL_TEX_STATUS_OK;
}
int egl_texture_count(EGL_Texture * texture)
{
return texture->textureCount;
return texture->planeCount;
}

View File

@@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h>
#include "shader.h"
#include "common/framebuffer.h"
#include <GL/gl.h>
@@ -34,10 +35,19 @@ enum EGL_PixelFormat
EGL_PF_YUV420
};
enum EGL_TexStatus
{
EGL_TEX_STATUS_NOTREADY,
EGL_TEX_STATUS_OK,
EGL_TEX_STATUS_ERROR
};
bool egl_texture_init(EGL_Texture ** tex);
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_update(EGL_Texture * texture, const uint8_t * buffer);
void egl_texture_bind (EGL_Texture * texture);
bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame);
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture);
int egl_texture_count (EGL_Texture * texture);

View File

@@ -32,8 +32,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#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"
@@ -77,7 +77,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
@@ -122,8 +123,8 @@ struct Inst
GLuint vboFormat;
GLuint dataFormat;
size_t texSize;
const LG_Decoder* decoder;
void * decoderData;
size_t texPos;
const FrameBuffer * frame;
uint64_t drawStart;
bool hasBuffers;
@@ -140,7 +141,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,8 +170,15 @@ 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);
@@ -288,6 +295,12 @@ void opengl_deinitialize(void * opaque)
free(this);
}
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)
{
struct Inst * this = (struct Inst *)opaque;
@@ -362,7 +375,7 @@ 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_event(void * opaque, const LG_RendererFormat format, const FrameBuffer * frame)
{
struct Inst * this = (struct Inst *)opaque;
if (!this)
@@ -394,12 +407,7 @@ bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const
LG_UNLOCK(this->formatLock);
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);
@@ -555,9 +563,17 @@ bool opengl_render(void * opaque, SDL_Window * window)
if (!this)
return false;
if (configure(this, window))
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);
@@ -813,6 +829,7 @@ const LG_Renderer LGR_OpenGL =
.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,
@@ -834,13 +851,13 @@ static bool _check_gl_error(unsigned int line, const char * name)
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,78 +866,32 @@ 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;
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"))
{
@@ -932,30 +903,36 @@ static bool configure(struct Inst * this, SDL_Window *window)
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;
for(int i = 0; i < BUFFER_COUNT; ++i)
{
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
this->texPixels[i] = aligned_alloc(pagesize, this->texSize);
if (!this->texPixels[i])
{
DEBUG_ERROR("Failed to allocate memory for texture");
return CONFIG_STATUS_ERROR;
}
memset(this->texPixels[i], 0, this->texSize);
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return false;
return CONFIG_STATUS_ERROR;
}
glBufferData(
GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
this->texSize,
this->texPixels[i],
GL_STREAM_DRAW);
GL_STREAM_DRAW
);
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return false;
return CONFIG_STATUS_ERROR;
}
}
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
@@ -968,7 +945,7 @@ static bool configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBindBuffer"))
{
LG_UNLOCK(this->formatLock);
return false;
return CONFIG_STATUS_ERROR;
}
glBufferData(
@@ -980,19 +957,18 @@ static bool configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBufferData"))
{
LG_UNLOCK(this->formatLock);
return false;
return CONFIG_STATUS_ERROR;
}
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
}
// create the frame textures
glGenTextures(BUFFER_COUNT, this->frames);
if (check_gl_error("glGenTextures"))
{
LG_UNLOCK(this->formatLock);
return false;
return CONFIG_STATUS_ERROR;
}
this->hasFrames = true;
@@ -1003,7 +979,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 +996,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);
}
// create the display lists
glNewList(this->texList + i, GL_COMPILE);
@@ -1066,7 +1027,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 +1043,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 +1055,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 +1062,13 @@ static void deconfigure(struct Inst * this)
glDeleteSync(this->fences[i]);
this->fences[i] = NULL;
}
if (this->texPixels[i])
{
free(this->texPixels[i]);
this->texPixels[i] = NULL;
}
}
if (this->decoderData)
{
this->decoder->destroy(this->decoderData);
this->decoderData = NULL;
}
this->configured = false;
@@ -1277,6 +1221,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,20 +1254,6 @@ static bool draw_frame(struct Inst * this)
LG_UNLOCK(this->syncLock);
LG_LOCK(this->formatLock);
if (this->decoder->has_gl)
{
if (!this->decoder->update_gl_texture(
this->decoderData,
this->decoderFrames[this->texIndex]
))
{
LG_UNLOCK(this->formatLock);
DEBUG_ERROR("Failed to update the texture from the decoder");
return false;
}
}
else
{
if (glIsSync(this->fences[this->texIndex]))
{
switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED))
@@ -1331,30 +1278,24 @@ static bool draw_frame(struct Inst * this)
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)
);
const int bpp = this->format.bpp / 8;
glPixelStorei(GL_UNPACK_ALIGNMENT , bpp);
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.width);
// update the buffer, this performs a DMA transfer if possible
glBufferSubData(
GL_PIXEL_UNPACK_BUFFER,
0,
this->texSize,
data
this->texPos = 0;
framebuffer_read_fn(
this->frame,
this->format.height,
this->format.width,
bpp,
this->format.pitch,
opengl_buffer_fn,
this
);
check_gl_error("glBufferSubData");
// update the texture
glTexSubImage2D(
@@ -1381,7 +1322,6 @@ static bool draw_frame(struct Inst * this)
// unbind the buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
const bool mipmap = this->opt.mipmap && (
(this->format.width > this->destRect.w) ||

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

@@ -66,7 +66,7 @@ KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void
void app_release_keybind(KeybindHandle * handle)
{
if (!handle)
if (!*handle)
return;
state.bindings[(*handle)->key] = NULL;

View File

@@ -30,14 +30,14 @@ Place, Suite 330, Boston, MA 02111-1307 USA
// forwards
static bool optRendererParse (struct Option * opt, const char * str);
static StringList optRendererValues (struct Option * opt);
static char * optRendererToString (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 char * optScancodeToString(struct Option * opt);
static void doLicense();
@@ -52,22 +52,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",
@@ -151,6 +135,13 @@ 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 = "borderless",
@@ -177,11 +168,18 @@ static struct Option options[] =
},
{
.module = "win",
.name = "fpsLimit",
.description = "Frame rate limit (0 = disable - not recommended)",
.name = "minimizeOnFocusLoss",
.description = "Minimize window on focus loss",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{
.module = "win",
.name = "fpsMin",
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)",
.shortopt = 'K',
.type = OPTION_TYPE_INT,
.value.x_int = 200,
.value.x_int = -1,
},
{
.module = "win",
@@ -249,6 +247,13 @@ static struct Option options[] =
.type = OPTION_TYPE_INT,
.value.x_int = 0,
},
{
.module = "input",
.name = "mouseRedraw",
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
// spice options
{
@@ -311,6 +316,13 @@ 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
},
{0}
};
@@ -373,8 +385,6 @@ 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" );
@@ -382,10 +392,11 @@ bool config_load(int argc, char * argv[])
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.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");
@@ -395,6 +406,9 @@ bool config_load(int argc, char * argv[])
params.escapeKey = option_get_int ("input", "escapeKey" );
params.hideMouse = option_get_bool ("input", "hideCursor" );
params.mouseSens = option_get_int ("input", "mouseSens" );
params.mouseRedraw = option_get_bool ("input", "mouseRedraw" );
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
if (option_get_bool("spice", "enable"))
{
@@ -414,6 +428,7 @@ bool config_load(int argc, char * argv[])
}
params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
params.captureOnStart = option_get_bool("spice", "captureOnStart");
}
return true;
@@ -450,6 +465,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;
@@ -491,6 +509,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;
@@ -528,6 +549,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)

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>

File diff suppressed because it is too large Load Diff

View File

@@ -18,17 +18,41 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdbool.h>
#include <stdatomic.h>
#include <SDL2/SDL.h>
#include "interface/app.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 CursorInfo
{
int x , y;
int hx, hy;
};
enum WarpState
{
WARP_STATE_ARMED,
WARP_STATE_ON,
WARP_STATE_ACTIVE,
WARP_STATE_OFF
};
struct AppState
{
bool running;
enum RunState state;
bool ignoreInput;
bool escapeActive;
SDL_Scancode escapeAction;
@@ -39,11 +63,27 @@ struct AppState
int windowW, windowH;
SDL_Point srcSize;
LG_RendererRect dstRect;
SDL_Point cursor;
struct CursorInfo cursor;
bool cursorVisible;
bool serverMode;
bool haveCursorPos;
bool drawCursor;
bool cursorInView;
bool updateCursor;
bool initialCursorSync;
float scaleX, scaleY;
float accX, accY;
int curLastX;
int curLastY;
bool haveCurLocal;
int curLocalX;
int curLocalY;
bool haveAligned;
enum WarpState warpState;
int warpFromX, warpFromY;
int warpToX , warpToY;
const LG_Renderer * lgr;
void * lgrData;
@@ -53,21 +93,30 @@ struct AppState
SpiceDataType cbType;
struct ll * cbRequestList;
SDL_SysWMinfo wminfo;
SDL_Window * window;
int shmFD;
struct KVMFRHeader * shm;
unsigned int shmSize;
uint64_t frameTime;
struct IVSHMEM shm;
PLGMPClient lgmp;
PLGMPClientQueue frameQueue;
PLGMPClientQueue pointerQueue;
atomic_uint_least64_t frameTime;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
uint64_t resizeTimeout;
bool resizeDone;
KeybindHandle kbFS;
KeybindHandle kbInput;
KeybindHandle kbQuit;
KeybindHandle kbMouseSensInc;
KeybindHandle kbMouseSensDec;
KeybindHandle kbCtrlAltFn[12];
int mouseSens;
float sensX, sensY;
@@ -78,15 +127,15 @@ struct AppParams
bool autoResize;
bool allowResize;
bool keepAspect;
bool forceAspect;
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;
unsigned int fpsMin;
bool showFPS;
bool useSpiceInput;
bool useSpiceClipboard;
@@ -101,6 +150,7 @@ struct AppParams
bool grabKeyboard;
SDL_Scancode escapeKey;
bool showAlerts;
bool captureOnStart;
unsigned int cursorPollInterval;
unsigned int framePollInterval;
@@ -110,6 +160,7 @@ struct AppParams
const char * windowTitle;
int mouseSens;
bool mouseRedraw;
};
struct CBRequest

View File

@@ -5,30 +5,23 @@ 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
)
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})
target_link_libraries(lg_common bfd)
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,8 @@ 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
typedef enum FrameType
{
@@ -34,6 +34,14 @@ typedef enum FrameType
}
FrameType;
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 +50,35 @@ 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 3
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 offset; // offset from the start of this header to the FrameBuffer header
}
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__)

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,36 @@
/*
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>
struct IVSHMEM
{
unsigned int size;
void * mem;
// internal use
void * opaque;
};
void ivshmemOptionsInit();
bool ivshmemOpen(struct IVSHMEM * dev);
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev);
void ivshmemClose(struct IVSHMEM * dev);

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

@@ -19,3 +19,6 @@ 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

@@ -0,0 +1,28 @@
/*
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>
typedef struct LGThread LGThread;
typedef int (*LGThreadFunction)(void * opaque);
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()
{
#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()
{
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

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

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

@@ -0,0 +1,186 @@
/*
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) {}
}
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;
const size_t blocks = linewidth / 64;
const size_t left = linewidth % 64;
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);
}
_mm_mfence();
__m128i * restrict s = (__m128i *)(frame->data + rp);
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

@@ -367,6 +367,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 +380,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 +482,14 @@ bool option_load(const char * filename)
(*p)[(*len)++] = c;
break;
case ';':
if (line)
{
comment = true;
break;
}
// fallthrough
default:
if (expectLine)
{
@@ -600,7 +613,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"

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,25 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform_code LANGUAGES C)
include_directories(
${PROJECT_SOURCE_DIR}/include
)
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,10 +17,11 @@ 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"
#if defined(ENABLE_BACKTRACE)
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
@@ -96,10 +97,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;
@@ -220,3 +229,12 @@ bool installCrashHandler(const char * exe)
return true;
}
#else //ENABLE_BACKTRACE
bool installCrashHandler(const char * exe)
{
return true;
}
#endif

View File

@@ -0,0 +1,201 @@
/*
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;
uint32_t flag;
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;
}
if (pthread_cond_init(&handle->cond, NULL) != 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);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
bool ret = true;
int res;
while(ret && !atomic_load(&handle->flag))
{
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 (handle->autoReset)
atomic_store(&handle->flag, 0);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return ret;
}
bool lgWaitEventNS(LGEvent * handle, unsigned int timeout)
{
if (timeout == TIMEOUT_INFINITE)
return lgWaitEventAbs(handle, NULL);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
uint64_t nsec = ts.tv_nsec + timeout;
if(nsec > 1e9)
{
ts.tv_nsec = nsec - 1e9;
++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 * 1000000);
}
bool lgSignalEvent(LGEvent * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
atomic_store(&handle->flag, 1);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
if (pthread_cond_broadcast(&handle->cond) != 0)
{
DEBUG_ERROR("Failed to signal the condition");
return false;
}
return true;
}
bool lgResetEvent(LGEvent * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
atomic_store(&handle->flag, 0);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}

View File

@@ -0,0 +1,267 @@
/*
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 <stdlib.h>
#include <string.h>
#include "common/debug.h"
#include "common/option.h"
#include "common/stringutils.h"
struct IVSHMEMInfo
{
int fd;
int size;
};
static int uioOpenFile(const char * shmDevice, const char * file)
{
char * path;
alloc_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 bool ivshmemDeviceValidator(struct Option * opt, const char ** error)
{
// if it's not a uio device, it must be a file on disk
if (strlen(opt->value.x_string) > 3 && memcmp(opt->value.x_string, "uio", 3) != 0)
{
struct stat st;
if (stat(opt->value.x_string, &st) != 0)
{
*error = "Invalid path to the ivshmem file specified";
return false;
}
return true;
}
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 ivshmemDeviceGetValues(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)
{
char * devName;
alloc_sprintf(&devName, "/dev/%s", dir->d_name);
stringlist_push(sl, devName);
}
free(name);
}
closedir(d);
return sl;
}
void ivshmemOptionsInit()
{
struct Option options[] =
{
{
.module = "app",
.name = "shmFile",
.shortopt = 'f',
.description = "The path to the shared memory file, or the name of the uio device to use, ie: uio0",
.type = OPTION_TYPE_STRING,
.value.x_string = "/dev/shm/looking-glass",
.validator = ivshmemDeviceValidator,
.getValues = ivshmemDeviceGetValues
},
{0}
};
option_register(options);
}
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;
dev->opaque = NULL;
DEBUG_INFO("KVMFR Device : %s", shmDevice);
if (strlen(shmDevice) > 8 && memcmp(shmDevice, "/dev/uio", 8) == 0)
{
const char * uioDev = shmDevice + 5;
// get the device size
int fd = uioOpenFile(uioDev, "maps/map0/size");
if (fd < 0)
{
DEBUG_ERROR("Failed to open %s/size", uioDev);
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);
devSize = strtoul(size, NULL, 16);
devFD = open(shmDevice, O_RDWR, (mode_t)0600);
if (devFD < 0)
{
DEBUG_ERROR("Failed to open: %s", shmDevice);
DEBUG_ERROR("Do you have permission to access the device?");
return false;
}
}
else
{
struct stat st;
if (stat(shmDevice, &st) != 0)
{
DEBUG_ERROR("Failed to stat: %s", shmDevice);
return false;
}
devSize = st.st_size;
devFD = open(shmDevice, O_RDWR, (mode_t)0600);
if (devFD < 0)
{
DEBUG_ERROR("Failed to open: %s", shmDevice);
return false;
}
}
void * map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, devFD, 0);
if (map == MAP_FAILED)
{
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
return false;
}
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo));
info->size = devSize;
info->fd = devFD;
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);
close(info->fd);
free(info);
dev->mem = NULL;
dev->size = 0;
dev->opaque = NULL;
}

View File

@@ -17,6 +17,7 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <unistd.h>
#include <GL/glx.h>
int sysinfo_gfx_max_multisample()
@@ -62,3 +63,8 @@ int sysinfo_gfx_max_multisample()
return maxSamples;
}
long sysinfo_getPageSize()
{
return sysconf(_SC_PAGESIZE);
}

View File

@@ -0,0 +1,76 @@
/*
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/thread.h"
#include <stdlib.h>
#include <pthread.h>
#include "common/debug.h"
struct LGThread
{
const char * name;
LGThreadFunction function;
void * opaque;
pthread_t handle;
int resultCode;
};
static void * threadWrapper(void * opaque)
{
LGThread * handle = (LGThread *)opaque;
handle->resultCode = handle->function(handle->opaque);
return NULL;
}
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle)
{
*handle = (LGThread*)malloc(sizeof(LGThread));
(*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;
}
pthread_setname_np((*handle)->handle, name);
return true;
}
bool lgJoinThread(LGThread * 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;
}

View File

@@ -0,0 +1,109 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2020-2020 Max Sistemich <maximilian.sistemich@rwth-aachen.de>
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/time.h"
#include "common/debug.h"
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct LGTimer
{
LGTimerFn fn;
void * udata;
timer_t id;
bool running;
};
static void TimerProc(union sigval arg)
{
LGTimer * timer = (LGTimer *)arg.sival_ptr;
if (!timer->fn(timer->udata))
{
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
timer->running = false;
}
}
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result)
{
LGTimer * ret = malloc(sizeof(LGTimer));
if (!ret)
{
DEBUG_ERROR("failed to malloc LGTimer struct");
return false;
}
ret->fn = fn;
ret->udata = udata;
ret->running = true;
struct sigevent sev =
{
.sigev_notify = SIGEV_THREAD,
.sigev_notify_function = &TimerProc,
.sigev_value.sival_ptr = ret,
};
if (timer_create(CLOCK_MONOTONIC, &sev, &ret->id))
{
DEBUG_ERROR("failed to create timer: %s", strerror(errno));
free(ret);
return false;
}
struct timespec interval =
{
.tv_sec = 0,
.tv_nsec = intervalMS * 1000 * 1000,
};
struct itimerspec spec =
{
.it_interval = interval,
.it_value = interval,
};
if (timer_settime(ret->id, 0, &spec, NULL))
{
DEBUG_ERROR("failed to set timer: %s", strerror(errno));
timer_delete(ret->id);
free(ret);
return false;
}
*result = ret;
return true;
}
void lgTimerDestroy(LGTimer * timer)
{
if (timer->running)
{
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
}
free(timer);
}

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform_code LANGUAGES C)
include_directories(
${PROJECT_TOP}/vendor/ivshmem
)
add_library(lg_common_platform_code STATIC
crash.c
sysinfo.c
thread.c
event.c
windebug.c
ivshmem.c
time.c
)
target_link_libraries(lg_common_platform_code
lg_common
setupapi
)

View File

@@ -0,0 +1,221 @@
/*
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/event.h"
#include "common/windebug.h"
#include "common/time.h"
#include <windows.h>
struct LGEvent
{
volatile int lock;
bool reset;
HANDLE handle;
bool wrapped;
unsigned int msSpinTime;
volatile bool signaled;
};
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime)
{
LGEvent * event = (LGEvent *)malloc(sizeof(LGEvent));
if (!event)
{
DEBUG_ERROR("out of ram");
return NULL;
}
event->lock = 0;
event->reset = autoReset;
event->handle = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL);
event->wrapped = false;
event->msSpinTime = msSpinTime;
event->signaled = false;
if (!event->handle)
{
DEBUG_WINERROR("Failed to create the event", GetLastError());
free(event);
return NULL;
}
return event;
}
LGEvent * lgWrapEvent(void * handle)
{
LGEvent * event = (LGEvent *)malloc(sizeof(LGEvent));
if (!event)
{
DEBUG_ERROR("out of ram");
return NULL;
}
event->lock = 0;
event->reset = false;
event->handle = (HANDLE)handle;
event->wrapped = true;
event->msSpinTime = 0;
event->signaled = false;
return event;
}
void lgFreeEvent(LGEvent * event)
{
CloseHandle(event->handle);
}
bool lgWaitEvent(LGEvent * event, unsigned int timeout)
{
// wrapped events can't be enahnced
if (!event->wrapped)
{
if (event->signaled)
{
if (event->reset)
event->signaled = false;
return true;
}
if (timeout == 0)
{
bool ret = event->signaled;
if (event->reset)
event->signaled = false;
return ret;
}
if (event->msSpinTime)
{
unsigned int spinTime = event->msSpinTime;
if (timeout != TIMEOUT_INFINITE)
{
if (timeout > event->msSpinTime)
timeout -= event->msSpinTime;
else
{
spinTime -= timeout;
timeout = 0;
}
}
uint64_t now = microtime();
uint64_t end = now + spinTime * 1000;
while(!event->signaled)
{
now = microtime();
if (now >= end)
break;
}
if (event->signaled)
{
if (event->reset)
event->signaled = false;
return true;
}
}
}
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
switch(WaitForSingleObject(event->handle, to))
{
case WAIT_OBJECT_0:
if (!event->reset)
event->signaled = true;
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 lgWaitEvents(LGEvent * events[], int count, bool waitAll, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
HANDLE * handles = (HANDLE *)malloc(sizeof(HANDLE) * count);
for(int i = 0; i < count; ++i)
handles[i] = events[i]->handle;
while(true)
{
DWORD result = WaitForMultipleObjects(count, handles, waitAll, to);
if (result >= WAIT_OBJECT_0 && result < count)
{
// null non signaled events from the handle list
for(int i = 0; i < count; ++i)
if (i != result && !lgWaitEvent(events[i], 0))
events[i] = NULL;
free(handles);
return true;
}
if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count)
continue;
switch(result)
{
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
free(handles);
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
free(handles);
return false;
}
DEBUG_ERROR("Unknown wait event return code");
free(handles);
return false;
}
}
bool lgSignalEvent(LGEvent * event)
{
event->signaled = true;
return SetEvent(event->handle);
}
bool lgResetEvent(LGEvent * event)
{
event->signaled = false;
return ResetEvent(event->handle);
}

View File

@@ -0,0 +1,153 @@
/*
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 "common/option.h"
#include "common/windebug.h"
#include <windows.h>
#include "ivshmem.h"
#include <assert.h>
#include <setupapi.h>
#include <io.h>
struct IVSHMEMInfo
{
HANDLE handle;
};
void ivshmemOptionsInit()
{
static struct Option options[] = {
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_INT,
.value.x_int = 0
},
{0}
};
option_register(options);
}
bool ivshmemOpen(struct IVSHMEM * dev)
{
assert(dev);
HANDLE devHandle;
{
HDEVINFO devInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
devInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
const int shmDevice = option_get_int("os", "shmDevice");
if (SetupDiEnumDeviceInterfaces(devInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &devInterfaceData) == 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(devInfoSet, &devInterfaceData, 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(devInfoSet, &devInterfaceData, infData, reqSize, NULL, NULL))
{
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
devHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (devHandle == INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(devInfoSet);
free(infData);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
return false;
}
free(infData);
SetupDiDestroyDeviceInfoList(devInfoSet);
}
IVSHMEM_SIZE size;
if (!DeviceIoControl(devHandle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return 0;
}
IVSHMEM_MMAP_CONFIG config = { .cacheMode = IVSHMEM_CACHE_WRITECOMBINED };
IVSHMEM_MMAP map = { 0 };
if (!DeviceIoControl(
devHandle,
IOCTL_IVSHMEM_REQUEST_MMAP,
&config, sizeof(IVSHMEM_MMAP_CONFIG),
&map , sizeof(IVSHMEM_MMAP),
NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return false;
}
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo));
info->handle = devHandle;
dev->opaque = info;
dev->size = (unsigned int)size;
dev->mem = map.ptr;
return true;
}
void ivshmemClose(struct IVSHMEM * dev)
{
assert(dev);
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)dev->opaque;
if (!DeviceIoControl(info->handle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL))
DEBUG_WINERROR("DeviceIoControl failed", GetLastError());
free(info);
}

View File

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

View File

@@ -0,0 +1,93 @@
/*
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/thread.h"
#include "common/debug.h"
#include "common/windebug.h"
#include <windows.h>
struct LGThread
{
const char * name;
LGThreadFunction function;
void * opaque;
HANDLE handle;
DWORD threadID;
int resultCode;
};
static DWORD WINAPI threadWrapper(LPVOID lpParameter)
{
LGThread * handle = (LGThread *)lpParameter;
handle->resultCode = handle->function(handle->opaque);
return 0;
}
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle)
{
*handle = (LGThread *)malloc(sizeof(LGThread));
(*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 lgJoinThread(LGThread * 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;
}

View File

@@ -0,0 +1,72 @@
/*
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/time.h"
#include "common/debug.h"
// decared by the platform
extern HWND MessageHWND;
struct LGTimer
{
LGTimerFn fn;
void * udata;
UINT_PTR handle;
bool running;
};
static void TimerProc(HWND Arg1, UINT Arg2, UINT_PTR Arg3, DWORD Arg4)
{
LGTimer * timer = (LGTimer *)Arg3;
if (!timer->fn(timer->udata))
{
KillTimer(Arg1, timer->handle);
timer->running = false;
}
}
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result)
{
LGTimer * ret = malloc(sizeof(LGTimer));
if (!ret)
{
DEBUG_ERROR("failed to malloc LGTimer struct");
return false;
}
ret->fn = fn;
ret->udata = udata;
ret->running = true;
ret->handle = SetTimer(MessageHWND, (UINT_PTR)ret, intervalMS, TimerProc);
*result = ret;
return true;
}
void lgTimerDestroy(LGTimer * timer)
{
if (timer->running)
{
if (!KillTimer(MessageHWND, timer->handle))
DEBUG_ERROR("failed to destroy the timer");
}
free(timer);
}

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,7 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "windows/debug.h"
#include "common/windebug.h"
#include <stdio.h>
void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status)
@@ -37,6 +37,30 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
if (buffer[i] == '\n' || buffer[i] == '\r')
buffer[i] = 0;
fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer);
fprintf(stderr, "%12" PRId64 " [E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", microtime(), file, line, function, desc, (int)status, buffer);
LocalFree(buffer);
}
/* credit for this function to: https://stackoverflow.com/questions/17399302/how-can-i-detect-windows-8-1-in-a-desktop-application */
inline static BOOL CompareWindowsVersion(DWORD dwMajorVersion, DWORD dwMinorVersion)
{
OSVERSIONINFOEX ver;
DWORDLONG dwlConditionMask = 0;
ZeroMemory(&ver, sizeof(OSVERSIONINFOEX));
ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
ver.dwMajorVersion = dwMajorVersion;
ver.dwMinorVersion = dwMinorVersion;
VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL);
return VerifyVersionInfo(&ver, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);
}
bool IsWindows8()
{
return
(CompareWindowsVersion(6, 3) == TRUE) ||
(CompareWindowsVersion(6, 2) == TRUE);
}

View File

@@ -21,26 +21,24 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdlib.h>
#include <stdarg.h>
int alloc_sprintf(char ** str, const char * format, ...)
static int valloc_sprintf(char ** str, const char * format, va_list ap)
{
if (!str)
return -1;
*str = NULL;
va_list ap;
va_start(ap, format);
int len = vsnprintf(NULL, 0, format, ap);
va_end(ap);
va_list ap1;
va_copy(ap1, ap);
int len = vsnprintf(NULL, 0, format, ap1);
va_end(ap1);
if (len < 0)
return len;
*str = malloc(len+1);
va_start(ap, format);
int ret = vsnprintf(*str, len + 1, format, ap);
va_end(ap);
if (ret < 0)
{
free(*str);
@@ -50,3 +48,12 @@ int alloc_sprintf(char ** str, const char * format, ...)
return ret;
}
int alloc_sprintf(char ** str, const char * format, ...)
{
va_list ap;
va_start(ap, format);
int ret = valloc_sprintf(str, format, ap);
va_end(ap);
return ret;
}

View File

@@ -14,6 +14,9 @@ if(OPTIMIZE_FOR_NATIVE)
endif()
endif()
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
add_compile_options(
"-Wall"
"-Werror"
@@ -38,7 +41,6 @@ get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
include_directories(
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
${PROJECT_TOP}/vendor/ivshmem
${PKGCONFIG_INCLUDE_DIRS}
${GMP_INCLUDE_DIR}
)
@@ -51,6 +53,7 @@ set(SOURCES
)
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
add_subdirectory(platform)
if(WIN32)
@@ -61,6 +64,7 @@ endif()
target_link_libraries(looking-glass-host
lg_common
platform
lgmp
)
set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections")
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-host DESTINATION bin/ COMPONENT binary)

View File

@@ -2,37 +2,26 @@
## What is this?
This is a rewrite of the host application in pure C using the MinGW toolchain.
The Looking Glass Host application for the Guest Virtual Machine.
## Why make this?
## What platforms does this support?
Several reasons:
1. The client is written in C and I would like to unify the project's language
2. The host is currently hard to build using MinGW and is very Windows specific
3. The host is a jumbled mess of code from all the experimentation going on
4. I would eventually like to be able to port this to run on Linux guests
## When will it be ready?
Soon :)
## Will it replace the C++ host?
Yes, but only when it is feature complete.
## Why doesn't this use CMake?
It does now...
~~Because win-builds doesn't distribute it, so to make it easy for everyone to compile we do not require it.~~
Currently only Windows is supported however there is some initial support for Linux at this time.
## How do I build it?
#### For Windows on Windows
1. download and install msys2 x86_64 from http://www.msys2.org/ following the setup instructions provided
3. execute `pacman -Fy` and then `pacman -Sy git make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake`
4. run "C:\msys64\mingw64.exe"
5. checkout the project
`git clone https://github.com/gnif/LookingGlass.git`
6. configure the project and build it
```
mkdir build
cd build
mkdir LookingGlass/host/build
cd LookingGlass/host/build
cmake -G "MSYS Makefiles" ..
make
```
@@ -55,17 +44,46 @@ cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
make
```
## Building the Windows installer
Install NSIS compiler
Build the host program, see above sections.
Build installer with `makensis platform/Windows/installer.nsi`
The resulting installer will be at
`platform/Windows/looking-glass-host-setup.exe`
## Where is the log?
It is in your user's temp directory:
%TEMP%\looking-glass-host.txt
For example:
Or if running as a system service it will be located in:
C:\Users\YourUser\AppData\Local\Temp\looking-glass-host.txt
C:\Windows\Temp\looking-glass-host.txt
You can also open it by simply right clicking the tray icon and selecting "Open Log File"
You can find out where the file is by right clicking on the tray icon and
selecting "Log File Location"
### High priority capture using DXGI and Secure Desktop (UAC) capture support
By default Windows gives priority to the foreground application for any GPU
work which causes issues with capture if the foreground application is consuming
100% of the available GPU resources. The looking glass host application is able
to increase the kernel GPU thread to realtime priority which fixes this, but in
order to do so it must run as the `SYSTEM` user account. To do this, Looking
Glass needs to run as a service. This can be accomplished by either using the
NSIS installer which will do this for you, or you can use the following command
to Install the service manually:
looking-glass-host.exe InstallService
To remove the service use the following command:
looking-glass-host.exe UninstallService
This will also enable the host application to capture the secure desktop which
includes things like the lock screen and UAC prompts.
## Why does this version require Administrator privileges

View File

@@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h>
#include <stdint.h>
#include "common/framebuffer.h"
typedef enum CaptureResult
{
@@ -55,36 +56,44 @@ typedef struct CaptureFrame
unsigned int pitch;
unsigned int stride;
CaptureFormat format;
void * data;
}
CaptureFrame;
typedef struct CapturePointer
{
bool positionUpdate;
int x, y;
bool visible;
bool shapeUpdate;
CaptureFormat format;
unsigned int hx, hy;
unsigned int width, height;
unsigned int pitch;
}
CapturePointer;
typedef bool (*CaptureGetPointerBuffer )(void ** data, uint32_t * size);
typedef void (*CapturePostPointerBuffer)(CapturePointer pointer);
typedef struct CaptureInterface
{
const char * (*getName )();
void (*initOptions )();
bool (*create )();
bool (*init )(void * pointerShape, const unsigned int pointerSize);
bool(*create)(
CaptureGetPointerBuffer getPointerBufferFn,
CapturePostPointerBuffer postPointerBufferFn
);
bool (*init )();
void (*stop )();
bool (*deinit )();
void (*free )();
unsigned int (*getMaxFrameSize)();
CaptureResult (*capture )();
CaptureResult (*getFrame )(CaptureFrame * frame );
CaptureResult (*getPointer)(CapturePointer * pointer);
CaptureResult (*waitFrame )(CaptureFrame * frame);
CaptureResult (*getFrame )(FrameBuffer * frame);
}
CaptureInterface;

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,14 @@ 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>
#pragma once
osEventHandle * os_wrapEvent(HANDLE event);
#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();
const char * os_getDataPath();

View File

@@ -20,6 +20,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h"
#include "interface/platform.h"
#include "common/debug.h"
#include "common/event.h"
#include <string.h>
#include <assert.h>
#include <stdlib.h>
@@ -37,7 +38,7 @@ struct xcb
uint32_t seg;
int shmID;
void * data;
osEventHandle * frameEvent;
LGEvent * frameEvent;
unsigned int width;
unsigned int height;
@@ -67,7 +68,7 @@ static bool xcb_create()
this = (struct xcb *)calloc(sizeof(struct xcb), 1);
this->shmID = -1;
this->data = (void *)-1;
this->frameEvent = os_createEvent(true);
this->frameEvent = lgCreateEvent(true, 20);
if (!this->frameEvent)
{
@@ -84,7 +85,7 @@ static bool xcb_init()
assert(this);
assert(!this->initialized);
os_resetEvent(this->frameEvent);
lgResetEvent(this->frameEvent);
this->xcb = xcb_connect(NULL, NULL);
if (!this->xcb || xcb_connection_has_error(this->xcb))
@@ -158,7 +159,7 @@ static bool xcb_deinit()
static void xcb_free()
{
os_freeEvent(this->frameEvent);
lgFreeEvent(this->frameEvent);
free(this);
this = NULL;
}
@@ -187,20 +188,29 @@ static CaptureResult xcb_capture()
0);
this->hasFrame = true;
os_signalEvent(this->frameEvent);
lgSignalEvent(this->frameEvent);
}
return CAPTURE_RESULT_OK;
}
static CaptureResult xcb_getFrame(CaptureFrame * frame)
static CaptureResult xcb_waitFrame(CaptureFrame * frame)
{
lgWaitEvent(this->frameEvent, TIMEOUT_INFINITE);
frame->width = this->width;
frame->height = this->height;
frame->pitch = this->width * 4;
frame->stride = this->width;
frame->format = CAPTURE_FMT_BGRA;
return CAPTURE_RESULT_OK;
}
static CaptureResult xcb_getFrame(FrameBuffer frame)
{
assert(this);
assert(this->initialized);
assert(frame);
assert(frame->data);
os_waitEvent(this->frameEvent, TIMEOUT_INFINITE);
xcb_shm_get_image_reply_t * img;
img = xcb_shm_get_image_reply(this->xcb, this->imgC, NULL);
@@ -210,12 +220,7 @@ static CaptureResult xcb_getFrame(CaptureFrame * frame)
return CAPTURE_RESULT_ERROR;
}
frame->width = this->width;
frame->height = this->height;
frame->pitch = this->width * 4;
frame->stride = this->width;
frame->format = CAPTURE_FMT_BGRA;
memcpy(frame->data, this->data, this->width * this->height * 4);
framebuffer_write(frame, this->data, this->width * this->height * 4);
free(img);
this->hasFrame = false;
@@ -237,6 +242,7 @@ struct CaptureInterface Capture_XCB =
.free = xcb_free,
.getMaxFrameSize = xcb_getMaxFrameSize,
.capture = xcb_capture,
.waitFrame = xcb_waitFrame,
.getFrame = xcb_getFrame,
.getPointer = xcb_getPointer
};

View File

@@ -0,0 +1,71 @@
/*
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 "common/thread.h"
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
struct app
{
const char * executable;
char * dataPath;
};
struct app app = { 0 };
int main(int argc, char * argv[])
{
app.executable = argv[0];
struct passwd * pw = getpwuid(getuid());
alloc_sprintf(&app.dataPath, "%s/", pw->pw_dir);
int result = app_main(argc, argv);
free(app.dataPath);
return result;
}
void sigHandler(int signo)
{
DEBUG_INFO("SIGINT");
app_quit();
}
bool app_init()
{
signal(SIGINT, sigHandler);
return true;
}
const char * os_getExecutable()
{
return app.executable;
}
const char * os_getDataPath()
{
return app.dataPath;
}

View File

@@ -7,7 +7,7 @@ include_directories(
add_library(platform_Windows STATIC
src/platform.c
src/windebug.c
src/service.c
src/mousehook.c
)
@@ -24,10 +24,19 @@ target_link_libraries(platform_Windows
"${PROJECT_BINARY_DIR}/resource.o"
lg_common
capture
setupapi
userenv
wtsapi32
psapi
)
target_include_directories(platform_Windows
PRIVATE
src
)
# these are for the nsis installer generator
configure_file("${PROJECT_SOURCE_DIR}/installer.nsi" "${PROJECT_BINARY_DIR}/installer.nsi" COPYONLY)
configure_file("${PROJECT_TOP}/resources/icon.ico" "${PROJECT_BINARY_DIR}/icon.ico" COPYONLY)
configure_file("${PROJECT_TOP}/VERSION" "${PROJECT_BINARY_DIR}/VERSION" COPYONLY)
configure_file("${PROJECT_TOP}/LICENSE" "${PROJECT_BINARY_DIR}/LICENSE.txt" COPYONLY)

View File

@@ -20,16 +20,34 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h"
#include "interface/platform.h"
#include "common/debug.h"
#include "common/windebug.h"
#include "common/option.h"
#include "windows/debug.h"
#include "common/locking.h"
#include "common/event.h"
#include <assert.h>
#include <stdatomic.h>
#include <unistd.h>
#include <dxgi.h>
#include <d3d11.h>
#include <d3dcommon.h>
#include "dxgi_extra.h"
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS
{
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
}
D3DKMT_SCHEDULINGPRIORITYCLASS;
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
#define LOCKED(x) INTERLOCKED_SECTION(this->deviceContextLock, x)
enum TextureState
{
TEXTURE_STATE_UNUSED,
@@ -39,44 +57,39 @@ enum TextureState
typedef struct Texture
{
enum TextureState state;
volatile enum TextureState state;
ID3D11Texture2D * tex;
D3D11_MAPPED_SUBRESOURCE map;
osEventHandle * mapped;
osEventHandle * free;
}
Texture;
typedef struct Pointer
{
unsigned int version;
unsigned int x, y;
unsigned int w, h;
bool visible;
unsigned int pitch;
CaptureFormat format;
}
Pointer;
// locals
struct iface
{
bool initialized;
LARGE_INTEGER perfFreq;
LARGE_INTEGER frameTime;
bool stop;
HDESK desktop;
IDXGIFactory1 * factory;
IDXGIAdapter1 * adapter;
IDXGIOutput * output;
ID3D11Device * device;
ID3D11DeviceContext * deviceContext;
LG_Lock deviceContextLock;
bool useAcquireLock;
D3D_FEATURE_LEVEL featureLevel;
IDXGIOutputDuplication * dup;
int maxTextures;
Texture * texture;
int texRIndex;
int texWIndex;
atomic_int texReady;
bool needsRelease;
osEventHandle * pointerEvent;
CaptureGetPointerBuffer getPointerBufferFn;
CapturePostPointerBuffer postPointerBufferFn;
LGEvent * frameEvent;
unsigned int width;
unsigned int height;
@@ -84,14 +97,8 @@ struct iface
unsigned int stride;
CaptureFormat format;
// pointer state
Pointer lastPointer;
Pointer pointer;
// pointer shape
void * pointerShape;
unsigned int pointerSize;
unsigned int pointerUsed;
int lastPointerX, lastPointerY;
bool lastPointerVisible;
};
static bool dpiDone = false;
@@ -134,13 +141,20 @@ static void dxgi_initOptions()
.type = OPTION_TYPE_INT,
.value.x_int = 3
},
{
.module = "dxgi",
.name = "useAcquireLock",
.description = "Enable locking around `AcquireFrame` (EXPERIMENTAL, leave enabled if you're not sure!)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{0}
};
option_register(options);
}
static bool dxgi_create()
static bool dxgi_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostPointerBuffer postPointerBufferFn)
{
assert(!this);
this = calloc(sizeof(struct iface), 1);
@@ -150,10 +164,10 @@ static bool dxgi_create()
return false;
}
this->pointerEvent = os_createEvent(true);
if (!this->pointerEvent)
this->frameEvent = lgCreateEvent(true, 17); // 60Hz = 16.7ms
if (!this->frameEvent)
{
DEBUG_ERROR("failed to create the pointer event");
DEBUG_ERROR("failed to create the frame event");
free(this);
return false;
}
@@ -162,14 +176,38 @@ static bool dxgi_create()
if (this->maxTextures <= 0)
this->maxTextures = 1;
this->useAcquireLock = option_get_bool("dxgi", "useAcquireLock");
this->texture = calloc(sizeof(struct Texture), this->maxTextures);
this->getPointerBufferFn = getPointerBufferFn;
this->postPointerBufferFn = postPointerBufferFn;
return true;
}
static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
static bool dxgi_init()
{
assert(this);
this->desktop = OpenInputDesktop(0, FALSE, GENERIC_READ);
if (!this->desktop)
DEBUG_WINERROR("Failed to open the desktop", GetLastError());
else
{
if (!SetThreadDesktop(this->desktop))
{
DEBUG_WINERROR("Failed to set thread desktop", GetLastError());
CloseDesktop(this->desktop);
this->desktop = NULL;
}
}
if (!this->desktop)
{
DEBUG_INFO("The above error(s) will prevent LG from being able to capture the secure desktop (UAC dialogs)");
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
DEBUG_INFO("looking-glass-host.exe InstallService");
}
// this is required for DXGI 1.5 support to function
if (!dpiDone)
{
@@ -189,13 +227,12 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
HRESULT status;
DXGI_OUTPUT_DESC outputDesc;
this->pointerShape = pointerShape;
this->pointerSize = pointerSize;
this->pointerUsed = 0;
this->stop = false;
this->texRIndex = 0;
this->texWIndex = 0;
atomic_store(&this->texReady, 0);
lgResetEvent(this->frameEvent);
status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory);
if (FAILED(status))
@@ -212,12 +249,16 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
if (optAdapter)
{
DXGI_ADAPTER_DESC1 adapterDesc;
IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
status = IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
if (FAILED(status))
{
DEBUG_WINERROR("Failed to get the device description", status);
goto fail;
}
const size_t s = (wcslen(adapterDesc.Description)+1) * 2;
size_t unused;
char * desc = malloc(s);
wcstombs_s(&unused, desc, s, adapterDesc.Description, _TRUNCATE);
wcstombs(desc, adapterDesc.Description, s);
if (strstr(desc, optAdapter) == NULL)
{
@@ -238,9 +279,8 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
if (optOutput)
{
const size_t s = (wcslen(outputDesc.DeviceName)+1) * 2;
size_t unused;
char * desc = malloc(s);
wcstombs_s(&unused, desc, s, outputDesc.DeviceName, _TRUNCATE);
wcstombs(desc, outputDesc.DeviceName, s);
if (strstr(desc, optOutput) == NULL)
{
@@ -276,7 +316,18 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
goto fail;
}
static const D3D_FEATURE_LEVEL featureLevels[] =
static const D3D_FEATURE_LEVEL win8[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
static const D3D_FEATURE_LEVEL win10[] =
{
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
@@ -289,6 +340,19 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
D3D_FEATURE_LEVEL_9_1
};
const D3D_FEATURE_LEVEL * featureLevels;
unsigned int featureLevelCount;
if (IsWindows8())
{
featureLevels = win8;
featureLevelCount = sizeof(win8) / sizeof(D3D_FEATURE_LEVEL);
}
else
{
featureLevels = win10;
featureLevelCount = sizeof(win10) / sizeof(D3D_FEATURE_LEVEL);
}
IDXGIAdapter * tmp;
status = IDXGIAdapter1_QueryInterface(this->adapter, &IID_IDXGIAdapter, (void **)&tmp);
if (FAILED(status))
@@ -302,12 +366,14 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
D3D_DRIVER_TYPE_UNKNOWN,
NULL,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
featureLevels, featureLevelCount,
D3D11_SDK_VERSION,
&this->device,
&this->featureLevel,
&this->deviceContext);
LG_LOCK_INIT(this->deviceContextLock);
IDXGIAdapter_Release(tmp);
if (FAILED(status))
@@ -329,9 +395,29 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
DEBUG_INFO("Shared Sys Mem : %u MiB" , (unsigned)(adapterDesc.SharedSystemMemory / 1048576));
DEBUG_INFO("Feature Level : 0x%x" , this->featureLevel);
DEBUG_INFO("Capture Size : %u x %u", this->width, this->height);
DEBUG_INFO("AcquireLock : %s" , this->useAcquireLock ? "enabled" : "disabled");
// bump up our priority
{
HMODULE gdi32 = GetModuleHandleA("GDI32");
if (gdi32)
{
PD3DKMTSetProcessSchedulingPriorityClass fn =
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
if (fn)
{
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
if (FAILED(status))
{
DEBUG_WARN("Failed to set realtime GPU priority.");
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
DEBUG_INFO("looking-glass-host.exe InstallService");
}
}
}
IDXGIDevice * dxgi;
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi);
if (FAILED(status))
@@ -344,6 +430,20 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
IDXGIDevice_Release(dxgi);
}
// try to reduce the latency
{
IDXGIDevice1 * dxgi;
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice1, (void **)&dxgi);
if (FAILED(status))
{
DEBUG_WINERROR("failed to query DXGI interface from device", status);
goto fail;
}
IDXGIDevice1_SetMaximumFrameLatency(dxgi, 1);
IDXGIDevice1_Release(dxgi);
}
IDXGIOutput5 * output5 = NULL;
status = IDXGIOutput_QueryInterface(this->output, &IID_IDXGIOutput5, (void **)&output5);
if (FAILED(status))
@@ -451,23 +551,6 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
DEBUG_WINERROR("Failed to create texture", status);
goto fail;
}
this->texture[i].free = os_createEvent(true);
if (!this->texture[i].free)
{
DEBUG_ERROR("Failed to create the texture free event");
goto fail;
}
// pre-signal the free events to flag as unused
os_signalEvent(this->texture[i].free);
this->texture[i].mapped = os_createEvent(false);
if (!this->texture[i].mapped)
{
DEBUG_ERROR("Failed to create the texture mapped event");
goto fail;
}
}
// map the texture simply to get the pitch and stride
@@ -482,6 +565,8 @@ static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
this->stride = mapping.RowPitch / 4;
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource *)this->texture[0].tex, 0);
QueryPerformanceFrequency(&this->perfFreq) ;
QueryPerformanceCounter (&this->frameTime);
this->initialized = true;
return true;
@@ -493,9 +578,6 @@ fail:
static void dxgi_stop()
{
this->stop = true;
os_signalEvent(this->texture[this->texRIndex].mapped);
os_signalEvent(this->pointerEvent);
}
static bool dxgi_deinit()
@@ -517,20 +599,6 @@ static bool dxgi_deinit()
ID3D11Texture2D_Release(this->texture[i].tex);
this->texture[i].tex = NULL;
}
if (this->texture[i].free)
{
os_signalEvent(this->texture[i].free);
os_freeEvent(this->texture[i].free);
this->texture[i].free = NULL;
}
if (this->texture[i].mapped)
{
os_signalEvent(this->texture[i].mapped);
os_freeEvent(this->texture[i].mapped);
this->texture[i].mapped = NULL;
}
}
if (this->dup)
@@ -576,6 +644,14 @@ static bool dxgi_deinit()
}
}
LG_LOCK_FREE(this->deviceContextLock);
if (this->desktop)
{
CloseDesktop(this->desktop);
this->desktop = NULL;
}
this->initialized = false;
return true;
}
@@ -587,7 +663,6 @@ static void dxgi_free()
if (this->initialized)
dxgi_deinit();
os_freeEvent(this->pointerEvent);
free(this->texture);
free(this);
@@ -602,53 +677,12 @@ static unsigned int dxgi_getMaxFrameSize()
return this->height * this->pitch;
}
static CaptureResult dxgi_capture()
static CaptureResult dxgi_hResultToCaptureResult(const HRESULT status)
{
assert(this);
assert(this->initialized);
CaptureResult result;
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource * res;
// if the read texture is pending a mapping
for(int i = 0; i < this->maxTextures; ++i)
{
if (this->texture[i].state != TEXTURE_STATE_PENDING_MAP)
continue;
Texture * tex = &this->texture[i];
// try to map the resource, but don't wait for it
status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);
if (status != DXGI_ERROR_WAS_STILL_DRAWING)
{
if (FAILED(status))
{
DEBUG_WINERROR("Failed to map the texture", status);
IDXGIResource_Release(res);
return CAPTURE_RESULT_ERROR;
}
// successful map, set the state and signal that there is a frame available
tex->state = TEXTURE_STATE_MAPPED;
os_signalEvent(tex->mapped);
}
}
// release the prior frame
result = dxgi_releaseFrame();
if (result != CAPTURE_RESULT_OK)
return result;
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1, &frameInfo, &res);
switch(status)
{
case S_OK:
this->needsRelease = true;
break;
return CAPTURE_RESULT_OK;
case DXGI_ERROR_WAIT_TIMEOUT:
return CAPTURE_RESULT_TIMEOUT;
@@ -658,27 +692,62 @@ static CaptureResult dxgi_capture()
return CAPTURE_RESULT_REINIT;
default:
DEBUG_WINERROR("AcquireNextFrame failed", status);
return CAPTURE_RESULT_ERROR;
}
}
static CaptureResult dxgi_capture()
{
assert(this);
assert(this->initialized);
Texture * tex;
CaptureResult result;
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource * res;
bool copyFrame = false;
bool copyPointer = false;
ID3D11Texture2D * src;
bool postPointer = false;
CapturePointer pointer = { 0 };
void * pointerShape = NULL;
UINT pointerShapeSize = 0;
// release the prior frame
result = dxgi_releaseFrame();
if (result != CAPTURE_RESULT_OK)
return result;
if (this->useAcquireLock)
{
LOCKED({
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1, &frameInfo, &res);
});
}
else
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1000, &frameInfo, &res);
result = dxgi_hResultToCaptureResult(status);
if (result != CAPTURE_RESULT_OK)
{
if (result == CAPTURE_RESULT_ERROR)
DEBUG_WINERROR("AcquireNextFrame failed", status);
return result;
}
this->needsRelease = true;
if (frameInfo.LastPresentTime.QuadPart != 0)
{
Texture * tex = &this->texture[this->texWIndex];
tex = &this->texture[this->texWIndex];
// check if the texture is free, if not skip the frame to keep up
if (!os_waitEvent(tex->free, 0))
if (tex->state == TEXTURE_STATE_UNUSED)
{
/*
NOTE: This is only informational for when debugging, skipping frames is
OK as we are likely getting frames faster then the client can render
them (ie, vsync off in a title)
*/
//DEBUG_WARN("Frame skipped");
}
else
{
ID3D11Texture2D * src;
copyFrame = true;
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
if (FAILED(status))
{
@@ -686,102 +755,192 @@ static CaptureResult dxgi_capture()
IDXGIResource_Release(res);
return CAPTURE_RESULT_ERROR;
}
// if the texture was mapped, unmap it
if (tex->state == TEXTURE_STATE_MAPPED)
{
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)tex->tex, 0);
tex->map.pData = NULL;
}
// issue the copy from GPU to CPU RAM and release the src
ID3D11DeviceContext_CopyResource(this->deviceContext,
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
ID3D11Texture2D_Release(src);
// pending map
tex->state = TEXTURE_STATE_PENDING_MAP;
// advance our write pointer
if (++this->texWIndex == this->maxTextures)
this->texWIndex = 0;
}
}
IDXGIResource_Release(res);
// if the pointer has moved or changed state
bool signalPointer = false;
if (frameInfo.LastMouseUpdateTime.QuadPart)
{
if (
frameInfo.PointerPosition.Position.x != this->lastPointer.x ||
frameInfo.PointerPosition.Position.y != this->lastPointer.y ||
frameInfo.PointerPosition.Visible != this->lastPointer.visible
)
{
this->pointer.x = frameInfo.PointerPosition.Position.x;
this->pointer.y = frameInfo.PointerPosition.Position.y;
this->pointer.visible = frameInfo.PointerPosition.Visible;
signalPointer = true;
}
}
// if the pointer shape has changed
uint32_t bufferSize;
if (frameInfo.PointerShapeBufferSize > 0)
{
// update the buffer
if (frameInfo.PointerShapeBufferSize > this->pointerSize)
DEBUG_WARN("The pointer shape is too large to fit in the buffer, ignoring the shape");
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
else
copyPointer = true;
}
if (copyFrame || copyPointer)
{
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, this->pointerSize, this->pointerShape, &this->pointerUsed, &shapeInfo);
if (FAILED(status))
LOCKED(
{
if (copyFrame)
{
// issue the copy from GPU to CPU RAM
ID3D11DeviceContext_CopyResource(this->deviceContext,
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
}
if (copyPointer)
{
// grab the pointer shape
status = IDXGIOutputDuplication_GetFramePointerShape(
this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);
}
ID3D11DeviceContext_Flush(this->deviceContext);
});
if (copyFrame)
{
ID3D11Texture2D_Release(src);
// set the state, and signal
tex->state = TEXTURE_STATE_PENDING_MAP;
if (atomic_fetch_add_explicit(&this->texReady, 1, memory_order_relaxed) == 0)
lgSignalEvent(this->frameEvent);
// advance the write index
if (++this->texWIndex == this->maxTextures)
this->texWIndex = 0;
// update the last frame time
this->frameTime.QuadPart = frameInfo.LastPresentTime.QuadPart;
}
if (copyPointer)
{
result = dxgi_hResultToCaptureResult(status);
if (result != CAPTURE_RESULT_OK)
{
if (result == CAPTURE_RESULT_ERROR)
DEBUG_WINERROR("Failed to get the new pointer shape", status);
return CAPTURE_RESULT_ERROR;
return result;
}
switch(shapeInfo.Type)
{
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : this->pointer.format = CAPTURE_FMT_COLOR ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: this->pointer.format = CAPTURE_FMT_MASKED; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : this->pointer.format = CAPTURE_FMT_MONO ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break;
default:
DEBUG_ERROR("Unsupported cursor format");
return CAPTURE_RESULT_ERROR;
}
this->pointer.w = shapeInfo.Width;
this->pointer.h = shapeInfo.Height;
this->pointer.pitch = shapeInfo.Pitch;
++this->pointer.version;
signalPointer = true;
CURSORINFO ci = { .cbSize = sizeof(CURSORINFO) };
if (!GetCursorInfo(&ci))
{
DEBUG_WINERROR("GetCursorInfo failed", GetLastError());
return CAPTURE_RESULT_ERROR;
}
if (ci.hCursor)
{
ICONINFO ii;
if (!GetIconInfo(ci.hCursor, &ii))
{
DEBUG_WINERROR("GetIconInfo failed", GetLastError());
return CAPTURE_RESULT_ERROR;
}
DeleteObject(ii.hbmMask);
DeleteObject(ii.hbmColor);
pointer.hx = ii.xHotspot;
pointer.hy = ii.yHotspot;
}
else
{
pointer.hx = 0;
pointer.hy = 0;
}
pointer.shapeUpdate = true;
pointer.width = shapeInfo.Width;
pointer.height = shapeInfo.Height;
pointer.pitch = shapeInfo.Pitch;
postPointer = true;
}
}
// signal about the pointer update
if (signalPointer)
os_signalEvent(this->pointerEvent);
if (frameInfo.LastMouseUpdateTime.QuadPart)
{
/* the pointer position is only valid if the pointer is visible */
if (frameInfo.PointerPosition.Visible &&
(frameInfo.PointerPosition.Position.x != this->lastPointerX ||
frameInfo.PointerPosition.Position.y != this->lastPointerY))
{
pointer.positionUpdate = true;
pointer.x =
this->lastPointerX =
frameInfo.PointerPosition.Position.x;
pointer.y =
this->lastPointerY =
frameInfo.PointerPosition.Position.y;
postPointer = true;
}
if (this->lastPointerVisible != frameInfo.PointerPosition.Visible)
{
this->lastPointerVisible = frameInfo.PointerPosition.Visible;
postPointer = true;
}
}
// post back the pointer information
if (postPointer)
{
pointer.visible = this->lastPointerVisible;
this->postPointerBufferFn(pointer);
}
return CAPTURE_RESULT_OK;
}
static CaptureResult dxgi_getFrame(CaptureFrame * frame)
static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
{
assert(this);
assert(this->initialized);
Texture * tex = &this->texture[this->texRIndex];
if (!os_waitEvent(tex->mapped, 1000))
// NOTE: the event may be signaled when there are no frames available
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
{
if (!lgWaitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
return CAPTURE_RESULT_REINIT;
// the count will still be zero if we are stopping
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
return CAPTURE_RESULT_TIMEOUT;
}
// only reset the event if we used the texture
os_resetEvent(tex->mapped);
Texture * tex = &this->texture[this->texRIndex];
// try to map the resource, but don't wait for it
for (int i = 0; ; ++i)
{
HRESULT status;
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
{
if (i == 100)
return CAPTURE_RESULT_TIMEOUT;
usleep(1);
continue;
}
if (FAILED(status))
{
DEBUG_WINERROR("Failed to map the texture", status);
return CAPTURE_RESULT_ERROR;
}
break;
}
tex->state = TEXTURE_STATE_MAPPED;
frame->width = this->width;
frame->height = this->height;
@@ -789,39 +948,23 @@ static CaptureResult dxgi_getFrame(CaptureFrame * frame)
frame->stride = this->stride;
frame->format = this->format;
memcpy(frame->data, tex->map.pData, this->pitch * this->height);
os_signalEvent(tex->free);
if (++this->texRIndex == this->maxTextures)
this->texRIndex = 0;
atomic_fetch_sub_explicit(&this->texReady, 1, memory_order_release);
return CAPTURE_RESULT_OK;
}
static CaptureResult dxgi_getPointer(CapturePointer * pointer)
static CaptureResult dxgi_getFrame(FrameBuffer * frame)
{
assert(this);
assert(this->initialized);
if (!os_waitEvent(this->pointerEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
Texture * tex = &this->texture[this->texRIndex];
if (this->stop)
return CAPTURE_RESULT_REINIT;
framebuffer_write(frame, tex->map.pData, this->pitch * this->height);
LOCKED({ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)tex->tex, 0);});
tex->state = TEXTURE_STATE_UNUSED;
Pointer p;
memcpy(&p, &this->pointer, sizeof(Pointer));
pointer->x = p.x;
pointer->y = p.y;
pointer->width = p.w;
pointer->height = p.h;
pointer->pitch = p.pitch;
pointer->visible = p.visible;
pointer->format = p.format;
pointer->shapeUpdate = p.version > this->lastPointer.version;
memcpy(&this->lastPointer, &p, sizeof(Pointer));
if (++this->texRIndex == this->maxTextures)
this->texRIndex = 0;
return CAPTURE_RESULT_OK;
}
@@ -832,7 +975,8 @@ static CaptureResult dxgi_releaseFrame()
if (!this->needsRelease)
return CAPTURE_RESULT_OK;
HRESULT status = IDXGIOutputDuplication_ReleaseFrame(this->dup);
HRESULT status;
LOCKED({status = IDXGIOutputDuplication_ReleaseFrame(this->dup);});
switch(status)
{
case S_OK:
@@ -869,6 +1013,6 @@ struct CaptureInterface Capture_DXGI =
.free = dxgi_free,
.getMaxFrameSize = dxgi_getMaxFrameSize,
.capture = dxgi_capture,
.getFrame = dxgi_getFrame,
.getPointer = dxgi_getPointer
.waitFrame = dxgi_waitFrame,
.getFrame = dxgi_getFrame
};

View File

@@ -35,9 +35,9 @@ HRESULT __stdcall CreateDXGIFactory1(REFIID riid, void **factory);
#endif
enum DXGI_OUTDUPL_POINTER_SHAPE_TYPE {
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME = 1,
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR,
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME = 0x1,
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR = 0x2,
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR = 0x4
};
typedef struct DXGI_OUTDUPL_DESC {

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,10 +19,12 @@ 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 "common/windebug.h"
#include "windows/mousehook.h"
#include "common/option.h"
#include "common/framebuffer.h"
#include "common/event.h"
#include "common/thread.h"
#include <assert.h>
#include <stdlib.h>
#include <windows.h>
@@ -36,17 +38,20 @@ struct iface
NvFBCHandle nvfbc;
bool seperateCursor;
void * pointerShape;
unsigned int pointerSize;
CaptureGetPointerBuffer getPointerBufferFn;
CapturePostPointerBuffer postPointerBufferFn;
LGThread * pointerThread;
unsigned int maxWidth, maxHeight;
unsigned int width , height;
uint8_t * frameBuffer;
uint8_t * diffMap;
NvFBCFrameGrabInfo grabInfo;
osEventHandle * frameEvent;
osEventHandle * cursorEvents[2];
LGEvent * frameEvent;
LGEvent * cursorEvents[2];
int mouseX, mouseY, mouseHotX, mouseHotY;
bool mouseVisible;
@@ -55,6 +60,7 @@ struct iface
static struct iface * this = NULL;
static void nvfbc_free();
static int pointerThread(void * unused);
static void getDesktopSize(unsigned int * width, unsigned int * height)
{
@@ -74,7 +80,7 @@ static void on_mouseMove(int x, int y)
{
this->mouseX = x;
this->mouseY = y;
os_signalEvent(this->cursorEvents[0]);
lgSignalEvent(this->cursorEvents[0]);
}
static const char * nvfbc_getName()
@@ -99,7 +105,9 @@ static void nvfbc_initOptions()
option_register(options);
}
static bool nvfbc_create()
static bool nvfbc_create(
CaptureGetPointerBuffer getPointerBufferFn,
CapturePostPointerBuffer postPointerBufferFn)
{
if (!NvFBCInit())
return false;
@@ -134,7 +142,7 @@ static bool nvfbc_create()
}
free(privData);
this->frameEvent = os_createEvent(true);
this->frameEvent = lgCreateEvent(true, 17);
if (!this->frameEvent)
{
DEBUG_ERROR("failed to create the frame event");
@@ -143,19 +151,17 @@ static bool nvfbc_create()
}
this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor");
this->getPointerBufferFn = getPointerBufferFn;
this->postPointerBufferFn = postPointerBufferFn;
return true;
}
static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
static bool nvfbc_init()
{
this->stop = false;
this->pointerShape = pointerShape;
this->pointerSize = pointerSize;
getDesktopSize(&this->width, &this->height);
os_resetEvent(this->frameEvent);
lgResetEvent(this->frameEvent);
HANDLE event;
if (!NvFBCToSysSetup(
@@ -163,38 +169,58 @@ static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
BUFFER_FMT_ARGB,
!this->seperateCursor,
this->seperateCursor,
false,
0,
true,
DIFFMAP_BLOCKSIZE_128X128,
(void **)&this->frameBuffer,
NULL,
(void **)&this->diffMap,
&event
))
{
return false;
}
this->cursorEvents[0] = os_createEvent(true);
this->cursorEvents[0] = lgCreateEvent(true, 10);
mouseHook_install(on_mouseMove);
if (this->seperateCursor)
this->cursorEvents[1] = os_wrapEvent(event);
this->cursorEvents[1] = lgWrapEvent(event);
DEBUG_INFO("Cursor mode : %s", this->seperateCursor ? "decoupled" : "integrated");
Sleep(100);
if (!lgCreateThread("NvFBCPointer", pointerThread, NULL, &this->pointerThread))
{
DEBUG_ERROR("Failed to create the NvFBCPointer thread");
return false;
}
return true;
}
static void nvfbc_stop()
{
this->stop = true;
os_signalEvent(this->cursorEvents[0]);
os_signalEvent(this->frameEvent);
lgSignalEvent(this->cursorEvents[0]);
lgSignalEvent(this->frameEvent);
if (this->pointerThread)
{
lgJoinThread(this->pointerThread, NULL);
this->pointerThread = NULL;
}
}
static bool nvfbc_deinit()
{
mouseHook_remove();
if (this->cursorEvents[0])
{
lgFreeEvent(this->cursorEvents[0]);
this->cursorEvents[0] = NULL;
}
return true;
}
@@ -203,7 +229,7 @@ static void nvfbc_free()
NvFBCToSysRelease(&this->nvfbc);
if (this->frameEvent)
os_freeEvent(this->frameEvent);
lgFreeEvent(this->frameEvent);
free(this);
this = NULL;
@@ -231,14 +257,28 @@ static CaptureResult nvfbc_capture()
if (result != CAPTURE_RESULT_OK)
return result;
bool changed = false;
const unsigned int h = (this->height + 127) / 128;
const unsigned int w = (this->width + 127) / 128;
for(unsigned int y = 0; y < h; ++y)
for(unsigned int x = 0; x < w; ++x)
if (this->diffMap[(y*w)+x])
{
changed = true;
break;
}
if (!changed)
return CAPTURE_RESULT_TIMEOUT;
memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo));
os_signalEvent(this->frameEvent);
lgSignalEvent(this->frameEvent);
return CAPTURE_RESULT_OK;
}
static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
{
if (!os_waitEvent(this->frameEvent, 1000))
if (!lgWaitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
@@ -266,36 +306,68 @@ static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
#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)
static CaptureResult nvfbc_getFrame(FrameBuffer * frame)
{
osEventHandle * events[2];
memcpy(&events, &this->cursorEvents, sizeof(osEventHandle *) * 2);
if (!os_waitEvents(events, this->seperateCursor ? 2 : 1, false, 1000))
return CAPTURE_RESULT_TIMEOUT;
framebuffer_write(
frame,
this->frameBuffer,
this->grabInfo.dwHeight * this->grabInfo.dwBufferWidth * 4
);
return CAPTURE_RESULT_OK;
}
static int pointerThread(void * unused)
{
while(!this->stop)
{
LGEvent * events[2];
memcpy(&events, &this->cursorEvents, sizeof(LGEvent *) * 2);
if (!lgWaitEvents(events, this->seperateCursor ? 2 : 1, false, 1000))
continue;
if (this->stop)
return CAPTURE_RESULT_REINIT;
break;
CaptureResult result;
pointer->shapeUpdate = false;
CapturePointer pointer = { 0 };
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;
void * data;
uint32_t size;
if (!this->getPointerBufferFn(&data, &size))
{
DEBUG_WARN("failed to get a pointer buffer");
continue;
}
pointer->visible = this->mouseVisible;
pointer->x = this->mouseX - this->mouseHotX;
pointer->y = this->mouseY - this->mouseHotY;
return CAPTURE_RESULT_OK;
result = NvFBCToSysGetCursor(this->nvfbc, &pointer, data, size);
if (result != CAPTURE_RESULT_OK)
{
DEBUG_WARN("NvFBCToSysGetCursor failed");
continue;
}
this->mouseVisible = pointer.visible;
this->mouseHotX = pointer.hx;
this->mouseHotY = pointer.hy;
}
if (events[0])
{
pointer.positionUpdate = true;
pointer.visible = this->mouseVisible;
pointer.x = this->mouseX - this->mouseHotX;
pointer.y = this->mouseY - this->mouseHotY;
}
this->postPointerBufferFn(pointer);
}
return 0;
}
struct CaptureInterface Capture_NVFBC =
@@ -310,6 +382,6 @@ struct CaptureInterface Capture_NVFBC =
.free = nvfbc_free,
.getMaxFrameSize = nvfbc_getMaxFrameSize,
.capture = nvfbc_capture,
.getFrame = nvfbc_getFrame,
.getPointer = nvfbc_getPointer
.waitFrame = nvfbc_waitFrame,
.getFrame = nvfbc_getFrame
};

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
@@ -18,7 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "wrapper.h"
#include "windows/debug.h"
#include "common/windebug.h"
#include <windows.h>
#include <NvFBC/nvFBCToSys.h>
@@ -288,8 +288,8 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
return CAPTURE_RESULT_ERROR;
}
pointer->x = params.dwXHotSpot;
pointer->y = params.dwYHotSpot;
pointer->hx = params.dwXHotSpot;
pointer->hy = params.dwYHotSpot;
pointer->width = params.dwWidth;
pointer->height = params.dwHeight;
pointer->pitch = params.dwPitch;

View File

@@ -0,0 +1,198 @@
/*
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
!include "MUI2.nsh"
!include "FileFunc.nsh"
!include "LogicLib.nsh"
!include "Sections.nsh"
;Settings
Name "Looking Glass (host)"
OutFile "looking-glass-host-setup.exe"
Unicode true
RequestExecutionLevel admin
ShowInstDetails "show"
ShowUninstDetails "show"
InstallDir "$PROGRAMFILES64\Looking Glass (host)"
!define MUI_ICON "icon.ico"
!define MUI_UNICON "icon.ico"
!define MUI_LICENSEPAGE_BUTTON "Agree"
!define /file VERSION "VERSION"
;Install and uninstall pages
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Function ShowHelpMessage
!define line1 "Command line options:$\r$\n$\r$\n"
!define line2 "/S - silent install (must be uppercase)$\r$\n"
!define line3 "/D=path\to\install\folder - Change install directory$\r$\n"
!define line4 " (Must be uppercase, the last option given and no quotes)$\r$\n$\r$\n"
!define line5 "/startmenu - create start menu shortcut$\r$\n"
!define line6 "/desktop - create desktop shortcut$\r$\n"
!define line7 "/noservice - do not create a service to auto start and elevate the host"
MessageBox MB_OK "${line1}${line2}${line3}${line4}${line5}${line6}${line7}"
Abort
FunctionEnd
Function .onInit
var /GLOBAL cmdLineParams
Push $R0
${GetParameters} $cmdLineParams
ClearErrors
${GetOptions} $cmdLineParams '/?' $R0
IfErrors +2 0
Call ShowHelpMessage
${GetOptions} $cmdLineParams '/H' $R0
IfErrors +2 0
Call ShowHelpMessage
Pop $R0
Var /GLOBAL option_startMenu
Var /GLOBAL option_desktop
Var /GlOBAL option_noservice
StrCpy $option_startMenu 0
StrCpy $option_desktop 0
StrCpy $option_noservice 0
Push $R0
${GetOptions} $cmdLineParams '/startmenu' $R0
IfErrors +2 0
StrCpy $option_startMenu 1
${GetOptions} $cmdLineParams '/desktop' $R0
IfErrors +2 0
StrCpy $option_desktop 1
${GetOptions} $cmdLineParams '/noservice' $R0
IfErrors +2 0
StrCpy $option_noservice 1
Pop $R0
FunctionEnd
;Install
Section "-Install" Section1
nsExec::Exec 'net.exe STOP "Looking Glass (host)"'
SetOutPath $INSTDIR
File ..\..\looking-glass-host.exe
File LICENSE.txt
WriteUninstaller $INSTDIR\uninstaller.exe
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"EstimatedSize" "$0"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"DisplayName" "Looking Glass (host)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"QuietUninstallString" "$\"$INSTDIR\uninstaller.exe$\" /S"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"Publisher" "Geoffrey McRae"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"DisplayIcon" "$\"$INSTDIR\looking-glass-host.exe$\""
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"NoRepair" "1"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"NoModify" "1"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"DisplayVersion" ${VERSION}
SectionEnd
Section "Looking Glass (host) Service" Section2
${If} $option_noservice == 0
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" UninstallService'
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" InstallService'
${EndIf}
SectionEnd
Section /o "Desktop Shortcut" Section3
StrCpy $option_desktop 1
SectionEnd
Section /o "Start Menu Shortcut" Section4
StrCpy $option_startMenu 1
SectionEnd
Section "-Hidden Start Menu" Section5
SetShellVarContext all
${If} $option_startMenu == 1
CreateShortCut "$SMPROGRAMS\Looking Glass (host).lnk" $INSTDIR\looking-glass-host.exe
${EndIf}
${If} $option_desktop == 1
CreateShortCut "$DESKTOP\Looking Glass (host).lnk" $INSTDIR\looking-glass-host.exe
${EndIf}
SectionEnd
Section "Uninstall" Section6
SetShellVarContext all
nsExec::Exec 'net.exe STOP "Looking Glass (host)"'
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" UninstallService'
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)"
Delete "$SMPROGRAMS\Looking Glass (host).lnk"
Delete "$DESKTOP\Looking Glass (host).lnk"
Delete "$INSTDIR\uninstaller.exe"
Delete "$INSTDIR\looking-glass-host.exe"
Delete "$INSTDIR\LICENSE.txt"
RMDir $INSTDIR
SectionEnd
;Description text for selection of install items
LangString DESC_Section1 ${LANG_ENGLISH} "Install Files into $INSTDIR"
LangString DESC_Section2 ${LANG_ENGLISH} "Install service to automatically start Looking Glass (host)."
LangString DESC_Section3 ${LANG_ENGLISH} "Create desktop shortcut icon."
LangString DESC_Section4 ${LANG_ENGLISH} "Create start menu shortcut."
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${Section1} $(DESC_Section1)
!insertmacro MUI_DESCRIPTION_TEXT ${Section2} $(DESC_Section2)
!insertmacro MUI_DESCRIPTION_TEXT ${Section3} $(DESC_Section3)
!insertmacro MUI_DESCRIPTION_TEXT ${Section4} $(DESC_Section4)
!insertmacro MUI_FUNCTION_DESCRIPTION_END

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