Compare commits

..

1 Commits

Author SHA1 Message Date
Geoffrey McRae
3df7d30cd5 [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:56:53 +11:00
238 changed files with 1524 additions and 23790 deletions

View File

@@ -18,9 +18,6 @@ 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
@@ -31,9 +28,8 @@ jobs:
libspice-protocol-dev nettle-dev \
libgl-dev libgles-dev \
libx11-dev libxss-dev libxi-dev libxinerama-dev libxcursor-dev libxpresent-dev \
libwayland-dev libxkbcommon-dev \
libsamplerate0-dev libpipewire-0.3-dev libpulse-dev \
$([ '${{ matrix.wayland_shell }}' = libdecor ] && echo 'libdecor-0-dev libdbus-1-dev') \
libwayland-dev wayland-protocols libxkbcommon-dev \
$([ '${{ matrix.wayland_shell }}' = libdecor ] && echo 'libdecor-dev libdbus-1-dev') \
$([ '${{ matrix.compiler.cc }}' = clang ] && echo 'clang-tools')
sudo pip3 install pyenchant
- name: Configure client
@@ -47,7 +43,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: |
@@ -76,16 +72,12 @@ 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 libxcb-xfixes0-dev \
libpipewire-0.3-dev
sudo apt-get install binutils-dev libgl1-mesa-dev libxcb-xfixes0-dev
- name: Configure Linux host
run: |
mkdir host/build
@@ -121,11 +113,6 @@ 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
@@ -183,7 +170,7 @@ jobs:
sudo apt-get update
- name: Install docs dependencies
run: |
sudo apt-get install python3-sphinx python3-sphinx-rtd-theme
sudo apt-get install python3-sphinx
sudo pip3 install sphinxcontrib-spelling
- name: Build docs
run: |

3
.gitmodules vendored
View File

@@ -7,6 +7,3 @@
[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,9 +54,3 @@ 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`
and `python3-sphinx-rtd-theme` installed.
installed.

View File

@@ -51,12 +51,6 @@ 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()
@@ -66,7 +60,6 @@ add_compile_options(
"-Wextra"
"-Wno-sign-compare"
"-Wno-unused-parameter"
"-Wstrict-prototypes"
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
"-Werror"
"-Wfatal-errors"
@@ -108,7 +101,6 @@ add_custom_command(
)
include_directories(
${PROJECT_TOP}
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
@@ -124,9 +116,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
@@ -140,8 +132,6 @@ 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.
@@ -170,16 +160,6 @@ 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

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

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

@@ -1,565 +0,0 @@
/**
* 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

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

@@ -1,393 +0,0 @@
/**
* 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,7 +23,6 @@ else()
endif()
add_library(displayserver_Wayland STATIC
activation.c
clipboard.c
cursor.c
gl.c
@@ -51,6 +50,8 @@ 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"
@@ -66,7 +67,6 @@ 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,6 +96,3 @@ 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

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

View File

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

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -72,9 +72,6 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -89,9 +89,6 @@ static bool waylandInit(const LG_DSInitParams params)
if (!waylandRegistryInit())
return false;
if (!waylandActivationInit())
return false;
if (!waylandIdleInit())
return false;
@@ -190,7 +187,6 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -47,7 +47,6 @@
#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);
@@ -181,8 +180,6 @@ 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;
@@ -234,11 +231,6 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -57,12 +57,6 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -28,13 +28,11 @@
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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -123,6 +123,8 @@ bool x11CBInit()
return false;
}
XFixesSelectSelectionInput(x11.display, x11.window,
XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask);
XFixesSelectSelectionInput(x11.display, x11.window,
x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
@@ -152,12 +154,6 @@ 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;
@@ -209,7 +205,7 @@ send:
static void x11CBSelectionClear(const XSelectionClearEvent e)
{
if (e.selection != x11atoms.CLIPBOARD)
if (e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD)
return;
x11cb.aCurSelection = BadValue;
@@ -295,7 +291,7 @@ out:
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
{
// check if the selection is valid and it isn't ourself
if (e.selection != x11atoms.CLIPBOARD ||
if ((e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD) ||
e.owner == x11.window || e.owner == 0)
{
return;
@@ -400,6 +396,7 @@ 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);
}
@@ -407,6 +404,7 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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(void);
bool x11CBInit();
void x11CBNotice(LG_ClipboardData type);
void x11CBRelease(void);
void x11CBRequest(LG_ClipboardData type);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -23,7 +23,6 @@
#include "x11.h"
#include "atoms.h"
#include "clipboard.h"
#include "resources/icondata.h"
#include <string.h>
#include <unistd.h>
@@ -533,8 +532,11 @@ static bool x11Init(const LG_DSInitParams params)
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
XISetMask(mask, XI_FocusIn );
XISetMask(mask, XI_FocusOut);
if (!x11.ewmhHasFocusEvent)
{
XISetMask(mask, XI_FocusIn );
XISetMask(mask, XI_FocusOut);
}
XISetMask(mask, XI_Enter );
XISetMask(mask, XI_Leave );
@@ -563,17 +565,6 @@ 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 };
@@ -1031,16 +1022,6 @@ 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;
@@ -1049,50 +1030,43 @@ 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;
setFocus(true, xie->event_x, xie->event_y);
x11.focused = true;
app_updateCursorPos(xie->event_x, xie->event_y);
app_handleFocusEvent(true);
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;
setFocus(false, xie->event_x, xie->event_y);
app_updateCursorPos(xie->event_x, xie->event_y);
app_handleFocusEvent(false);
x11.focused = false;
return;
}
@@ -1825,28 +1799,6 @@ 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);
@@ -1937,7 +1889,6 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -102,21 +102,11 @@ 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, GraphFormatFn formatFn);
GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max);
void app_unregisterGraph(GraphHandle handle);
void app_invalidateGraph(GraphHandle handle);
void app_overlayConfigRegister(const char * title,
void (*callback)(void * udata, int * id), void * udata);
@@ -138,20 +128,9 @@ 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
@@ -173,11 +152,4 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it

View File

@@ -1,89 +0,0 @@
/**
* 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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);
void (*startup)();
/* called just before final window destruction, before final free */
void (*shutdown)(void);
void (*shutdown)();
/* final free */
void (*free)(void);
void (*free)();
/*
* 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);
void (*ungrabKeyboard)(void);
void (*grabKeyboard)();
void (*ungrabKeyboard)();
/* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */
void (*grabPointer)(void);
void (*ungrabPointer)(void);
void (*grabPointer)();
void (*ungrabPointer)();
/* (un)capturePointer is used do toggle special cursor tracking in capture mode */
void (*capturePointer)(void);
void (*uncapturePointer)(void);
void (*capturePointer)();
void (*uncapturePointer)();
/* exiting = true if the warp is to leave the window */
void (*warpPointer)(int x, int y, bool exiting);
@@ -185,17 +185,14 @@ 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);
void (*realignPointer)();
/* returns true if the position specified is actually valid */
bool (*isValidPointerPos)(int x, int y);
/* called to disable/enable the screensaver */
void (*inhibitIdle)(void);
void (*uninhibitIdle)(void);
/* called to request activation */
void (*requestActivation)(void);
void (*inhibitIdle)();
void (*uninhibitIdle)();
/* 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -43,10 +43,6 @@ 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
@@ -63,15 +59,6 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -66,11 +66,9 @@ LG_RendererRotate;
typedef struct LG_RendererFormat
{
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
FrameType type; // frame type
unsigned int width; // image width
unsigned int height; // image height
unsigned int stride; // scanline width (zero if compresed)
unsigned int pitch; // scanline bytes (or compressed size)
unsigned int bpp; // bits per pixel (zero if compressed)
@@ -141,8 +139,8 @@ typedef struct LG_RendererOps
/* called when the mouse has moved or changed visibillity
* Context: cursorThread */
bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, int x, int y,
const int hx, const int hy);
bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, const int x,
const int y);
/* called when the frame format has changed
* Context: frameThread */

View File

@@ -18,6 +18,16 @@
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <windows.h>
#include <stdbool.h>
struct ll;
void captureOutputDebugString(void);
struct ll * ll_new();
void ll_free (struct ll * list);
void ll_push (struct ll * list, void * data);
bool ll_shift (struct ll * list, void ** data);
bool ll_peek_head(struct ll * list, void ** data);
bool ll_peek_tail(struct ll * list, void ** data);
unsigned int ll_count (struct ll * list);
void ll_reset (struct ll * list);
bool ll_walk (struct ll * list, void ** data);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -24,7 +24,12 @@
#include <stdlib.h>
#include <stdbool.h>
#include "common/types.h"
#include "common/util.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))
// reads the specified file into a new buffer
// the callee must free the buffer

View File

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

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -45,10 +45,8 @@ 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 hx, const float hy);
const float x, const float y);
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
LG_RendererRotate rotate, int width, int height);

View File

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

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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++ = y + (inner * s);
*dst++ = 0.0f;
*dst++ = x + (outer * c);
*dst++ = y + (outer * s);
*dst++ = 0.0f;
*dst = x + (inner * c); ++dst;
*dst = y + (inner * s); ++dst;
*dst = 0.0f; ++dst;
*dst = x + (outer * c); ++dst;
*dst = y + (outer * s); ++dst;
*dst = 0.0f; ++dst;
}
egl_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++ = y + (inner * s);
*dst++ = 0.0f;
*dst++ = x + (outer * c);
*dst++ = y + (outer * s);
*dst++ = 0.0f;
*dst = x + (inner * c); ++dst;
*dst = y + (inner * s); ++dst;
*dst = 0.0f; ++dst;
*dst = x + (outer * c); ++dst;
*dst = y + (outer * s); ++dst;
*dst = 0.0f; ++dst;
}
egl_modelAddVerts(model, v, NULL, (pts + 1) * 2);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -39,6 +39,7 @@
#include <math.h>
#include <string.h>
#include "app.h"
#include "egl_dynprocs.h"
#include "model.h"
#include "shader.h"
@@ -102,11 +103,9 @@ struct Inst
bool cursorVisible;
int cursorX , cursorY;
int cursorHX , cursorHY;
float mouseWidth , mouseHeight;
float mouseScaleX, mouseScaleY;
bool showDamage;
bool scalePointer;
struct CursorState cursorLast;
@@ -198,13 +197,6 @@ 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}
};
@@ -257,8 +249,7 @@ 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, NULL);
this->importGraph = app_registerGraph("IMPORT", this->importTimings, 0.0f, 5.0f);
*needsOpenGL = false;
return true;
@@ -340,18 +331,18 @@ static void egl_calc_mouse_size(struct Inst * this)
{
case LG_ROTATE_0:
case LG_ROTATE_180:
this->mouseScaleX = 2.0f / this->format.screenWidth;
this->mouseScaleY = 2.0f / this->format.screenHeight;
w = this->format.screenWidth;
h = this->format.screenHeight;
this->mouseScaleX = 2.0f / this->format.width;
this->mouseScaleY = 2.0f / this->format.height;
w = this->format.width;
h = this->format.height;
break;
case LG_ROTATE_90:
case LG_ROTATE_270:
this->mouseScaleX = 2.0f / this->format.screenHeight;
this->mouseScaleY = 2.0f / this->format.screenWidth;
w = this->format.screenHeight;
h = this->format.screenWidth;
this->mouseScaleX = 2.0f / this->format.height;
this->mouseScaleY = 2.0f / this->format.width;
w = this->format.height;
h = this->format.width;
break;
default:
@@ -390,10 +381,8 @@ 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->cursorHX * this->mouseScaleX) * this->scaleX,
((float)this->cursorHY * this->mouseScaleY) * this->scaleY
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
);
break;
@@ -402,10 +391,8 @@ 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->cursorHX * this->mouseScaleX) * this->scaleY,
((float)this->cursorHY * this->mouseScaleY) * this->scaleX
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX
);
break;
}
@@ -419,14 +406,14 @@ static void egl_update_scale_type(struct Inst * this)
{
case LG_ROTATE_0:
case LG_ROTATE_180:
width = this->format.frameWidth;
height = this->format.frameHeight;
width = this->format.width;
height = this->format.height;
break;
case LG_ROTATE_90:
case LG_ROTATE_270:
width = this->format.frameHeight;
height = this->format.frameWidth;
width = this->format.height;
height = this->format.width;
break;
}
@@ -478,16 +465,6 @@ 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;
@@ -495,7 +472,6 @@ 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);
@@ -521,15 +497,12 @@ static bool egl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cur
return true;
}
static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible,
int x, int y, const int hx, const int hy)
static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible, const int x, const int y)
{
struct Inst * this = UPCAST(struct Inst, renderer);
this->cursorVisible = visible;
this->cursorX = x + hx;
this->cursorY = y + hy;
this->cursorHX = hx;
this->cursorHY = hy;
this->cursorX = x;
this->cursorY = y;
egl_calc_mouse_state(this);
return true;
}
@@ -561,14 +534,8 @@ 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.frameWidth, format.frameHeight);
egl_damageSetup(this->damage, format.width, format.height);
/* we need full screen damage when the format changes */
INTERLOCKED_SECTION(this->desktopDamageLock, {
@@ -769,13 +736,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
}
const char * client_exts = eglQueryString(this->display, EGL_EXTENSIONS);
if (!client_exts)
{
DEBUG_ERROR("Failed to query EGL_EXTENSIONS");
return false;
}
bool debug = option_get_bool("egl", "debug");
bool debugContext = option_get_bool("egl", "debug");
EGLint ctxattr[5];
int ctxidx = 0;
@@ -785,17 +746,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++] = debug ? EGL_TRUE : EGL_FALSE;
ctxattr[ctxidx++] = debugContext ? EGL_TRUE : EGL_FALSE;
}
else if (util_hasGLExt(client_exts, "EGL_KHR_create_context"))
{
ctxattr[ctxidx++] = EGL_CONTEXT_FLAGS_KHR;
ctxattr[ctxidx++] = debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0;
ctxattr[ctxidx++] = debugContext ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0;
}
else if (debug)
else if (debugContext)
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)
@@ -823,30 +784,15 @@ 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));
if (debug)
{
DEBUG_INFO("EGL Exts: %s", client_exts);
DEBUG_INFO("GL Exts : %s", gl_exts);
}
DEBUG_INFO("EGL Exts: %s", client_exts);
DEBUG_INFO("GL Exts : %s", gl_exts);
GLint esMaj, esMin;
glGetIntegerv(GL_MAJOR_VERSION, &esMaj);
@@ -872,8 +818,6 @@ 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)
@@ -895,7 +839,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
}
}
if (debug)
if (debugContext)
{
if ((esMaj > 3 || (esMaj == 3 && esMin >= 2)) && g_egl_dynProcs.glDebugMessageCallback)
{
@@ -1045,8 +989,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.frameWidth - x, rect->width + 2),
.height = min(this->format.frameHeight - y, rect->height + 2),
.width = min(this->format.width - x, rect->width + 2),
.height = min(this->format.height - y, rect->height + 2),
};
}
}
@@ -1059,8 +1003,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
if (!renderAll)
{
double matrix[6];
egl_screenToDesktopMatrix(matrix,
this->format.frameWidth, this->format.frameHeight,
egl_screenToDesktopMatrix(matrix, this->format.width, this->format.height,
this->translateX, this->translateY, this->scaleX, this->scaleY, rotate,
this->width, this->height);
@@ -1079,7 +1022,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.frameWidth, this->format.frameHeight
this->format.width, this->format.height
);
}
@@ -1187,8 +1130,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
else
{
double matrix[6];
egl_desktopToScreenMatrix(matrix,
this->format.frameWidth, this->format.frameHeight,
egl_desktopToScreenMatrix(matrix, this->format.width, this->format.height,
this->translateX, this->translateY, this->scaleX, this->scaleY, rotate,
this->width, this->height);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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 (igSelectable_Bool(filterNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
if (igSelectableBool(filterNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
{
redraw = true;
this->filter = i;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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 "common/ll.h"
#include "ll.h"
#include <stdlib.h>
#include <string.h>
@@ -124,31 +124,10 @@ 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);
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;
}
fl->v = malloc(sizeof(GLfloat) * count * 3);
fl->u = malloc(sizeof(GLfloat) * count * 2);
memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3);
if (uvs)
@@ -185,20 +164,18 @@ void egl_modelRender(EGL_Model * model)
/* buffer the verticies */
struct FloatList * fl;
ll_lock(model->verticies);
ll_forEachNL(model->verticies, item, fl)
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
{
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 3, fl->v);
offset += sizeof(GLfloat) * fl->count * 3;
}
/* buffer the uvs */
ll_forEachNL(model->verticies, item, fl)
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
{
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 2, fl->u);
offset += sizeof(GLfloat) * fl->count * 2;
}
ll_unlock(model->verticies);
/* set up vertex arrays in the VAO */
glEnableVertexAttribArray(0);
@@ -222,13 +199,11 @@ void egl_modelRender(EGL_Model * model)
/* draw the arrays */
GLint offset = 0;
struct FloatList * fl;
ll_lock(model->verticies);
ll_forEachNL(model->verticies, item, fl)
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -74,12 +74,6 @@ 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);
@@ -88,8 +82,6 @@ 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;
@@ -122,9 +114,6 @@ 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)
@@ -138,21 +127,10 @@ 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);
if (preset)
{
if (this->activePreset > -1)
loadPreset(this, preset);
else
DEBUG_WARN("egl:preset '%s' does not exist", preset);
}
this->activePreset = -1;
return;
fail:
@@ -356,8 +334,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
for (unsigned i = 0; i < stringlist_count(this->presets); ++i)
{
bool selected = i == this->activePreset;
if (igSelectable_Bool(stringlist_at(this->presets, i), selected, 0,
(ImVec2) { 0.0f, 0.0f }))
if (igSelectableBool(stringlist_at(this->presets, i), selected, 0, (ImVec2) { 0.0f, 0.0f }))
{
this->activePreset = i;
redraw = true;
@@ -388,7 +365,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
if (igButton("Save preset as...", (ImVec2) { 0.0f, 0.0f }))
{
this->presetEdit[0] = '\0';
igOpenPopup_Str("Save preset as...", ImGuiPopupFlags_None);
igOpenPopup("Save preset as...", ImGuiPopupFlags_None);
}
igSameLine(0.0f, -1.0f);
@@ -424,7 +401,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
}
if (this->presetError)
igOpenPopup_Str("Preset error", ImGuiPopupFlags_None);
igOpenPopup("Preset error", ImGuiPopupFlags_None);
if (igBeginPopupModal("Preset error", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
@@ -448,7 +425,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
static void drawDropTarget(void)
{
igPushStyleColor_Vec4(ImGuiCol_Separator, (ImVec4) { 1.0f, 1.0f, 0.0f, 1.0f });
igPushStyleColorVec4(ImGuiCol_Separator, (ImVec4) { 1.0f, 1.0f, 0.0f, 1.0f });
igSeparator();
igPopStyleColor(1);
}
@@ -479,8 +456,8 @@ static void configUI(void * opaque, int * id)
if (moving && mouseIdx < moveIdx && i == mouseIdx)
drawDropTarget();
igPushID_Ptr(filter);
bool draw = igCollapsingHeader_BoolPtr(filter->ops.name, NULL, 0);
igPushIDPtr(filter);
bool draw = igCollapsingHeaderBoolPtr(filter->ops.name, NULL, 0);
if (igIsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
mouseIdx = i;

View File

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

View File

@@ -5,25 +5,11 @@ in vec2 uv;
out vec4 color;
uniform sampler2D sampler1;
uniform float scale;
void main()
{
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);
vec4 tmp = texture(sampler1, uv);
if (tmp.rgb == vec3(0.0, 0.0, 0.0))
discard;
color = tmp;
}

View File

@@ -3,26 +3,16 @@ precision mediump float;
#include "color_blind.h"
in vec2 uv;
out vec4 color;
in vec2 uv;
out vec4 color;
uniform sampler2D sampler1;
uniform float scale;
uniform int cbMode;
uniform int cbMode;
void main()
{
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);
color = texture(sampler1, uv);
if (cbMode > 0)
color = cbTransform(color, cbMode);

View File

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

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -29,6 +29,7 @@
#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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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, 40000000)) // 40ms
switch(glClientWaitSync(this->sync, 0, 20000000)) // 20ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:

View File

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

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -25,6 +25,7 @@
#include "util.h"
#include "clipboard.h"
#include "ll.h"
#include "kb.h"
#include "common/debug.h"
@@ -38,6 +39,8 @@
#include <math.h>
#include <string.h>
#define ALERT_TIMEOUT 2000000
bool app_isRunning(void)
{
return
@@ -62,23 +65,7 @@ bool app_isFormatValid(void)
bool app_isOverlayMode(void)
{
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;
return g_state.overlayInput;
}
void app_updateCursorPos(double x, double y)
@@ -87,7 +74,7 @@ void app_updateCursorPos(double x, double y)
g_cursor.pos.y = y;
g_cursor.valid = true;
if (app_isOverlayMode())
if (g_state.overlayInput)
g_state.io->MousePos = (ImVec2) { x, y };
}
@@ -96,7 +83,7 @@ void app_handleFocusEvent(bool focused)
g_state.focused = focused;
// release any imgui buttons/keys if we lost focus
if (!focused && app_isOverlayMode())
if (!focused && g_state.overlayInput)
core_resetOverlayInputState();
if (!core_inputEnabled())
@@ -146,7 +133,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 (app_isOverlayMode())
if (g_state.overlayInput)
{
g_state.io->MouseDown[ImGuiMouseButton_Left ] = false;
g_state.io->MouseDown[ImGuiMouseButton_Right ] = false;
@@ -167,7 +154,7 @@ void app_clipboardRelease(void)
if (!g_params.clipboardToVM)
return;
purespice_clipboardRelease();
spice_clipboard_release();
}
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
@@ -177,15 +164,15 @@ void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
if (count == 0)
{
purespice_clipboardRelease();
spice_clipboard_release();
return;
}
PSDataType conv[count];
SpiceDataType conv[count];
for(int i = 0; i < count; ++i)
conv[i] = cb_lgTypeToSpiceType(types[i]);
purespice_clipboardGrab(conv, count);
spice_clipboard_grab(conv, count);
}
void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
@@ -195,7 +182,7 @@ void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
if (type == LG_CLIPBOARD_DATA_NONE)
{
purespice_clipboardRelease();
spice_clipboard_release();
return;
}
@@ -203,7 +190,7 @@ void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
g_state.cbChunked = size > 0;
g_state.cbXfer = size;
purespice_clipboardDataStart(g_state.cbType, size);
spice_clipboard_data_start(g_state.cbType, size);
}
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
@@ -218,9 +205,9 @@ void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
}
if (!g_state.cbChunked)
purespice_clipboardDataStart(g_state.cbType, size);
spice_clipboard_data_start(g_state.cbType, size);
purespice_clipboardData(g_state.cbType, data, (uint32_t)size);
spice_clipboard_data(g_state.cbType, data, (uint32_t)size);
g_state.cbXfer -= size;
}
@@ -230,18 +217,13 @@ 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);
purespice_clipboardRequest(g_state.cbType);
spice_clipboard_request(g_state.cbType);
}
static int mapSpiceToImGuiButton(uint32_t button)
@@ -263,7 +245,7 @@ void app_handleButtonPress(int button)
{
g_cursor.buttons |= (1U << button);
if (app_isOverlayMode())
if (g_state.overlayInput)
{
int igButton = mapSpiceToImGuiButton(button);
if (igButton != -1)
@@ -274,7 +256,7 @@ void app_handleButtonPress(int button)
if (!core_inputEnabled() || !g_cursor.inView)
return;
if (!purespice_mousePress(button))
if (!spice_mouse_press(button))
DEBUG_ERROR("app_handleButtonPress: failed to send message");
}
@@ -282,7 +264,7 @@ void app_handleButtonRelease(int button)
{
g_cursor.buttons &= ~(1U << button);
if (app_isOverlayMode())
if (g_state.overlayInput)
{
int igButton = mapSpiceToImGuiButton(button);
if (igButton != -1)
@@ -293,19 +275,19 @@ void app_handleButtonRelease(int button)
if (!core_inputEnabled())
return;
if (!purespice_mouseRelease(button))
if (!spice_mouse_release(button))
DEBUG_ERROR("app_handleButtonRelease: failed to send message");
}
void app_handleWheelMotion(double motion)
{
if (app_isOverlayMode())
if (g_state.overlayInput)
g_state.io->MouseWheel -= motion;
}
void app_handleKeyPress(int sc)
{
if (!app_isOverlayMode() || !g_state.io->WantCaptureKeyboard)
if (!g_state.overlayInput || !g_state.io->WantCaptureKeyboard)
{
if (sc == g_params.escapeKey && !g_state.escapeActive)
{
@@ -318,14 +300,11 @@ 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 (app_isOverlayMode())
if (g_state.overlayInput)
{
if (sc == KEY_ESC)
app_setOverlay(false);
@@ -346,7 +325,7 @@ void app_handleKeyPress(int sc)
if (!ps2)
return;
if (purespice_keyDown(ps2))
if (spice_key_down(ps2))
g_state.keyDown[sc] = true;
else
{
@@ -362,16 +341,24 @@ void app_handleKeyRelease(int sc)
{
if (g_state.escapeAction == -1)
{
if (!g_state.escapeHelp && g_params.useSpiceInput &&
!app_isOverlayMode())
if (!g_state.escapeHelp && g_params.useSpiceInput && !g_state.overlayInput)
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 (app_isOverlayMode())
if (g_state.overlayInput)
{
g_state.io->KeysDown[sc] = false;
return;
@@ -391,7 +378,7 @@ void app_handleKeyRelease(int sc)
if (!ps2)
return;
if (purespice_keyUp(ps2))
if (spice_key_up(ps2))
g_state.keyDown[sc] = false;
else
{
@@ -423,14 +410,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 (!purespice_keyModifiers(modifiers))
if (!spice_key_modifiers(modifiers))
DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message");
}
void app_handleMouseRelative(double normx, double normy,
double rawx, double rawy)
{
if (app_isOverlayMode())
if (g_state.overlayInput)
return;
if (g_cursor.grab)
@@ -452,8 +439,7 @@ 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 ||
app_isOverlayMode())
if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused || g_state.overlayInput)
return;
if (!core_inputEnabled())
@@ -482,7 +468,7 @@ void app_handleMouseBasic()
g_cursor.projected.x += x;
g_cursor.projected.y += y;
if (!purespice_mouseMotion(x, y))
if (!spice_mouse_motion(x, y))
DEBUG_ERROR("failed to send mouse motion message");
}
@@ -533,10 +519,6 @@ 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);
}
@@ -566,6 +548,15 @@ 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);
}
@@ -634,48 +625,19 @@ 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);
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);
valloc_sprintf(&buffer, fmt, args);
va_end(args);
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);
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);
}
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
@@ -688,12 +650,6 @@ 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;
@@ -723,10 +679,9 @@ void app_releaseAllKeybinds(void)
}
}
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
float min, float max, GraphFormatFn formatFn)
GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max)
{
return overlayGraph_register(name, buffer, min, max, formatFn);
return overlayGraph_register(name, buffer, min, max);
}
void app_unregisterGraph(GraphHandle handle)
@@ -734,22 +689,20 @@ void app_unregisterGraph(GraphHandle handle)
overlayGraph_unregister(handle);
}
void app_invalidateGraph(GraphHandle handle)
struct Overlay
{
overlayGraph_invalidate(handle);
}
const struct LG_OverlayOps * ops;
const void * params;
void * udata;
int lastRectCount;
struct Rect lastRects[MAX_OVERLAY_RECTS];
};
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;
@@ -763,17 +716,15 @@ void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params)
void app_initOverlays(void)
{
struct Overlay * overlay;
ll_lock(g_state.overlays);
ll_forEachNL(g_state.overlays, item, overlay)
for (ll_reset(g_state.overlays);
ll_walk(g_state.overlays, (void **)&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)
@@ -818,26 +769,22 @@ static inline LG_DSPointer mapImGuiCursor(ImGuiMouseCursor cursor)
bool app_overlayNeedsRender(void)
{
if (app_isOverlayMode())
struct Overlay * overlay;
if (g_state.overlayInput)
return true;
bool result = false;
struct Overlay * overlay;
ll_lock(g_state.overlays);
ll_forEachNL(g_state.overlays, item, overlay)
for (ll_reset(g_state.overlays);
ll_walk(g_state.overlays, (void **)&overlay); )
{
if (!overlay->ops->needs_render)
continue;
if (overlay->ops->needs_render(overlay->udata, false))
{
result = true;
break;
}
if (overlay->ops->needs_render(overlay->udata, g_state.overlayInput))
return true;
}
ll_unlock(g_state.overlays);
return result;
return false;
}
int app_renderOverlay(struct Rect * rects, int maxRects)
@@ -856,34 +803,24 @@ 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();
const bool overlayMode = app_isOverlayMode();
if (overlayMode)
if (g_state.overlayInput)
{
totalDamage = true;
ImDrawList_AddRectFilled(igGetBackgroundDrawList_Nil(), (ImVec2) { 0.0f , 0.0f },
g_state.io->DisplaySize,
igGetColorU32_Col(ImGuiCol_ModalWindowDimBg, 1.0f),
0, 0);
ImDrawList_AddRectFilled(igGetBackgroundDrawListNil(), (ImVec2) { 0.0f , 0.0f },
g_state.io->DisplaySize, igGetColorU32Col(ImGuiCol_ModalWindowDimBg, 1.0f), 0, 0);
// bool test;
// igShowDemoWindow(&test);
}
const bool msgModal = overlayMsg_modal();
// render the overlays
ll_lock(g_state.overlays);
ll_forEachNL(g_state.overlays, item, overlay)
for (ll_reset(g_state.overlays);
ll_walk(g_state.overlays, (void **)&overlay); )
{
if (msgModal && overlay->ops != &LGOverlayMsg)
continue;
const int written =
overlay->ops->render(overlay->udata, overlayMode,
overlay->ops->render(overlay->udata, g_state.overlayInput,
buffer, MAX_OVERLAY_RECTS);
for (int i = 0; i < written; ++i)
@@ -919,9 +856,8 @@ render_again:
memcpy(overlay->lastRects, buffer, sizeof(struct Rect) * written);
overlay->lastRectCount = written;
}
ll_unlock(g_state.overlays);
if (overlayMode)
if (g_state.overlayInput)
{
ImGuiMouseCursor cursor = igGetMouseCursor();
if (cursor != g_state.cursorLast)
@@ -933,16 +869,6 @@ render_again:
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;
}
@@ -958,11 +884,31 @@ void app_freeOverlays(void)
void app_setOverlay(bool enable)
{
static bool wasGrabbed = false;
if (g_state.overlayInput == enable)
return;
g_state.overlayInput = enable;
core_updateOverlayState();
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);
}
}
void app_overlayConfigRegister(const char * title,
@@ -976,35 +922,3 @@ 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;
}

View File

@@ -1,873 +0,0 @@
/**
* 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

View File

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

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 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 <purespice.h>
#include "spice/spice.h"
#include "interface/displayserver.h"
LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type);
PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type);
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
void cb_spiceNotice(const PSDataType type);
void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size);
void cb_spiceNotice(const SpiceDataType type);
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size);
void cb_spiceRelease(void);
void cb_spiceRequest(const PSDataType type);
void cb_spiceRequest(const SpiceDataType type);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 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(void);
static void doLicense();
static struct Option options[] =
{
@@ -163,13 +163,6 @@ 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",
@@ -435,13 +428,6 @@ 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",
@@ -471,36 +457,6 @@ 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}
};
@@ -526,38 +482,22 @@ bool config_load(int argc, char * argv[])
// load config from user's home directory
struct passwd * pw = getpwuid(getuid());
if (!pw)
DEBUG_WARN("getpwuid failed, unable to load user configuration");
else
char * localFile;
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
if (stat(localFile, &st) >= 0 && S_ISREG(st.st_mode))
{
char * localFile;
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
if (!localFile)
DEBUG_INFO("Loading config from: %s", localFile);
if (!option_load(localFile))
{
DEBUG_ERROR("out of memory");
free(localFile);
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))
{
@@ -610,7 +550,6 @@ 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" );
@@ -670,7 +609,6 @@ 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)
{
@@ -690,11 +628,6 @@ 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;
}
@@ -709,7 +642,7 @@ static void doLicense(void)
// BEGIN LICENSE BLOCK
"\n"
"Looking Glass\n"
"Copyright © 2017-2022 The Looking Glass Authors\n"
"Copyright © 2017-2021 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"
@@ -754,8 +687,6 @@ 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)
@@ -798,9 +729,6 @@ 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;
@@ -813,11 +741,6 @@ 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;
@@ -841,9 +764,6 @@ 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;
}
@@ -852,11 +772,6 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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);
void config_init();
bool config_load(int argc, char * argv[]);
void config_free(void);
void config_free();

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -31,45 +31,39 @@
#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_invalidatePointer(bool detectInView)
void core_setCursorInView(bool enable)
{
// if the state has not changed, don't do anything else
if (g_cursor.inView == enable)
return;
if (enable && !g_state.focused)
return;
// do not allow the view to become active if any mouse buttons are being held,
// this fixes issues with meta window resizing.
if (enable && g_cursor.buttons)
return;
g_cursor.inView = enable;
g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
? true : enable;
g_cursor.redraw = true;
/* if the display server does not support warp, then we can not operate in
* always relative mode and we should not grab the pointer */
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
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;
g_cursor.warpState = enable ? WARP_STATE_ON : WARP_STATE_OFF;
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 (enable)
{
if (g_params.hideMouse)
g_state.ds->setPointer(LG_POINTER_NONE);
@@ -94,19 +88,6 @@ void core_invalidatePointer(bool detectInView)
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);
@@ -166,7 +147,7 @@ void core_setGrabQuiet(bool enable)
bool core_warpPointer(int x, int y, bool exiting)
{
if ((!g_cursor.inWindow && !exiting) ||
app_isOverlayMode() ||
g_state.overlayInput ||
g_cursor.warpState == WARP_STATE_OFF)
return false;
@@ -223,20 +204,6 @@ 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;
@@ -390,7 +357,7 @@ void core_handleGuestMouseUpdate(void)
if (!util_guestCurToLocal(&localPos))
return;
if (app_isOverlayMode() || !g_cursor.inView)
if (g_state.overlayInput || !g_cursor.inView)
return;
g_state.ds->guestPointerUpdated(
@@ -425,19 +392,25 @@ void core_handleMouseGrabbed(double ex, double ey)
if (x == 0 && y == 0)
return;
if (!purespice_mouseMotion(x, y))
if (!spice_mouse_motion(x, y))
DEBUG_ERROR("failed to send mouse motion message");
}
static bool isInView(void)
{
return
g_cursor.pos.x >= g_state.dstRect.x &&
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
g_cursor.pos.y >= g_state.dstRect.y &&
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
}
void core_handleMouseNormal(double ex, double ey)
{
// prevent cursor handling outside of capture if the position is not known
if (!g_cursor.guest.valid)
return;
if (g_cursor.realigning)
return;
if (!core_inputEnabled())
return;
@@ -485,36 +458,15 @@ void core_handleMouseNormal(double ex, double ey)
};
uint32_t setPosSerial;
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
if (lgmpClientSendData(g_state.pointerQueue,
&msg, sizeof(msg), &setPosSerial) == LGMP_OK)
{
/* 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;
@@ -523,11 +475,9 @@ 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.realigning = false;
g_cursor.redraw = true;
g_cursor.guest.x = msg.x;
g_cursor.guest.y = msg.y;
g_cursor.realign = false;
if (!g_cursor.inWindow)
return;
@@ -538,7 +488,6 @@ 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);
@@ -653,7 +602,7 @@ fallback:
g_cursor.guest.y += y;
}
if (!purespice_mouseMotion(x, y))
if (!spice_mouse_motion(x, y))
DEBUG_ERROR("failed to send mouse motion message");
}
@@ -665,34 +614,3 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
@@ -24,7 +24,6 @@
#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);
@@ -40,6 +39,5 @@ 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-2022 The Looking Glass Authors
* Copyright © 2017-2021 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