Compare commits

...

324 Commits

Author SHA1 Message Date
Geoffrey McRae
655c993c5b [client] main: add new option for integer only upscaling
The new option `win:intUpscale` will limit upscaling to integer sizes
only if enabled.
2022-05-09 14:05:04 +10:00
Geoffrey McRae
7b7a06b63f [client] fix invalid bitwise comparison 2022-05-04 11:02:02 +10:00
Geoffrey McRae
81f91caf0e [host] pw: fix build due to frame info structure changes 2022-05-04 10:59:44 +10:00
Geoffrey McRae
3d727a2254 [host] xcb: fix build due to frame info structure changes 2022-05-04 10:58:19 +10:00
Geoffrey McRae
e4a4e2331a [host] nvfbc: fix build due to frame info structure changes 2022-05-04 10:37:52 +10:00
Geoffrey McRae
8682ec207e [host] dxgi: check for invalid usage of d3d12 & downsampling 2022-05-04 10:23:42 +10:00
Geoffrey McRae
c8a5293645 [host] dxgi: refactor dxgi_copyFrame 2022-05-04 09:27:52 +10:00
Geoffrey McRae
15334c89d6 [host] dxgi: match the last entry in the downsample rule list 2022-05-03 17:58:41 +10:00
Geoffrey McRae
2eec459b47 [host] dxgi: finish downsample support for d3d11 backend 2022-05-03 12:15:24 +10:00
Geoffrey McRae
947325e00d [host] dxgi: fix failure to copy to texture with sub resources
`CopyResource` will silently fail when trying to copy from a texture
with no subresources to one with. Instead we must use `ResolveSubresource`
2022-05-02 15:53:20 +10:00
Geoffrey McRae
eae559b4c9 [client/obs] update to support downscaled frames coming from the host 2022-05-01 19:51:25 +10:00
Geoffrey McRae
3134ec84de [host] dxgi: add support for downsampling the capture before sending
This is an experimental & incomplete feature for those using
supersampling. Anything > 1200p will be downsampled by 50% before
copying out of the GPU to save on memory bandwidth.

Unfinished! Has issues with damage tracking and currently can not
be configured. Only dx11 has been tested at this point, everything
else will likely have problems/crash.
2022-05-01 19:45:44 +10:00
Netboy3
132d0e3c42 [client] audio/pw: pw_stream_get_time() deprecated
pw_stream_get_time() is deprecated in PipeWire 0.3.50.
Use pw_stream_get_time_n() instead based on PipeWire
library version.
2022-04-14 01:47:34 +10:00
Babbaj
4bbdd30284 [all] Add Babbaj to AUTHORS 2022-04-09 16:19:11 +10:00
Babbaj
6d06320fb2 [client] audio/pw: fix muting
Pipewire documents the mute parameter as a bool, however `pw_stream_set_control` expects a float value and converts it to a bool.

6ad6300ec6/src/pipewire/stream.c (L2063)
2022-04-09 16:19:11 +10:00
Quantum
f3fe774f69 [client] overlay/record: do not invalidate window during shutdown 2022-03-19 18:52:07 +11:00
Quantum
e053c014f7 [client] audio: display record indicator when necessary 2022-03-19 18:52:07 +11:00
Quantum
9c8a8a1b44 [client] config: add new option audio:micShowIndicator
This will be used to control the display of the microphone recording
indicator.
2022-03-19 18:52:07 +11:00
Quantum
1685249f3a [client] overlay: add record indicator 2022-03-19 18:52:07 +11:00
Quantum
97cef000fd [client] audio: avoid prompting when changing record format
If a recording is already in progress, we should not prompt again.
2022-03-19 15:10:39 +11:00
Quantum
8f45290beb [client] audio: cancel confirm dialog when a new recording starts 2022-03-19 10:04:23 +11:00
Quantum
9afe170413 [client] audio: prompt before allowing audio
If the user clicks no, the guest only receives silence.
2022-03-19 10:04:23 +11:00
Quantum
dd6d9c44df [client] config: add new audio:micAlwaysAllow option
This will be used to always grant access to microphones instead of
prompting every time.
2022-03-19 10:04:23 +11:00
Quantum
fb5a71c47e [client] audiodevs/pipewire: implement proper recording stop/restart
Before, pipewire_recordStop did nothing since pw.record.active was
always false.
2022-03-19 10:04:23 +11:00
Quantum
75370e464d [client] overlay/msg: fix type for app_msgBoxClose
It should not be taking a pointer to MsgBoxHandle.

Also changed the type of MsgBoxHandle to prevent similar bugs.
2022-03-19 10:04:23 +11:00
Quantum
c55d0a82f2 [client] overlay: add support for confirmation dialogs 2022-03-19 10:04:23 +11:00
Quantum
f28084e653 [client] core: remove state tracking in core_updateOverlayState
The state is never updated when a message box is dismissed, so the
cursor is never displayed when a second message box shows up.

The only other caller, app_setOverlay, has state tracking already.
2022-03-19 10:04:23 +11:00
vmfortress
6c76d6ada5 [host] xcb: fixed early start of pointer thread by adding xcb_start 2022-03-08 06:12:17 +11:00
Geoffrey McRae
3a8cb6a613 [client/common] fixes for issues detected through static analysis. 2022-03-07 10:14:52 +11:00
Tudor Brindus
a3820536ab [client] overlay: make "Show timing graphs" checkbox consistent in case 2022-03-06 17:21:32 +11:00
vmfortress
36f97f08ad [host] dxgi: add nanosecond-scale sleep capability to d3d12
The nsleep() call lets d3d12 sleep for a more precise amount of
time while maintaining the current millisecond-scale sleep
interface in the configuration file.
2022-03-06 17:21:14 +11:00
Shootfast
48cf099638 [client] input: fix confine_pointer argument marshalling
Under Wayland, if the mouse pointer is disconnected whilst captured
(like say via KVM switch), the waylandWarpPointer code will be called
but the pointer will be NULL. This results in the cryptic message:

error marshalling arguments for confine_pointer (signature noo?ou): null value passed for arg 2
Error marshalling request: Invalid argument

This patch adds a check on the wlWm.pointer pointer before attempting
to warp the pointer, and avoids the crash.
2022-03-05 09:33:45 +11:00
Tudor Brindus
88d60d4b3d [client] enforce building with -Wstrict-prototypes 2022-02-28 11:56:26 +11:00
Tudor Brindus
3189c7bcd6 [client] kb: update for ImGui 1.87 2022-02-28 11:56:26 +11:00
Tudor Brindus
d1da0d62ed [client] update cimgui submodule 2022-02-28 11:56:26 +11:00
Chris Spencer
72033f3822 [client] audio: reduce hardcoded minimum latency
The current minimum target latency is partially based upon the default qemu
behaviour whereby audio packets are delivered in a sawtooth pattern, with
packet timestamps drifting between 5ms above and below the measured clock.
This 5ms error is baked into the minimum target latency to avoid
underrunning.

This sawtooth pattern can be reduced by specifying a lower timer period in
the qemu configuration, so remove it from the hardcoded minimum latency and
add it to the default configurable buffer latency instead. This allows
users that have configured their VM appropriately to reduce the overall
latency.
2022-02-28 11:52:16 +11:00
Chris Spencer
c2523be4b4 [client] audio: reduce resampler latency
The best quality resampler has an intrinsic latency of about 3ms, and the
processing itself takes another 1-2ms per 10ms block. The faster setting
has an intrinsic latency of about 0.4ms, with about 0.04ms processing time.
This makes for an overall saving of about 4ms, with negligible loss in
quality.
2022-02-27 23:47:43 +11:00
Chris Spencer
7efc274e81 [client] audio: use block comments 2022-02-27 23:47:43 +11:00
Chris Spencer
7c2d493bb5 [client] audio: add latency tuning parameter
This adds a new `audio:bufferLatency` option which allows the user to
adjust the amount of buffering LG does over the absolute bare minimum. By
default, this is set large enough to absorb typical timing jitter from
Spice. Users may reduce this if they care more about latency than audio
quality.
2022-02-25 20:41:47 +11:00
Chris Spencer
9908b737b0 [client] audio: make the requested audio device period size configurable
This adds a new `audio:periodSize` option which defaults to 2048 frames.
For PipeWire, this controls the `PIPEWIRE_LATENCY` value. For PulseAudio,
the controls the target buffer length (`tlength`) value.
2022-02-25 20:41:47 +11:00
Chris Spencer
0dad9b1e76 [client] audio: fix latency calculation if audio device starts early
If the audio device starts earlier than required, we slew the read pointer
backwards to avoid underrunning. We need to apply this same offset to the
recorded device position, otherwise the Spice thread will think playback is
further ahead than it really is and inject unnecessary latency to
compensate.
2022-02-25 20:41:47 +11:00
Chris Spencer
a13c90bd27 [client] audio/pa: wait until stream is ready before starting playback
Uncorking the stream does not work if the stream is not ready yet.
2022-02-25 20:41:47 +11:00
Chris Spencer
84b5478b02 [client] audio/pa: fix assertion failure when keep alive playback stops
When the 'keep alive' playback times out, playback is stopped from the
audio callback, resulting in an assertion failure inside PulseAudio as we
try to lock the main loop thread while already inside it.
2022-02-25 20:41:47 +11:00
Tudor Brindus
38340d3497 [common] allow building with -Wstrict-prototypes 2022-02-25 20:38:44 +11:00
Tudor Brindus
eeefc15e46 [host] build with -Wstrict-prototypes 2022-02-25 20:38:44 +11:00
Tudor Brindus
91d6e3a82a [client] allow building with -Wstrict-prototypes
This is not yet turned on because cimgui does not build with it enabled.
2022-02-25 20:38:44 +11:00
Geoffrey McRae
7387a4a8e1 [all] update submodules 2022-02-14 15:14:36 +11:00
Chris Spencer
d9dc399522 [client] audio/pw: request real-time priority
This is as per the PipeWire ALSA plugin [1]. The existing
`PW_STREAM_FLAG_RT_PROCESS` flag is misleading and doesn't really have
anything to do with real-time priority; it just tells PipeWire to pull data
from the application synchronously from its main processing thread. More
detail at [2].

[1] f5d47c079e/pipewire-alsa/alsa-plugins/pcm_pipewire.c
[2] https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2024
2022-02-14 15:11:43 +11:00
Chris Spencer
70158a64e7 [client] audio: open device earlier
The actual time between opening the device and the device starting to pull
data can range anywhere between nearly instant and hundreds of
milliseconds. To minimise startup latency, open the device as soon as the
first playback data is received from Spice. If the device starts earlier
than required, insert a period of silence at the beginning of playback to
avoid underrunning. If it starts later, just accept the higher latency and
let the adaptive resampling deal with it.
2022-02-14 15:09:13 +11:00
Chris Spencer
1ca43c4727 [common] appstrings: add spencercw 2022-02-14 15:09:13 +11:00
Quantum
fc96b6691e [host] linux: remove useless libX11 dependency 2022-02-14 15:08:29 +11:00
Quantum
5a94f82f10 [host] linux: remove useless GL dependency 2022-02-14 15:08:29 +11:00
Quantum
5b7c38a4dd [host] ci: install pipewire on Linux 2022-02-12 22:13:31 +11:00
Quantum
f01489720f [host] linux: build with PipeWire by default
Since the client already depends on PipeWire by default, there is no
reason why the host shouldn't.
2022-02-12 22:13:31 +11:00
Geoffrey McRae
202116786c [client] main: fix invalid bit logic 2022-02-10 20:42:25 +11:00
Geoffrey McRae
8b4551c39c [all] convert KVMFR frame bools to flags in a bitfield
This will allow us to add additional flags in the future while remaining
backwards compatible with the host.
2022-02-10 20:32:38 +11:00
Geoffrey McRae
29698362ed [client] x11: added request activation support 2022-02-10 20:20:34 +11:00
Geoffrey McRae
f24db8d0cd Revert "[client] x11: check if the EWMH atoms exist before using them"
This reverts commit cfd2e6ff32.
2022-02-10 13:36:40 +11:00
Geoffrey McRae
cfd2e6ff32 [client] x11: check if the EWMH atoms exist before using them 2022-02-10 13:35:19 +11:00
Chris Spencer
e96311eb7b [client] audio: keep audio device open after playback
We can set the startup latency for the next playback far more precisely if
we have the device open already.

Only keep the device open with no playback for 30 seconds to avoid keeping
the device open unnecessarily forever.
2022-02-10 07:50:01 +11:00
Chris Spencer
0d97a51802 [client] audio: increase startup latency
Underruns can still happen quite easily at the beginning of playback,
particularly at very low latency settings. Further increase the startup
latency to avoid this.
2022-02-10 07:50:01 +11:00
Chris Spencer
5e1b8f2abe [client] spice: update submodule to disable audio draining 2022-02-10 07:50:01 +11:00
Netboy3
e0c0451b52 [client] x11: Add inline icon
Many X11 window managers will present an application on their
taskbar as a combination of the application name and an icon
imagery pulled from the X-Property _NET_WM_ICON. Applications
built under frameworks such as Qt or GTK have this property
populated by the framework. This commit adds the Atom _NET_WM_ICON
and populates it with a 64x64 icon of Looking Glass.
2022-02-09 12:07:55 +11:00
Tudor Brindus
9ddfa585ec [host] cleanup some missing (void) parameters 2022-02-08 16:37:17 +11:00
Tudor Brindus
0ea188faf8 [client] ci: stop installing wayland-protocols from apt 2022-02-08 16:05:51 +11:00
Tudor Brindus
e1ac838796 [client] wayland: use wayland-protocols from submodule 2022-02-08 16:05:51 +11:00
Tudor Brindus
770a4279ee [repos] add wayland-protocol submodule 2022-02-08 16:05:51 +11:00
Geoffrey McRae
1cfbcba813 [client] main: fix failure to check KVMFR udata at connect 2022-02-08 15:50:22 +11:00
Paul Hollinsky
3890c72159 [client] egl: use texture sampler for desktop
The desktop doesn't need its own sampler, there is already an identically
configured one in the `desktop->texture`.

For some reason, using the texture sampler fixes a black screen issue
with my GTX 660 using the 470.86 driver. Maybe hitting some limit
for how many samplers can be allocated?
2022-02-08 15:34:54 +11:00
Paul Hollinsky
4223a5e38f [client] egl: remove extra sampler from TextureBuffer
It was unused, there is a sampler in EGL_Texture (base).
2022-02-08 15:34:54 +11:00
Tudor Brindus
809e1095bd [host] windows: plumb guest activation request to host 2022-02-08 15:27:27 +11:00
Tudor Brindus
fd28d0604e [host/client] kvmfr: request activation based on guest state 2022-02-08 15:27:27 +11:00
Tudor Brindus
30c57f411d [client] ci: install newer wayland-protocols repository
The Ubuntu one lacks `xdg-activation-v1`, despite having been released 6
months ago. Pull in a newer one from @quantum5's repository.
2022-02-08 14:54:55 +11:00
Tudor Brindus
9cd8027901 [client] main: request WM activation on first frame 2022-02-08 14:54:55 +11:00
Tudor Brindus
969ac4d1d1 [client] wayland: activate our window if we're allowed 2022-02-08 14:54:55 +11:00
Tudor Brindus
da548e3858 [client] ds: expose activation requesting 2022-02-08 14:54:55 +11:00
Tudor Brindus
21a349343b [client] wayland: implement activation request 2022-02-08 14:54:55 +11:00
Tudor Brindus
4ee6bdf198 [client] wayland: bind xdg_activation_v1 when available 2022-02-08 14:54:55 +11:00
Tudor Brindus
b13582a911 [client] wayland: build xdg-activation-v1 header 2022-02-08 14:54:55 +11:00
Chris Spencer
05ca59ed48 [client] audio/pw: increase startup latency
PipeWire startup latency varies wildly depending on what else is, or was
last using the audio device. In the worst case, PipeWire can request two
full buffers within a very short period of time immediately at the start of
playback, so make sure we've got enough data in the buffer to support this.
2022-02-04 16:27:12 +11:00
Chris Spencer
e1e60fdaa6 [client] audio: tune target latency
The target latency is now based upon the device maximum period size
(which may be configured by setting the `PIPEWIRE_LATENCY` environment
variable if using PipeWire), with some allowance for timing jitter from
Spice and the audio device.

PipeWire can change the period size dynamically at any time which must be
taken into account when selecting the target latency to avoid underruns
when the period size is increased. This is explained in detail within the
commit body.
2022-02-04 16:27:12 +11:00
Chris Spencer
ca29fe80a6 Revert "[client] audio: tune the target latency based on the latency jitter"
This reverts commit febd081202.

This causes severe underruns when the quantum size increases.
2022-02-04 16:27:12 +11:00
Geoffrey McRae
35bf30910b Revert "[common] option: fix bounds check in the argument parser."
This reverts commit db78c8e468.
I need some sleep, this was already fine as it was...
2022-01-30 20:25:02 +11:00
Geoffrey McRae
db78c8e468 [common] option: fix bounds check in the argument parser.
The recent `pwnkit` exploit brought this to my attention, not that we
are a setuid process we should still do this properly... who knows where
this code might get used in the future.
2022-01-30 19:56:06 +11:00
Geoffrey McRae
febd081202 [client] audio: tune the target latency based on the latency jitter 2022-01-28 12:11:56 +11:00
Geoffrey McRae
5bbc1d44bf [client] audio/pw: get the period size to determine when to start audio
Previously this was hardcoded to 100ms which is far too high in most
instances, instead we get the initial period size and use whichever is
greater out of 50ms or the period size.

The idea is to reduce the amount of time it takes for the latency to
come down after initial stream start.
2022-01-28 12:08:58 +11:00
Geoffrey McRae
22b968ff53 [client] audio: change the audio latency graph sample point
This removes the need for locking while also giving a better result in
the graph output. Also when the graph is disabled via the overlay
options it will no longer cause redraws.
2022-01-28 10:59:12 +11:00
Geoffrey McRae
a0477466d2 Revert "[client] audio: allow the audiodev to return the periodFrames"
This reverts commit 41884bfcc5.

PipeWire can change it's period size on the fly on us making this
approach invalid.
2022-01-28 10:00:35 +11:00
Geoffrey McRae
c2a766c2ee [client] audio: fix setfault due to failure to properly reset 2022-01-27 19:20:16 +11:00
Geoffrey McRae
4ff39616b2 [common] rb: correct invalid accesses of atomics 2022-01-27 18:20:03 +11:00
Geoffrey McRae
2201ed869e [github] add libsamplerate0-dev to the workflow 2022-01-27 18:05:24 +11:00
Geoffrey McRae
d0b3c09456 [doc] build: add missing audio dependencies 2022-01-27 18:03:11 +11:00
Geoffrey McRae
a560a610d9 [client] audio: allow building without any audio support 2022-01-27 18:03:11 +11:00
Geoffrey McRae
a7db3d3a0f [client] audio: check for malloc failure 2022-01-27 18:03:11 +11:00
Geoffrey McRae
016001da67 [client] audio: cosmetics 2022-01-27 18:03:11 +11:00
Geoffrey McRae
41884bfcc5 [client] audio: allow the audiodev to return the periodFrames
This change allows the audiodevs to return the minimum period frames
needed to start playback instead of having to rely on a pull to obtain
these details.

Additionally we are using this information to select an initial start
latency as well as to train the desired latency in order to keep it as
low as possible.
2022-01-27 18:03:11 +11:00
Chris Spencer
dd2d84a080 [client] audio: adjust playback speed to match audio device clock
This change is based on the techniques described in [1] and [2].

The input audio stream from Spice is not synchronised to the audio playback
device. While the input and output may be both nominally running at 48 kHz,
when compared against each other, they will differ by a tiny fraction of a
percent. Given enough time (typically on the order of a few hours), this
will result in the ring buffer becoming completely full or completely
empty. It will stay in this state permanently, periodically resulting in
glitches as the buffer repeatedly underruns or overruns.

To address this, adjust the speed of the received data to match the rate at
which it is being consumed by the audio device. This will result in a
slight pitch shift, but the changes should be small and smooth enough that
this is unnoticeable to the user.

The process works roughly as follows:
1. Every time audio data is received from Spice, or consumed by the audio
   device, sample the current time. These are fed into a pair of delay
   locked loops to produce smoothed approximations of the two clocks.
2. Compute the difference between the two clocks and compare this against
   the target latency to produce an error value. This error value will be
   quite stable during normal operation, but can change quite rapidly due
   to external factors, particularly at the start of playback. To smooth
   out any sudden changes in playback speed, which would be noticeable to
   the user, this value is also filtered through another delay locked loop.
3. Feed this error value into a PI controller to produce a ratio value.
   This is the target playback speed in order to bring the error value
   towards zero.
4. Resample the input audio using the computed ratio to apply the speed
   change. The output of the resampler is what is ultimately inserted into
   the ring buffer for consumption by the audio device.

Since this process targets a specific latency value, rather than simply
trying to rate match the input and output, it also has the effect of
'correcting' latency issues. If a high latency application (such as a media
player) is already running, the time between requesting the start of
playback and the audio device actually starting to consume samples can be
very high, easily in the hundreds of milliseconds. The changes here will
automatically adjust the playback speed over the course of a few minutes to
bring the latency back down to the target value.

[1] https://kokkinizita.linuxaudio.org/papers/adapt-resamp.pdf
[2] https://kokkinizita.linuxaudio.org/papers/usingdll.pdf
2022-01-27 18:03:11 +11:00
Chris Spencer
599fdd6ffd [common] ringbuffer: add unbounded mode
In unbounded mode, the read and write pointers are free to move
independently of one another. This is useful where the input and output
streams are progressing at the same rate on average, and we want to keep
the latency stable in the event than an underrun or overrun occurs.

If an underrun occurs (i.e., there is not enough data in the buffer to
satisfy a read request), the missing values with be filled with zeros. When
the writer catches up, the same number of values will be skipped from the
input.

If an overrun occurs (i.e., there is not enough free space in the buffer to
satisfy a write request), excess values will be discarded. When the reader
catches up, the same number of values will be zeroed in the output.

Unbounded mode is currently unused since our audio input and output
streams are not synchronised. This will be implemented in a later commit.

Also reimplemented as a lock-free queue which is safer for use in audio
device callbacks.
2022-01-27 18:03:11 +11:00
Chris Spencer
b34b253814 [client] audio: stop playback immediately if new playback is started
If a new playback is started while the previous playback is still flushing,
we simply allow the stream to continue playing and effectively cancel the
flush. In general this is not safe because there may not be enough data in
the buffer to avoid underrunning. We could handle this better later by
trying to insert the right number of silent samples into the buffer, but
for now just completely stop the previous stream before starting the new
one.
2022-01-27 18:03:11 +11:00
Chris Spencer
68b42e1c1a [client] audio/pw: drop restarting state
Automatically restarting playback once draining has completed could result
in playback starting too early (i.e., before there is enough data in the
ring buffer to avoid underrunning). `audio_playbackData` will keep invoking
`start` until it returns true anyway, so we can just allow draining to
complete normally and wait for `start` to be called again.
2022-01-27 18:03:11 +11:00
Chris Spencer
8580978321 [client] audio/pw: drop redundant flushing state
We do not stop the audio device until after the internal buffer has already
been flushed, so this state does nothing useful.
2022-01-27 18:03:11 +11:00
Quantum
d93510e9f2 [host] linux: allow getting system version
Result is something like:

    Debian GNU/Linux 11 (bullseye), kernel: Linux 5.14.0-0.bpo.2-amd64 on x86_64
2022-01-27 05:47:53 +11:00
Quantum
75ec3c0478 [host] nvfbc: shorten nvfbc_getName() result
To avoid client showing "Using    : NVFBC (NVidia Frame Buffer Capt".
This happens because the string is truncated to 31 characters to fit
in the char capture[32]; member of KVMFRRecord_VMInfo.
2022-01-26 23:06:47 +11:00
Quantum
e85fd68d82 [host] windows: read ProductName from registry if possible
For Windows 10, it so happens that the major.minor is 10.0. This is not
usually a given, e.g. on Windows 7 where it would read 6.1, on
Windows 8 it would read 6.2, and on Windows 8.1 it would read 6.3.

This is obviously undesirable, so we should just read the ProductName
from registry if possible. This results in something like:

    OS Name: Windows 10 Pro for Workstations (Build: 19043)
2022-01-26 23:06:14 +11:00
Quantum
f247d7f0da [host] app: don't read from UUID if none is returned
This allows the Linux host to start.
2022-01-26 23:05:31 +11:00
Geoffrey McRae
b0568ca404 [client] egl: use a sigmoid curve for nv 2022-01-26 23:03:35 +11:00
Geoffrey McRae
3c9b9e6370 [client] main: fix heap-buffer-overflow on cursor update 2022-01-26 20:05:51 +11:00
Geoffrey McRae
db3d20f935 [host] windows: report windows version information 2022-01-26 19:58:46 +11:00
Geoffrey McRae
ccdf7b7c0e [host] app: report the full capture name to the client 2022-01-26 19:32:55 +11:00
Geoffrey McRae
efa49391fc [client] fix race segfault on pointer queue unsubscribe/timeout 2022-01-26 17:20:12 +11:00
Geoffrey McRae
fb4bdaee2b [client] egl: set the active preset if specified at launch 2022-01-26 16:07:17 +11:00
Geoffrey McRae
c7389285f9 [client] egl: fix null pointer access when no preset is set 2022-01-26 16:04:15 +11:00
Geoffrey McRae
aa426d13a7 [client] egl: added egl:preset to load a default preset at startup 2022-01-26 16:00:07 +11:00
Geoffrey McRae
89c83dafc1 [client] egl: make egl less noisy unless debug is specified 2022-01-26 15:42:33 +11:00
Geoffrey McRae
05e363e009 [client] x11: cleanup duplicated code 2022-01-26 15:30:38 +11:00
Geoffrey McRae
e17b289759 [client] x11: sync the mouse on meta resize 2022-01-26 14:55:30 +11:00
Geoffrey McRae
79e986cc60 [client] x11: fix failure to unfocus when performing meta resize/move 2022-01-26 14:43:11 +11:00
Geoffrey McRae
22f3cf5ba6 [client] egl: fix masked color cursor blend operation for xor drawing 2022-01-26 12:23:05 +11:00
Geoffrey McRae
3067bdaa15 [client] egl: properly apply xor mask to masked color cursors 2022-01-26 12:11:43 +11:00
Geoffrey McRae
f3ebde7d9f [common] ivshmem: fix memory leak 2022-01-25 03:37:56 +11:00
Geoffrey McRae
905c1d7f58 [common] ivshmem: check for failure to find a device 2022-01-25 03:37:56 +11:00
Quantum
11800029f0 [client] core: redraw cursor after warping guest cursor 2022-01-24 17:07:15 +11:00
Quantum
71901414d1 [client] overlay: realign cursor when turning off overlay
This is only done in non-capture mode to avoid messing up games.
2022-01-24 17:07:15 +11:00
Geoffrey McRae
96fa8891c8 [client] egl: fixed incorrect drawing of masked color cursors 2022-01-24 06:56:32 +11:00
Geoffrey McRae
1082875b8e [client] opengl: fix startup since ImGui upgrade 2022-01-24 06:42:16 +11:00
Geoffrey McRae
dc918c55b6 [client] main: only copy the needed cursor data instead of everything 2022-01-24 06:36:15 +11:00
Geoffrey McRae
a8ba014b52 [client] main: lgmpClientMessageDone is not idempotent 2022-01-24 04:10:49 +11:00
Quantum
9a6aa3ce66 [client] egl: remove duplicate #include "app.h" 2022-01-23 08:49:15 +11:00
Quantum
f2fbb2b27c [client] opengl: make draw functions static 2022-01-23 08:49:01 +11:00
Geoffrey McRae
829db8a0e4 [client] spice: update the submodule to fix invalid header errors 2022-01-22 18:23:33 +11:00
Geoffrey McRae
9601bc677f [client] audio: report the buffered frames and not the buffer length 2022-01-22 16:06:16 +11:00
Jonathan Rubenstein
aba30e9541 [doc] build: Add source comment for listing deps 2022-01-20 17:49:35 +11:00
Geoffrey McRae
c84879717f [client] audio: fix failure to properly handle restart if draining 2022-01-20 07:02:00 +11:00
Geoffrey McRae
b3c81bcedf [client] audio: fix use after free race 2022-01-19 18:50:39 +11:00
Geoffrey McRae
7f4dcd1ced [client] spice: update PureSpice submodule to reduce audio latency 2022-01-19 18:32:22 +11:00
Geoffrey McRae
15f76339c8 [client] audio: move the memory copy into the pull function 2022-01-19 10:29:49 +11:00
Geoffrey McRae
99536eaf9d dxgi: check for memory leaks in d3d12 2022-01-19 09:58:14 +11:00
Geoffrey McRae
f8b4874799 dxgi: cosmetics 2022-01-19 09:58:14 +11:00
Geoffrey McRae
cff64ee7d3 dxgi: cache shared handles instead of re-creating them
This will cache up to 10 handles, in practice I have never seen DXGI
return anything but the same resource each time but we allow for more
anyway should MS change something in the future.

Should the cache get over filled it is disabled entirely and we revert
to the original behaviour.
2022-01-19 09:58:07 +11:00
Geoffrey McRae
04ae9217e8 [client] audio: allow the audiodev to determine the start fill level 2022-01-19 01:52:19 +11:00
Geoffrey McRae
46da447429 [client] audio: fix latency calculation 2022-01-19 00:58:48 +11:00
Geoffrey McRae
4b080f7610 [client] audio: don't lock when consuming frames from the buffer 2022-01-19 00:25:52 +11:00
Geoffrey McRae
d6bbc4f89c [client] audio/pw: return the actual playback latency 2022-01-19 00:03:16 +11:00
Geoffrey McRae
4fadf3a130 [client] audio: tell the audiodev to stop on the last packet, not after 2022-01-18 23:43:12 +11:00
Geoffrey McRae
73dc08e5f9 [client] audio: remove duplicated line 2022-01-18 23:42:02 +11:00
Geoffrey McRae
07c92ec2e8 [client] audio: drain buffers on stop instead of just discarding them 2022-01-18 23:39:05 +11:00
Geoffrey McRae
b334f22223 [client] audio: rework audiodevs to be pull model from a common buffer 2022-01-18 09:02:44 +11:00
Geoffrey McRae
aad65c1cab [client] graphs: overlay on removal of graph 2022-01-17 22:53:52 +11:00
Geoffrey McRae
0ad26b7da7 [client] audio: redraw the graphs if they have been updated 2022-01-17 22:49:19 +11:00
Geoffrey McRae
775ac7ce8b [client] audio: reduce timing graph sample count to 30 seconds 2022-01-17 22:23:37 +11:00
Geoffrey McRae
689cc53255 [client] audio: add audio playback latency interface and graph 2022-01-17 22:13:41 +11:00
Geoffrey McRae
5629655f74 [client] audio/pw: fix memory leak and gracefully shutdown 2022-01-17 22:10:41 +11:00
Geoffrey McRae
54e7542414 [client] overlay/graph: actually remove unregistered overlays
ll now supports removal of elements, so actually do it
2022-01-17 22:09:41 +11:00
Geoffrey McRae
464fee3e20 [client] overlay/graphs: allow the graph to have a custom title format 2022-01-17 22:08:56 +11:00
Geoffrey McRae
42ed0d7638 [client] app: allow key-repeat to work with keybinds 2022-01-17 20:33:57 +11:00
Geoffrey McRae
5a3fe151e4 [client] main: don't use Linux keybinds if the guest is not Linux 2022-01-17 20:26:45 +11:00
Quantum
afd5e2d057 [host] dxgi: remove duplicate AcquireLock print 2022-01-17 15:12:54 +11:00
Quantum
508c491967 [host] dxgi: allow the debug layer to be turned on via config 2022-01-17 15:12:54 +11:00
Quantum
b117bbafe5 [host] dxgi: add ability to capture OutputDebugMessage
This is how Direct3D 12's debug layer outputs messages. Normally, these
are read by the debugger, but we want them logged instead.
2022-01-17 15:12:54 +11:00
Quantum
5392f815af [vendor] directx: add d3d12sdklayers.h
This is an MIT-licensed header from Microsoft, which contains the
Direct3D 12 debug layer.

This header is slightly modified to be able to compile on older
MinGW versions.
2022-01-17 15:12:54 +11:00
Quantum
4c271f8744 [host] dxgi: move d3d12.h to vendor/directx 2022-01-17 15:12:54 +11:00
Jonathan Rubenstein
745169fae2 [client] Add capture mode support for media keys
Supports Play/Pause, Stop, Next, and Previous
2022-01-17 15:09:36 +11:00
Jonathan Rubenstein
7f79352320 [client] Add support for volume keys in capture mode
These include Volume Up, Volume Down, and Mute

Co-authored-by: Quantum <quantum2048@gmail.com>
2022-01-17 15:09:36 +11:00
Quantum
b020372972 [client] imgui: allow arrows to be displayed 2022-01-17 15:09:03 +11:00
Quantum
5fe529f213 [client] spice: allow volume control keys to be sent to the guest
These are implemented as ScrollLock+Up/Down for volume up and down, and
ScrollLock+M to toggle audio mute. These should prove useful especially
when Looking Glass now supports streaming audio, and the volume is
defined in the guest and set on the output stream.
2022-01-17 15:09:03 +11:00
Quantum
7c91c922e6 [client] input: avoid reentrancy when realigning guest cursor
This prevents LGMP_ERR_QUEUE_FULL from happening with high polling rate
mice, which is caused by receiving many more mouse events while the
guest cursor warps, triggering more warps.
2022-01-15 19:25:30 +11:00
Geoffrey McRae
9c49dc6efd [common] linux/time: fix memory leak 2022-01-14 14:19:16 +11:00
Chris Spencer
f635077a2c [client] egl: increase texture processing timeout
On my machine (Intel UHD Graphics 770), texture processing occasionally
(about 5% of the time) takes more than 20ms (the highest I have seen is
around 32ms) when the host resolution is 2560x1440. This results in the
frame being discarded and the client displays a stale image. Increase the
timeout to 40ms.
2022-01-14 12:31:02 +11:00
arcnmx
a9b5302a51 [module] fix compile on 5.16
The `DMA_BUF` namespace was introduced in:
16b0314aa7
2022-01-14 12:14:21 +11:00
Chris Spencer
3d0a8f6987 [host] dxgi: fix frame damage method parameters
The buffer input sizes to the `IDXGIOutputDuplication` methods are measured
in bytes. This dramatically increases the number of dirty/move rects that
can be handled.
2022-01-13 09:34:14 +11:00
Chris Spencer
786a252b23 [client] x11: don't use primary selection for clipboard
This behaviour is more consistent with other applications where text
selections do not influence explicit clipboard operations.
2022-01-13 08:18:56 +11:00
Chris Spencer
f145225dbc [host] dxgi: fix d3d11 assertion failure
`DEBUG_ASSERT(!this)` in `d3d11_create` is firing on the second
instantiation because we are not clearing `this` in `d3d11_free`.
2022-01-13 08:17:59 +11:00
Geoffrey McRae
b38a5ce89e [client] spice: update submodule to fix corrpution during connect 2022-01-13 02:21:48 +11:00
Geoffrey McRae
344d2ec599 [common] linux: replace create_timer with a single threaded timer
Now LG uses a 25Hz tick timer it is an issue that `create_timer` spawns
a new thread for every single timer event, so instead multiplex all the
timers into a single thread with a 1ms resolution.
2022-01-12 13:00:12 +11:00
Geoffrey McRae
6bba9bc25d [client/common] move ll from the client into the common code module 2022-01-12 12:22:18 +11:00
Geoffrey McRae
1851002fc1 [client] all: remove ll_walk and migrate over to ll_forEachNL 2022-01-12 12:17:29 +11:00
Geoffrey McRae
b99e1ea38e [client] ll: fix error in ll_forEachNL macro 2022-01-12 12:17:06 +11:00
Geoffrey McRae
2ecfa0a3ec [client] msg: add missing header file 2022-01-12 10:08:29 +11:00
Geoffrey McRae
ca0bc7c514 [client] close message boxes if the client connects 2022-01-12 10:04:16 +11:00
Geoffrey McRae
4122841b09 [client] spice: fix memory leak 2022-01-12 10:03:56 +11:00
Geoffrey McRae
e94252ad65 squash with ll 2022-01-12 10:03:31 +11:00
Geoffrey McRae
6fc0c69b2e [client] overlay/msg: provide a method to close messages from code 2022-01-12 09:35:09 +11:00
Geoffrey McRae
ced952a4c6 [client] ll: add new functionallity to allow removal of items 2022-01-12 09:33:36 +11:00
Geoffrey McRae
4411d21135 [client] spice: update submodule to prevent segfault on shutdown 2022-01-12 07:42:01 +11:00
Geoffrey McRae
70683010a6 [client] spice: update submodule to fix double free bug 2022-01-12 07:16:00 +11:00
vmfortress
7da2becfbd [host] dxgi: Replace standard asserts with DEBUG_ASSERT 2022-01-11 11:11:42 +11:00
Chris Spencer
8a61c8ebc2 [client] audio/pw: use rate matching
This can prevent glitches when the PipeWire quantum size changes.
2022-01-11 09:45:30 +11:00
Chris Spencer
ef9b2958ec [client] audio/pw: set maximum node latency
This prevents severe buffer underruns if the PipeWire quantum is bigger
than the ring buffer size. This could happen if a media player is running
at the same time as Looking Glass if it requests a very large quantum size,
for example.
2022-01-11 09:45:30 +11:00
Chris Spencer
e72e138267 [client] audio/pw: delay playback to avoid glitches 2022-01-11 09:45:30 +11:00
Chris Spencer
4c389a9274 [client] audio/pw: flush playback buffers before stopping
This stops the end of the playback from being truncated. It also prevents
an audible glitch when playback next starts due to the truncated data being
left behind in the ring buffer.
2022-01-11 09:45:30 +11:00
Chris Spencer
b9c646074d [client] audio/pw: don't discard playback data
This can cause significant glitching, particularly around the start of
playback.
2022-01-11 09:45:30 +11:00
Quantum
042a7d0925 [host] dxgi: add configurable sleep before D3D12 copy 2022-01-10 14:45:51 +11:00
Quantum
c69b19e68f [host] dxgi: add option to disable damage-aware copies 2022-01-10 14:45:51 +11:00
Quantum
cf7d501bc4 [host] dxgi: allow copy backend selection 2022-01-10 14:45:51 +11:00
Quantum
68e5b812a9 [host] dxgi: add preRelease callback
This is meant to avoid freeing the texture before the copy has finished.
2022-01-10 14:45:51 +11:00
Quantum
5a93f1e00c [host] dxgi: implement Direct3D 12 texture copy backend 2022-01-10 14:45:51 +11:00
Quantum
891f00a011 [host] dxgi: add d3d12.h from latest MinGW
This header was added in late 2020 and hasn't made its way into the GitHub
Actions image yet.
2022-01-10 14:45:51 +11:00
Quantum
137171a8a2 [host] dxgi: refactor to support additional copy backends 2022-01-10 14:45:51 +11:00
Geoffrey McRae
36892839f3 [host] lgmp: update repo to fix compliler error 2022-01-09 21:25:46 +11:00
Geoffrey McRae
0fc87576f3 [client] core: fallback to manual realignment if the LGMP message fails 2022-01-09 21:25:40 +11:00
Geoffrey McRae
3ffefb5281 [host] lgmp: update submodule to fix memory alignment issues 2022-01-09 21:19:41 +11:00
Geoffrey McRae
fd12d9901a [host] app: dont use pointers when realloc may have changed them
This code was completely broken and corrupts the stack, replace it with
something that is actually safe.
2022-01-09 21:19:17 +11:00
Geoffrey McRae
c05282c38c [host] cmake: add ubsan and asan to CMake (needs clang64 on windows)
Note that this still is a pain to make work as you require the MS
runtime DLLs, and to build with clang64.
2022-01-09 21:18:32 +11:00
Quantum
a391e271c3 [host] dxgi: damage all textures when skipping frame 2022-01-09 16:36:26 +11:00
Geoffrey McRae
24193aaaa6 [client] main: added user feedback during LGMP/KVMFR version check 2022-01-09 02:15:18 +11:00
Geoffrey McRae
f9b907a6b1 [client] msg: allow messages to contain blank lines and separators 2022-01-09 02:14:01 +11:00
Geoffrey McRae
b8866a2ce4 [client] spice: upgrade submodule to avoid errors on intentional stop 2022-01-08 20:33:18 +11:00
Geoffrey McRae
d42e409728 [client] spice: show message when connected to the wrong guest 2022-01-08 19:32:58 +11:00
Geoffrey McRae
780cf5f362 [client] overlay: add modal message dialog support 2022-01-08 18:58:48 +11:00
Geoffrey McRae
0080e5f1b9 [client] overlay: add app_invalidateOverlay method 2022-01-08 15:18:40 +11:00
Geoffrey McRae
ad6fa5a504 [client] app: move all alert management into overlay/alert.c 2022-01-08 14:33:12 +11:00
Geoffrey McRae
db2e38ae4d [client] overlay: add 25Hz tick function
This allows an overlay to manage itself for timed events like
alerts/messages, etc.
2022-01-08 14:33:07 +11:00
Geoffrey McRae
35334333ac [client] imgui: render twice for alerts
When using jitRender, or on the first frame of an alert the window
doesn't get resized immediately causing it to cut off the end of the
text.

ImGui needs two passes to calulate the bounding box for automatically
sized windows, this is per it's design and not a bug, see:

https://github.com/ocornut/imgui/issues/2158#issuecomment-434223618
2022-01-08 00:46:16 +11:00
Geoffrey McRae
ec0bd6adc8 [client] imgui: update to cimgui 1.86 2022-01-08 00:26:12 +11:00
Quantum
8e8d8834de [client] main: print guest CPU socket count information 2022-01-07 21:03:20 +11:00
Quantum
bf059a6eda [host] app: send CPU socket count information 2022-01-07 21:03:20 +11:00
Quantum
2834c7d95b [common] kvmfr: add field for CPU socket count 2022-01-07 21:03:20 +11:00
Quantum
2099161b7e [client] cpuinfo: implement CPU socket count for Windows 2022-01-07 21:03:20 +11:00
Quantum
a40a964b30 [client] cpuinfo: implement CPU socket count for Linux 2022-01-07 21:03:20 +11:00
Quantum
194241c5a3 [common] cpuinfo: add sockets to interface 2022-01-07 21:03:20 +11:00
Geoffrey McRae
32134b33ea [client] audio: remove more debug output spam 2022-01-07 16:35:46 +11:00
Geoffrey McRae
9d894065c8 [client] audio: remove debug spam 2022-01-07 16:27:29 +11:00
Johnathon Weaver
62c5d68fc6 Update AUTHORS 2022-01-07 01:46:35 +11:00
Johnathon Weaver
0f998582b9 [host] nvfbc: Fix dwmapi linking error
Fixed linking for DwmFlush and also rearranged as per how DXGI is.
2022-01-07 01:46:35 +11:00
Geoffrey McRae
7263159428 [client] audio/pw: implement record support 2022-01-07 00:54:44 +11:00
Geoffrey McRae
52f06ec332 [client] audio: don't call record.mute if it's not supported 2022-01-07 00:22:35 +11:00
Geoffrey McRae
7f93bbd675 [client] audio/pw: fixed another search/replace mistake 2022-01-07 00:09:34 +11:00
Geoffrey McRae
5c20a851c6 [client] audio/pw: fix search/replace error 2022-01-06 23:58:02 +11:00
Geoffrey McRae
11acaa2957 [client] audio/pw: refactor to use playback for playback methods 2022-01-06 23:56:12 +11:00
Geoffrey McRae
fe7973ea24 [client] audio: implement record interface and glue 2022-01-06 23:49:20 +11:00
Geoffrey McRae
ff2ca20235 [client] audio: always store the volume & mute state for restore 2022-01-06 23:31:39 +11:00
Geoffrey McRae
a114ea3de4 [client] audio: move audio code into it's own unit 2022-01-06 23:24:13 +11:00
Geoffrey McRae
e6bd36ec7c [client] audio: refactor audio to playback and add record funcs 2022-01-06 22:47:22 +11:00
Geoffrey McRae
34e5f7e968 [host] windows: fix usage of MCSS and try to get priority "Capture" 2022-01-06 20:14:55 +11:00
Geoffrey McRae
2f8b139131 [host] windows: set DwmFlush default to off
This new feature while helps on some systems, others using freesync or
higher refresh rates where the capture can't keep up will limit to
fractions of the refresh rate. Better to disable and allow users to
opt-in.
2022-01-06 19:20:08 +11:00
Geoffrey McRae
b058cbe9fe [host] nvfbc: add DwmFlush here too as it makes a large difference 2022-01-06 19:01:29 +11:00
Geoffrey McRae
443f98d2fa [host] windows: opt into the Multimedia Class Schedule Service
This improves overall application performance as windows will give this
process higher priority for low latency multimedia tasks.
2022-01-06 18:41:48 +11:00
Geoffrey McRae
92f27cc0f0 [host] dxgi: use DwmFlush to sync to presentation interval
This change reduces the host GPU and CPU load by a large margin
improving guest system performance along with removing latency spikes
when moving the mouse. This is default enabled but can be disabled with
the new option `dxgi:dwmFlush=no` as it limits the capture rate to the
refresh rate of the guests output which may not be desireable.
2022-01-06 18:39:08 +11:00
Geoffrey McRae
208b722348 [client] main: failure to find a renderer is an error 2022-01-06 15:19:35 +11:00
Geoffrey McRae
67509d7a2d [client] main: format the guest information a bit better 2022-01-06 15:19:16 +11:00
Geoffrey McRae
c20bb27b67 [client] main: move checkUUID to after informational prints are done
This change makes the client print the check failure as the last thing
before disconnecting making it more obvious to the user.
2022-01-06 15:18:10 +11:00
Geoffrey McRae
8cdeaceed9 [client] main: fix reversed check logic from testing/debug 2022-01-06 14:40:34 +11:00
Geoffrey McRae
7bcd0dd97f [client] main: if spice is in use check the guest uuid matches
If the guest supports sending us it's UUID and PureSpice has also
reported the guest's UUID, check them to see if the user has
accidentially connected to the wrong spice socket.
2022-01-06 14:38:46 +11:00
Geoffrey McRae
5bb1f01dea [host] windows: parse the UUID from the SMBIOS 2022-01-06 14:22:38 +11:00
Geoffrey McRae
297d0be2dc [obs] allow for the larger KVMFR header size now 2022-01-05 21:57:49 +11:00
Geoffrey McRae
fdb38a227e [host] app: implement stubs for platform specific guest information 2022-01-05 21:04:57 +11:00
Geoffrey McRae
7ccd202d36 [client] main: fix out by one error in the bounds check for os type 2022-01-05 20:40:13 +11:00
Geoffrey McRae
177a997883 [client] main: parse the KVMFR records and log the details 2022-01-05 20:27:43 +11:00
Geoffrey McRae
b3f6c75ade [host] app: zero memory allocated for lgmp userdata 2022-01-05 20:13:12 +11:00
Geoffrey McRae
912ca62a7b [common] only define min/max if they have not already been defined 2022-01-05 19:45:09 +11:00
Geoffrey McRae
952ebea2c5 [all] refresh copyright dates 2022-01-05 19:42:46 +11:00
Geoffrey McRae
0d27092ef5 [all] move min/max and upcast macros into common/util.h 2022-01-05 19:41:57 +11:00
Geoffrey McRae
ebf20dd108 [host] nvfbc: fix failure to startup 2022-01-05 19:31:47 +11:00
Geoffrey McRae
7cc9b5f77c [host] app: remove debug line and fix my failure to count 2022-01-05 19:25:51 +11:00
Geoffrey McRae
0ccc84959e [host] app: fix out by one compiler warning 2022-01-05 19:23:19 +11:00
Geoffrey McRae
ba9f2b85b6 [host/client] kvmfr: update to include extra user data about the VM
This change allows the host to provide information to the client about
how the VM is configured, information such as the UUID, CPU
configuration and capture method both for informational display in the
client as well as debugging in the client's logs.

The format of the records allows this to be extended later with new
record types without needing to bump the KVMFR version.
2022-01-05 19:18:43 +11:00
Geoffrey McRae
ed61a7adf9 [client] spice: update PureSpice submodule to fix UUID format bug 2022-01-05 12:39:05 +11:00
Geoffrey McRae
d708651c53 [client] egl: check for null gl strings 2022-01-05 12:38:38 +11:00
Geoffrey McRae
0d00936aac [client] spice: update PureSpice submodule 2022-01-04 19:06:14 +11:00
Geoffrey McRae
6347f02efe [client] fix accidental submodule version change 2022-01-04 11:04:56 +11:00
Geoffrey McRae
dfdc407bc6 [all] ci: libdecor-dev is now libdecor-0-dev 2022-01-04 10:48:13 +11:00
Geoffrey McRae
ac2c62e560 [host] intiialize the app state earlier to prevent ovewriting re-inits
If there is LGMP corruption the LGMP thread will set the state to
REINIT which if this happens early enough will get overwritten if the
inital app state is set too late. Instead set the application initial
state early to avoid this.
2022-01-04 10:40:00 +11:00
Geoffrey McRae
4b8255aa28 [client] spice: allow spice startup to happen in parallel 2022-01-01 21:07:55 +11:00
Geoffrey McRae
b6fedf1420 update purespice submodule 2022-01-01 20:51:54 +11:00
Geoffrey McRae
c8b4787cb1 [client] don't report a spice error on graceful shutdown 2021-12-30 21:02:53 +11:00
Geoffrey McRae
d43126f433 [client] main: release the grab if the host application is stopped 2021-12-30 17:08:19 +11:00
Geoffrey McRae
6f39434bdc [host] correct reinit logic when LGMP corruption is detected 2021-12-30 14:34:45 +11:00
Geoffrey McRae
9b202d5566 [host] detect header corruption and re-initialize if so 2021-12-30 13:49:33 +11:00
Geoffrey McRae
764e52fb20 [client] undo commented code from debugging 2021-12-30 13:19:59 +11:00
Geoffrey McRae
6f17e89b16 [host] lgmp: update to fix regression in initialization of the header 2021-12-30 13:00:56 +11:00
Geoffrey McRae
d8e7a83226 [host] lgmp: update the lgmp submodule 2021-12-30 12:49:49 +11:00
Geoffrey McRae
c74d48691f [client] spice: update submodule and update to use new PSConfig members 2021-12-29 20:22:07 +11:00
Geoffrey McRae
7c8f42855d [client] spice: update submodule for improved logging 2021-12-29 18:18:33 +11:00
Geoffrey McRae
d1a765c179 [client] spice: update PureSpice submodule and adjust for new API usage 2021-12-29 16:02:22 +11:00
Geoffrey McRae
2ed3c82de0 [common] provide debug print methods for dependent libraries to use 2021-12-29 16:01:42 +11:00
Geoffrey McRae
17b77cfbc1 [client] spice: update PureSpice submodule and update includes 2021-12-29 00:18:25 +11:00
Geoffrey McRae
65ba2e8df9 [client] spice: update submodule and refactor calls & types 2021-12-28 22:04:35 +11:00
Quantum
e7fdf7e77a [host] pipewire: report stream state
This should provide some useful debug information and report stream
errors.
2021-12-28 19:23:14 +11:00
Jonathan Rubenstein
aa5922a1b4 [version.cmake] Add reminder to synchronize git-describe with docs/lgrelease.py 2021-12-28 19:18:42 +11:00
Geoffrey McRae
142902b7b3 [doc] fix typo and add Debian to the word list 2021-12-28 19:07:33 +11:00
Quantum
10110dd940 [host] app: support force quitting
This makes it much less painful to develop the host, as sometimes it
hangs and ^C doesn't work.
2021-12-28 19:04:47 +11:00
Geoffrey McRae
192fb1cdc7 [doc] add link to the wiki for dependencies on other distributions
ref #909
2021-12-28 19:04:24 +11:00
Geoffrey McRae
35efa551ef [client] egl: determine mouse scale using both horiz and vert size 2021-12-28 19:04:24 +11:00
Jonathan Rubenstein
f53adc7a05 [doc] build: Fix apt command overflowing page 2021-12-28 18:50:56 +11:00
Quantum
a21e897bb5 [host] pipewire: use new format for 10-bit colour
I recently added little-endian 10-bit colour formats to PipeWire, which
is what we actually use. The old r210 format is big endian.
2021-12-28 18:50:29 +11:00
Quantum
136737f25b [client] egl: simplify EGL torus code 2021-12-28 10:19:36 +11:00
Geoffrey McRae
95987a9c91 [client] core: set the correct cursor when exiting overlay 2021-12-27 11:52:06 +11:00
Geoffrey McRae
bbd9c84896 [client] core: invalidate the pointer state when overlay is disabled 2021-12-27 11:22:12 +11:00
vmfortress
8ab130deba xcb: added basic cursor support via xcb_fixes
xcb interface now properly supports cursor integration through
a `pointerThread` similar to the nvfbc implementation.
2021-12-27 11:10:07 +11:00
vmfortress
fbf294efd9 xcb: Fixed return value of xcb_deinit
`xcb_deinit` returns `true` to fit the capture interface standard.
2021-12-27 11:10:07 +11:00
vmfortress
2824238b4d xcb: added xcb_stop and xcb_initOptions support
Added basic functions and the necessary variables for the
`.initOptions` and `.stop` items in the capture interface.
2021-12-27 11:10:07 +11:00
Geoffrey McRae
bb74a9d9c8 [client] core: don't try to send LGMP messages if the video is stopped
If the video stream is stopped the LGMP session is not valid, so we
can't send messages to the client.
2021-12-27 09:55:56 +11:00
Jonathan Rubenstein
9ff476bd09 [doc] Minor cleanup to release spelling check
Co-authored-by: Guanzhong Chen <quantum2048@gmail.com>
2021-12-27 09:23:31 +11:00
Jonathan Rubenstein
6ef3fea05e [doc] Version no longer gets spellchecked 2021-12-27 09:23:31 +11:00
Geoffrey McRae
02ec25b008 [client] audio/pw: it's Looking Glass, not LookingGlass 2021-12-26 18:49:35 +11:00
Geoffrey McRae
4e75c576b2 [client] ci: add new libpulse-dev dependency 2021-12-26 18:30:05 +11:00
Geoffrey McRae
90dd1f3913 [client] audio/pa: added initial pulseaudio implementation 2021-12-26 18:22:51 +11:00
Jonathan Rubenstein
a8ddf72318 [doc] conf: Fix typo in release git-describe command
We got the gist of it, but in the end, didn't git it right.
2021-12-26 14:12:39 +11:00
Jonathan Rubenstein
5d9db8b2f5 [docs] all: Change nav background from transparent to explicit 2021-12-26 13:57:05 +11:00
Jonathan Rubenstein
672cd246ab [docs] all: Fix mobile layout 2021-12-26 13:57:05 +11:00
Jonathan Rubenstein
936688ddac [doc] all: Center content when there's room 2021-12-26 13:28:40 +11:00
Jonathan Rubenstein
ff6c46f7ca [docs] all: Switch to sphinx-readthedocs-theme
From https://github.com/readthedocs/sphinx_rtd_theme
2021-12-26 12:46:23 +11:00
Quantum
4dccd725bf [doc] automatically detect version
We prefer the VERSION file if available, otherwise falling back to
querying version directly from git.
2021-12-26 12:08:24 +11:00
Quantum
6f8745a89b [host] installer: enable DPI awareness
This should make the installer look less blurry on high DPI displays.
2021-12-26 11:34:43 +11:00
Quantum
f971a01801 [host] ci: test IVSHMEM installer build 2021-12-26 11:31:32 +11:00
Quantum
3d1eedd4ef [host] installer: add ability to install IVSHMEM driver
To use this, run makensis with -DIVSHMEM with the driver files in the
ivshmem subdirectory under the build directory.
2021-12-26 11:31:32 +11:00
Quantum
d073f9969c [host] installer: clean up trailing whitespace 2021-12-26 11:31:32 +11:00
WYF
b21d842f0e [host] nvfbc: add an option to specify adapter 2021-12-26 11:14:17 +11:00
Geoffrey McRae
9fa643484c [client] audio/pw: use scaling forumula provided by quantum 2021-12-26 11:09:42 +11:00
Geoffrey McRae
64b64b61be [client] audio/pw: implement volume and mute control 2021-12-26 11:09:42 +11:00
Geoffrey McRae
433a5420cb [client] audio: update PureSpice and add support for volume control/mute 2021-12-26 11:09:42 +11:00
Geoffrey McRae
e408ea51e2 [client] audio/pw: gracefully shutdown when asked to 2021-12-26 11:09:42 +11:00
Geoffrey McRae
cca6492069 [client] audio: call audioDev->free on spice thread exit 2021-12-26 11:09:42 +11:00
Quantum
141d5d3731 [client] ci: build with PipeWire 2021-12-26 11:09:42 +11:00
Geoffrey McRae
ebdc847ef1 [client] audio/pw: don't re-create an idle stream with matching format 2021-12-26 11:09:42 +11:00
Geoffrey McRae
2ea24516d2 [client] audio/pw: limit how much data gets buffered to reduce latency 2021-12-26 11:09:42 +11:00
Geoffrey McRae
dd04a46403 [client] audio/pw: make use of the new ringbuffer consume/append funcs 2021-12-26 11:09:42 +11:00
Geoffrey McRae
d99ec3e9c0 [common] ringbuffer: remove exta locking and add append/consume funcs
As the ringbuffer is now in use for audio it makes sense to provide bulk
append and consume functions that are thread safe instead of adding
locking over all of the functions. This partially reverts the prior
commit that added the extra locking.
2021-12-26 11:09:42 +11:00
Geoffrey McRae
f403033ab1 [client] audio/pw: properly manage the stream state 2021-12-26 11:09:42 +11:00
Geoffrey McRae
11ef94c134 [client] audio/pw: set the node name 2021-12-26 11:09:42 +11:00
Geoffrey McRae
75e46128d4 [client] audio/pw: don't actually stop when SPICE signals a stop 2021-12-26 11:09:42 +11:00
Geoffrey McRae
e810577317 [client] audio: initial addition of PipeWire audio support via SPICE 2021-12-26 11:09:42 +11:00
Geoffrey McRae
8ba4b56dba [common] ringbuffer: added shift and locking for thread safety 2021-12-26 11:09:42 +11:00
Geoffrey McRae
d69069fb09 [client] egl: keep the mouse cursor 1:1 when downscaling
This keeps the cursor a usable size when the guest is running a high
resolution and downscaling (ie, 4K -> FHD).
2021-12-26 11:08:42 +11:00
238 changed files with 23777 additions and 1510 deletions

View File

@@ -18,6 +18,9 @@ jobs:
- name: Install libdecor PPA
run: sudo add-apt-repository ppa:christianrauch/libdecoration
if: ${{ matrix.wayland_shell == 'libdecor' }}
- name: Install PipeWire repository
run: |
echo 'deb [trusted=yes] https://pipewire-ubuntu.quantum5.workers.dev ./' | sudo tee /etc/apt/sources.list.d/pipewire.list
- name: Update apt
run: |
sudo apt-get update
@@ -28,8 +31,9 @@ jobs:
libspice-protocol-dev nettle-dev \
libgl-dev libgles-dev \
libx11-dev libxss-dev libxi-dev libxinerama-dev libxcursor-dev libxpresent-dev \
libwayland-dev wayland-protocols libxkbcommon-dev \
$([ '${{ matrix.wayland_shell }}' = libdecor ] && echo 'libdecor-dev libdbus-1-dev') \
libwayland-dev libxkbcommon-dev \
libsamplerate0-dev libpipewire-0.3-dev libpulse-dev \
$([ '${{ matrix.wayland_shell }}' = libdecor ] && echo 'libdecor-0-dev libdbus-1-dev') \
$([ '${{ matrix.compiler.cc }}' = clang ] && echo 'clang-tools')
sudo pip3 install pyenchant
- name: Configure client
@@ -43,7 +47,7 @@ jobs:
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DCMAKE_LINKER:FILEPATH=/usr/bin/ld \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DENABLE_LIBDECOR=${{ matrix.wayland_shell == 'libdecor' }} \
-DENABLE_LIBDECOR=${{ matrix.wayland_shell == 'libdecor' }} \
..
- name: Build client
run: |
@@ -72,12 +76,16 @@ jobs:
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Install PipeWire repository
run: |
echo 'deb [trusted=yes] https://pipewire-ubuntu.quantum5.workers.dev ./' | sudo tee /etc/apt/sources.list.d/pipewire.list
- name: Update apt
run: |
sudo apt-get update
- name: Install Linux host dependencies
run: |
sudo apt-get install binutils-dev libgl1-mesa-dev libxcb-xfixes0-dev
sudo apt-get install binutils-dev libxcb-xfixes0-dev \
libpipewire-0.3-dev
- name: Configure Linux host
run: |
mkdir host/build
@@ -113,6 +121,11 @@ jobs:
run: |
cd host/build
makensis platform/Windows/installer.nsi
- name: Build Windows host installer with IVSHMEM drivers
run: |
cd host/build
curl https://dl.quantum2.xyz/ivshmem.tar.gz | tar xz
makensis -DIVSHMEM platform/Windows/installer.nsi
host-windows-native:
runs-on: windows-latest
@@ -170,7 +183,7 @@ jobs:
sudo apt-get update
- name: Install docs dependencies
run: |
sudo apt-get install python3-sphinx
sudo apt-get install python3-sphinx python3-sphinx-rtd-theme
sudo pip3 install sphinxcontrib-spelling
- name: Build docs
run: |

3
.gitmodules vendored
View File

@@ -7,3 +7,6 @@
[submodule "repos/cimgui"]
path = repos/cimgui
url = https://github.com/cimgui/cimgui.git
[submodule "repos/wayland-protocols"]
path = repos/wayland-protocols
url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git

View File

@@ -54,3 +54,9 @@ thejavascriptman <thejavascriptman@outlook.com> (thejavascriptman)
vroad <396351+vroad@users.noreply.github.com> (vroad)
williamvds <w.vigolodasilva@gmail.com> (williamvds)
SytheZN <sythe.zn@gmail.com> (SytheZN)
RTXUX <wyf@rtxux.xyz> (RTXUX)
Vincent LaRocca <vincentmlarocca@gmail.com> (VMFortress)
Johnathon Paul Weaver <weaver123_johnathon@hotmail.com> (8BallBomBom)
Chris Spencer <spencercw@gmail.com> (spencercw)
Mark Boorer <markboo99@gmail.com> (Shootfast)
babbaj <babbaj45@gmail.com> (Babbaj)

View File

@@ -19,4 +19,4 @@ https://looking-glass.io/downloads
Source code for the documentation can be found in the `/doc` directory.
You may view this locally as HTML by running `make html` with `python3-sphinx`
installed.
and `python3-sphinx-rtd-theme` installed.

View File

@@ -51,6 +51,12 @@ add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
option(ENABLE_LIBDECOR "Build with libdecor support" OFF)
add_feature_info(ENABLE_LIBDECOR ENABLE_LIBDECOR "libdecor support.")
option(ENABLE_PIPEWIRE "Build with PipeWire audio output support" ON)
add_feature_info(ENABLE_PIPEWIRE ENABLE_PIPEWIRE "PipeWire audio support.")
option(ENABLE_PULSEAUDIO "Build with PulseAudio audio output support" ON)
add_feature_info(ENABLE_PULSEAUDIO ENABLE_PULSEAUDIO "PulseAudio audio support.")
if (NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
message(FATAL_ERROR "Either ENABLE_X11 or ENABLE_WAYLAND must be on")
endif()
@@ -60,6 +66,7 @@ add_compile_options(
"-Wextra"
"-Wno-sign-compare"
"-Wno-unused-parameter"
"-Wstrict-prototypes"
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
"-Werror"
"-Wfatal-errors"
@@ -101,6 +108,7 @@ add_custom_command(
)
include_directories(
${PROJECT_TOP}
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
@@ -116,9 +124,9 @@ set(SOURCES
src/main.c
src/core.c
src/app.c
src/audio.c
src/config.c
src/keybind.c
src/ll.c
src/util.c
src/clipboard.c
src/kb.c
@@ -132,6 +140,8 @@ set(SOURCES
src/overlay/graphs.c
src/overlay/help.c
src/overlay/config.c
src/overlay/msg.c
src/overlay/record.c
)
# Force cimgui to build as a static library.
@@ -160,6 +170,16 @@ target_link_libraries(looking-glass-client
cimgui
)
if (ENABLE_PIPEWIRE OR ENABLE_PULSEAUDIO)
add_definitions(-D ENABLE_AUDIO)
add_subdirectory(audiodevs)
pkg_check_modules(SAMPLERATE REQUIRED IMPORTED_TARGET samplerate)
target_link_libraries(looking-glass-client
PkgConfig::SAMPLERATE
audiodevs
)
endif()
install(TARGETS looking-glass-client
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT binary)

View File

@@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.0)
project(audiodevs LANGUAGES C)
set(AUDIODEV_H "${CMAKE_BINARY_DIR}/include/dynamic/audiodev.h")
set(AUDIODEV_C "${CMAKE_BINARY_DIR}/src/audiodev.c")
file(WRITE ${AUDIODEV_H} "#include \"interface/audiodev.h\"\n\n")
file(APPEND ${AUDIODEV_H} "extern struct LG_AudioDevOps * LG_AudioDevs[];\n\n")
file(WRITE ${AUDIODEV_C} "#include \"interface/audiodev.h\"\n\n")
file(APPEND ${AUDIODEV_C} "#include <stddef.h>\n\n")
set(AUDIODEVS "_")
set(AUDIODEVS_LINK "_")
function(add_audiodev name)
set(AUDIODEVS "${AUDIODEVS};${name}" PARENT_SCOPE)
set(AUDIODEVS_LINK "${AUDIODEVS_LINK};audiodev_${name}" PARENT_SCOPE)
add_subdirectory(${name})
endfunction()
# Add/remove audiodevs here!
if(ENABLE_PIPEWIRE)
add_audiodev(PipeWire)
endif()
if(ENABLE_PULSEAUDIO)
add_audiodev(PulseAudio)
endif()
list(REMOVE_AT AUDIODEVS 0)
list(REMOVE_AT AUDIODEVS_LINK 0)
list(LENGTH AUDIODEVS AUDIODEV_COUNT)
file(APPEND ${AUDIODEV_H} "#define LG_AUDIODEV_COUNT ${AUDIODEV_COUNT}\n")
foreach(audiodev ${AUDIODEVS})
file(APPEND ${AUDIODEV_C} "extern struct LG_AudioDevOps LGAD_${audiodev};\n")
endforeach()
file(APPEND ${AUDIODEV_C} "\nconst struct LG_AudioDevOps * LG_AudioDevs[] =\n{\n")
foreach(audiodev ${AUDIODEVS})
file(APPEND ${AUDIODEV_C} " &LGAD_${audiodev},\n")
endforeach()
file(APPEND ${AUDIODEV_C} " NULL\n};")
add_library(audiodevs STATIC ${AUDIODEV_C})
target_link_libraries(audiodevs ${AUDIODEVS_LINK})

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.0)
project(audiodev_PipeWire LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(AUDIODEV_PipeWire REQUIRED IMPORTED_TARGET
libpipewire-0.3
)
add_library(audiodev_PipeWire STATIC
pipewire.c
)
target_link_libraries(audiodev_PipeWire
PkgConfig::AUDIODEV_PipeWire
lg_common
)
target_include_directories(audiodev_PipeWire
PRIVATE
src
)

View File

@@ -0,0 +1,565 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/audiodev.h"
#include <spa/param/audio/format-utils.h>
#include <spa/param/props.h>
#include <pipewire/pipewire.h>
#include <math.h>
#include "common/debug.h"
#include "common/stringutils.h"
#include "common/util.h"
typedef enum
{
STREAM_STATE_INACTIVE,
STREAM_STATE_ACTIVE,
STREAM_STATE_DRAINING
}
StreamState;
struct PipeWire
{
struct pw_loop * loop;
struct pw_context * context;
struct pw_thread_loop * thread;
struct
{
struct pw_stream * stream;
struct spa_io_rate_match * rateMatch;
int channels;
int sampleRate;
int stride;
LG_AudioPullFn pullFn;
int maxPeriodFrames;
int startFrames;
StreamState state;
}
playback;
struct
{
struct pw_stream * stream;
int channels;
int sampleRate;
int stride;
LG_AudioPushFn pushFn;
bool active;
}
record;
};
static struct PipeWire pw = {0};
static void pipewire_onPlaybackIoChanged(void * userdata, uint32_t id,
void * data, uint32_t size)
{
switch (id)
{
case SPA_IO_RateMatch:
pw.playback.rateMatch = data;
break;
}
}
static void pipewire_onPlaybackProcess(void * userdata)
{
struct pw_buffer * pbuf;
if (!(pbuf = pw_stream_dequeue_buffer(pw.playback.stream)))
{
DEBUG_WARN("out of buffers");
return;
}
struct spa_buffer * sbuf = pbuf->buffer;
uint8_t * dst;
if (!(dst = sbuf->datas[0].data))
return;
int frames = sbuf->datas[0].maxsize / pw.playback.stride;
if (pw.playback.rateMatch && pw.playback.rateMatch->size > 0)
frames = min(frames, pw.playback.rateMatch->size);
frames = pw.playback.pullFn(dst, frames);
if (!frames)
{
sbuf->datas[0].chunk->size = 0;
pw_stream_queue_buffer(pw.playback.stream, pbuf);
return;
}
sbuf->datas[0].chunk->offset = 0;
sbuf->datas[0].chunk->stride = pw.playback.stride;
sbuf->datas[0].chunk->size = frames * pw.playback.stride;
pw_stream_queue_buffer(pw.playback.stream, pbuf);
}
static void pipewire_onPlaybackDrained(void * userdata)
{
pw_thread_loop_lock(pw.thread);
pw_stream_set_active(pw.playback.stream, false);
pw.playback.state = STREAM_STATE_INACTIVE;
pw_thread_loop_unlock(pw.thread);
}
static bool pipewire_init(void)
{
pw_init(NULL, NULL);
pw.loop = pw_loop_new(NULL);
pw.context = pw_context_new(
pw.loop,
pw_properties_new(
// Request real-time priority on the PipeWire threads
PW_KEY_CONFIG_NAME, "client-rt.conf",
NULL
),
0);
if (!pw.context)
{
DEBUG_ERROR("Failed to create a context");
goto err;
}
/* this is just to test for PipeWire availabillity */
struct pw_core * core = pw_context_connect(pw.context, NULL, 0);
if (!core)
goto err_context;
/* PipeWire is available so create the loop thread and start it */
pw.thread = pw_thread_loop_new_full(pw.loop, "PipeWire", NULL);
if (!pw.thread)
{
DEBUG_ERROR("Failed to create the thread loop");
goto err_context;
}
pw_thread_loop_start(pw.thread);
return true;
err_context:
pw_context_destroy(pw.context);
err:
pw_loop_destroy(pw.loop);
pw_deinit();
return false;
}
static void pipewire_playbackStopStream(void)
{
if (!pw.playback.stream)
return;
pw_thread_loop_lock(pw.thread);
pw_stream_destroy(pw.playback.stream);
pw.playback.stream = NULL;
pw.playback.rateMatch = NULL;
pw_thread_loop_unlock(pw.thread);
}
static void pipewire_playbackSetup(int channels, int sampleRate,
int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames,
LG_AudioPullFn pullFn)
{
const struct spa_pod * params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
static const struct pw_stream_events events =
{
.version = PW_VERSION_STREAM_EVENTS,
.io_changed = pipewire_onPlaybackIoChanged,
.process = pipewire_onPlaybackProcess,
.drained = pipewire_onPlaybackDrained
};
if (pw.playback.stream &&
pw.playback.channels == channels &&
pw.playback.sampleRate == sampleRate)
{
*maxPeriodFrames = pw.playback.maxPeriodFrames;
*startFrames = pw.playback.startFrames;
return;
}
pipewire_playbackStopStream();
char requestedNodeLatency[32];
snprintf(requestedNodeLatency, sizeof(requestedNodeLatency), "%d/%d",
requestedPeriodFrames, sampleRate);
pw.playback.channels = channels;
pw.playback.sampleRate = sampleRate;
pw.playback.stride = sizeof(float) * channels;
pw.playback.pullFn = pullFn;
pw_thread_loop_lock(pw.thread);
pw.playback.stream = pw_stream_new_simple(
pw.loop,
"Looking Glass",
pw_properties_new(
PW_KEY_NODE_NAME , "Looking Glass",
PW_KEY_MEDIA_TYPE , "Audio",
PW_KEY_MEDIA_CATEGORY, "Playback",
PW_KEY_MEDIA_ROLE , "Music",
PW_KEY_NODE_LATENCY , requestedNodeLatency,
NULL
),
&events,
NULL
);
// The user can override the default node latency with the PIPEWIRE_LATENCY
// environment variable, so get the actual node latency value from the stream.
// The actual quantum size may be lower than this value depending on what else
// is using the audio device, but we can treat this value as a maximum
const struct pw_properties * properties =
pw_stream_get_properties(pw.playback.stream);
const char *actualNodeLatency =
pw_properties_get(properties, PW_KEY_NODE_LATENCY);
DEBUG_ASSERT(actualNodeLatency != NULL);
unsigned num, denom;
if (sscanf(actualNodeLatency, "%u/%u", &num, &denom) != 2 ||
denom != sampleRate)
{
DEBUG_WARN(
"PIPEWIRE_LATENCY value '%s' is invalid or does not match stream sample "
"rate; using %d/%d", actualNodeLatency, requestedPeriodFrames,
sampleRate);
struct spa_dict_item items[] = {
{ PW_KEY_NODE_LATENCY, requestedNodeLatency }
};
pw_stream_update_properties(pw.playback.stream,
&SPA_DICT_INIT_ARRAY(items));
pw.playback.maxPeriodFrames = requestedPeriodFrames;
}
else
pw.playback.maxPeriodFrames = num;
// If the previous quantum size was very small, PipeWire can request two full
// periods almost immediately at the start of playback
pw.playback.startFrames = pw.playback.maxPeriodFrames * 2;
*maxPeriodFrames = pw.playback.maxPeriodFrames;
*startFrames = pw.playback.startFrames;
if (!pw.playback.stream)
{
pw_thread_loop_unlock(pw.thread);
DEBUG_ERROR("Failed to create the stream");
return;
}
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
&SPA_AUDIO_INFO_RAW_INIT(
.format = SPA_AUDIO_FORMAT_F32,
.channels = channels,
.rate = sampleRate
));
pw_stream_connect(
pw.playback.stream,
PW_DIRECTION_OUTPUT,
PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS |
PW_STREAM_FLAG_INACTIVE,
params, 1);
pw_thread_loop_unlock(pw.thread);
}
static void pipewire_playbackStart(void)
{
if (!pw.playback.stream)
return;
if (pw.playback.state != STREAM_STATE_ACTIVE)
{
pw_thread_loop_lock(pw.thread);
switch (pw.playback.state)
{
case STREAM_STATE_INACTIVE:
pw_stream_set_active(pw.playback.stream, true);
pw.playback.state = STREAM_STATE_ACTIVE;
break;
case STREAM_STATE_DRAINING:
// We are in the middle of draining the PipeWire buffers; we need to
// wait for this to complete before allowing the new playback to start
break;
default:
DEBUG_UNREACHABLE();
}
pw_thread_loop_unlock(pw.thread);
}
}
static void pipewire_playbackStop(void)
{
if (pw.playback.state != STREAM_STATE_ACTIVE)
return;
pw_thread_loop_lock(pw.thread);
pw_stream_flush(pw.playback.stream, true);
pw.playback.state = STREAM_STATE_DRAINING;
pw_thread_loop_unlock(pw.thread);
}
static void pipewire_playbackVolume(int channels, const uint16_t volume[])
{
if (channels != pw.playback.channels)
return;
float param[channels];
for(int i = 0; i < channels; ++i)
param[i] = 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787;
pw_thread_loop_lock(pw.thread);
pw_stream_set_control(pw.playback.stream, SPA_PROP_channelVolumes,
channels, param, 0);
pw_thread_loop_unlock(pw.thread);
}
static void pipewire_playbackMute(bool mute)
{
pw_thread_loop_lock(pw.thread);
float val = mute ? 1.0f : 0.0f;
pw_stream_set_control(pw.playback.stream, SPA_PROP_mute, 1, &val, 0);
pw_thread_loop_unlock(pw.thread);
}
static size_t pipewire_playbackLatency(void)
{
struct pw_time time = { 0 };
pw_thread_loop_lock(pw.thread);
#if PW_CHECK_VERSION(0, 3, 50)
if (pw_stream_get_time_n(pw.playback.stream, &time, sizeof(time)) < 0)
#else
if (pw_stream_get_time(pw.playback.stream, &time) < 0)
#endif
DEBUG_ERROR("pw_stream_get_time failed");
pw_thread_loop_unlock(pw.thread);
return time.delay + time.queued / pw.playback.stride;
}
static void pipewire_recordStopStream(void)
{
if (!pw.record.stream)
return;
pw_thread_loop_lock(pw.thread);
pw_stream_destroy(pw.record.stream);
pw.record.stream = NULL;
pw_thread_loop_unlock(pw.thread);
}
static void pipewire_onRecordProcess(void * userdata)
{
struct pw_buffer * pbuf;
if (!(pbuf = pw_stream_dequeue_buffer(pw.record.stream)))
{
DEBUG_WARN("out of buffers");
return;
}
struct spa_buffer * sbuf = pbuf->buffer;
uint8_t * dst;
if (!(dst = sbuf->datas[0].data))
return;
dst += sbuf->datas[0].chunk->offset;
pw.record.pushFn(dst,
min(
sbuf->datas[0].chunk->size,
sbuf->datas[0].maxsize - sbuf->datas[0].chunk->offset) / pw.record.stride
);
pw_stream_queue_buffer(pw.record.stream, pbuf);
}
static void pipewire_recordStart(int channels, int sampleRate,
LG_AudioPushFn pushFn)
{
const struct spa_pod * params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
static const struct pw_stream_events events =
{
.version = PW_VERSION_STREAM_EVENTS,
.process = pipewire_onRecordProcess
};
if (pw.record.stream &&
pw.record.channels == channels &&
pw.record.sampleRate == sampleRate)
{
if (!pw.record.active)
{
pw_thread_loop_lock(pw.thread);
pw_stream_set_active(pw.record.stream, true);
pw.record.active = true;
pw_thread_loop_unlock(pw.thread);
}
return;
}
pipewire_recordStopStream();
pw.record.channels = channels;
pw.record.sampleRate = sampleRate;
pw.record.stride = sizeof(uint16_t) * channels;
pw.record.pushFn = pushFn;
pw_thread_loop_lock(pw.thread);
pw.record.stream = pw_stream_new_simple(
pw.loop,
"Looking Glass",
pw_properties_new(
PW_KEY_NODE_NAME , "Looking Glass",
PW_KEY_MEDIA_TYPE , "Audio",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_ROLE , "Music",
NULL
),
&events,
NULL
);
if (!pw.record.stream)
{
pw_thread_loop_unlock(pw.thread);
DEBUG_ERROR("Failed to create the stream");
return;
}
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
&SPA_AUDIO_INFO_RAW_INIT(
.format = SPA_AUDIO_FORMAT_S16,
.channels = channels,
.rate = sampleRate
));
pw_stream_connect(
pw.record.stream,
PW_DIRECTION_INPUT,
PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
params, 1);
pw_thread_loop_unlock(pw.thread);
pw.record.active = true;
}
static void pipewire_recordStop(void)
{
if (!pw.record.active)
return;
pw_thread_loop_lock(pw.thread);
pw_stream_set_active(pw.record.stream, false);
pw.record.active = false;
pw_thread_loop_unlock(pw.thread);
}
static void pipewire_recordVolume(int channels, const uint16_t volume[])
{
if (channels != pw.record.channels)
return;
float param[channels];
for(int i = 0; i < channels; ++i)
param[i] = 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787;
pw_thread_loop_lock(pw.thread);
pw_stream_set_control(pw.record.stream, SPA_PROP_channelVolumes,
channels, param, 0);
pw_thread_loop_unlock(pw.thread);
}
static void pipewire_recordMute(bool mute)
{
pw_thread_loop_lock(pw.thread);
float val = mute ? 1.0f : 0.0f;
pw_stream_set_control(pw.record.stream, SPA_PROP_mute, 1, &val, 0);
pw_thread_loop_unlock(pw.thread);
}
static void pipewire_free(void)
{
pipewire_playbackStopStream();
pipewire_recordStopStream();
pw_thread_loop_stop(pw.thread);
pw_thread_loop_destroy(pw.thread);
pw_context_destroy(pw.context);
pw_loop_destroy(pw.loop);
pw.loop = NULL;
pw.context = NULL;
pw.thread = NULL;
pw_deinit();
}
struct LG_AudioDevOps LGAD_PipeWire =
{
.name = "PipeWire",
.init = pipewire_init,
.free = pipewire_free,
.playback =
{
.setup = pipewire_playbackSetup,
.start = pipewire_playbackStart,
.stop = pipewire_playbackStop,
.volume = pipewire_playbackVolume,
.mute = pipewire_playbackMute,
.latency = pipewire_playbackLatency
},
.record =
{
.start = pipewire_recordStart,
.stop = pipewire_recordStop,
.volume = pipewire_recordVolume,
.mute = pipewire_recordMute
}
};

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.0)
project(audiodev_PulseAudio LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(AUDIODEV_PulseAudio REQUIRED IMPORTED_TARGET
libpulse
)
add_library(audiodev_PulseAudio STATIC
pulseaudio.c
)
target_link_libraries(audiodev_PulseAudio
PkgConfig::AUDIODEV_PulseAudio
lg_common
)
target_include_directories(audiodev_PulseAudio
PRIVATE
src
)

View File

@@ -0,0 +1,393 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/audiodev.h"
#include <pulse/pulseaudio.h>
#include <string.h>
#include <math.h>
#include "common/debug.h"
struct PulseAudio
{
pa_threaded_mainloop * loop;
pa_mainloop_api * api;
pa_context * context;
pa_operation * contextSub;
pa_stream * sink;
int sinkIndex;
bool sinkCorked;
bool sinkMuted;
bool sinkStarting;
int sinkMaxPeriodFrames;
int sinkStartFrames;
int sinkSampleRate;
int sinkChannels;
int sinkStride;
LG_AudioPullFn sinkPullFn;
};
static struct PulseAudio pa = {0};
static void pulseaudio_sink_input_cb(pa_context *c, const pa_sink_input_info *i,
int eol, void *userdata)
{
if (eol < 0 || eol == 1)
return;
pa.sinkIndex = i->index;
}
static void pulseaudio_subscribe_cb(pa_context *c,
pa_subscription_event_type_t t, uint32_t index, void *userdata)
{
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
{
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
pa.sinkIndex = 0;
else
{
pa_operation *o = pa_context_get_sink_input_info(c, index,
pulseaudio_sink_input_cb, NULL);
pa_operation_unref(o);
}
break;
}
}
static void pulseaudio_ctx_state_change_cb(pa_context * c, void * userdata)
{
switch (pa_context_get_state(c))
{
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
case PA_CONTEXT_READY:
DEBUG_INFO("Connected to PulseAudio server");
pa_context_set_subscribe_callback(c, pulseaudio_subscribe_cb, NULL);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL);
pa_threaded_mainloop_signal(pa.loop, 0);
break;
case PA_CONTEXT_TERMINATED:
if (pa.contextSub)
{
pa_operation_unref(pa.contextSub);
pa.contextSub = NULL;
}
break;
case PA_CONTEXT_FAILED:
default:
DEBUG_ERROR("context error: %s", pa_strerror(pa_context_errno(c)));
break;
}
}
static bool pulseaudio_init(void)
{
pa.loop = pa_threaded_mainloop_new();
if (!pa.loop)
{
DEBUG_ERROR("Failed to create the main loop");
goto err;
}
pa.api = pa_threaded_mainloop_get_api(pa.loop);
if (pa_signal_init(pa.api) != 0)
{
DEBUG_ERROR("Failed to init signals");
goto err_loop;
}
if (pa_threaded_mainloop_start(pa.loop) < 0)
{
DEBUG_ERROR("Failed to start the main loop");
goto err_loop;
}
pa_proplist * propList = pa_proplist_new();
if (!propList)
{
DEBUG_ERROR("Failed to create the proplist");
goto err_thread;
}
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "video");
pa_threaded_mainloop_lock(pa.loop);
pa.context = pa_context_new_with_proplist(
pa.api,
"Looking Glass",
propList);
if (!pa.context)
{
DEBUG_ERROR("Failed to create the context");
goto err_context;
}
pa_context_set_state_callback(pa.context,
pulseaudio_ctx_state_change_cb, NULL);
if (pa_context_connect(pa.context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0)
{
DEBUG_ERROR("Failed to connect to the context server");
goto err_context;
}
for(;;)
{
pa_context_state_t state = pa_context_get_state(pa.context);
if(!PA_CONTEXT_IS_GOOD(state))
{
DEBUG_ERROR("Context is bad");
goto err_context;
}
if (state == PA_CONTEXT_READY)
break;
pa_threaded_mainloop_wait(pa.loop);
}
pa_threaded_mainloop_unlock(pa.loop);
pa_proplist_free(propList);
return true;
err_context:
pa_threaded_mainloop_unlock(pa.loop);
pa_proplist_free(propList);
err_thread:
pa_threaded_mainloop_stop(pa.loop);
err_loop:
pa_threaded_mainloop_free(pa.loop);
err:
return false;
}
static void pulseaudio_sink_close_nl(void)
{
if (!pa.sink)
return;
pa_stream_set_write_callback(pa.sink, NULL, NULL);
pa_stream_flush(pa.sink, NULL, NULL);
pa_stream_unref(pa.sink);
pa.sink = NULL;
}
static void pulseaudio_free(void)
{
pa_threaded_mainloop_lock(pa.loop);
pulseaudio_sink_close_nl();
pa_context_set_state_callback(pa.context, NULL, NULL);
pa_context_set_subscribe_callback(pa.context, NULL, NULL);
pa_context_disconnect(pa.context);
pa_context_unref(pa.context);
if (pa.contextSub)
{
pa_operation_unref(pa.contextSub);
pa.contextSub = NULL;
}
pa_threaded_mainloop_unlock(pa.loop);
}
static void pulseaudio_state_cb(pa_stream * p, void * userdata)
{
if (pa.sinkStarting && pa_stream_get_state(pa.sink) == PA_STREAM_READY)
{
pa_stream_cork(pa.sink, 0, NULL, NULL);
pa.sinkCorked = false;
pa.sinkStarting = false;
}
}
static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata)
{
// PulseAudio tries to pull data from the stream as soon as it is created for
// some reason, even though it is corked
if (pa.sinkCorked)
return;
uint8_t * dst;
pa_stream_begin_write(p, (void **)&dst, &nbytes);
int frames = nbytes / pa.sinkStride;
frames = pa.sinkPullFn(dst, frames);
pa_stream_write(p, dst, frames * pa.sinkStride, NULL, 0, PA_SEEK_RELATIVE);
}
static void pulseaudio_underflow_cb(pa_stream * p, void * userdata)
{
DEBUG_WARN("Underflow");
}
static void pulseaudio_overflow_cb(pa_stream * p, void * userdata)
{
DEBUG_WARN("Overflow");
}
static void pulseaudio_setup(int channels, int sampleRate,
int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames,
LG_AudioPullFn pullFn)
{
if (pa.sink && pa.sinkChannels == channels && pa.sinkSampleRate == sampleRate)
{
*maxPeriodFrames = pa.sinkMaxPeriodFrames;
*startFrames = pa.sinkStartFrames;
return;
}
pa_sample_spec spec = {
.format = PA_SAMPLE_FLOAT32,
.rate = sampleRate,
.channels = channels
};
int stride = channels * sizeof(float);
int bufferSize = requestedPeriodFrames * 2 * stride;
pa_buffer_attr attribs =
{
.maxlength = -1,
.tlength = bufferSize,
.prebuf = 0,
.minreq = (uint32_t)-1
};
pa_threaded_mainloop_lock(pa.loop);
pulseaudio_sink_close_nl();
pa.sinkChannels = channels;
pa.sinkSampleRate = sampleRate;
pa.sink = pa_stream_new(pa.context, "Looking Glass", &spec, NULL);
pa_stream_set_state_callback (pa.sink, pulseaudio_state_cb , NULL);
pa_stream_set_write_callback (pa.sink, pulseaudio_write_cb , NULL);
pa_stream_set_underflow_callback(pa.sink, pulseaudio_underflow_cb, NULL);
pa_stream_set_overflow_callback (pa.sink, pulseaudio_overflow_cb , NULL);
pa_stream_connect_playback(pa.sink, NULL, &attribs, PA_STREAM_START_CORKED,
NULL, NULL);
pa.sinkStride = stride;
pa.sinkPullFn = pullFn;
pa.sinkMaxPeriodFrames = requestedPeriodFrames;
pa.sinkCorked = true;
pa.sinkStarting = false;
// If something else is, or was recently using a small latency value,
// PulseAudio can request way more data at startup than is reasonable
pa.sinkStartFrames = requestedPeriodFrames * 4;
*maxPeriodFrames = requestedPeriodFrames;
*startFrames = pa.sinkStartFrames;
pa_threaded_mainloop_unlock(pa.loop);
}
static void pulseaudio_start(void)
{
if (!pa.sink)
return;
pa_threaded_mainloop_lock(pa.loop);
pa_stream_state_t state = pa_stream_get_state(pa.sink);
if (state == PA_STREAM_CREATING)
pa.sinkStarting = true;
else
{
pa_stream_cork(pa.sink, 0, NULL, NULL);
pa.sinkCorked = false;
}
pa_threaded_mainloop_unlock(pa.loop);
}
static void pulseaudio_stop(void)
{
if (!pa.sink)
return;
bool needLock = !pa_threaded_mainloop_in_thread(pa.loop);
if (needLock)
pa_threaded_mainloop_lock(pa.loop);
pa_stream_cork(pa.sink, 1, NULL, NULL);
pa.sinkCorked = true;
pa.sinkStarting = false;
if (needLock)
pa_threaded_mainloop_unlock(pa.loop);
}
static void pulseaudio_volume(int channels, const uint16_t volume[])
{
if (!pa.sink || !pa.sinkIndex)
return;
struct pa_cvolume v = { .channels = channels };
for(int i = 0; i < channels; ++i)
v.values[i] = pa_sw_volume_from_linear(
9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787);
pa_threaded_mainloop_lock(pa.loop);
pa_context_set_sink_input_volume(pa.context, pa.sinkIndex, &v, NULL, NULL);
pa_threaded_mainloop_unlock(pa.loop);
}
static void pulseaudio_mute(bool mute)
{
if (!pa.sink || !pa.sinkIndex || pa.sinkMuted == mute)
return;
pa.sinkMuted = mute;
pa_threaded_mainloop_lock(pa.loop);
pa_context_set_sink_input_mute(pa.context, pa.sinkIndex, mute, NULL, NULL);
pa_threaded_mainloop_unlock(pa.loop);
}
struct LG_AudioDevOps LGAD_PulseAudio =
{
.name = "PulseAudio",
.init = pulseaudio_init,
.free = pulseaudio_free,
.playback =
{
.setup = pulseaudio_setup,
.start = pulseaudio_start,
.stop = pulseaudio_stop,
.volume = pulseaudio_volume,
.mute = pulseaudio_mute
}
};

View File

@@ -23,6 +23,7 @@ else()
endif()
add_library(displayserver_Wayland STATIC
activation.c
clipboard.c
cursor.c
gl.c
@@ -50,8 +51,6 @@ target_include_directories(displayserver_Wayland
)
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15)
pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir)
macro(wayland_generate protocol_file output_file)
add_custom_command(OUTPUT "${output_file}.h"
@@ -67,6 +66,7 @@ macro(wayland_generate protocol_file output_file)
target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c")
endmacro()
set(WAYLAND_PROTOCOLS_BASE "${PROJECT_TOP}/repos/wayland-protocols")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland")
include_directories("${CMAKE_BINARY_DIR}/wayland")
wayland_generate(
@@ -96,3 +96,6 @@ wayland_generate(
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-output/xdg-output-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-output-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/staging/xdg-activation/xdg-activation-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-activation-v1-client-protocol")

View File

@@ -0,0 +1,71 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "wayland.h"
#include <stdbool.h>
#include <wayland-client.h>
#include "common/debug.h"
bool waylandActivationInit(void)
{
if (!wlWm.xdgActivation)
DEBUG_WARN("xdg_activation_v1 not exported by compositor, will not be able "
"to request host focus on behalf of guest applications");
return true;
}
void waylandActivationFree(void)
{
if (wlWm.xdgActivation)
{
xdg_activation_v1_destroy(wlWm.xdgActivation);
}
}
static void activationTokenDone(void * data,
struct xdg_activation_token_v1 * xdgToken, const char * token)
{
xdg_activation_v1_activate(wlWm.xdgActivation, token, wlWm.surface);
xdg_activation_token_v1_destroy(xdgToken);
}
static const struct xdg_activation_token_v1_listener activationTokenListener = {
.done = &activationTokenDone,
};
void waylandActivationRequestActivation(void)
{
if (!wlWm.xdgActivation) return;
struct xdg_activation_token_v1 * token =
xdg_activation_v1_get_activation_token(wlWm.xdgActivation);
if (!token)
{
DEBUG_ERROR("failed to retrieve XDG activation token");
return;
}
xdg_activation_token_v1_add_listener(token, &activationTokenListener, NULL);
xdg_activation_token_v1_set_surface(token, wlWm.surface);
xdg_activation_token_v1_commit(token);
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -616,7 +616,10 @@ void waylandRealignPointer(void)
void waylandGuestPointerUpdated(double x, double y, double localX, double localY)
{
if (!wlWm.warpSupport || !wlWm.pointerInSurface || wlWm.lockedPointer)
if ( !wlWm.pointer ||
!wlWm.warpSupport ||
!wlWm.pointerInSurface ||
wlWm.lockedPointer )
return;
waylandWarpPointer((int) localX, (int) localY, false);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -86,7 +86,8 @@ bool waylandPresentationInit(void)
if (wlWm.presentation)
{
wlWm.photonTimings = ringbuffer_new(256, sizeof(float));
wlWm.photonGraph = app_registerGraph("PHOTON", wlWm.photonTimings, 0.0f, 30.0f);
wlWm.photonGraph = app_registerGraph("PHOTON", wlWm.photonTimings,
0.0f, 30.0f, NULL);
wp_presentation_add_listener(wlWm.presentation, &presentationListener, NULL);
}
return true;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -72,6 +72,9 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry,
wlWm.xdgOutputManager = wl_registry_bind(wlWm.registry, name,
// we only need v2 to run, but v3 saves a callback
&zxdg_output_manager_v1_interface, version > 3 ? 3 : version);
else if (!strcmp(interface, xdg_activation_v1_interface.name))
wlWm.xdgActivation = wl_registry_bind(wlWm.registry, name,
&xdg_activation_v1_interface, 1);
}
static void registryGlobalRemoveHandler(void * data,

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -89,6 +89,9 @@ static bool waylandInit(const LG_DSInitParams params)
if (!waylandRegistryInit())
return false;
if (!waylandActivationInit())
return false;
if (!waylandIdleInit())
return false;
@@ -187,6 +190,7 @@ struct LG_DisplayServerOps LGDS_Wayland =
.warpPointer = waylandWarpPointer,
.realignPointer = waylandRealignPointer,
.isValidPointerPos = waylandIsValidPointerPos,
.requestActivation = waylandActivationRequestActivation,
.inhibitIdle = waylandInhibitIdle,
.uninhibitIdle = waylandUninhibitIdle,
.wait = waylandWait,

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -47,6 +47,7 @@
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
#include "wayland-xdg-output-unstable-v1-client-protocol.h"
#include "wayland-xdg-activation-v1-client-protocol.h"
typedef void (*WaylandPollCallback)(uint32_t events, void * opaque);
@@ -180,6 +181,8 @@ struct WaylandDSState
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
struct zwp_idle_inhibitor_v1 * idleInhibitor;
struct xdg_activation_v1 * xdgActivation;
struct wp_viewporter * viewporter;
struct wp_viewport * viewport;
struct zxdg_output_manager_v1 * xdgOutputManager;
@@ -231,6 +234,11 @@ struct WCBState
extern struct WaylandDSState wlWm;
extern struct WCBState wlCb;
// activation module
bool waylandActivationInit(void);
void waylandActivationFree(void);
void waylandActivationRequestActivation(void);
// clipboard module
bool waylandCBInit(void);
void waylandCBRequest(LG_ClipboardData type);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -57,6 +57,12 @@ void waylandWindowUpdateScale(void)
static void wlSurfaceEnterHandler(void * data, struct wl_surface * surface, struct wl_output * output)
{
struct SurfaceOutput * node = malloc(sizeof(*node));
if (!node)
{
DEBUG_ERROR("out of memory");
return;
}
node->output = output;
wl_list_insert(&wlWm.surfaceOutputs, &node->link);
waylandWindowUpdateScale();

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -28,11 +28,13 @@
DEF_ATOM(_NET_REQUEST_FRAME_EXTENTS, True) \
DEF_ATOM(_NET_FRAME_EXTENTS, True) \
DEF_ATOM(_NET_WM_BYPASS_COMPOSITOR, False) \
DEF_ATOM(_NET_WM_ICON, True) \
DEF_ATOM(_NET_WM_STATE, True) \
DEF_ATOM(_NET_WM_STATE_FULLSCREEN, True) \
DEF_ATOM(_NET_WM_STATE_FOCUSED, True) \
DEF_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ, True) \
DEF_ATOM(_NET_WM_STATE_MAXIMIZED_VERT, True) \
DEF_ATOM(_NET_WM_STATE_DEMANDS_ATTENTION, True) \
DEF_ATOM(_NET_WM_WINDOW_TYPE, True) \
DEF_ATOM(_NET_WM_WINDOW_TYPE_NORMAL, True) \
DEF_ATOM(_NET_WM_WINDOW_TYPE_UTILITY, True) \

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -123,8 +123,6 @@ bool x11CBInit()
return false;
}
XFixesSelectSelectionInput(x11.display, x11.window,
XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask);
XFixesSelectSelectionInput(x11.display, x11.window,
x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
@@ -154,6 +152,12 @@ static void x11CBReplyFn(void * opaque, LG_ClipboardData type,
static void x11CBSelectionRequest(const XSelectionRequestEvent e)
{
XEvent * s = malloc(sizeof(*s));
if (!s)
{
DEBUG_ERROR("out of memory");
return;
}
s->xselection.type = SelectionNotify;
s->xselection.requestor = e.requestor;
s->xselection.selection = e.selection;
@@ -205,7 +209,7 @@ send:
static void x11CBSelectionClear(const XSelectionClearEvent e)
{
if (e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD)
if (e.selection != x11atoms.CLIPBOARD)
return;
x11cb.aCurSelection = BadValue;
@@ -291,7 +295,7 @@ out:
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
{
// check if the selection is valid and it isn't ourself
if ((e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD) ||
if (e.selection != x11atoms.CLIPBOARD ||
e.owner == x11.window || e.owner == 0)
{
return;
@@ -396,7 +400,6 @@ void x11CBNotice(LG_ClipboardData type)
{
x11cb.haveRequest = true;
x11cb.type = type;
XSetSelectionOwner(x11.display, XA_PRIMARY , x11.window, CurrentTime);
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, x11.window, CurrentTime);
XFlush(x11.display);
}
@@ -404,7 +407,6 @@ void x11CBNotice(LG_ClipboardData type)
void x11CBRelease(void)
{
x11cb.haveRequest = false;
XSetSelectionOwner(x11.display, XA_PRIMARY , None, CurrentTime);
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, None, CurrentTime);
XFlush(x11.display);
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -28,7 +28,7 @@
bool x11CBEventThread(const XEvent xe);
bool x11CBInit();
bool x11CBInit(void);
void x11CBNotice(LG_ClipboardData type);
void x11CBRelease(void);
void x11CBRequest(LG_ClipboardData type);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -23,6 +23,7 @@
#include "x11.h"
#include "atoms.h"
#include "clipboard.h"
#include "resources/icondata.h"
#include <string.h>
#include <unistd.h>
@@ -532,11 +533,8 @@ static bool x11Init(const LG_DSInitParams params)
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
if (!x11.ewmhHasFocusEvent)
{
XISetMask(mask, XI_FocusIn );
XISetMask(mask, XI_FocusOut);
}
XISetMask(mask, XI_FocusIn );
XISetMask(mask, XI_FocusOut);
XISetMask(mask, XI_Enter );
XISetMask(mask, XI_Leave );
@@ -565,6 +563,17 @@ static bool x11Init(const LG_DSInitParams params)
1
);
XChangeProperty(
x11.display,
x11.window,
x11atoms._NET_WM_ICON,
XA_CARDINAL,
32,
PropModeReplace,
(unsigned char *)icondata,
sizeof(icondata) / sizeof(icondata[0])
);
/* create the blank cursor */
{
static char data[] = { 0x00 };
@@ -1022,6 +1031,16 @@ static void updateModifiers(void)
);
}
static void setFocus(bool focused, double x, double y)
{
if (x11.focused == focused)
return;
x11.focused = focused;
app_updateCursorPos(x, y);
app_handleFocusEvent(focused);
}
static void x11XInputEvent(XGenericEventCookie *cookie)
{
static int button_state = 0;
@@ -1030,43 +1049,50 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
{
case XI_FocusIn:
{
XIFocusOutEvent *xie = cookie->data;
if (x11.ewmhHasFocusEvent)
{
// if meta ungrab for move/resize
if (xie->mode == XINotifyUngrab)
setFocus(true, xie->event_x, xie->event_y);
return;
}
atomic_store(&x11.lastWMEvent, microtime());
if (x11.focused)
return;
XIFocusOutEvent *xie = cookie->data;
if (xie->mode != XINotifyNormal &&
xie->mode != XINotifyWhileGrabbed &&
xie->mode != XINotifyUngrab)
return;
x11.focused = true;
app_updateCursorPos(xie->event_x, xie->event_y);
app_handleFocusEvent(true);
setFocus(true, xie->event_x, xie->event_y);
return;
}
case XI_FocusOut:
{
XIFocusOutEvent *xie = cookie->data;
if (x11.ewmhHasFocusEvent)
{
// if meta grab for move/resize
if (xie->mode == XINotifyGrab)
setFocus(false, xie->event_x, xie->event_y);
return;
}
atomic_store(&x11.lastWMEvent, microtime());
if (!x11.focused)
return;
XIFocusOutEvent *xie = cookie->data;
if (xie->mode != XINotifyNormal &&
xie->mode != XINotifyWhileGrabbed &&
xie->mode != XINotifyGrab)
return;
app_updateCursorPos(xie->event_x, xie->event_y);
app_handleFocusEvent(false);
x11.focused = false;
setFocus(false, xie->event_x, xie->event_y);
return;
}
@@ -1799,6 +1825,28 @@ static bool x11IsValidPointerPos(int x, int y)
return ret;
}
static void x11RequestActivation(void)
{
XEvent e =
{
.xclient = {
.type = ClientMessage,
.send_event = true,
.message_type = x11atoms._NET_WM_STATE,
.format = 32,
.window = x11.window,
.data.l = {
_NET_WM_STATE_ADD,
x11atoms._NET_WM_STATE_DEMANDS_ATTENTION,
0
}
}
};
XSendEvent(x11.display, DefaultRootWindow(x11.display), False,
SubstructureNotifyMask | SubstructureRedirectMask, &e);
}
static void x11InhibitIdle(void)
{
XScreenSaverSuspend(x11.display, true);
@@ -1889,6 +1937,7 @@ struct LG_DisplayServerOps LGDS_X11 =
.warpPointer = x11WarpPointer,
.realignPointer = x11RealignPointer,
.isValidPointerPos = x11IsValidPointerPos,
.requestActivation = x11RequestActivation,
.inhibitIdle = x11InhibitIdle,
.uninhibitIdle = x11UninhibitIdle,
.wait = x11Wait,

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -102,11 +102,21 @@ int app_renderOverlay(struct Rect * rects, int maxRects);
void app_freeOverlays(void);
/**
* invalidate the window to update the overlay, if renderTwice is set the imgui
* render code will run twice so that auto sized windows are calculated correctly
*/
void app_invalidateOverlay(bool renderTwice);
struct OverlayGraph;
typedef struct OverlayGraph * GraphHandle;
typedef const char * (*GraphFormatFn)(const char * name,
float min, float max, float avg, float freq, float last);
GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max);
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
float min, float max, GraphFormatFn formatFn);
void app_unregisterGraph(GraphHandle handle);
void app_invalidateGraph(GraphHandle handle);
void app_overlayConfigRegister(const char * title,
void (*callback)(void * udata, int * id), void * udata);
@@ -128,9 +138,20 @@ void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque);
*/
void app_alert(LG_MsgAlert type, const char * fmt, ...);
typedef struct MsgBoxHandle * MsgBoxHandle;
MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...);
typedef void (*MsgBoxConfirmCallback)(bool yes, void * opaque);
MsgBoxHandle app_confirmMsgBox(const char * caption,
MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...);
void app_msgBoxClose(MsgBoxHandle handle);
typedef struct KeybindHandle * KeybindHandle;
typedef void (*KeybindFn)(int sc, void * opaque);
void app_showRecord(bool show);
/**
* Register a handler for the <super>+<key> combination
* @param sc The scancode to register
@@ -152,4 +173,11 @@ void app_releaseKeybind(KeybindHandle * handle);
*/
void app_releaseAllKeybinds(void);
bool app_guestIsLinux(void);
bool app_guestIsWindows(void);
bool app_guestIsOSX(void);
bool app_guestIsBSD(void);
bool app_guestIsOther(void);
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -0,0 +1,89 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _H_I_AUDIODEV_
#define _H_I_AUDIODEV_
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
typedef int (*LG_AudioPullFn)(uint8_t * dst, int frames);
typedef void (*LG_AudioPushFn)(uint8_t * src, int frames);
struct LG_AudioDevOps
{
/* internal name of the audio for debugging */
const char * name;
/* called very early to allow for option registration, optional */
void (*earlyInit)(void);
/* called to initialize the audio backend */
bool (*init)(void);
/* final free */
void (*free)(void);
struct
{
/* setup the stream for playback but don't start it yet
* Note: the pull function returns f32 samples
*/
void (*setup)(int channels, int sampleRate, int requestedPeriodFrames,
int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn);
/* called when there is data available to start playback */
void (*start)(void);
/* called when SPICE reports the audio stream has stopped */
void (*stop)(void);
/* [optional] called to set the volume of the channels */
void (*volume)(int channels, const uint16_t volume[]);
/* [optional] called to set muting of the output */
void (*mute)(bool mute);
/* return the current total playback latency in microseconds */
uint64_t (*latency)(void);
}
playback;
struct
{
/* start the record stream
* Note: currently SPICE only supports S16 samples so always assume so
*/
void (*start)(int channels, int sampleRate, LG_AudioPushFn pushFn);
/* called when SPICE reports the audio stream has stopped */
void (*stop)(void);
/* [optional] called to set the volume of the channels */
void (*volume)(int channels, const uint16_t volume[]);
/* [optional] called to set muting of the input */
void (*mute)(bool mute);
}
record;
};
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -123,13 +123,13 @@ struct LG_DisplayServerOps
bool (*init)(const LG_DSInitParams params);
/* called at startup after window creation, renderer and SPICE is ready */
void (*startup)();
void (*startup)(void);
/* called just before final window destruction, before final free */
void (*shutdown)();
void (*shutdown)(void);
/* final free */
void (*free)();
void (*free)(void);
/*
* return a system specific property, returns false if unsupported or failure
@@ -170,14 +170,14 @@ struct LG_DisplayServerOps
/* dm specific cursor implementations */
void (*guestPointerUpdated)(double x, double y, double localX, double localY);
void (*setPointer)(LG_DSPointer pointer);
void (*grabKeyboard)();
void (*ungrabKeyboard)();
void (*grabKeyboard)(void);
void (*ungrabKeyboard)(void);
/* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */
void (*grabPointer)();
void (*ungrabPointer)();
void (*grabPointer)(void);
void (*ungrabPointer)(void);
/* (un)capturePointer is used do toggle special cursor tracking in capture mode */
void (*capturePointer)();
void (*uncapturePointer)();
void (*capturePointer)(void);
void (*uncapturePointer)(void);
/* exiting = true if the warp is to leave the window */
void (*warpPointer)(int x, int y, bool exiting);
@@ -185,14 +185,17 @@ struct LG_DisplayServerOps
/* called when the client needs to realign the pointer. This should simply
* call the appropriate app_handleMouse* method for the platform with zero
* deltas */
void (*realignPointer)();
void (*realignPointer)(void);
/* returns true if the position specified is actually valid */
bool (*isValidPointerPos)(int x, int y);
/* called to disable/enable the screensaver */
void (*inhibitIdle)();
void (*uninhibitIdle)();
void (*inhibitIdle)(void);
void (*uninhibitIdle)(void);
/* called to request activation */
void (*requestActivation)(void);
/* wait for the specified time without blocking UI processing/event loops */
void (*wait)(unsigned int time);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -43,6 +43,10 @@ struct LG_OverlayOps
* optional, if omitted assumes false */
bool (*needs_render)(void * udata, bool interactive);
/* return true if the overlay currently requires overlay mode
* optional, if omitted assumes false */
bool (*needs_overlay)(void * udata);
/* perform the actual drawing/rendering
*
* `interactive` is true if the application is currently in overlay interaction
@@ -59,6 +63,15 @@ struct LG_OverlayOps
int (*render)(void * udata, bool interactive, struct Rect * windowRects,
int maxRects);
/* called 25 times a second by the application
*
* Note: This may not run in the same context as `render`!
*
* return true if the frame needs to be rendered
* optional, if omitted assumes false
*/
bool (*tick)(void * udata, unsigned long long tickCount);
/* TODO: add load/save settings capabillity */
};

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -66,9 +66,11 @@ LG_RendererRotate;
typedef struct LG_RendererFormat
{
FrameType type; // frame type
unsigned int width; // image width
unsigned int height; // image height
FrameType type; // frame type
unsigned int screenWidth; // actual width of the host
unsigned int screenHeight; // actual height of the host
unsigned int frameWidth; // width of frame transmitted
unsigned int frameHeight; // height of frame transmitted
unsigned int stride; // scanline width (zero if compresed)
unsigned int pitch; // scanline bytes (or compressed size)
unsigned int bpp; // bits per pixel (zero if compressed)
@@ -139,8 +141,8 @@ typedef struct LG_RendererOps
/* called when the mouse has moved or changed visibillity
* Context: cursorThread */
bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, const int x,
const int y);
bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, int x, int y,
const int hx, const int hy);
/* called when the frame format has changed
* Context: frameThread */

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -24,12 +24,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include "common/types.h"
#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; })
#define UPCAST(type, x) \
(type *)((uintptr_t)(x) - offsetof(type, base))
#include "common/util.h"
// reads the specified file into a new buffer
// the callee must free the buffer

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -42,6 +42,7 @@ struct CursorTex
struct EGL_Texture * texture;
struct EGL_Shader * shader;
GLuint uMousePos;
GLuint uScale;
GLuint uRotate;
GLuint uCBMode;
};
@@ -73,7 +74,9 @@ struct EGL_Cursor
int cbMode;
_Atomic(struct CursorPos) pos;
_Atomic(struct CursorPos) hs;
_Atomic(struct CursorSize) size;
_Atomic(float) scale;
struct CursorTex norm;
struct CursorTex mono;
@@ -103,28 +106,22 @@ static bool cursorTexInit(struct CursorTex * t,
return false;
}
t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" );
t->uRotate = egl_shaderGetUniform(t->shader, "rotate");
t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode");
t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" );
t->uScale = egl_shaderGetUniform(t->shader, "scale" );
t->uRotate = egl_shaderGetUniform(t->shader, "rotate" );
t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" );
return true;
}
static inline void setCursorTexUniforms(EGL_Cursor * cursor,
struct CursorTex * t, bool mono, float x, float y, float w, float h)
struct CursorTex * t, bool mono, float x, float y,
float w, float h, float scale)
{
if (mono)
{
glUniform4f(t->uMousePos, x, y, w, h / 2);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
}
else
{
glUniform4f(t->uMousePos, x, y, w, h);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
}
glUniform4f(t->uMousePos, x, y, w, mono ? h / 2 : h);
glUniform1f(t->uScale , scale);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
}
static void cursorTexFree(struct CursorTex * t)
@@ -166,9 +163,12 @@ bool egl_cursorInit(EGL_Cursor ** cursor)
(*cursor)->cbMode = option_get_int("egl", "cbMode");
struct CursorPos pos = { .x = 0, .y = 0 };
struct CursorPos hs = { .x = 0, .y = 0 };
struct CursorSize size = { .w = 0, .h = 0 };
atomic_init(&(*cursor)->pos, pos);
atomic_init(&(*cursor)->size, size);
atomic_init(&(*cursor)->pos , pos );
atomic_init(&(*cursor)->hs , hs );
atomic_init(&(*cursor)->size , size);
atomic_init(&(*cursor)->scale, 1.0f);
return true;
}
@@ -229,11 +229,19 @@ void egl_cursorSetSize(EGL_Cursor * cursor, const float w, const float h)
atomic_store(&cursor->size, size);
}
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible, const float x, const float y)
void egl_cursorSetScale(EGL_Cursor * cursor, const float scale)
{
atomic_store(&cursor->scale, scale);
}
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
const float x, const float y, const float hx, const float hy)
{
cursor->visible = visible;
struct CursorPos pos = { .x = x, .y = y};
struct CursorPos pos = { .x = x , .y = y };
struct CursorPos hs = { .x = hx, .y = hy };
atomic_store(&cursor->pos, pos);
atomic_store(&cursor->hs , hs);
}
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
@@ -252,22 +260,43 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
switch(cursor->type)
{
case LG_CURSOR_MASKED_COLOR:
// fall through
{
uint32_t xor[cursor->height][cursor->width];
for(int y = 0; y < cursor->height; ++y)
for(int x = 0; x < cursor->width; ++x)
{
uint32_t * src = (uint32_t *)(data + (cursor->stride * y) + x * 4);
const bool masked = (*src & 0xFF000000) != 0;
if (masked)
*src = xor[y][x] = *src & 0x00FFFFFF;
else
{
xor[y][x] = 0xFF000000;
*src |= 0xFF000000;
}
}
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
cursor->width, cursor->height, sizeof(xor[0]));
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor);
}
// fall through
case LG_CURSOR_COLOR:
{
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride);
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
cursor->width, cursor->height, cursor->stride);
egl_textureUpdate(cursor->norm.texture, data);
egl_modelSetTexture(cursor->model, cursor->norm.texture);
break;
}
case LG_CURSOR_MONOCHROME:
{
uint32_t and[cursor->width * cursor->height];
uint32_t xor[cursor->width * cursor->height];
uint32_t and[cursor->height][cursor->width];
uint32_t xor[cursor->height][cursor->width];
for(int y = 0; y < cursor->height; ++y)
{
for(int x = 0; x < cursor->width; ++x)
{
const uint8_t * srcAnd = data + (cursor->stride * y) + (x / 8);
@@ -276,12 +305,15 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
and[y * cursor->width + x] = andMask;
xor[y * cursor->width + x] = xorMask;
and[y][x] = andMask;
xor[y][x] = xorMask;
}
}
egl_textureSetup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4);
egl_textureSetup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4);
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
cursor->width, cursor->height, sizeof(and[0]));
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
cursor->width, cursor->height, sizeof(xor[0]));
egl_textureUpdate(cursor->norm.texture, (uint8_t *)and);
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor);
break;
@@ -292,8 +324,15 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
cursor->rotate = rotate;
struct CursorPos pos = atomic_load(&cursor->pos);
struct CursorSize size = atomic_load(&cursor->size);
struct CursorPos pos = atomic_load(&cursor->pos );
float scale = atomic_load(&cursor->scale);
struct CursorPos hs = atomic_load(&cursor->hs );
struct CursorSize size = atomic_load(&cursor->size );
pos.x -= hs.x * scale;
pos.y -= hs.y * scale;
size.w *= scale;
size.h *= scale;
struct CursorState state = {
.visible = true,
@@ -342,13 +381,33 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
case LG_CURSOR_MONOCHROME:
{
egl_shaderUse(cursor->norm.shader);
setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y, size.w, size.h);
setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
egl_modelSetTexture(cursor->model, cursor->norm.texture);
egl_modelRender(cursor->model);
egl_shaderUse(cursor->mono.shader);
setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y, size.w, size.h);
setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_modelSetTexture(cursor->model, cursor->mono.texture);
egl_modelRender(cursor->model);
break;
}
case LG_CURSOR_MASKED_COLOR:
{
egl_shaderUse(cursor->norm.shader);
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
egl_modelSetTexture(cursor->model, cursor->norm.texture);
egl_modelRender(cursor->model);
egl_shaderUse(cursor->mono.shader);
setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_modelSetTexture(cursor->model, cursor->mono.texture);
egl_modelRender(cursor->model);
@@ -358,17 +417,10 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
case LG_CURSOR_COLOR:
{
egl_shaderUse(cursor->norm.shader);
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y, size.w, size.h);
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
egl_modelRender(cursor->model);
break;
}
case LG_CURSOR_MASKED_COLOR:
{
egl_shaderUse(cursor->mono.shader);
setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y, size.w, size.h);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_modelSetTexture(cursor->model, cursor->norm.texture);
egl_modelRender(cursor->model);
break;
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -45,8 +45,10 @@ bool egl_cursorSetShape(
void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y);
void egl_cursorSetScale(EGL_Cursor * cursor, const float scale);
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
const float x, const float y);
const float x, const float y, const float hx, const float hy);
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
LG_RendererRotate rotate, int width, int height);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -57,7 +57,6 @@ struct EGL_Desktop
EGLDisplay * display;
EGL_Texture * texture;
GLuint sampler;
struct DesktopShader shader;
EGL_DesktopRects * mesh;
CountedBuffer * matrix;
@@ -228,7 +227,8 @@ void egl_desktopConfigUI(EGL_Desktop * desktop)
for (int i = 0; i < EGL_SCALE_MAX; ++i)
{
bool selected = i == desktop->scaleAlgo;
if (igSelectableBool(algorithmNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
if (igSelectable_Bool(algorithmNames[i], selected, 0,
(ImVec2) { 0.0f, 0.0f }))
desktop->scaleAlgo = i;
if (selected)
igSetItemDefaultFocus();
@@ -280,14 +280,14 @@ bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
return false;
}
desktop->width = format.width;
desktop->height = format.height;
desktop->width = format.frameWidth;
desktop->height = format.frameHeight;
if (!egl_textureSetup(
desktop->texture,
pixFmt,
format.width,
format.height,
format.frameWidth,
format.frameHeight,
format.pitch
))
{
@@ -295,12 +295,6 @@ bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
return false;
}
glGenSamplers(1, &desktop->sampler);
glSamplerParameteri(desktop->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(desktop->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(desktop->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glSamplerParameteri(desktop->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
return true;
}
@@ -395,7 +389,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glBindSampler(0, desktop->sampler);
glBindSampler(0, desktop->texture->sampler);
if (finalSizeX > desktop->width || finalSizeY > desktop->height)
scaleType = EGL_DESKTOP_DOWNSCALE;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -33,12 +33,12 @@ void egl_drawTorus(EGL_Model * model, unsigned int pts, float x, float y,
const float angle = (i / (float)pts) * M_PI * 2.0f;
const float c = cos(angle);
const float s = sin(angle);
*dst = x + (inner * c); ++dst;
*dst = y + (inner * s); ++dst;
*dst = 0.0f; ++dst;
*dst = x + (outer * c); ++dst;
*dst = y + (outer * s); ++dst;
*dst = 0.0f; ++dst;
*dst++ = x + (inner * c);
*dst++ = y + (inner * s);
*dst++ = 0.0f;
*dst++ = x + (outer * c);
*dst++ = y + (outer * s);
*dst++ = 0.0f;
}
egl_modelAddVerts(model, v, NULL, (pts + 1) * 2);
@@ -56,12 +56,12 @@ void egl_drawTorusArc(EGL_Model * model, unsigned int pts, float x, float y,
const float angle = s + ((i / (float)pts) * e);
const float c = cos(angle);
const float s = sin(angle);
*dst = x + (inner * c); ++dst;
*dst = y + (inner * s); ++dst;
*dst = 0.0f; ++dst;
*dst = x + (outer * c); ++dst;
*dst = y + (outer * s); ++dst;
*dst = 0.0f; ++dst;
*dst++ = x + (inner * c);
*dst++ = y + (inner * s);
*dst++ = 0.0f;
*dst++ = x + (outer * c);
*dst++ = y + (outer * s);
*dst++ = 0.0f;
}
egl_modelAddVerts(model, v, NULL, (pts + 1) * 2);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -39,7 +39,6 @@
#include <math.h>
#include <string.h>
#include "app.h"
#include "egl_dynprocs.h"
#include "model.h"
#include "shader.h"
@@ -103,9 +102,11 @@ struct Inst
bool cursorVisible;
int cursorX , cursorY;
int cursorHX , cursorHY;
float mouseWidth , mouseHeight;
float mouseScaleX, mouseScaleY;
bool showDamage;
bool scalePointer;
struct CursorState cursorLast;
@@ -197,6 +198,13 @@ static struct Option egl_options[] =
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{
.module = "egl",
.name = "scalePointer",
.description = "Keep the pointer size 1:1 when downscaling",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{0}
};
@@ -249,7 +257,8 @@ static bool egl_create(LG_Renderer ** renderer, const LG_RendererParams params,
this->desktopDamage[0].count = -1;
this->importTimings = ringbuffer_new(256, sizeof(float));
this->importGraph = app_registerGraph("IMPORT", this->importTimings, 0.0f, 5.0f);
this->importGraph = app_registerGraph("IMPORT", this->importTimings,
0.0f, 5.0f, NULL);
*needsOpenGL = false;
return true;
@@ -331,18 +340,18 @@ static void egl_calc_mouse_size(struct Inst * this)
{
case LG_ROTATE_0:
case LG_ROTATE_180:
this->mouseScaleX = 2.0f / this->format.width;
this->mouseScaleY = 2.0f / this->format.height;
w = this->format.width;
h = this->format.height;
this->mouseScaleX = 2.0f / this->format.screenWidth;
this->mouseScaleY = 2.0f / this->format.screenHeight;
w = this->format.screenWidth;
h = this->format.screenHeight;
break;
case LG_ROTATE_90:
case LG_ROTATE_270:
this->mouseScaleX = 2.0f / this->format.height;
this->mouseScaleY = 2.0f / this->format.width;
w = this->format.height;
h = this->format.width;
this->mouseScaleX = 2.0f / this->format.screenHeight;
this->mouseScaleY = 2.0f / this->format.screenWidth;
w = this->format.screenHeight;
h = this->format.screenWidth;
break;
default:
@@ -381,8 +390,10 @@ static void egl_calc_mouse_state(struct Inst * this)
egl_cursorSetState(
this->cursor,
this->cursorVisible,
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY,
((float)this->cursorHX * this->mouseScaleX) * this->scaleX,
((float)this->cursorHY * this->mouseScaleY) * this->scaleY
);
break;
@@ -391,8 +402,10 @@ static void egl_calc_mouse_state(struct Inst * this)
egl_cursorSetState(
this->cursor,
this->cursorVisible,
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX,
((float)this->cursorHX * this->mouseScaleX) * this->scaleY,
((float)this->cursorHY * this->mouseScaleY) * this->scaleX
);
break;
}
@@ -406,14 +419,14 @@ static void egl_update_scale_type(struct Inst * this)
{
case LG_ROTATE_0:
case LG_ROTATE_180:
width = this->format.width;
height = this->format.height;
width = this->format.frameWidth;
height = this->format.frameHeight;
break;
case LG_ROTATE_90:
case LG_ROTATE_270:
width = this->format.height;
height = this->format.width;
width = this->format.frameHeight;
height = this->format.frameWidth;
break;
}
@@ -465,6 +478,16 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
this->screenScaleY = 1.0f / this->height;
egl_calc_mouse_state(this);
if (this->scalePointer)
{
float scale = max(1.0f,
this->formatValid ?
max(
(float)this->format.screenWidth / this->width,
(float)this->format.screenHeight / this->height)
: 1.0f);
egl_cursorSetScale(this->cursor, scale);
}
INTERLOCKED_SECTION(this->desktopDamageLock, {
this->desktopDamage[this->desktopDamageIdx].count = -1;
@@ -472,6 +495,7 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
// this is needed to refresh the font atlas texture
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplOpenGL3_Init("#version 300 es");
ImGui_ImplOpenGL3_NewFrame();
egl_damageResize(this->damage, this->translateX, this->translateY, this->scaleX, this->scaleY);
@@ -497,12 +521,15 @@ static bool egl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cur
return true;
}
static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible, const int x, const int y)
static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible,
int x, int y, const int hx, const int hy)
{
struct Inst * this = UPCAST(struct Inst, renderer);
this->cursorVisible = visible;
this->cursorX = x;
this->cursorY = y;
this->cursorX = x + hx;
this->cursorY = y + hy;
this->cursorHX = hx;
this->cursorHY = hy;
egl_calc_mouse_state(this);
return true;
}
@@ -534,8 +561,14 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
}
}
if (this->scalePointer)
{
float scale = max(1.0f, (float)format.screenWidth / this->width);
egl_cursorSetScale(this->cursor, scale);
}
egl_update_scale_type(this);
egl_damageSetup(this->damage, format.width, format.height);
egl_damageSetup(this->damage, format.frameWidth, format.frameHeight);
/* we need full screen damage when the format changes */
INTERLOCKED_SECTION(this->desktopDamageLock, {
@@ -736,7 +769,13 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
}
const char * client_exts = eglQueryString(this->display, EGL_EXTENSIONS);
bool debugContext = option_get_bool("egl", "debug");
if (!client_exts)
{
DEBUG_ERROR("Failed to query EGL_EXTENSIONS");
return false;
}
bool debug = option_get_bool("egl", "debug");
EGLint ctxattr[5];
int ctxidx = 0;
@@ -746,17 +785,17 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
if (maj > 1 || (maj == 1 && min >= 5))
{
ctxattr[ctxidx++] = EGL_CONTEXT_OPENGL_DEBUG;
ctxattr[ctxidx++] = debugContext ? EGL_TRUE : EGL_FALSE;
ctxattr[ctxidx++] = debug ? EGL_TRUE : EGL_FALSE;
}
else if (util_hasGLExt(client_exts, "EGL_KHR_create_context"))
{
ctxattr[ctxidx++] = EGL_CONTEXT_FLAGS_KHR;
ctxattr[ctxidx++] = debugContext ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0;
ctxattr[ctxidx++] = debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0;
}
else if (debugContext)
else if (debug)
DEBUG_WARN("Cannot create debug contexts before EGL 1.5 without EGL_KHR_create_context");
ctxattr[ctxidx++] = EGL_NONE;
ctxattr[ctxidx] = EGL_NONE;
this->context = eglCreateContext(this->display, this->configs, EGL_NO_CONTEXT, ctxattr);
if (this->context == EGL_NO_CONTEXT)
@@ -784,15 +823,30 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS);
if (!gl_exts)
{
DEBUG_ERROR("Failed to query GL_EXTENSIONS");
return false;
}
const char * vendor = (const char *)glGetString(GL_VENDOR);
if (!vendor)
{
DEBUG_ERROR("Failed to query GL_VENDOR");
return false;
}
DEBUG_INFO("EGL : %d.%d", maj, min);
DEBUG_INFO("Vendor : %s", vendor);
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
DEBUG_INFO("EGL APIs: %s", eglQueryString(this->display, EGL_CLIENT_APIS));
DEBUG_INFO("EGL Exts: %s", client_exts);
DEBUG_INFO("GL Exts : %s", gl_exts);
if (debug)
{
DEBUG_INFO("EGL Exts: %s", client_exts);
DEBUG_INFO("GL Exts : %s", gl_exts);
}
GLint esMaj, esMin;
glGetIntegerv(GL_MAJOR_VERSION, &esMaj);
@@ -818,6 +872,8 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
if (this->noSwapDamage)
DEBUG_WARN("egl:noSwapDamage specified, disabling swap buffers with damage.");
this->scalePointer = option_get_bool("egl", "scalePointer");
if (!g_egl_dynProcs.glEGLImageTargetTexture2DOES)
DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled");
else if (!g_egl_dynProcs.eglCreateImage || !g_egl_dynProcs.eglDestroyImage)
@@ -839,7 +895,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
}
}
if (debugContext)
if (debug)
{
if ((esMaj > 3 || (esMaj == 3 && esMin >= 2)) && g_egl_dynProcs.glDebugMessageCallback)
{
@@ -989,8 +1045,8 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
int y = rect->y > 0 ? rect->y - 1 : 0;
accumulated->rects[accumulated->count++] = (struct FrameDamageRect) {
.x = x, .y = y,
.width = min(this->format.width - x, rect->width + 2),
.height = min(this->format.height - y, rect->height + 2),
.width = min(this->format.frameWidth - x, rect->width + 2),
.height = min(this->format.frameHeight - y, rect->height + 2),
};
}
}
@@ -1003,7 +1059,8 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
if (!renderAll)
{
double matrix[6];
egl_screenToDesktopMatrix(matrix, this->format.width, this->format.height,
egl_screenToDesktopMatrix(matrix,
this->format.frameWidth, this->format.frameHeight,
this->translateX, this->translateY, this->scaleX, this->scaleY, rotate,
this->width, this->height);
@@ -1022,7 +1079,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
for (int j = 0; j < count; ++j)
accumulated->count += egl_screenToDesktop(
accumulated->rects + accumulated->count, matrix, damage + j,
this->format.width, this->format.height
this->format.frameWidth, this->format.frameHeight
);
}
@@ -1130,7 +1187,8 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
else
{
double matrix[6];
egl_desktopToScreenMatrix(matrix, this->format.width, this->format.height,
egl_desktopToScreenMatrix(matrix,
this->format.frameWidth, this->format.frameHeight,
this->translateX, this->translateY, this->scaleX, this->scaleY, rotate,
this->width, this->height);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -268,7 +268,7 @@ static bool egl_filterDownscaleImguiConfig(EGL_Filter * filter)
for (int i = 0; i < DOWNSCALE_COUNT; ++i)
{
bool selected = i == this->filter;
if (igSelectableBool(filterNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
if (igSelectable_Bool(filterNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
{
redraw = true;
this->filter = i;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
#include "texture.h"
#include "common/debug.h"
#include "ll.h"
#include "common/ll.h"
#include <stdlib.h>
#include <string.h>
@@ -124,10 +124,31 @@ void egl_modelSetDefault(EGL_Model * model, bool flipped)
void egl_modelAddVerts(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count)
{
struct FloatList * fl = malloc(sizeof(*fl));
if (!fl)
{
DEBUG_ERROR("out of memory");
return;
}
fl->count = count;
fl->v = malloc(sizeof(GLfloat) * count * 3);
fl->u = malloc(sizeof(GLfloat) * count * 2);
fl->v = malloc(sizeof(GLfloat) * count * 3);
if (!fl->v)
{
DEBUG_ERROR("out of memory");
free(fl);
return;
}
fl->u = malloc(sizeof(GLfloat) * count * 2);
if (!fl->u)
{
DEBUG_ERROR("out of memory");
free(fl->v);
free(fl);
return;
}
memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3);
if (uvs)
@@ -164,18 +185,20 @@ void egl_modelRender(EGL_Model * model)
/* buffer the verticies */
struct FloatList * fl;
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
ll_lock(model->verticies);
ll_forEachNL(model->verticies, item, fl)
{
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 3, fl->v);
offset += sizeof(GLfloat) * fl->count * 3;
}
/* buffer the uvs */
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
ll_forEachNL(model->verticies, item, fl)
{
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 2, fl->u);
offset += sizeof(GLfloat) * fl->count * 2;
}
ll_unlock(model->verticies);
/* set up vertex arrays in the VAO */
glEnableVertexAttribArray(0);
@@ -199,11 +222,13 @@ void egl_modelRender(EGL_Model * model)
/* draw the arrays */
GLint offset = 0;
struct FloatList * fl;
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
ll_lock(model->verticies);
ll_forEachNL(model->verticies, item, fl)
{
glDrawArrays(GL_TRIANGLE_STRIP, offset, fl->count);
offset += fl->count;
}
ll_unlock(model->verticies);
/* unbind and cleanup */
glBindTexture(GL_TEXTURE_2D, 0);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -74,6 +74,12 @@ void egl_postProcessEarlyInit(void)
.type = OPTION_TYPE_STRING,
.value.x_string = ""
},
{
.module = "egl",
.name = "preset",
.description = "The initial filter preset to load",
.type = OPTION_TYPE_STRING
},
{ 0 }
};
option_register(options);
@@ -82,6 +88,8 @@ void egl_postProcessEarlyInit(void)
EGL_Filters[i]->earlyInit();
}
static void loadPreset(struct EGL_PostProcess * this, const char * name);
static void loadPresetList(struct EGL_PostProcess * this)
{
DIR * dir = NULL;
@@ -114,6 +122,9 @@ static void loadPresetList(struct EGL_PostProcess * this)
}
struct dirent * entry;
const char * preset = option_get_string("egl", "preset");
this->activePreset = -1;
while ((entry = readdir(dir)) != NULL)
{
if (entry->d_type != DT_REG)
@@ -127,10 +138,21 @@ static void loadPresetList(struct EGL_PostProcess * this)
goto fail;
}
stringlist_push(this->presets, name);
if (preset && strcmp(preset, name) == 0)
this->activePreset = stringlist_count(this->presets) - 1;
}
closedir(dir);
this->activePreset = -1;
if (preset)
{
if (this->activePreset > -1)
loadPreset(this, preset);
else
DEBUG_WARN("egl:preset '%s' does not exist", preset);
}
return;
fail:
@@ -334,7 +356,8 @@ static bool presetsUI(struct EGL_PostProcess * this)
for (unsigned i = 0; i < stringlist_count(this->presets); ++i)
{
bool selected = i == this->activePreset;
if (igSelectableBool(stringlist_at(this->presets, i), selected, 0, (ImVec2) { 0.0f, 0.0f }))
if (igSelectable_Bool(stringlist_at(this->presets, i), selected, 0,
(ImVec2) { 0.0f, 0.0f }))
{
this->activePreset = i;
redraw = true;
@@ -365,7 +388,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
if (igButton("Save preset as...", (ImVec2) { 0.0f, 0.0f }))
{
this->presetEdit[0] = '\0';
igOpenPopup("Save preset as...", ImGuiPopupFlags_None);
igOpenPopup_Str("Save preset as...", ImGuiPopupFlags_None);
}
igSameLine(0.0f, -1.0f);
@@ -401,7 +424,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
}
if (this->presetError)
igOpenPopup("Preset error", ImGuiPopupFlags_None);
igOpenPopup_Str("Preset error", ImGuiPopupFlags_None);
if (igBeginPopupModal("Preset error", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
@@ -425,7 +448,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
static void drawDropTarget(void)
{
igPushStyleColorVec4(ImGuiCol_Separator, (ImVec4) { 1.0f, 1.0f, 0.0f, 1.0f });
igPushStyleColor_Vec4(ImGuiCol_Separator, (ImVec4) { 1.0f, 1.0f, 0.0f, 1.0f });
igSeparator();
igPopStyleColor(1);
}
@@ -456,8 +479,8 @@ static void configUI(void * opaque, int * id)
if (moving && mouseIdx < moveIdx && i == mouseIdx)
drawDropTarget();
igPushIDPtr(filter);
bool draw = igCollapsingHeaderBoolPtr(filter->ops.name, NULL, 0);
igPushID_Ptr(filter);
bool draw = igCollapsingHeader_BoolPtr(filter->ops.name, NULL, 0);
if (igIsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
mouseIdx = i;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -119,10 +119,15 @@ bool egl_shaderCompile(EGL_Shader * this, const char * vertex_code,
if (logLength > 0)
{
char *log = malloc(logLength + 1);
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
log[logLength] = 0;
DEBUG_ERROR("%s", log);
free(log);
if (!log)
DEBUG_ERROR("out of memory");
else
{
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
log[logLength] = 0;
DEBUG_ERROR("%s", log);
free(log);
}
}
glDeleteShader(vertexShader);
@@ -145,10 +150,15 @@ bool egl_shaderCompile(EGL_Shader * this, const char * vertex_code,
if (logLength > 0)
{
char *log = malloc(logLength + 1);
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
log[logLength] = 0;
DEBUG_ERROR("%s", log);
free(log);
if (!log)
DEBUG_ERROR("out of memory");
else
{
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
log[logLength] = 0;
DEBUG_ERROR("%s", log);
free(log);
}
}
glDeleteShader(fragmentShader);
@@ -201,6 +211,12 @@ void egl_shaderSetUniforms(EGL_Shader * this, EGL_Uniform * uniforms, int count)
{
free(this->uniforms);
this->uniforms = malloc(sizeof(*this->uniforms) * count);
if (!this->uniforms)
{
DEBUG_ERROR("out of memory");
return;
}
this->uniformCount = count;
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -5,11 +5,25 @@ in vec2 uv;
out vec4 color;
uniform sampler2D sampler1;
uniform float scale;
void main()
{
vec4 tmp = texture(sampler1, uv);
vec4 tmp;
if (scale > 1.0)
{
vec2 ts = vec2(textureSize(sampler1, 0));
vec2 px = (uv - (0.5 / ts)) * ts;
if (px.x < 0.0 || px.y < 0.0)
discard;
tmp = texelFetch(sampler1, ivec2(px), 0);
}
else
tmp = texture(sampler1, uv);
if (tmp.rgb == vec3(0.0, 0.0, 0.0))
discard;
color = tmp;
}

View File

@@ -3,16 +3,26 @@ precision mediump float;
#include "color_blind.h"
in vec2 uv;
out vec4 color;
in vec2 uv;
out vec4 color;
uniform sampler2D sampler1;
uniform int cbMode;
uniform float scale;
uniform int cbMode;
void main()
{
color = texture(sampler1, uv);
if (scale > 1.0)
{
vec2 ts = vec2(textureSize(sampler1, 0));
vec2 px = (uv - (0.5 / ts)) * ts;
if (px.x < 0.0 || px.y < 0.0)
discard;
color = texelFetch(sampler1, ivec2(px), 0);
}
else
color = texture(sampler1, uv);
if (cbMode > 0)
color = cbTransform(color, cbMode);

View File

@@ -37,8 +37,9 @@ void main()
if (nvGain > 0.0)
{
highp float lumi = 1.0 - (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
color *= 1.0 + lumi;
highp float lumi = (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
if (lumi < 0.5)
color *= atanh((1.0 - lumi) * 2.0 - 1.0) + 1.0;
color *= nvGain;
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -29,7 +29,6 @@
#include "common/types.h"
#include "util.h"
#include "ll.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -225,7 +225,7 @@ EGL_TexStatus egl_texBufferStreamGet(EGL_Texture * texture, GLuint * tex)
if (this->sync)
{
switch(glClientWaitSync(this->sync, 0, 20000000)) // 20ms
switch(glClientWaitSync(this->sync, 0, 40000000)) // 40ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -33,7 +33,6 @@ typedef struct TextureBuffer
int texCount;
GLuint tex[EGL_TEX_BUFFER_MAX];
GLuint sampler;
EGL_TexBuffer buf[EGL_TEX_BUFFER_MAX];
int bufFree;
GLsync sync;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -36,7 +36,6 @@
#include "common/framebuffer.h"
#include "common/locking.h"
#include "gl_dynprocs.h"
#include "ll.h"
#include "util.h"
#define BUFFER_COUNT 2
@@ -300,14 +299,15 @@ void opengl_onResize(LG_Renderer * renderer, const int width, const int height,
{
glTranslatef(this->destRect.x, this->destRect.y, 0.0f);
glScalef(
(float)this->destRect.w / (float)this->format.width,
(float)this->destRect.h / (float)this->format.height,
(float)this->destRect.w / (float)this->format.frameWidth,
(float)this->destRect.h / (float)this->format.frameHeight,
1.0f
);
}
// this is needed to refresh the font atlas texture
ImGui_ImplOpenGL2_Shutdown();
ImGui_ImplOpenGL2_Init();
ImGui_ImplOpenGL2_NewFrame();
}
@@ -327,7 +327,14 @@ bool opengl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor,
{
if (this->mouseData)
free(this->mouseData);
this->mouseData = malloc(size);
this->mouseData = malloc(size);
if (!this->mouseData)
{
DEBUG_ERROR("out of memory");
return false;
}
this->mouseDataSize = size;
}
@@ -338,7 +345,8 @@ bool opengl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor,
return true;
}
bool opengl_onMouseEvent(LG_Renderer * renderer, const bool visible, const int x, const int y)
bool opengl_onMouseEvent(LG_Renderer * renderer, const bool visible,
int x, int y, const int hx, const int hy)
{
struct Inst * this = UPCAST(struct Inst, renderer);
@@ -528,7 +536,8 @@ bool opengl_render(LG_Renderer * renderer, LG_RendererRotate rotate, const bool
return true;
}
void drawTorus(float x, float y, float inner, float outer, unsigned int pts)
static void drawTorus(float x, float y, float inner, float outer,
unsigned int pts)
{
glBegin(GL_QUAD_STRIP);
for (unsigned int i = 0; i <= pts; ++i)
@@ -540,7 +549,8 @@ void drawTorus(float x, float y, float inner, float outer, unsigned int pts)
glEnd();
}
void drawTorusArc(float x, float y, float inner, float outer, unsigned int pts, float s, float e)
static void drawTorusArc(float x, float y, float inner, float outer,
unsigned int pts, float s, float e)
{
glBegin(GL_QUAD_STRIP);
for (unsigned int i = 0; i <= pts; ++i)
@@ -728,7 +738,7 @@ static enum ConfigStatus configure(struct Inst * this)
}
// calculate the texture size in bytes
this->texSize = this->format.height * this->format.pitch;
this->texSize = this->format.frameHeight * this->format.pitch;
this->texPos = 0;
g_gl_dynProcs.glGenBuffers(BUFFER_COUNT, this->vboID);
@@ -825,8 +835,8 @@ static enum ConfigStatus configure(struct Inst * this)
GL_TEXTURE_2D,
0,
this->intFormat,
this->format.width,
this->format.height,
this->format.frameWidth,
this->format.frameHeight,
0,
this->vboFormat,
this->dataFormat,
@@ -849,10 +859,11 @@ static enum ConfigStatus configure(struct Inst * this)
glBindTexture(GL_TEXTURE_2D, this->frames[i]);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
glTexCoord2f(1.0f, 0.0f); glVertex2i(this->format.width, 0 );
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , this->format.height);
glTexCoord2f(1.0f, 1.0f); glVertex2i(this->format.width, this->format.height);
glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0);
glTexCoord2f(1.0f, 0.0f); glVertex2i(this->format.frameWidth, 0);
glTexCoord2f(0.0f, 1.0f); glVertex2i(0, this->format.frameHeight);
glTexCoord2f(1.0f, 1.0f);
glVertex2i(this->format.frameWidth, this->format.frameHeight);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glEndList();
@@ -1109,14 +1120,14 @@ static bool drawFrame(struct Inst * this)
const int bpp = this->format.bpp / 8;
glPixelStorei(GL_UNPACK_ALIGNMENT , bpp);
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.width);
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.frameWidth);
this->texPos = 0;
framebuffer_read_fn(
this->frame,
this->format.height,
this->format.width,
this->format.frameHeight,
this->format.frameWidth,
bpp,
this->format.pitch,
opengl_bufferFn,
@@ -1131,8 +1142,8 @@ static bool drawFrame(struct Inst * this)
0,
0,
0,
this->format.width ,
this->format.height,
this->format.frameWidth ,
this->format.frameHeight,
this->vboFormat,
this->dataFormat,
(void*)0
@@ -1140,7 +1151,8 @@ static bool drawFrame(struct Inst * this)
if (check_gl_error("glTexSubImage2D"))
{
DEBUG_ERROR("texWIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
this->texWIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
this->texWIndex, this->format.frameWidth, this->format.frameHeight,
this->vboFormat, this->texSize
);
}
@@ -1148,8 +1160,8 @@ static bool drawFrame(struct Inst * this)
g_gl_dynProcs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
const bool mipmap = this->opt.mipmap && (
(this->format.width > this->destRect.w) ||
(this->format.height > this->destRect.h));
(this->format.frameWidth > this->destRect.w) ||
(this->format.frameHeight > this->destRect.h));
if (mipmap)
{

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -25,7 +25,6 @@
#include "util.h"
#include "clipboard.h"
#include "ll.h"
#include "kb.h"
#include "common/debug.h"
@@ -39,8 +38,6 @@
#include <math.h>
#include <string.h>
#define ALERT_TIMEOUT 2000000
bool app_isRunning(void)
{
return
@@ -65,7 +62,23 @@ bool app_isFormatValid(void)
bool app_isOverlayMode(void)
{
return g_state.overlayInput;
if (g_state.overlayInput)
return true;
bool result = false;
struct Overlay * overlay;
ll_lock(g_state.overlays);
ll_forEachNL(g_state.overlays, item, overlay)
{
if (overlay->ops->needs_overlay && overlay->ops->needs_overlay(overlay))
{
result = true;
break;
}
}
ll_unlock(g_state.overlays);
return result;
}
void app_updateCursorPos(double x, double y)
@@ -74,7 +87,7 @@ void app_updateCursorPos(double x, double y)
g_cursor.pos.y = y;
g_cursor.valid = true;
if (g_state.overlayInput)
if (app_isOverlayMode())
g_state.io->MousePos = (ImVec2) { x, y };
}
@@ -83,7 +96,7 @@ void app_handleFocusEvent(bool focused)
g_state.focused = focused;
// release any imgui buttons/keys if we lost focus
if (!focused && g_state.overlayInput)
if (!focused && app_isOverlayMode())
core_resetOverlayInputState();
if (!core_inputEnabled())
@@ -133,7 +146,7 @@ void app_handleEnterEvent(bool entered)
// stop the user being able to drag windows off the screen and work around
// the mouse button release being missed due to not being in capture mode.
if (g_state.overlayInput)
if (app_isOverlayMode())
{
g_state.io->MouseDown[ImGuiMouseButton_Left ] = false;
g_state.io->MouseDown[ImGuiMouseButton_Right ] = false;
@@ -154,7 +167,7 @@ void app_clipboardRelease(void)
if (!g_params.clipboardToVM)
return;
spice_clipboard_release();
purespice_clipboardRelease();
}
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
@@ -164,15 +177,15 @@ void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
if (count == 0)
{
spice_clipboard_release();
purespice_clipboardRelease();
return;
}
SpiceDataType conv[count];
PSDataType conv[count];
for(int i = 0; i < count; ++i)
conv[i] = cb_lgTypeToSpiceType(types[i]);
spice_clipboard_grab(conv, count);
purespice_clipboardGrab(conv, count);
}
void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
@@ -182,7 +195,7 @@ void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
if (type == LG_CLIPBOARD_DATA_NONE)
{
spice_clipboard_release();
purespice_clipboardRelease();
return;
}
@@ -190,7 +203,7 @@ void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
g_state.cbChunked = size > 0;
g_state.cbXfer = size;
spice_clipboard_data_start(g_state.cbType, size);
purespice_clipboardDataStart(g_state.cbType, size);
}
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
@@ -205,9 +218,9 @@ void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
}
if (!g_state.cbChunked)
spice_clipboard_data_start(g_state.cbType, size);
purespice_clipboardDataStart(g_state.cbType, size);
spice_clipboard_data(g_state.cbType, data, (uint32_t)size);
purespice_clipboardData(g_state.cbType, data, (uint32_t)size);
g_state.cbXfer -= size;
}
@@ -217,13 +230,18 @@ void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
return;
struct CBRequest * cbr = malloc(sizeof(*cbr));
if (!cbr)
{
DEBUG_ERROR("out of memory");
return;
}
cbr->type = g_state.cbType;
cbr->replyFn = replyFn;
cbr->opaque = opaque;
ll_push(g_state.cbRequestList, cbr);
spice_clipboard_request(g_state.cbType);
purespice_clipboardRequest(g_state.cbType);
}
static int mapSpiceToImGuiButton(uint32_t button)
@@ -245,7 +263,7 @@ void app_handleButtonPress(int button)
{
g_cursor.buttons |= (1U << button);
if (g_state.overlayInput)
if (app_isOverlayMode())
{
int igButton = mapSpiceToImGuiButton(button);
if (igButton != -1)
@@ -256,7 +274,7 @@ void app_handleButtonPress(int button)
if (!core_inputEnabled() || !g_cursor.inView)
return;
if (!spice_mouse_press(button))
if (!purespice_mousePress(button))
DEBUG_ERROR("app_handleButtonPress: failed to send message");
}
@@ -264,7 +282,7 @@ void app_handleButtonRelease(int button)
{
g_cursor.buttons &= ~(1U << button);
if (g_state.overlayInput)
if (app_isOverlayMode())
{
int igButton = mapSpiceToImGuiButton(button);
if (igButton != -1)
@@ -275,19 +293,19 @@ void app_handleButtonRelease(int button)
if (!core_inputEnabled())
return;
if (!spice_mouse_release(button))
if (!purespice_mouseRelease(button))
DEBUG_ERROR("app_handleButtonRelease: failed to send message");
}
void app_handleWheelMotion(double motion)
{
if (g_state.overlayInput)
if (app_isOverlayMode())
g_state.io->MouseWheel -= motion;
}
void app_handleKeyPress(int sc)
{
if (!g_state.overlayInput || !g_state.io->WantCaptureKeyboard)
if (!app_isOverlayMode() || !g_state.io->WantCaptureKeyboard)
{
if (sc == g_params.escapeKey && !g_state.escapeActive)
{
@@ -300,11 +318,14 @@ void app_handleKeyPress(int sc)
if (g_state.escapeActive)
{
g_state.escapeAction = sc;
KeybindHandle handle = g_state.bindings[sc];
if (handle)
handle->callback(sc, handle->opaque);
return;
}
}
if (g_state.overlayInput)
if (app_isOverlayMode())
{
if (sc == KEY_ESC)
app_setOverlay(false);
@@ -325,7 +346,7 @@ void app_handleKeyPress(int sc)
if (!ps2)
return;
if (spice_key_down(ps2))
if (purespice_keyDown(ps2))
g_state.keyDown[sc] = true;
else
{
@@ -341,24 +362,16 @@ void app_handleKeyRelease(int sc)
{
if (g_state.escapeAction == -1)
{
if (!g_state.escapeHelp && g_params.useSpiceInput && !g_state.overlayInput)
if (!g_state.escapeHelp && g_params.useSpiceInput &&
!app_isOverlayMode())
core_setGrab(!g_cursor.grab);
}
else
{
KeybindHandle handle = g_state.bindings[sc];
if (handle)
{
handle->callback(sc, handle->opaque);
return;
}
}
if (sc == g_params.escapeKey)
g_state.escapeActive = false;
}
if (g_state.overlayInput)
if (app_isOverlayMode())
{
g_state.io->KeysDown[sc] = false;
return;
@@ -378,7 +391,7 @@ void app_handleKeyRelease(int sc)
if (!ps2)
return;
if (spice_key_up(ps2))
if (purespice_keyUp(ps2))
g_state.keyDown[sc] = false;
else
{
@@ -410,14 +423,14 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock)
(numLock ? 2 /* SPICE_NUM_LOCK_MODIFIER */ : 0) |
(capsLock ? 4 /* SPICE_CAPS_LOCK_MODIFIER */ : 0);
if (!spice_key_modifiers(modifiers))
if (!purespice_keyModifiers(modifiers))
DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message");
}
void app_handleMouseRelative(double normx, double normy,
double rawx, double rawy)
{
if (g_state.overlayInput)
if (app_isOverlayMode())
return;
if (g_cursor.grab)
@@ -439,7 +452,8 @@ void app_handleMouseRelative(double normx, double normy,
void app_handleMouseBasic()
{
/* do not pass mouse events to the guest if we do not have focus */
if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused || g_state.overlayInput)
if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused ||
app_isOverlayMode())
return;
if (!core_inputEnabled())
@@ -468,7 +482,7 @@ void app_handleMouseBasic()
g_cursor.projected.x += x;
g_cursor.projected.y += y;
if (!spice_mouse_motion(x, y))
if (!purespice_mouseMotion(x, y))
DEBUG_ERROR("failed to send mouse motion message");
}
@@ -519,6 +533,10 @@ void app_invalidateWindow(bool full)
{
if (full)
atomic_store(&g_state.invalidateWindow, true);
if (g_state.jitRender && g_state.ds->stopWaitFrame)
g_state.ds->stopWaitFrame();
lgSignalEvent(g_state.frameEvent);
}
@@ -548,15 +566,6 @@ void app_handleRenderEvent(const uint64_t timeUs)
}
}
if (g_state.alertShow)
if (g_state.alertTimeout < timeUs)
{
g_state.alertShow = false;
free(g_state.alertMessage);
g_state.alertMessage = NULL;
invalidate = true;
}
if (invalidate)
app_invalidateWindow(false);
}
@@ -625,19 +634,48 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...)
if (!g_state.lgr || !g_params.showAlerts)
return;
char * buffer;
va_list args;
va_start(args, fmt);
valloc_sprintf(&buffer, fmt, args);
overlayAlert_show(type, fmt, args);
va_end(args);
}
MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...)
{
va_list args;
va_start(args, fmt);
MsgBoxHandle handle = overlayMsg_show(caption, NULL, NULL, fmt, args);
va_end(args);
free(g_state.alertMessage);
g_state.alertMessage = buffer;
g_state.alertTimeout = microtime() + ALERT_TIMEOUT;
g_state.alertType = type;
g_state.alertShow = true;
app_invalidateWindow(false);
core_updateOverlayState();
return handle;
}
MsgBoxHandle app_confirmMsgBox(const char * caption,
MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...)
{
va_list args;
va_start(args, fmt);
MsgBoxHandle handle = overlayMsg_show(caption, callback, opaque, fmt, args);
va_end(args);
core_updateOverlayState();
return handle;
}
void app_msgBoxClose(MsgBoxHandle handle)
{
if (!handle)
return;
overlayMsg_close(handle);
}
void app_showRecord(bool show)
{
overlayRecord_show(show);
}
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
@@ -650,6 +688,12 @@ KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, con
}
KeybindHandle handle = malloc(sizeof(*handle));
if (!handle)
{
DEBUG_ERROR("out of memory");
return NULL;
}
handle->sc = sc;
handle->callback = callback;
handle->opaque = opaque;
@@ -679,9 +723,10 @@ void app_releaseAllKeybinds(void)
}
}
GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max)
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
float min, float max, GraphFormatFn formatFn)
{
return overlayGraph_register(name, buffer, min, max);
return overlayGraph_register(name, buffer, min, max, formatFn);
}
void app_unregisterGraph(GraphHandle handle)
@@ -689,20 +734,22 @@ void app_unregisterGraph(GraphHandle handle)
overlayGraph_unregister(handle);
}
struct Overlay
void app_invalidateGraph(GraphHandle handle)
{
const struct LG_OverlayOps * ops;
const void * params;
void * udata;
int lastRectCount;
struct Rect lastRects[MAX_OVERLAY_RECTS];
};
overlayGraph_invalidate(handle);
}
void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params)
{
ASSERT_LG_OVERLAY_VALID(ops);
struct Overlay * overlay = malloc(sizeof(*overlay));
if (!overlay)
{
DEBUG_ERROR("out of ram");
return;
}
overlay->ops = ops;
overlay->params = params;
overlay->udata = NULL;
@@ -716,15 +763,17 @@ void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params)
void app_initOverlays(void)
{
struct Overlay * overlay;
for (ll_reset(g_state.overlays);
ll_walk(g_state.overlays, (void **)&overlay); )
ll_lock(g_state.overlays);
ll_forEachNL(g_state.overlays, item, overlay)
{
DEBUG_ASSERT(overlay->ops);
if (!overlay->ops->init(&overlay->udata, overlay->params))
{
DEBUG_ERROR("Overlay `%s` failed to initialize", overlay->ops->name);
overlay->ops = NULL;
}
}
ll_unlock(g_state.overlays);
}
static inline void mergeRect(struct Rect * dest, const struct Rect * a, const struct Rect * b)
@@ -769,22 +818,26 @@ static inline LG_DSPointer mapImGuiCursor(ImGuiMouseCursor cursor)
bool app_overlayNeedsRender(void)
{
struct Overlay * overlay;
if (g_state.overlayInput)
if (app_isOverlayMode())
return true;
for (ll_reset(g_state.overlays);
ll_walk(g_state.overlays, (void **)&overlay); )
bool result = false;
struct Overlay * overlay;
ll_lock(g_state.overlays);
ll_forEachNL(g_state.overlays, item, overlay)
{
if (!overlay->ops->needs_render)
continue;
if (overlay->ops->needs_render(overlay->udata, g_state.overlayInput))
return true;
if (overlay->ops->needs_render(overlay->udata, false))
{
result = true;
break;
}
}
ll_unlock(g_state.overlays);
return false;
return result;
}
int app_renderOverlay(struct Rect * rects, int maxRects)
@@ -803,24 +856,34 @@ int app_renderOverlay(struct Rect * rects, int maxRects)
g_state.io->DeltaTime = (now - g_state.lastImGuiFrame) * 1e-9f;
g_state.lastImGuiFrame = now;
render_again:
igNewFrame();
if (g_state.overlayInput)
const bool overlayMode = app_isOverlayMode();
if (overlayMode)
{
totalDamage = true;
ImDrawList_AddRectFilled(igGetBackgroundDrawListNil(), (ImVec2) { 0.0f , 0.0f },
g_state.io->DisplaySize, igGetColorU32Col(ImGuiCol_ModalWindowDimBg, 1.0f), 0, 0);
ImDrawList_AddRectFilled(igGetBackgroundDrawList_Nil(), (ImVec2) { 0.0f , 0.0f },
g_state.io->DisplaySize,
igGetColorU32_Col(ImGuiCol_ModalWindowDimBg, 1.0f),
0, 0);
// bool test;
// igShowDemoWindow(&test);
}
const bool msgModal = overlayMsg_modal();
// render the overlays
for (ll_reset(g_state.overlays);
ll_walk(g_state.overlays, (void **)&overlay); )
ll_lock(g_state.overlays);
ll_forEachNL(g_state.overlays, item, overlay)
{
if (msgModal && overlay->ops != &LGOverlayMsg)
continue;
const int written =
overlay->ops->render(overlay->udata, g_state.overlayInput,
overlay->ops->render(overlay->udata, overlayMode,
buffer, MAX_OVERLAY_RECTS);
for (int i = 0; i < written; ++i)
@@ -856,8 +919,9 @@ int app_renderOverlay(struct Rect * rects, int maxRects)
memcpy(overlay->lastRects, buffer, sizeof(struct Rect) * written);
overlay->lastRectCount = written;
}
ll_unlock(g_state.overlays);
if (g_state.overlayInput)
if (overlayMode)
{
ImGuiMouseCursor cursor = igGetMouseCursor();
if (cursor != g_state.cursorLast)
@@ -869,6 +933,16 @@ int app_renderOverlay(struct Rect * rects, int maxRects)
igRender();
/* imgui requires two passes to calculate the bounding box of auto sized
* windows, this is by design
* ref: https://github.com/ocornut/imgui/issues/2158#issuecomment-434223618
*/
if (g_state.renderImGuiTwice)
{
g_state.renderImGuiTwice = false;
goto render_again;
}
return totalDamage ? -1 : totalRects;
}
@@ -884,31 +958,11 @@ void app_freeOverlays(void)
void app_setOverlay(bool enable)
{
static bool wasGrabbed = false;
if (g_state.overlayInput == enable)
return;
g_state.overlayInput = enable;
g_state.cursorLast = -2;
if (g_state.overlayInput)
{
wasGrabbed = g_cursor.grab;
g_state.io->ConfigFlags &= ~ImGuiConfigFlags_NoMouse;
g_state.io->MousePos = (ImVec2) { g_cursor.pos.x, g_cursor.pos.y };
core_setGrabQuiet(false);
core_setCursorInView(false);
}
else
{
g_state.io->ConfigFlags |= ImGuiConfigFlags_NoMouse;
core_resetOverlayInputState();
core_setGrabQuiet(wasGrabbed);
app_invalidateWindow(false);
}
core_updateOverlayState();
}
void app_overlayConfigRegister(const char * title,
@@ -922,3 +976,35 @@ void app_overlayConfigRegisterTab(const char * title,
{
overlayConfig_registerTab(title, callback, udata);
}
void app_invalidateOverlay(bool renderTwice)
{
if (renderTwice)
g_state.renderImGuiTwice = true;
app_invalidateWindow(false);
}
bool app_guestIsLinux(void)
{
return g_state.guestOS == KVMFR_OS_LINUX;
}
bool app_guestIsWindows(void)
{
return g_state.guestOS == KVMFR_OS_WINDOWS;
}
bool app_guestIsOSX(void)
{
return g_state.guestOS == KVMFR_OS_OSX;
}
bool app_guestIsBSD(void)
{
return g_state.guestOS == KVMFR_OS_BSD;
}
bool app_guestIsOther(void)
{
return g_state.guestOS == KVMFR_OS_OTHER;
}

873
client/src/audio.c Normal file
View File

@@ -0,0 +1,873 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#if ENABLE_AUDIO
#include "audio.h"
#include "main.h"
#include "common/array.h"
#include "common/util.h"
#include "common/ringbuffer.h"
#include "dynamic/audiodev.h"
#include <float.h>
#include <math.h>
#include <samplerate.h>
#include <stdalign.h>
#include <string.h>
typedef enum
{
STREAM_STATE_STOP,
STREAM_STATE_SETUP_SPICE,
STREAM_STATE_SETUP_DEVICE,
STREAM_STATE_RUN,
STREAM_STATE_KEEP_ALIVE
}
StreamState;
#define STREAM_ACTIVE(state) \
(state == STREAM_STATE_RUN || state == STREAM_STATE_KEEP_ALIVE)
typedef struct
{
int periodFrames;
double periodSec;
int64_t nextTime;
int64_t nextPosition;
double b;
double c;
}
PlaybackDeviceData;
typedef struct
{
float * framesIn;
float * framesOut;
int framesOutSize;
int periodFrames;
double periodSec;
int64_t nextTime;
int64_t nextPosition;
double b;
double c;
int devPeriodFrames;
int64_t devLastTime;
int64_t devNextTime;
int64_t devLastPosition;
int64_t devNextPosition;
double offsetError;
double offsetErrorIntegral;
double ratioIntegral;
SRC_STATE * src;
}
PlaybackSpiceData;
typedef struct
{
struct LG_AudioDevOps * audioDev;
struct
{
StreamState state;
int volumeChannels;
uint16_t volume[8];
bool mute;
int channels;
int sampleRate;
int stride;
int deviceMaxPeriodFrames;
int deviceStartFrames;
int targetStartFrames;
RingBuffer buffer;
RingBuffer deviceTiming;
RingBuffer timings;
GraphHandle graph;
/* These two structs contain data specifically for use in the device and
* Spice data threads respectively. Keep them on separate cache lines to
* avoid false sharing. */
alignas(64) PlaybackDeviceData deviceData;
alignas(64) PlaybackSpiceData spiceData;
}
playback;
struct
{
bool started;
int volumeChannels;
uint16_t volume[8];
bool mute;
int stride;
uint32_t time;
MsgBoxHandle confirmHandle;
int confirmChannels;
int confirmSampleRate;
PSAudioFormat confirmFormat;
}
record;
}
AudioState;
static AudioState audio = { 0 };
typedef struct
{
int periodFrames;
int64_t nextTime;
int64_t nextPosition;
}
PlaybackDeviceTick;
static void playbackStop(void);
void audio_init(void)
{
// search for the best audiodev to use
for(int i = 0; i < LG_AUDIODEV_COUNT; ++i)
if (LG_AudioDevs[i]->init())
{
audio.audioDev = LG_AudioDevs[i];
DEBUG_INFO("Using AudioDev: %s", audio.audioDev->name);
return;
}
DEBUG_WARN("Failed to initialize an audio backend");
}
void audio_free(void)
{
if (!audio.audioDev)
return;
// immediate stop of the stream, do not wait for drain
playbackStop();
audio_recordStop();
audio.audioDev->free();
audio.audioDev = NULL;
}
bool audio_supportsPlayback(void)
{
return audio.audioDev && audio.audioDev->playback.start;
}
static const char * audioGraphFormatFn(const char * name,
float min, float max, float avg, float freq, float last)
{
static char title[64];
snprintf(title, sizeof(title),
"%s: min:%4.2f max:%4.2f avg:%4.2f now:%4.2f",
name, min, max, avg, last);
return title;
}
static void playbackStop(void)
{
if (audio.playback.state == STREAM_STATE_STOP)
return;
audio.playback.state = STREAM_STATE_STOP;
audio.audioDev->playback.stop();
ringbuffer_free(&audio.playback.buffer);
ringbuffer_free(&audio.playback.deviceTiming);
audio.playback.spiceData.src = src_delete(audio.playback.spiceData.src);
if (audio.playback.spiceData.framesIn)
{
free(audio.playback.spiceData.framesIn);
free(audio.playback.spiceData.framesOut);
audio.playback.spiceData.framesIn = NULL;
audio.playback.spiceData.framesOut = NULL;
}
if (audio.playback.timings)
{
app_unregisterGraph(audio.playback.graph);
ringbuffer_free(&audio.playback.timings);
}
}
static int playbackPullFrames(uint8_t * dst, int frames)
{
DEBUG_ASSERT(frames >= 0);
if (frames == 0)
return frames;
PlaybackDeviceData * data = &audio.playback.deviceData;
int64_t now = nanotime();
if (audio.playback.buffer)
{
if (audio.playback.state == STREAM_STATE_SETUP_DEVICE)
{
/* If necessary, slew backwards to play silence until we reach the target
* startup latency. This avoids underrunning the buffer if the audio
* device starts earlier than required. */
int offset = ringbuffer_getCount(audio.playback.buffer) -
audio.playback.targetStartFrames;
if (offset < 0)
{
data->nextPosition += offset;
ringbuffer_consume(audio.playback.buffer, NULL, offset);
}
audio.playback.state = STREAM_STATE_RUN;
}
// Measure the device clock and post to the Spice thread
if (frames != data->periodFrames)
{
double newPeriodSec = (double) frames / audio.playback.sampleRate;
bool init = data->periodFrames == 0;
if (init)
data->nextTime = now + llrint(newPeriodSec * 1.0e9);
else
/* Due to the double-buffered nature of audio playback, we are filling
* in the next buffer while the device is playing the previous buffer.
* This results in slightly unintuitive behaviour when the period size
* changes. The device will request enough samples for the new period
* size, but won't call us again until the previous buffer at the old
* size has finished playing. So, to avoid a blip in the timing
* calculations, we must set the estimated next wakeup time based upon
* the previous period size, not the new one. */
data->nextTime += llrint(data->periodSec * 1.0e9);
data->periodFrames = frames;
data->periodSec = newPeriodSec;
data->nextPosition += frames;
double bandwidth = 0.05;
double omega = 2.0 * M_PI * bandwidth * data->periodSec;
data->b = M_SQRT2 * omega;
data->c = omega * omega;
}
else
{
double error = (now - data->nextTime) * 1.0e-9;
if (fabs(error) >= 0.2)
{
// Clock error is too high; slew the read pointer and reset the timing
// parameters to avoid getting too far out of sync
int slewFrames = round(error * audio.playback.sampleRate);
ringbuffer_consume(audio.playback.buffer, NULL, slewFrames);
data->periodSec = (double) frames / audio.playback.sampleRate;
data->nextTime = now + llrint(data->periodSec * 1.0e9);
data->nextPosition += slewFrames + frames;
}
else
{
data->nextTime +=
llrint((data->b * error + data->periodSec) * 1.0e9);
data->periodSec += data->c * error;
data->nextPosition += frames;
}
}
PlaybackDeviceTick tick =
{
.periodFrames = data->periodFrames,
.nextTime = data->nextTime,
.nextPosition = data->nextPosition
};
ringbuffer_push(audio.playback.deviceTiming, &tick);
ringbuffer_consume(audio.playback.buffer, dst, frames);
}
else
frames = 0;
// Close the stream if nothing has played for a while
if (audio.playback.state == STREAM_STATE_KEEP_ALIVE)
{
int stopTimeSec = 30;
int stopTimeFrames = stopTimeSec * audio.playback.sampleRate;
if (ringbuffer_getCount(audio.playback.buffer) <= -stopTimeFrames)
playbackStop();
}
return frames;
}
void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format,
uint32_t time)
{
if (!audio.audioDev)
return;
static int lastChannels = 0;
static int lastSampleRate = 0;
if (audio.playback.state == STREAM_STATE_KEEP_ALIVE &&
channels == lastChannels && sampleRate == lastSampleRate)
return;
if (audio.playback.state != STREAM_STATE_STOP)
playbackStop();
int srcError;
audio.playback.spiceData.src = src_new(SRC_SINC_FASTEST, channels, &srcError);
if (!audio.playback.spiceData.src)
{
DEBUG_ERROR("Failed to create resampler: %s", src_strerror(srcError));
return;
}
const int bufferFrames = sampleRate;
audio.playback.buffer = ringbuffer_newUnbounded(bufferFrames,
channels * sizeof(float));
audio.playback.deviceTiming = ringbuffer_new(16, sizeof(PlaybackDeviceTick));
lastChannels = channels;
lastSampleRate = sampleRate;
audio.playback.channels = channels;
audio.playback.sampleRate = sampleRate;
audio.playback.stride = channels * sizeof(float);
audio.playback.state = STREAM_STATE_SETUP_SPICE;
audio.playback.deviceData.periodFrames = 0;
audio.playback.deviceData.nextPosition = 0;
audio.playback.spiceData.periodFrames = 0;
audio.playback.spiceData.nextPosition = 0;
audio.playback.spiceData.devPeriodFrames = 0;
audio.playback.spiceData.devLastTime = INT64_MIN;
audio.playback.spiceData.devNextTime = INT64_MIN;
audio.playback.spiceData.offsetError = 0.0;
audio.playback.spiceData.offsetErrorIntegral = 0.0;
audio.playback.spiceData.ratioIntegral = 0.0;
int requestedPeriodFrames = max(g_params.audioPeriodSize, 1);
audio.playback.deviceMaxPeriodFrames = 0;
audio.playback.deviceStartFrames = 0;
audio.audioDev->playback.setup(channels, sampleRate, requestedPeriodFrames,
&audio.playback.deviceMaxPeriodFrames, &audio.playback.deviceStartFrames,
playbackPullFrames);
DEBUG_ASSERT(audio.playback.deviceMaxPeriodFrames > 0);
// if a volume level was stored, set it before we return
if (audio.playback.volumeChannels)
audio.audioDev->playback.volume(
audio.playback.volumeChannels,
audio.playback.volume);
// set the inital mute state
if (audio.audioDev->playback.mute)
audio.audioDev->playback.mute(audio.playback.mute);
// if the audio dev can report it's latency setup a timing graph
audio.playback.timings = ringbuffer_new(1200, sizeof(float));
audio.playback.graph = app_registerGraph("PLAYBACK",
audio.playback.timings, 0.0f, 200.0f, audioGraphFormatFn);
}
void audio_playbackStop(void)
{
if (!audio.audioDev)
return;
switch (audio.playback.state)
{
case STREAM_STATE_RUN:
{
// Keep the audio device open for a while to reduce startup latency if
// playback starts again
audio.playback.state = STREAM_STATE_KEEP_ALIVE;
// Reset the resampler so it is safe to use for the next playback
int error = src_reset(audio.playback.spiceData.src);
if (error)
{
DEBUG_ERROR("Failed to reset resampler: %s", src_strerror(error));
playbackStop();
}
break;
}
case STREAM_STATE_SETUP_SPICE:
case STREAM_STATE_SETUP_DEVICE:
// Playback hasn't actually started yet so just clean up
playbackStop();
break;
case STREAM_STATE_KEEP_ALIVE:
case STREAM_STATE_STOP:
// Nothing to do
break;
}
}
void audio_playbackVolume(int channels, const uint16_t volume[])
{
if (!audio.audioDev || !audio.audioDev->playback.volume)
return;
// store the values so we can restore the state if the stream is restarted
channels = min(ARRAY_LENGTH(audio.playback.volume), channels);
memcpy(audio.playback.volume, volume, sizeof(uint16_t) * channels);
audio.playback.volumeChannels = channels;
if (!STREAM_ACTIVE(audio.playback.state))
return;
audio.audioDev->playback.volume(channels, volume);
}
void audio_playbackMute(bool mute)
{
if (!audio.audioDev || !audio.audioDev->playback.mute)
return;
// store the value so we can restore it if the stream is restarted
audio.playback.mute = mute;
if (!STREAM_ACTIVE(audio.playback.state))
return;
audio.audioDev->playback.mute(mute);
}
static double computeDevicePosition(int64_t curTime)
{
// Interpolate to calculate the current device position
PlaybackSpiceData * spiceData = &audio.playback.spiceData;
return spiceData->devLastPosition +
(spiceData->devNextPosition - spiceData->devLastPosition) *
((double) (curTime - spiceData->devLastTime) /
(spiceData->devNextTime - spiceData->devLastTime));
}
void audio_playbackData(uint8_t * data, size_t size)
{
if (audio.playback.state == STREAM_STATE_STOP || !audio.audioDev || size == 0)
return;
PlaybackSpiceData * spiceData = &audio.playback.spiceData;
int64_t now = nanotime();
// Convert from s16 to f32 samples
int spiceStride = audio.playback.channels * sizeof(int16_t);
int frames = size / spiceStride;
bool periodChanged = frames != spiceData->periodFrames;
bool init = spiceData->periodFrames == 0;
if (periodChanged)
{
if (spiceData->framesIn)
{
free(spiceData->framesIn);
free(spiceData->framesOut);
}
spiceData->periodFrames = frames;
spiceData->framesIn = malloc(frames * audio.playback.stride);
if (!spiceData->framesIn)
{
DEBUG_ERROR("Failed to malloc framesIn");
playbackStop();
return;
}
spiceData->framesOutSize = round(frames * 1.1);
spiceData->framesOut =
malloc(spiceData->framesOutSize * audio.playback.stride);
if (!spiceData->framesOut)
{
DEBUG_ERROR("Failed to malloc framesOut");
playbackStop();
return;
}
}
src_short_to_float_array((int16_t *) data, spiceData->framesIn,
frames * audio.playback.channels);
// Receive timing information from the audio device thread
PlaybackDeviceTick deviceTick;
while (ringbuffer_consume(audio.playback.deviceTiming, &deviceTick, 1))
{
spiceData->devPeriodFrames = deviceTick.periodFrames;
spiceData->devLastTime = spiceData->devNextTime;
spiceData->devLastPosition = spiceData->devNextPosition;
spiceData->devNextTime = deviceTick.nextTime;
spiceData->devNextPosition = deviceTick.nextPosition;
}
/* Determine the target latency. This is made up of the maximum audio device
* period (plus a little extra to absorb timing jitter) and a configurable
* additional buffer period. The default is set high enough to absorb typical
* timing jitter from qemu. */
int configLatencyMs = max(g_params.audioBufferLatency, 0);
double targetLatencyFrames =
audio.playback.deviceMaxPeriodFrames * 1.1 +
configLatencyMs * audio.playback.sampleRate / 1000.0;
/* If the device is currently at a lower period size than its maximum (which
* can happen, for example, if another application has requested a lower
* latency) then we need to take that into account in our target latency.
*
* The reason to do this is not necessarily obvious, since we already set the
* target latency based upon the maximum period size. The problem stems from
* the way the device changes the period size. When the period size is
* reduced, there will be a transitional period where `playbackPullFrames` is
* invoked with the new smaller period size, but the time until the next
* invocation is based upon the previous size. This happens because the device
* is preparing the next small buffer while still playing back the previous
* large buffer. The result of this is that we end up with a surplus of data
* in the ring buffer. The overall latency is unchanged, but the balance has
* shifted: there is more data in our ring buffer and less in the device
* buffer.
*
* Unaccounted for, this would be detected as an offset error and playback
* would be sped up to bring things back in line. In isolation, this is not
* inherently problematic, and may even be desirable because it would reduce
* the overall latency. The real problem occurs when the period size goes back
* up.
*
* When the period size increases, the exact opposite happens. The device will
* suddenly request data at the new period size, but the timing interval will
* be based upon the previous period size during the transition. If there is
* not enough data to satisfy this then playback will start severely
* underrunning until the timing loop can correct for the error.
*
* To counteract this issue, if the current period size is smaller than the
* maximum period size then we increase the target latency by the difference.
* This keeps the offset error stable and ensures we have enough data in the
* buffer to absorb rate increases. */
if (spiceData->devPeriodFrames != 0 &&
spiceData->devPeriodFrames < audio.playback.deviceMaxPeriodFrames)
targetLatencyFrames +=
audio.playback.deviceMaxPeriodFrames - spiceData->devPeriodFrames;
// Measure the Spice audio clock
int64_t curTime;
int64_t curPosition;
double devPosition = DBL_MIN;
if (periodChanged)
{
if (init)
spiceData->nextTime = now;
curTime = spiceData->nextTime;
curPosition = spiceData->nextPosition;
spiceData->periodSec = (double) frames / audio.playback.sampleRate;
spiceData->nextTime += llrint(spiceData->periodSec * 1.0e9);
double bandwidth = 0.05;
double omega = 2.0 * M_PI * bandwidth * spiceData->periodSec;
spiceData->b = M_SQRT2 * omega;
spiceData->c = omega * omega;
}
else
{
double error = (now - spiceData->nextTime) * 1.0e-9;
if (fabs(error) >= 0.2 || audio.playback.state == STREAM_STATE_KEEP_ALIVE)
{
/* Clock error is too high or we are starting a new playback; slew the
* write pointer and reset the timing parameters to get back in sync. If
* we know the device playback position then we can slew directly to the
* target latency, otherwise just slew based upon the error amount */
int slewFrames;
if (spiceData->devLastTime != INT64_MIN)
{
devPosition = computeDevicePosition(now);
double targetPosition = devPosition + targetLatencyFrames;
// If starting a new playback we need to allow a little extra time for
// the resampler startup latency
if (audio.playback.state == STREAM_STATE_KEEP_ALIVE)
{
int resamplerLatencyFrames = 20;
targetPosition += resamplerLatencyFrames;
}
slewFrames = round(targetPosition - spiceData->nextPosition);
}
else
slewFrames = round(error * audio.playback.sampleRate);
ringbuffer_append(audio.playback.buffer, NULL, slewFrames);
curTime = now;
curPosition = spiceData->nextPosition + slewFrames;
spiceData->periodSec = (double) frames / audio.playback.sampleRate;
spiceData->nextTime = now + llrint(spiceData->periodSec * 1.0e9);
spiceData->nextPosition = curPosition;
spiceData->offsetError = 0.0;
spiceData->offsetErrorIntegral = 0.0;
spiceData->ratioIntegral = 0.0;
audio.playback.state = STREAM_STATE_RUN;
}
else
{
curTime = spiceData->nextTime;
curPosition = spiceData->nextPosition;
spiceData->nextTime +=
llrint((spiceData->b * error + spiceData->periodSec) * 1.0e9);
spiceData->periodSec += spiceData->c * error;
}
}
/* Measure the offset between the Spice position and the device position,
* and how far away this is from the target latency. We use this to adjust
* the playback speed to bring them back in line. This value can change
* quite rapidly, particularly at the start of playback, so filter it to
* avoid sudden pitch shifts which will be noticeable to the user. */
double actualOffset = 0.0;
double offsetError = spiceData->offsetError;
if (spiceData->devLastTime != INT64_MIN)
{
if (devPosition == DBL_MIN)
devPosition = computeDevicePosition(curTime);
actualOffset = curPosition - devPosition;
double actualOffsetError = -(actualOffset - targetLatencyFrames);
double error = actualOffsetError - offsetError;
spiceData->offsetError += spiceData->b * error +
spiceData->offsetErrorIntegral;
spiceData->offsetErrorIntegral += spiceData->c * error;
}
// Resample the audio to adjust the playback speed. Use a PI controller to
// adjust the resampling ratio based upon the measured offset
double kp = 0.5e-6;
double ki = 1.0e-16;
spiceData->ratioIntegral += offsetError * spiceData->periodSec;
double piOutput = kp * offsetError + ki * spiceData->ratioIntegral;
double ratio = 1.0 + piOutput;
int consumed = 0;
while (consumed < frames)
{
SRC_DATA srcData =
{
.data_in = spiceData->framesIn +
consumed * audio.playback.channels,
.data_out = spiceData->framesOut,
.input_frames = frames - consumed,
.output_frames = spiceData->framesOutSize,
.input_frames_used = 0,
.output_frames_gen = 0,
.end_of_input = 0,
.src_ratio = ratio
};
int error = src_process(spiceData->src, &srcData);
if (error)
{
DEBUG_ERROR("Resampling failed: %s", src_strerror(error));
return;
}
ringbuffer_append(audio.playback.buffer, spiceData->framesOut,
srcData.output_frames_gen);
consumed += srcData.input_frames_used;
spiceData->nextPosition += srcData.output_frames_gen;
}
if (audio.playback.state == STREAM_STATE_SETUP_SPICE)
{
/* Latency corrections at startup can be quite significant due to poor
* packet pacing from Spice, so require at least two full Spice periods'
* worth of data in addition to the startup delay requested by the device
* before starting playback to minimise the chances of underrunning. */
int startFrames =
spiceData->periodFrames * 2 + audio.playback.deviceStartFrames;
audio.playback.targetStartFrames = startFrames;
/* The actual time between opening the device and the device starting to
* pull data can range anywhere between nearly instant and hundreds of
* milliseconds. To minimise startup latency, we open the device
* immediately. If the device starts earlier than required (as per the
* `startFrames` value we just calculated), then a period of silence will be
* inserted at the beginning of playback to avoid underrunning. If it starts
* later, then we just accept the higher latency and let the adaptive
* resampling deal with it. */
audio.playback.state = STREAM_STATE_SETUP_DEVICE;
audio.audioDev->playback.start();
}
double latencyFrames = actualOffset;
if (audio.audioDev->playback.latency)
latencyFrames += audio.audioDev->playback.latency();
const float latency = latencyFrames * 1000.0 / audio.playback.sampleRate;
ringbuffer_push(audio.playback.timings, &latency);
app_invalidateGraph(audio.playback.graph);
}
bool audio_supportsRecord(void)
{
return audio.audioDev && audio.audioDev->record.start;
}
static void recordPushFrames(uint8_t * data, int frames)
{
purespice_writeAudio(data, frames * audio.record.stride, 0);
}
static void realRecordStart(int channels, int sampleRate, PSAudioFormat format)
{
audio.record.started = true;
audio.record.stride = channels * sizeof(uint16_t);
audio.audioDev->record.start(channels, sampleRate, recordPushFrames);
// if a volume level was stored, set it before we return
if (audio.record.volumeChannels)
audio.audioDev->record.volume(
audio.playback.volumeChannels,
audio.playback.volume);
// set the inital mute state
if (audio.audioDev->record.mute)
audio.audioDev->record.mute(audio.playback.mute);
if (g_params.micShowIndicator)
app_showRecord(true);
}
struct AudioFormat
{
int channels;
int sampleRate;
PSAudioFormat format;
};
static void recordConfirm(bool yes, void * opaque)
{
if (yes)
{
DEBUG_INFO("Microphone access granted");
realRecordStart(
audio.record.confirmChannels,
audio.record.confirmSampleRate,
audio.record.confirmFormat
);
}
else
DEBUG_INFO("Microphone access denied");
audio.record.confirmHandle = NULL;
}
void audio_recordStart(int channels, int sampleRate, PSAudioFormat format)
{
if (!audio.audioDev)
return;
static int lastChannels = 0;
static int lastSampleRate = 0;
if (audio.record.started)
{
if (channels != lastChannels || sampleRate != lastSampleRate)
audio.audioDev->record.stop();
else
return;
}
lastChannels = channels;
lastSampleRate = sampleRate;
if (audio.record.started)
realRecordStart(channels, sampleRate, format);
else if (g_params.micAlwaysAllow)
{
DEBUG_INFO("Microphone access granted by default");
realRecordStart(channels, sampleRate, format);
}
else
{
if (audio.record.confirmHandle)
app_msgBoxClose(audio.record.confirmHandle);
audio.record.confirmChannels = channels;
audio.record.confirmSampleRate = sampleRate;
audio.record.confirmFormat = format;
audio.record.confirmHandle = app_confirmMsgBox(
"Microphone", recordConfirm, NULL,
"An application just opened the microphone!\n"
"Do you want it to access your microphone?");
}
}
void audio_recordStop(void)
{
if (!audio.audioDev || !audio.record.started)
return;
DEBUG_INFO("Microphone recording stopped");
audio.audioDev->record.stop();
audio.record.started = false;
if (g_params.micShowIndicator)
app_showRecord(false);
}
void audio_recordVolume(int channels, const uint16_t volume[])
{
if (!audio.audioDev || !audio.audioDev->record.volume)
return;
// store the values so we can restore the state if the stream is restarted
channels = min(ARRAY_LENGTH(audio.record.volume), channels);
memcpy(audio.record.volume, volume, sizeof(uint16_t) * channels);
audio.record.volumeChannels = channels;
if (!audio.record.started)
return;
audio.audioDev->record.volume(channels, volume);
}
void audio_recordMute(bool mute)
{
if (!audio.audioDev || !audio.audioDev->record.mute)
return;
// store the value so we can restore it if the stream is restarted
audio.record.mute = mute;
if (!audio.record.started)
return;
audio.audioDev->record.mute(mute);
}
#endif

48
client/src/audio.h Normal file
View File

@@ -0,0 +1,48 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#if ENABLE_AUDIO
#include <stdbool.h>
#include <purespice.h>
void audio_init(void);
void audio_free(void);
bool audio_supportsPlayback(void);
void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format,
uint32_t time);
void audio_playbackStop(void);
void audio_playbackVolume(int channels, const uint16_t volume[]);
void audio_playbackMute(bool mute);
void audio_playbackData(uint8_t * data, size_t size);
bool audio_supportsRecord(void);
void audio_recordStart(int channels, int sampleRate, PSAudioFormat format);
void audio_recordStop(void);
void audio_recordVolume(int channels, const uint16_t volume[]);
void audio_recordMute(bool mute);
#else
static inline void audio_init(void) {}
static inline void audio_free(void) {}
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -21,11 +21,10 @@
#include "clipboard.h"
#include "main.h"
#include "ll.h"
#include "common/debug.h"
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type)
LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type)
{
switch(type)
{
@@ -40,7 +39,7 @@ LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type)
}
}
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
{
switch(type)
{
@@ -55,7 +54,7 @@ SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
}
}
void cb_spiceNotice(const SpiceDataType type)
void cb_spiceNotice(const PSDataType type)
{
if (!g_params.clipboardToLocal)
return;
@@ -67,7 +66,7 @@ void cb_spiceNotice(const SpiceDataType type)
g_state.ds->cbNotice(cb_spiceTypeToLGType(type));
}
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size)
void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size)
{
if (!g_params.clipboardToLocal)
return;
@@ -107,7 +106,7 @@ void cb_spiceRelease(void)
g_state.ds->cbRelease();
}
void cb_spiceRequest(const SpiceDataType type)
void cb_spiceRequest(const PSDataType type)
{
if (!g_params.clipboardToVM)
return;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -18,13 +18,13 @@
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "spice/spice.h"
#include <purespice.h>
#include "interface/displayserver.h"
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type);
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type);
PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
void cb_spiceNotice(const SpiceDataType type);
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size);
void cb_spiceNotice(const PSDataType type);
void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size);
void cb_spiceRelease(void);
void cb_spiceRequest(const SpiceDataType type);
void cb_spiceRequest(const PSDataType type);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -46,7 +46,7 @@ static bool optScancodeValidate(struct Option * opt, const char ** error);
static char * optScancodeToString(struct Option * opt);
static bool optRotateValidate (struct Option * opt, const char ** error);
static void doLicense();
static void doLicense(void);
static struct Option options[] =
{
@@ -163,6 +163,13 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "intUpscale",
.description = "Allow only integer upscaling",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "win",
.name = "shrinkOnUpscale",
@@ -428,6 +435,13 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "audio",
.description = "Enable SPICE audio support",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "scaleCursor",
@@ -457,6 +471,36 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
// audio options
{
.module = "audio",
.name = "periodSize",
.description = "Requested audio device period size in samples",
.type = OPTION_TYPE_INT,
.value.x_int = 2048
},
{
.module = "audio",
.name = "bufferLatency",
.description = "Additional buffer latency in milliseconds",
.type = OPTION_TYPE_INT,
.value.x_int = 13
},
{
.module = "audio",
.name = "micAlwaysAllow",
.description = "Always allow guest attempts to access the microphone",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{
.module = "audio",
.name = "micShowIndicator",
.description = "Display microphone usage indicator",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{0}
};
@@ -482,22 +526,38 @@ bool config_load(int argc, char * argv[])
// load config from user's home directory
struct passwd * pw = getpwuid(getuid());
char * localFile;
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
if (stat(localFile, &st) >= 0 && S_ISREG(st.st_mode))
if (!pw)
DEBUG_WARN("getpwuid failed, unable to load user configuration");
else
{
DEBUG_INFO("Loading config from: %s", localFile);
if (!option_load(localFile))
char * localFile;
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
if (!localFile)
{
free(localFile);
DEBUG_ERROR("out of memory");
return false;
}
if (stat(localFile, &st) >= 0 && S_ISREG(st.st_mode))
{
DEBUG_INFO("Loading config from: %s", localFile);
if (!option_load(localFile))
{
free(localFile);
return false;
}
}
free(localFile);
}
free(localFile);
// load config from XDG_CONFIG_HOME
char * xdgFile;
alloc_sprintf(&xdgFile, "%s/client.ini", lgConfigDir());
if (!xdgFile)
{
DEBUG_ERROR("out of memory");
return false;
}
if (xdgFile && stat(xdgFile, &st) >= 0 && S_ISREG(st.st_mode))
{
@@ -550,6 +610,7 @@ bool config_load(int argc, char * argv[])
g_params.keepAspect = option_get_bool ("win", "keepAspect" );
g_params.forceAspect = option_get_bool ("win", "forceAspect" );
g_params.dontUpscale = option_get_bool ("win", "dontUpscale" );
g_params.intUpscale = option_get_bool ("win", "intUpscale" );
g_params.shrinkOnUpscale = option_get_bool ("win", "shrinkOnUpscale");
g_params.borderless = option_get_bool ("win", "borderless" );
g_params.fullscreen = option_get_bool ("win", "fullScreen" );
@@ -609,6 +670,7 @@ bool config_load(int argc, char * argv[])
g_params.useSpiceInput = option_get_bool("spice", "input" );
g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
g_params.useSpiceAudio = option_get_bool("spice", "audio" );
if (g_params.useSpiceClipboard)
{
@@ -628,6 +690,11 @@ bool config_load(int argc, char * argv[])
g_params.showCursorDot = option_get_bool("spice", "showCursorDot");
}
g_params.audioPeriodSize = option_get_int("audio", "periodSize");
g_params.audioBufferLatency = option_get_int("audio", "bufferLatency");
g_params.micAlwaysAllow = option_get_bool("audio", "micAlwaysAllow");
g_params.micShowIndicator = option_get_bool("audio", "micShowIndicator");
return true;
}
@@ -642,7 +709,7 @@ static void doLicense(void)
// BEGIN LICENSE BLOCK
"\n"
"Looking Glass\n"
"Copyright © 2017-2021 The Looking Glass Authors\n"
"Copyright © 2017-2022 The Looking Glass Authors\n"
"https://looking-glass.io\n"
"\n"
"This program is free software; you can redistribute it and/or modify it under\n"
@@ -687,6 +754,8 @@ static bool optRendererParse(struct Option * opt, const char * str)
static StringList optRendererValues(struct Option * opt)
{
StringList sl = stringlist_new(false);
if (!sl)
return NULL;
// this typecast is safe as the stringlist doesn't own the values
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
@@ -729,6 +798,9 @@ static bool optPosParse(struct Option * opt, const char * str)
static StringList optPosValues(struct Option * opt)
{
StringList sl = stringlist_new(false);
if (!sl)
return NULL;
stringlist_push(sl, "center");
stringlist_push(sl, "<left>x<top>, e.g. 100x100");
return sl;
@@ -741,6 +813,11 @@ static char * optPosToString(struct Option * opt)
int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y);
char * str = malloc(len + 1);
if (!str)
{
DEBUG_ERROR("out of memory");
return NULL;
}
sprintf(str, "%dx%d", g_params.x, g_params.y);
return str;
@@ -764,6 +841,9 @@ static bool optSizeParse(struct Option * opt, const char * str)
static StringList optSizeValues(struct Option * opt)
{
StringList sl = stringlist_new(false);
if (!sl)
return NULL;
stringlist_push(sl, "<left>x<top>, e.g. 100x100");
return sl;
}
@@ -772,6 +852,11 @@ static char * optSizeToString(struct Option * opt)
{
int len = snprintf(NULL, 0, "%ux%u", g_params.w, g_params.h);
char * str = malloc(len + 1);
if (!str)
{
DEBUG_ERROR("out of memory");
return NULL;
}
sprintf(str, "%ux%u", g_params.w, g_params.h);
return str;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -20,6 +20,6 @@
#include <stdbool.h>
void config_init();
void config_init(void);
bool config_load(int argc, char * argv[]);
void config_free();
void config_free(void);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -31,39 +31,45 @@
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
static bool isInView(void)
{
return
g_cursor.pos.x >= g_state.dstRect.x &&
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
g_cursor.pos.y >= g_state.dstRect.y &&
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
}
bool core_inputEnabled(void)
{
return g_params.useSpiceInput && !g_state.ignoreInput &&
((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly);
}
void core_setCursorInView(bool enable)
void core_invalidatePointer(bool detectInView)
{
// if the state has not changed, don't do anything else
if (g_cursor.inView == enable)
return;
if (enable && !g_state.focused)
return;
// do not allow the view to become active if any mouse buttons are being held,
// this fixes issues with meta window resizing.
if (enable && g_cursor.buttons)
return;
g_cursor.inView = enable;
g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
? true : enable;
g_cursor.redraw = true;
/* if the display server does not support warp, then we can not operate in
* always relative mode and we should not grab the pointer */
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
g_cursor.warpState = enable ? WARP_STATE_ON : WARP_STATE_OFF;
if (detectInView)
{
bool inView = isInView();
// do not allow the view to become active if any mouse buttons are being held,
// this fixes issues with meta window resizing.
if (inView && g_cursor.buttons)
return;
if (enable)
g_cursor.inView = inView;
}
g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
? true : g_cursor.inView;
g_cursor.redraw = true;
g_cursor.warpState = g_cursor.inView ? WARP_STATE_ON : WARP_STATE_OFF;
if (g_cursor.inView)
{
if (g_params.hideMouse)
g_state.ds->setPointer(LG_POINTER_NONE);
@@ -88,6 +94,19 @@ void core_setCursorInView(bool enable)
g_cursor.warpState = WARP_STATE_ON;
}
void core_setCursorInView(bool enable)
{
// if the state has not changed, don't do anything else
if (g_cursor.inView == enable)
return;
if (enable && !g_state.focused)
return;
g_cursor.inView = enable;
core_invalidatePointer(false);
}
void core_setGrab(bool enable)
{
core_setGrabQuiet(enable);
@@ -147,7 +166,7 @@ void core_setGrabQuiet(bool enable)
bool core_warpPointer(int x, int y, bool exiting)
{
if ((!g_cursor.inWindow && !exiting) ||
g_state.overlayInput ||
app_isOverlayMode() ||
g_cursor.warpState == WARP_STATE_OFF)
return false;
@@ -204,6 +223,20 @@ void core_updatePositionInfo(void)
g_state.dstRect.y = g_state.windowCY - srcH / 2;
}
else
if (g_params.intUpscale &&
srcW <= g_state.windowW &&
srcH <= g_state.windowH)
{
force = false;
const int scale = min(
floor(g_state.windowW / srcW),
floor(g_state.windowH / srcH));
g_state.dstRect.w = srcW * scale;
g_state.dstRect.h = srcH * scale;
g_state.dstRect.x = g_state.windowCX - g_state.dstRect.w / 2;
g_state.dstRect.y = g_state.windowCY - g_state.dstRect.h / 2;
}
else
if ((int)(wndAspect * 1000) == (int)(srcAspect * 1000))
{
force = false;
@@ -357,7 +390,7 @@ void core_handleGuestMouseUpdate(void)
if (!util_guestCurToLocal(&localPos))
return;
if (g_state.overlayInput || !g_cursor.inView)
if (app_isOverlayMode() || !g_cursor.inView)
return;
g_state.ds->guestPointerUpdated(
@@ -392,25 +425,19 @@ void core_handleMouseGrabbed(double ex, double ey)
if (x == 0 && y == 0)
return;
if (!spice_mouse_motion(x, y))
if (!purespice_mouseMotion(x, y))
DEBUG_ERROR("failed to send mouse motion message");
}
static bool isInView(void)
{
return
g_cursor.pos.x >= g_state.dstRect.x &&
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
g_cursor.pos.y >= g_state.dstRect.y &&
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
}
void core_handleMouseNormal(double ex, double ey)
{
// prevent cursor handling outside of capture if the position is not known
if (!g_cursor.guest.valid)
return;
if (g_cursor.realigning)
return;
if (!core_inputEnabled())
return;
@@ -448,7 +475,8 @@ void core_handleMouseNormal(double ex, double ey)
struct DoublePoint guest;
util_localCurToGuest(&guest);
if (g_state.kvmfrFeatures & KVMFR_FEATURE_SETCURSORPOS)
if (!g_state.stopVideo &&
g_state.kvmfrFeatures & KVMFR_FEATURE_SETCURSORPOS)
{
const KVMFRSetCursorPos msg = {
.msg.type = KVMFR_MESSAGE_SETCURSORPOS,
@@ -457,15 +485,36 @@ void core_handleMouseNormal(double ex, double ey)
};
uint32_t setPosSerial;
if (lgmpClientSendData(g_state.pointerQueue,
&msg, sizeof(msg), &setPosSerial) == LGMP_OK)
LGMP_STATUS status;
if ((status = lgmpClientSendData(g_state.pointerQueue,
&msg, sizeof(msg), &setPosSerial)) != LGMP_OK)
{
DEBUG_WARN("Message send failed: %s", lgmpStatusString(status));
goto fallback;
}
else
{
/* wait for the move request to be processed */
g_cursor.realigning = true;
do
{
LG_LOCK(g_state.pointerQueueLock);
if (!g_state.pointerQueue)
{
/* the queue is nolonger valid, assume complete */
g_cursor.realigning = false;
LG_UNLOCK(g_state.pointerQueueLock);
break;
}
uint32_t hostSerial;
if (lgmpClientGetSerial(g_state.pointerQueue, &hostSerial) != LGMP_OK)
{
g_cursor.realigning = false;
LG_UNLOCK(g_state.pointerQueueLock);
return;
}
LG_UNLOCK(g_state.pointerQueueLock);
if (hostSerial >= setPosSerial)
break;
@@ -474,9 +523,11 @@ void core_handleMouseNormal(double ex, double ey)
}
while(app_isRunning());
g_cursor.guest.x = msg.x;
g_cursor.guest.y = msg.y;
g_cursor.realign = false;
g_cursor.guest.x = msg.x;
g_cursor.guest.y = msg.y;
g_cursor.realign = false;
g_cursor.realigning = false;
g_cursor.redraw = true;
if (!g_cursor.inWindow)
return;
@@ -487,6 +538,7 @@ void core_handleMouseNormal(double ex, double ey)
}
else
{
fallback:
/* add the difference to the offset */
ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx);
ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy);
@@ -601,7 +653,7 @@ void core_handleMouseNormal(double ex, double ey)
g_cursor.guest.y += y;
}
if (!spice_mouse_motion(x, y))
if (!purespice_mouseMotion(x, y))
DEBUG_ERROR("failed to send mouse motion message");
}
@@ -613,3 +665,34 @@ void core_resetOverlayInputState(void)
for(int key = 0; key < ARRAY_LENGTH(g_state.io->KeysDown); key++)
g_state.io->KeysDown[key] = false;
}
void core_updateOverlayState(void)
{
g_state.cursorLast = -2;
static bool wasGrabbed = false;
if (app_isOverlayMode())
{
wasGrabbed = g_cursor.grab;
g_state.io->ConfigFlags &= ~ImGuiConfigFlags_NoMouse;
g_state.io->MousePos = (ImVec2) { g_cursor.pos.x, g_cursor.pos.y };
core_setGrabQuiet(false);
core_setCursorInView(false);
}
else
{
g_state.io->ConfigFlags |= ImGuiConfigFlags_NoMouse;
core_resetOverlayInputState();
core_setGrabQuiet(wasGrabbed);
core_invalidatePointer(true);
app_invalidateWindow(false);
if (!g_cursor.grab)
{
g_cursor.realign = true;
core_handleMouseNormal(0, 0);
}
}
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -24,6 +24,7 @@
#include <stdbool.h>
bool core_inputEnabled(void);
void core_invalidatePointer(bool detectInView);
void core_setCursorInView(bool enable);
void core_setGrab(bool enable);
void core_setGrabQuiet(bool enable);
@@ -39,5 +40,6 @@ void core_handleGuestMouseUpdate(void);
void core_handleMouseGrabbed(double ex, double ey);
void core_handleMouseNormal(double ex, double ey);
void core_resetOverlayInputState(void);
void core_updateOverlayState(void);
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* Copyright © 2017-2022 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

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