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
429 changed files with 5535 additions and 59222 deletions

4
.github/CODEOWNERS vendored
View File

@@ -1,4 +0,0 @@
# Jonathan Rubenstein (JJRcop)
# - Primary documentation manager. Does not require direct approval for every
# - change, but should be consulted for large additions and changes.
/doc/ jrubcop@gmail.com

View File

@@ -1,9 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Looking Glass on Level1Tech's Forum
url: https://forum.level1techs.com/c/software/lookingGlass/142
about: Ask for help by creating a New Topic on the Level1Tech's forum
- name: Looking Glass Discord Server
url: https://discord.gg/52SMupxkvt
about: Ask for help in the Looking Glass discord server

View File

@@ -1,9 +1,3 @@
---
name: Bug report
about: Report bugs in Looking Glass (only confirmed bugs and feature requests please)
---
### Issues are for Bug Reports and Feature Requests Only!
If you are looking for help or support please use one of the following methods
@@ -70,4 +64,3 @@ https://www.youtube.com/watch?v=EqxxJK9Yo64
```
PASTE FULL BACKTRACE HERE
```

View File

@@ -2,7 +2,7 @@ name: build
on: [push, pull_request]
jobs:
client:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
@@ -12,9 +12,12 @@ jobs:
wayland_shell: [xdg-shell, libdecor]
build_type: [Release, Debug]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Install libdecor PPA
run: sudo add-apt-repository ppa:christianrauch/libdecoration
if: ${{ matrix.wayland_shell == 'libdecor' }}
- name: Update apt
run: |
sudo apt-get update
@@ -25,10 +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 \
libfontconfig-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
@@ -42,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: |
@@ -57,7 +58,7 @@ jobs:
module:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Build kernel module
@@ -68,7 +69,7 @@ jobs:
host-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Update apt
@@ -76,8 +77,7 @@ jobs:
sudo apt-get update
- name: Install Linux host dependencies
run: |
sudo apt-get install binutils-dev libglib2.0-dev libxcb-xfixes0-dev \
libpipewire-0.3-dev libxcb-shm0-dev
sudo apt-get install binutils-dev libgl1-mesa-dev libxcb-xfixes0-dev
- name: Configure Linux host
run: |
mkdir host/build
@@ -89,9 +89,9 @@ jobs:
make -j$(nproc)
host-windows-cross:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Update apt
@@ -113,40 +113,13 @@ 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-2025
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Install NSIS
shell: powershell
run: |
winget install -e --id NSIS.NSIS --silent --accept-source-agreements --accept-package-agreements
$paths = @(
(Join-Path ${env:ProgramFiles} 'NSIS\Bin'),
(Join-Path ${env:ProgramFiles(x86)} 'NSIS\Bin')
) | Where-Object { Test-Path $_ }
if ($paths.Count -eq 0) {
Write-Error "NSIS 'Bin' folder not found after install."
exit 1
}
$paths | ForEach-Object {
Add-Content -Path $env:GITHUB_PATH -Value $_
}
Write-Host "makensis location(s): $($paths -join ', ')"
- name: Test NSIS
run: |
makensis /VERSION
- name: Configure Windows host for native MinGW-w64
run: |
mkdir host\build
@@ -161,43 +134,13 @@ jobs:
cd host\build
makensis -DBUILD_32BIT platform\Windows\installer.nsi
idd:
runs-on: windows-2022
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v2
with:
msbuild-architecture: x64
- name: Install NuGet packages
run: |
cd idd
nuget restore LGIdd.sln
- name: Build IDD
run: |
cd idd
msbuild.exe LGIdd.sln /t:Build /p:Configuration=Release /p:Platform=x64
- name: Build NSIS Installer
run: |
cd idd\x64\Release\LGIdd
makensis -DBUILD_32BIT installer.nsi
- name: Build NSIS installer with IVSHMEM drivers
run: |
cd idd\x64\Release
Invoke-WebRequest https://dl.quantum2.xyz/ivshmem.tar.gz -OutFile ivshmem.tar.gz
tar -xzvf ivshmem.tar.gz
cd LGIdd
makensis -DBUILD_32BIT -DIVSHMEM installer.nsi
obs:
runs-on: ubuntu-latest
strategy:
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v1
with:
submodules: recursive
- name: Update apt
@@ -227,8 +170,8 @@ jobs:
sudo apt-get update
- name: Install docs dependencies
run: |
sudo apt-get install python3-sphinx python3-sphinx-rtd-theme \
python3-sphinxcontrib.spelling
sudo apt-get install python3-sphinx
sudo pip3 install sphinxcontrib-spelling
- name: Build docs
run: |
cd doc

View File

@@ -3,36 +3,16 @@ on: pull_request
jobs:
authors:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v1
- name: Check AUTHORS file
id: check-authors
run: |
user="$(curl -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -s https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} | jq -r .user.login)"
echo "user=$user" >> "$GITHUB_OUTPUT"
echo "Checking if GitHub user $user is in AUTHORS file..."
if grep -q -E '> \('"$user"'\)' AUTHORS; then
echo "$user found in AUTHORS file, all good!"
else
echo "$user not found in AUTHORS file."
echo "Please add yourself to the AUTHORS file and try again."
echo "not-found=yes" >> "$GITHUB_OUTPUT"
exit 1
fi
- name: 'Not found: Create review requesting changes'
if: ${{ steps.check-authors.outputs.not-found }}
uses: actions/github-script@v7
with:
script: |
github.rest.pulls.createReview({
owner: context.issue.owner,
repo: context.issue.repo,
pull_number: context.issue.number,
event: "REQUEST_CHANGES",
body: "@${{ steps.check-authors.outputs.user }} not found in AUTHORS file.\n" +
"Please add yourself to the AUTHORS file and try again."
});
- name: 'Not found: Fail job'
if: ${{ steps.check-authors.outputs.not-found }}
run: exit 1

9
.gitignore vendored
View File

@@ -10,12 +10,3 @@ module/modules.order
*/build
__pycache__
*.py[co]
*/.vs
*.user
idd/Debug
idd/x64
idd/*/x64
idd/*/Debug
idd/*/VersionInfo.h
idd/*/VERSION
idd/packages

6
.gitmodules vendored
View File

@@ -7,9 +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://github.com/quantum5/wayland-protocols.git
[submodule "repos/nanosvg"]
path = repos/nanosvg
url = https://github.com/memononen/nanosvg.git

View File

@@ -1,34 +0,0 @@
---
labels:
platform: windows/amd64
matrix:
BUILD_TYPE:
- Debug
- Release
when:
- event: push
branch: master
repo: gnif/LookingGlass
clone:
- name: clone
image: woodpeckerci/plugin-git
pull: false
settings:
tags: true
steps:
- name: idd
image: lg-vs2022:latest
pull: true
environment:
VS_PATH: "\"C:\\\\Program Files (x86)\\\\Microsoft Visual Studio\\\\2022\\\\\""
entrypoint:
- cmd
- /C
- >
%VS_PATH%\BuildTools\Common7\Tools\VsDevCmd.bat -arch=amd64 &&
msbuild /restore idd\LGIdd.sln /p:Configuration=${BUILD_TYPE} /p:RestorePackagesConfig=true /p:Platform=x64 /p:SignMode=Off /m &&
IF EXIST C:\artifacts\build.cmd (cmd /C C:\artifacts\build.cmd)

22
AUTHORS
View File

@@ -9,7 +9,7 @@ arcnmx <arcnmx@users.noreply.github.com> (arcnmx)
TheCakeIsNaOH <TheCakeIsNaOH@gmail.com> (TheCakeIsNaOH)
NamoDev <namodev@users.noreply.github.com> (NamoDev)
feltcat <58396817+feltcat@users.noreply.github.com> (feltcat)
Ali Abdel-Qader <abdelqaderali@protonmail.com> (thrifty-txt)
Ali Abdel-Qader <abdelqaderali@protonmail.com>
Jack Karamanian <karamanian.jack@gmail.com>
Mikko Rasa <tdb@tdb.fi> (DataBeaver)
Omar Pakker <Omar007@users.noreply.github.com> (Omar007)
@@ -54,23 +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)
Matthew McMullin <matthew@mcmullin.one> (matthewjmc)
Leonard Fricke <leonard.fricke98@gmail.com> (Leo1998)
David Meier <meier_david_91@hotmail.com> (Kenny.ch)
Daniel Cordero <looking-glass@0xdc.io> (0xdc)
esi <git@esibun.net> (esibun)
MakiseKurisu <saberconer@gmail.com> (MakiseKurisu)
Zenithal <i@zenithal.me> (ZenithalHourlyRate)
Kamplom <6284968128@protonmail.ch> (kamplom)
Jacob McNamee <jacob@jacobmcnamee.com> (jacobmcnamee)
Marco Antonio J. Costa <marco.antonio.costa@gmail.com> (majcosta)
rs189 <35667100+rs189@users.noreply.github.com> (rs189)
Jérôme Poulin <jeromepoulin@gmail.com> (ticpu)
Marco Rodolfi <marco.rodolfi@tuta.io> (RodoMa92)
Stewart Borle <stewi1014@gmail.com> (stewi1014)

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

@@ -1,16 +1,16 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.0)
project(looking-glass-client C CXX)
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
message(FATAL_ERROR
"\n"
"In-source builds are not supported\n"
"See build instructions provided in: "
"${PROJECT_TOP}/doc/build.rst\n"
"Refusing to continue"
)
message(FATAL_ERROR
"\n"
"In-source builds are not supported\n"
"See build instructions provided in: "
"${PROJECT_TOP}/doc/build.rst\n"
"Refusing to continue"
)
endif()
list(APPEND CMAKE_MODULE_PATH "${PROJECT_TOP}/cmake/" "${PROJECT_SOURCE_DIR}/cmake/")
@@ -42,18 +42,24 @@ add_feature_info(ENABLE_ASAN ENABLE_ASAN "AddressSanitizer support.")
option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF)
add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.")
option(ENABLE_PIPEWIRE "Build with PipeWire audio output support" ON)
add_feature_info(ENABLE_PIPEWIRE ENABLE_PIPEWIRE "PipeWire audio support.")
option(ENABLE_X11 "Build with X11 support" ON)
add_feature_info(ENABLE_X11 ENABLE_X11 "X11 support.")
option(ENABLE_PULSEAUDIO "Build with PulseAudio audio output support" ON)
add_feature_info(ENABLE_PULSEAUDIO ENABLE_PULSEAUDIO "PulseAudio audio support.")
option(ENABLE_WAYLAND "Build with Wayland support" ON)
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.")
if (NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
message(FATAL_ERROR "Either ENABLE_X11 or ENABLE_WAYLAND must be on")
endif()
add_compile_options(
"-Wall"
"-Wextra"
"-Wno-sign-compare"
"-Wno-unused-parameter"
"$<$<COMPILE_LANGUAGE:C>:-Wstrict-prototypes>"
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
"-Werror"
"-Wfatal-errors"
@@ -88,61 +94,49 @@ add_definitions(-D ATOMIC_LOCKING)
add_definitions(-D GL_GLEXT_PROTOTYPES)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/version.c
${CMAKE_BINARY_DIR}/_version.c
COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P
${PROJECT_TOP}/version.cmake
OUTPUT ${CMAKE_BINARY_DIR}/version.c
${CMAKE_BINARY_DIR}/_version.c
COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P
${PROJECT_TOP}/version.cmake
)
include_directories(
${PROJECT_TOP}
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}/include
${PROJECT_TOP}/repos/nanosvg/src
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
link_libraries(
${CMAKE_DL_LIBS}
rt
m
${CMAKE_DL_LIBS}
rt
m
)
set(SOURCES
${CMAKE_BINARY_DIR}/version.c
src/main.c
src/core.c
src/app.c
src/message.c
src/audio.c
src/config.c
src/keybind.c
src/util.c
src/clipboard.c
src/kb.c
src/gl_dynprocs.c
src/egl_dynprocs.c
src/eglutil.c
src/overlay_utils.c
src/render_queue.c
src/evdev.c
${CMAKE_BINARY_DIR}/version.c
src/main.c
src/core.c
src/app.c
src/config.c
src/keybind.c
src/ll.c
src/util.c
src/clipboard.c
src/kb.c
src/gl_dynprocs.c
src/egl_dynprocs.c
src/eglutil.c
src/overlay_utils.c
src/overlay/splash.c
src/overlay/alert.c
src/overlay/fps.c
src/overlay/graphs.c
src/overlay/help.c
src/overlay/config.c
src/overlay/msg.c
src/overlay/status.c
src/overlay/alert.c
src/overlay/fps.c
src/overlay/graphs.c
src/overlay/help.c
src/overlay/config.c
)
# Force cimgui to build as a static library.
set(IMGUI_STATIC "yes" CACHE STRING "Build as a static library")
add_definitions("-DCIMGUI_USE_OPENGL2=1")
add_definitions("-DCIMGUI_USE_OPENGL3=1")
add_subdirectory("${PROJECT_TOP}/resources" "${CMAKE_BINARY_DIR}/resources")
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" )
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" )
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
@@ -151,41 +145,23 @@ add_subdirectory("${PROJECT_TOP}/repos/cimgui" "${CMAKE_BINARY_DIR}/cimgui" E
add_subdirectory(displayservers)
add_subdirectory(renderers)
configure_file("${PROJECT_TOP}/resources/looking-glass-client.desktop.in" "${CMAKE_BINARY_DIR}/resources/looking-glass-client.desktop" @ONLY)
add_executable(looking-glass-client ${SOURCES})
target_compile_definitions(looking-glass-client PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1)
target_link_libraries(looking-glass-client
${EXE_FLAGS}
PkgConfig::FONTCONFIG
lg_resources
lg_common
displayservers
lgmp
purespice
renderers
cimgui
${EXE_FLAGS}
PkgConfig::FONTCONFIG
lg_common
displayservers
lgmp
purespice
renderers
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)
install(FILES "${CMAKE_BINARY_DIR}/resources/looking-glass-client.desktop"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
install(FILES "${PROJECT_TOP}/resources/lg-logo.svg"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps"
RENAME "looking-glass.svg")
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT binary)
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)

View File

@@ -1,46 +0,0 @@
cmake_minimum_required(VERSION 3.10)
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.10)
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,647 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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"
#include "common/option.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;
struct pw_time time;
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 PW_CHECK_VERSION(0, 3, 50)
if (pw_stream_get_time_n(pw.playback.stream, &pw.playback.time,
sizeof(pw.playback.time)) < 0)
#else
if (pw_stream_get_time(pw.playback.stream, &pw.playback.time) < 0)
#endif
DEBUG_ERROR("pw_stream_get_time failed");
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;
}
pbuf->size = frames;
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 struct Option pipewire_options[] =
{
{
.module = "pipewire",
.name = "outDevice",
.description = "The default playback device to use",
.type = OPTION_TYPE_STRING
},
{
.module = "pipewire",
.name = "recDevice",
.description = "The default record device to use",
.type = OPTION_TYPE_STRING
},
{0}
};
static void pipewire_earlyInit(void)
{
option_register(pipewire_options);
}
static bool pipewire_init(void)
{
pw_init(NULL, NULL);
pw.loop = pw_loop_new(NULL);
#if PW_CHECK_VERSION(1, 3, 81)
pw.context = pw_context_new(
pw.loop,
NULL,
0);
#else
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);
#endif
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);
struct pw_properties * props =
pw_properties_new(
PW_KEY_APP_NAME , "Looking Glass",
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
);
const char * device = option_get_string("pipewire", "outDevice");
if (device)
{
#ifdef PW_KEY_TARGET_OBJECT
pw_properties_set(props, PW_KEY_TARGET_OBJECT, device);
#else
pw_properties_set(props, PW_KEY_NODE_TARGET, device);
#endif
}
pw.playback.stream = pw_stream_new_simple(
pw.loop,
"Looking Glass",
props,
&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 uint64_t pipewire_playbackLatency(void)
{
#if PW_CHECK_VERSION(0, 3, 50)
if (pw.playback.time.rate.num == 0)
return 0;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
// diff in ns
int64_t diff = SPA_TIMESPEC_TO_NSEC(&ts) - pw.playback.time.now;
// elapsed frames
int64_t elapsed =
(pw.playback.time.rate.denom * diff) /
(pw.playback.time.rate.num * SPA_NSEC_PER_SEC);
const int64_t buffered = pw.playback.time.buffered + pw.playback.time.queued;
int64_t latency = (buffered * 1000 / pw.playback.sampleRate) +
((pw.playback.time.delay - elapsed) * 1000 *
pw.playback.time.rate.num / pw.playback.time.rate.denom);
return max(0, -latency);
#else
return pw.playback.time.delay + pw.playback.time.queued / pw.playback.stride;
#endif
}
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;
struct pw_properties * props =
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
);
const char * device = option_get_string("pipewire", "recDevice");
if (device)
{
#ifdef PW_KEY_TARGET_OBJECT
pw_properties_set(props, PW_KEY_TARGET_OBJECT, device);
#else
pw_properties_set(props, PW_KEY_NODE_TARGET, device);
#endif
}
pw_thread_loop_lock(pw.thread);
pw.record.stream = pw_stream_new_simple(
pw.loop,
"Looking Glass",
props,
&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",
.earlyInit = pipewire_earlyInit,
.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.10)
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-2025 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

@@ -4,20 +4,17 @@ function(make_object out_var)
foreach(in_f ${ARGN})
file(RELATIVE_PATH out_f ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}")
set(out_h "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.h")
set(out_f "${out_f}.o")
set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.o")
string(REGEX REPLACE "[/.-]" "_" sym_in "${in_f}")
add_custom_command(OUTPUT ${out_f}
COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/${in_f} ${out_f}.tmp
COMMAND dd if=/dev/zero bs=1 count=1 of=${out_f}.tmp oflag=append conv=notrunc status=none
COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} ${out_f}.tmp
COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} "${in_f}"
COMMAND ${CMAKE_OBJCOPY} --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_o_tmp_start=b_${sym_in} ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_o_tmp_end=b_${sym_in}_end ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_o_tmp_size=b_${sym_in}_size ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_start=b_${sym_in} ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_end=b_${sym_in}_end ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --strip-symbol _binary_${sym_in}_size ${out_f} ${out_f}
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Creating object from ${in_f}"
VERBATIM
)
@@ -37,10 +34,9 @@ function(make_object out_var)
endfunction()
function(make_defines in_file out_file)
file(RELATIVE_PATH rel_f ${CMAKE_CURRENT_SOURCE_DIR} "${in_file}")
add_custom_command(OUTPUT ${out_file}
COMMAND grep "^#define" "${in_file}" > "${out_file}"
DEPENDS ${in_file}
COMMENT "Creating #defines from ${rel_f}"
COMMENT "Creating #defines from ${in_file}"
)
endfunction()

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.0)
project(displayservers LANGUAGES C)
set(DISPLAYSERVER_H "${CMAKE_BINARY_DIR}/include/dynamic/displayservers.h")
@@ -18,16 +18,6 @@ function(add_displayserver name)
add_subdirectory(${name})
endfunction()
option(ENABLE_X11 "Build with X11 support" ON)
add_feature_info(ENABLE_X11 ENABLE_X11 "X11 support.")
option(ENABLE_WAYLAND "Build with Wayland support" ON)
add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
if (NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
message(FATAL_ERROR "Either ENABLE_X11 or ENABLE_WAYLAND must be on")
endif()
# Add/remove displayservers here!
if (ENABLE_WAYLAND)
add_displayserver(Wayland)

View File

@@ -1,42 +1,98 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.0)
project(displayserver_Wayland LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(DISPLAYSERVER_Wayland REQUIRED IMPORTED_TARGET
wayland-client
wayland-cursor
xkbcommon
wayland-client
wayland-cursor
xkbcommon
)
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
set(DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES "")
set(displayserver_Wayland_SHELL_SRC "")
if (ENABLE_LIBDECOR)
pkg_check_modules(DISPLAYSERVER_Wayland_LIBDECOR REQUIRED IMPORTED_TARGET
libdecor-0
)
list(APPEND DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES PkgConfig::DISPLAYSERVER_Wayland_LIBDECOR)
list(APPEND displayserver_Wayland_SHELL_SRC shell_libdecor.c)
add_compile_definitions(ENABLE_LIBDECOR)
else()
list(APPEND displayserver_Wayland_SHELL_SRC shell_xdg.c)
endif()
add_library(displayserver_Wayland STATIC
activation.c
clipboard.c
cursor.c
gl.c
idle.c
input.c
output.c
poll.c
presentation.c
state.c
registry.c
wayland.c
window.c
clipboard.c
cursor.c
gl.c
idle.c
input.c
output.c
poll.c
presentation.c
state.c
registry.c
wayland.c
window.c
${displayserver_Wayland_SHELL_SRC}
)
add_subdirectory(protocol)
add_subdirectory(desktops)
target_link_libraries(displayserver_Wayland
PkgConfig::DISPLAYSERVER_Wayland
lg_common
wayland_protocol
wayland_desktops
PkgConfig::DISPLAYSERVER_Wayland
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES}
lg_common
)
target_include_directories(displayserver_Wayland
PRIVATE
.
PRIVATE
src
)
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15)
pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir)
macro(wayland_generate protocol_file output_file)
add_custom_command(OUTPUT "${output_file}.h"
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h"
DEPENDS "${protocol_file}"
VERBATIM)
add_custom_command(OUTPUT "${output_file}.c"
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c"
DEPENDS "${protocol_file}"
VERBATIM)
target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c")
endmacro()
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland")
include_directories("${CMAKE_BINARY_DIR}/wayland")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/stable/presentation-time/presentation-time.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-presentation-time-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/stable/viewporter/viewporter.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-viewporter-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-output/xdg-output-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-output-unstable-v1-client-protocol")

View File

@@ -1,71 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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
@@ -376,7 +376,7 @@ static void clipboardReadCancel(struct ClipboardRead * data)
static void clipboardReadCallback(uint32_t events, void * opaque)
{
struct ClipboardRead * data = opaque;
if (events & EPOLLERR)
if (events & EPOLLERR)
{
clipboardReadCancel(data);
return;
@@ -475,7 +475,6 @@ void waylandCBRequest(LG_ClipboardData type)
close(data->fd);
free(data->buf);
free(data);
return;
}
wlCb.currentRead = data;
@@ -513,13 +512,6 @@ error:
free(data);
}
static void dataSourceHandleTarget(void * data, struct wl_data_source * source,
const char * mimetype)
{
// Certain Wayland clients send this for copy-paste operations even though
// it only makes sense for drag-and-drop. We just do nothing.
}
static void dataSourceHandleSend(void * data, struct wl_data_source * source,
const char * mimetype, int fd)
{
@@ -555,8 +547,7 @@ static void dataSourceHandleCancelled(void * data,
}
static const struct wl_data_source_listener dataSourceListener = {
.target = dataSourceHandleTarget,
.send = dataSourceHandleSend,
.send = dataSourceHandleSend,
.cancelled = dataSourceHandleCancelled,
};

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,58 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(wayland_desktops LANGUAGES C)
set(DESKTOP_H "${CMAKE_BINARY_DIR}/include/dynamic/wayland_desktops.h")
set(DESKTOP_C "${CMAKE_BINARY_DIR}/src/wayland_desktops.c")
file(WRITE ${DESKTOP_H} "#include \"interface/desktop.h\"\n\n")
file(APPEND ${DESKTOP_H} "extern struct WL_DesktopOps * WL_Desktops[];\n\n")
file(WRITE ${DESKTOP_C} "#include \"interface/desktop.h\"\n\n")
file(APPEND ${DESKTOP_C} "#include <stddef.h>\n\n")
set(DESKTOPS "_")
set(DESKTOPS_LINK "_")
function(add_desktop name)
set(DESKTOPS "${DESKTOPS};${name}" PARENT_SCOPE)
set(DESKTOPS_LINK "${DESKTOPS_LINK};wayland_desktop_${name}" PARENT_SCOPE)
add_subdirectory(${name})
endfunction()
# Add/remove desktops here!
# the first entry here is the default
add_desktop(xdg)
pkg_check_modules(LIBDECOR IMPORTED_TARGET libdecor-0)
if(LIBDECOR_FOUND)
option(ENABLE_LIBDECOR "Build with libdecor support" ON)
else()
option(ENABLE_LIBDECOR "Build with libdecor support" OFF)
endif()
add_feature_info(ENABLE_LIBDECOR ENABLE_LIBDECOR "libdecor support.")
if (ENABLE_LIBDECOR)
add_desktop(libdecor)
endif()
list(REMOVE_AT DESKTOPS 0)
list(REMOVE_AT DESKTOPS_LINK 0)
list(LENGTH DESKTOPS DESKTOP_COUNT)
file(APPEND ${DESKTOP_H} "#define WL_DESKTOP_COUNT ${DESKTOP_COUNT}\n")
foreach(desktop ${DESKTOPS})
file(APPEND ${DESKTOP_C} "extern struct WL_DesktopOps WLD_${desktop};\n")
endforeach()
file(APPEND ${DESKTOP_C} "\nconst struct WL_DesktopOps * WL_Desktops[] =\n{\n")
foreach(desktop ${DESKTOPS})
file(APPEND ${DESKTOP_C} " &WLD_${desktop},\n")
endforeach()
file(APPEND ${DESKTOP_C} " NULL\n};")
add_library(wayland_desktops STATIC ${DESKTOP_C})
target_link_libraries(wayland_desktops ${DESKTOPS_LINK})
target_include_directories(wayland_desktops
PRIVATE
../
)

View File

@@ -1,16 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(wayland_desktop_libdecor LANGUAGES C)
add_library(wayland_desktop_libdecor STATIC
libdecor.c
)
target_link_libraries(wayland_desktop_libdecor
lg_common
wayland_protocol
PkgConfig::LIBDECOR
)
include_directories(
"../../"
)

View File

@@ -1,281 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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/desktop.h"
#include "wayland-xdg-shell-client-protocol.h"
#include "wayland.h"
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <sys/epoll.h>
#include <libdecor.h>
#include <wayland-client.h>
#include "app.h"
#include "common/debug.h"
// Maximum number of fds we can process at once in waylandWait
#define MAX_EPOLL_EVENTS 10
typedef struct LibDecorState
{
bool configured;
struct libdecor * libdecor;
struct libdecor_frame * libdecorFrame;
int32_t width, height;
bool needsResize;
bool fullscreen;
bool borderless;
uint32_t resizeSerial;
}
LibDecorState;
static LibDecorState state = {0};
struct libdecor_configuration
{
uint32_t serial;
bool has_window_state;
enum libdecor_window_state window_state;
bool has_size;
int window_width;
int window_height;
};
static void libdecorHandleError(struct libdecor * context, enum libdecor_error error,
const char *message)
{
DEBUG_ERROR("Got libdecor error (%d): %s", error, message);
}
static void libdecorFrameConfigure(struct libdecor_frame * frame,
struct libdecor_configuration * configuration, void * opaque)
{
if (!state.configured)
{
xdg_surface_ack_configure(libdecor_frame_get_xdg_surface(frame), configuration->serial);
state.configured = true;
return;
}
int width, height;
if (libdecor_configuration_get_content_size(configuration, frame, &width, &height))
{
state.width = width;
state.height = height;
struct libdecor_state * s = libdecor_state_new(width, height);
libdecor_frame_commit(state.libdecorFrame, s, NULL);
libdecor_state_free(s);
}
enum libdecor_window_state windowState;
if (libdecor_configuration_get_window_state(configuration, &windowState))
state.fullscreen = windowState & LIBDECOR_WINDOW_STATE_FULLSCREEN;
state.resizeSerial = configuration->serial;
waylandNeedsResize();
}
static void libdecorFrameClose(struct libdecor_frame * frame, void * opaque)
{
app_handleCloseEvent();
}
static void libdecorFrameCommit(struct libdecor_frame * frame, void * opaque)
{
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
static struct libdecor_interface libdecorListener =
{
libdecorHandleError,
};
static struct libdecor_frame_interface libdecorFrameListener =
{
libdecorFrameConfigure,
libdecorFrameClose,
libdecorFrameCommit,
};
#pragma GCC diagnostic pop
static void libdecorCallback(uint32_t events, void * opaque)
{
libdecor_dispatch(state.libdecor, 0);
}
static bool libdecor_shellInit(
struct wl_display * display, struct wl_surface * surface,
const char * title, const char * appId, bool fullscreen,
bool maximize, bool borderless, bool resizable)
{
state.libdecor = libdecor_new(display, &libdecorListener);
state.libdecorFrame = libdecor_decorate(state.libdecor, surface,
&libdecorFrameListener, NULL);
libdecor_frame_set_app_id(state.libdecorFrame, appId);
libdecor_frame_set_title(state.libdecorFrame, title);
libdecor_frame_map(state.libdecorFrame);
if (fullscreen)
libdecor_frame_set_fullscreen(state.libdecorFrame, NULL);
if (maximize)
libdecor_frame_set_maximized(state.libdecorFrame);
if (borderless)
libdecor_frame_set_visibility(state.libdecorFrame, false);
state.borderless = borderless;
if (resizable)
libdecor_frame_set_capabilities(state.libdecorFrame,
LIBDECOR_ACTION_RESIZE);
else
libdecor_frame_unset_capabilities(state.libdecorFrame,
LIBDECOR_ACTION_RESIZE);
while (!state.configured)
libdecor_dispatch(state.libdecor, 0);
if (!waylandPollRegister(libdecor_get_fd(state.libdecor),
libdecorCallback, NULL, EPOLLIN))
{
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
return false;
}
return true;
}
static void libdecor_shellAckConfigureIfNeeded(void)
{
if (state.resizeSerial)
{
xdg_surface_ack_configure(
libdecor_frame_get_xdg_surface(state.libdecorFrame), state.resizeSerial);
state.resizeSerial = 0;
}
}
static void libdecor_setFullscreen(bool fs)
{
if (fs)
libdecor_frame_set_fullscreen(state.libdecorFrame, NULL);
else
libdecor_frame_unset_fullscreen(state.libdecorFrame);
if (!state.borderless)
libdecor_frame_set_visibility(state.libdecorFrame, !fs);
}
static bool libdecor_getFullscreen(void)
{
return state.fullscreen;
}
static void libdecor_minimize(void)
{
libdecor_frame_set_minimized(state.libdecorFrame);
}
static void libdecor_shellResize(int w, int h)
{
if (!libdecor_frame_is_floating(state.libdecorFrame))
return;
state.width = w;
state.height = h;
struct libdecor_state * s = libdecor_state_new(w, h);
libdecor_frame_commit(state.libdecorFrame, s, NULL);
libdecor_state_free(s);
waylandNeedsResize();
}
static void libdecor_setSize(int w, int h)
{
state.width = w;
state.height = h;
}
static void libdecor_getSize(int * w, int * h)
{
*w = state.width;
*h = state.height;
}
static bool libdecor_registryGlobalHandler(void * data,
struct wl_registry * registry, uint32_t name, const char * interface,
uint32_t version)
{
return false;
}
bool libdecor_pollInit(struct wl_display * display)
{
return true;
}
void libdecor_pollWait(struct wl_display * display, int epollFd,
unsigned int time)
{
libdecor_dispatch(state.libdecor, 0);
struct epoll_event events[MAX_EPOLL_EVENTS];
int count;
if ((count = epoll_wait(epollFd, events, MAX_EPOLL_EVENTS, time)) < 0)
{
if (errno != EINTR)
DEBUG_INFO("epoll failed: %s", strerror(errno));
return;
}
for (int i = 0; i < count; ++i)
{
struct WaylandPoll * poll = events[i].data.ptr;
if (!poll->removed)
poll->callback(events[i].events, poll->opaque);
}
}
WL_DesktopOps WLD_libdecor =
{
.name = "libdecor",
.compositor = "gnome-shell",
.shellInit = libdecor_shellInit,
.shellAckConfigureIfNeeded = libdecor_shellAckConfigureIfNeeded,
.setFullscreen = libdecor_setFullscreen,
.getFullscreen = libdecor_getFullscreen,
.minimize = libdecor_minimize,
.shellResize = libdecor_shellResize,
.setSize = libdecor_setSize,
.getSize = libdecor_getSize,
.registryGlobalHandler = libdecor_registryGlobalHandler,
.pollInit = libdecor_pollInit,
.pollWait = libdecor_pollWait
};

View File

@@ -1,15 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(wayland_desktop_xdg LANGUAGES C)
add_library(wayland_desktop_xdg STATIC
xdg.c
)
target_link_libraries(wayland_desktop_xdg
lg_common
wayland_protocol
)
include_directories(
"../../"
)

View File

@@ -1,322 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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 "wayland-xdg-shell-client-protocol.h"
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include <stdbool.h>
#include <string.h>
#include <sys/epoll.h>
#include <errno.h>
#include <wayland-client.h>
#include "app.h"
#include "common/debug.h"
// Maximum number of fds we can process at once in waylandWait
#define MAX_EPOLL_EVENTS 10
typedef struct XDGState
{
bool configured;
struct xdg_wm_base * wmBase;
struct xdg_surface * surface;
struct xdg_toplevel * toplevel;
struct zxdg_decoration_manager_v1 * decorationManager;
struct zxdg_toplevel_decoration_v1 * toplevelDecoration;
int32_t width, height;
uint32_t resizeSerial;
bool fullscreen;
bool floating;
bool resizable;
int displayFd;
}
XDGState;
static XDGState state = {0};
// XDG WM base listeners.
static void xdgWmBasePing(void * data, struct xdg_wm_base * xdgWmBase, uint32_t serial)
{
xdg_wm_base_pong(xdgWmBase, serial);
}
static const struct xdg_wm_base_listener xdgWmBaseListener = {
.ping = xdgWmBasePing,
};
// XDG Surface listeners.
static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface,
uint32_t serial)
{
if (state.configured)
{
state.resizeSerial = serial;
waylandNeedsResize();
}
else
{
xdg_surface_ack_configure(xdgSurface, serial);
state.configured = true;
}
}
static const struct xdg_surface_listener xdgSurfaceListener = {
.configure = xdgSurfaceConfigure,
};
// XDG Toplevel listeners.
static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel,
int32_t width, int32_t height, struct wl_array * states)
{
state.width = width;
state.height = height;
state.fullscreen = false;
state.floating = true;
enum xdg_toplevel_state * s;
wl_array_for_each(s, states)
{
switch (*s)
{
case XDG_TOPLEVEL_STATE_FULLSCREEN:
state.fullscreen = true;
// fallthrough
case XDG_TOPLEVEL_STATE_MAXIMIZED:
case XDG_TOPLEVEL_STATE_TILED_LEFT:
case XDG_TOPLEVEL_STATE_TILED_RIGHT:
case XDG_TOPLEVEL_STATE_TILED_TOP:
case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
state.floating = false;
break;
default:
break;
}
}
}
static void xdgToplevelClose(void * data, struct xdg_toplevel * xdgToplevel)
{
app_handleCloseEvent();
}
static const struct xdg_toplevel_listener xdgToplevelListener = {
.configure = xdgToplevelConfigure,
.close = xdgToplevelClose,
};
bool xdg_shellInit(struct wl_display * display, struct wl_surface * surface,
const char * title, const char * appId, bool fullscreen, bool maximize, bool borderless,
bool resizable)
{
if (!state.wmBase)
{
DEBUG_ERROR("Compositor missing xdg_wm_base, will not proceed");
return false;
}
xdg_wm_base_add_listener(state.wmBase, &xdgWmBaseListener, NULL);
state.surface = xdg_wm_base_get_xdg_surface(state.wmBase, surface);
xdg_surface_add_listener(state.surface, &xdgSurfaceListener, NULL);
state.toplevel = xdg_surface_get_toplevel(state.surface);
xdg_toplevel_add_listener(state.toplevel, &xdgToplevelListener, NULL);
xdg_toplevel_set_title(state.toplevel, title);
xdg_toplevel_set_app_id(state.toplevel, appId);
if (fullscreen)
xdg_toplevel_set_fullscreen(state.toplevel, NULL);
if (maximize)
xdg_toplevel_set_maximized(state.toplevel);
if (!resizable)
{
xdg_toplevel_set_min_size(state.toplevel, state.width, state.height);
xdg_toplevel_set_max_size(state.toplevel, state.width, state.height);
}
state.resizable = resizable;
if (state.decorationManager)
{
state.toplevelDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
state.decorationManager, state.toplevel);
if (state.toplevelDecoration)
{
zxdg_toplevel_decoration_v1_set_mode(state.toplevelDecoration,
borderless ?
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE :
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
}
return true;
}
static void xdg_shellAckConfigureIfNeeded(void)
{
if (state.resizeSerial)
{
xdg_surface_ack_configure(state.surface, state.resizeSerial);
state.resizeSerial = 0;
}
}
static void xdg_setFullscreen(bool fs)
{
if (fs)
xdg_toplevel_set_fullscreen(state.toplevel, NULL);
else
xdg_toplevel_unset_fullscreen(state.toplevel);
}
static bool xdg_getFullscreen(void)
{
return state.fullscreen;
}
static void xdg_minimize(void)
{
xdg_toplevel_set_minimized(state.toplevel);
}
static void xdg_shellResize(int w, int h)
{
if (!state.floating || !state.resizable)
return;
state.width = w;
state.height = h;
xdg_surface_set_window_geometry(state.surface, 0, 0, w, h);
waylandNeedsResize();
}
static void xdg_setSize(int w, int h)
{
state.width = w;
state.height = h;
}
static void xdg_getSize(int * w, int * h)
{
*w = state.width;
*h = state.height;
}
static bool xdg_registryGlobalHandler(void * data,
struct wl_registry * registry, uint32_t name, const char * interface,
uint32_t version)
{
if (!strcmp(interface, xdg_wm_base_interface.name))
{
state.wmBase = wl_registry_bind(registry, name,
&xdg_wm_base_interface, 1);
return true;
}
if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name))
{
state.decorationManager = wl_registry_bind(registry, name,
&zxdg_decoration_manager_v1_interface, 1);
return true;
}
return false;
}
static void waylandDisplayCallback(uint32_t events, void * opaque)
{
struct wl_display * display = (struct wl_display *)opaque;
if (events & EPOLLERR)
wl_display_cancel_read(display);
else
wl_display_read_events(display);
wl_display_dispatch_pending(display);
}
static bool xdg_pollInit(struct wl_display * display)
{
state.displayFd = wl_display_get_fd(display);
if (!waylandPollRegister(state.displayFd, waylandDisplayCallback,
display, EPOLLIN))
{
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
return false;
}
return true;
}
void xdg_pollWait(struct wl_display * display, int epollFd,
unsigned int time)
{
while (wl_display_prepare_read(display))
wl_display_dispatch_pending(display);
wl_display_flush(display);
struct epoll_event events[MAX_EPOLL_EVENTS];
int count;
if ((count = epoll_wait(epollFd, events, MAX_EPOLL_EVENTS, time)) < 0)
{
if (errno != EINTR)
DEBUG_INFO("epoll failed: %s", strerror(errno));
wl_display_cancel_read(display);
return;
}
bool sawDisplay = false;
for (int i = 0; i < count; ++i) {
struct WaylandPoll * poll = events[i].data.ptr;
if (!poll->removed)
poll->callback(events[i].events, poll->opaque);
if (poll->fd == state.displayFd)
sawDisplay = true;
}
if (!sawDisplay)
wl_display_cancel_read(display);
}
WL_DesktopOps WLD_xdg =
{
.name = "xdg",
.compositor = "",
.shellInit = xdg_shellInit,
.shellAckConfigureIfNeeded = xdg_shellAckConfigureIfNeeded,
.setFullscreen = xdg_setFullscreen,
.getFullscreen = xdg_getFullscreen,
.minimize = xdg_minimize,
.shellResize = xdg_shellResize,
.setSize = xdg_setSize,
.getSize = xdg_getSize,
.registryGlobalHandler = xdg_registryGlobalHandler,
.pollInit = xdg_pollInit,
.pollWait = xdg_pollWait
};

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -88,26 +88,16 @@ void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct
if (wlWm.needsResize)
{
bool skipResize = false;
wl_egl_window_resize(wlWm.eglWindow, wl_fixed_to_int(wlWm.width * wlWm.scale),
wl_fixed_to_int(wlWm.height * wlWm.scale), 0, 0);
int width, height;
wlWm.desktop->getSize(&width, &height);
wl_egl_window_resize(wlWm.eglWindow, wl_fixed_to_int(width * wlWm.scale),
wl_fixed_to_int(height * wlWm.scale), 0, 0);
if (width == 0 || height == 0)
skipResize = true;
else if (wlWm.fractionalScale)
if (wlWm.fractionalScale)
{
wl_surface_set_buffer_scale(wlWm.surface, 1);
if (!wlWm.viewport)
wlWm.viewport = wp_viewporter_get_viewport(wlWm.viewporter, wlWm.surface);
wp_viewport_set_source(
wlWm.viewport,
wl_fixed_from_int(-1), wl_fixed_from_int(-1),
wl_fixed_from_int(-1), wl_fixed_from_int(-1)
);
wp_viewport_set_destination(wlWm.viewport, width, height);
wp_viewport_set_source(wlWm.viewport, 0, 0, wlWm.width * wlWm.scale, wlWm.height * wlWm.scale);
wp_viewport_set_destination(wlWm.viewport, wlWm.width, wlWm.height);
}
else
{
@@ -127,18 +117,18 @@ void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct
}
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
wl_region_add(region, 0, 0, width, height);
wl_region_add(region, 0, 0, wlWm.width, wlWm.height);
wl_surface_set_opaque_region(wlWm.surface, region);
wl_region_destroy(region);
app_handleResizeEvent(width, height, wl_fixed_to_double(wlWm.scale),
app_handleResizeEvent(wlWm.width, wlWm.height, wl_fixed_to_double(wlWm.scale),
(struct Border) {0, 0, 0, 0});
app_invalidateWindow(true);
waylandStopWaitFrame();
wlWm.needsResize = skipResize;
wlWm.needsResize = false;
}
wlWm.desktop->shellAckConfigureIfNeeded();
waylandShellAckConfigureIfNeeded();
}
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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
@@ -209,17 +209,6 @@ done:
close(fd);
}
int waylandGetCharCode(int key)
{
key += 8; // xkb scancode is evdev scancode + 8
xkb_keysym_t sym = xkb_state_key_get_one_sym(wlWm.xkbState, key);
if (sym == XKB_KEY_NoSymbol)
return 0;
sym = xkb_keysym_to_upper(sym);
return xkb_keysym_to_utf32(sym);
}
static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
{
@@ -465,7 +454,7 @@ void waylandGrabPointer(void)
INTERLOCKED_SECTION(wlWm.confineLock,
{
if (!wlWm.confinedPointer && !wlWm.lockedPointer)
if (!wlWm.confinedPointer)
{
wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
@@ -590,13 +579,10 @@ void waylandWarpPointer(int x, int y, bool exiting)
return;
}
int width, height;
wlWm.desktop->getSize(&width, &height);
if (x < 0) x = 0;
else if (x >= width) x = width - 1;
else if (x >= wlWm.width) x = wlWm.width - 1;
if (y < 0) y = 0;
else if (y >= height) y = height - 1;
else if (y >= wlWm.height) y = wlWm.height - 1;
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
wl_region_add(region, x, y, 1, 1);
@@ -630,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,64 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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_WAYLAND_DESKTOP_H_
#define _H_WAYLAND_DESKTOP_H_
#include <stdbool.h>
#include <wayland-client.h>
typedef struct WL_DesktopOps
{
// the friendly name
const char * name;
// the compositor process name to match
const char * compositor;
bool (*shellInit)(
struct wl_display * display, struct wl_surface * surface,
const char * title, const char * appId, bool fullscreen, bool maximize,
bool borderless, bool resizable);
void (*shellAckConfigureIfNeeded)(void);
void (*setFullscreen)(bool fs);
bool (*getFullscreen)(void);
void (*minimize)(void);
void (*shellResize)(int w, int h);
void (*setSize)(int w, int h);
void (*getSize)(int * w, int * h);
bool (*registryGlobalHandler)(
void * data, struct wl_registry * registry,
uint32_t name, const char * interface, uint32_t version);
bool (*pollInit)(struct wl_display * display);
void (*pollWait)(struct wl_display * display, int epollFd, unsigned int time);
}
WL_DesktopOps;
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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
@@ -30,6 +30,23 @@
#include "common/debug.h"
#include "common/locking.h"
#ifdef ENABLE_LIBDECOR
#include <libdecor.h>
#endif
#define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait
#ifndef ENABLE_LIBDECOR
static void waylandDisplayCallback(uint32_t events, void * opaque)
{
if (events & EPOLLERR)
wl_display_cancel_read(wlWm.display);
else
wl_display_read_events(wlWm.display);
wl_display_dispatch_pending(wlWm.display);
}
#endif
bool waylandPollInit(void)
{
wlWm.epollFd = epoll_create1(EPOLL_CLOEXEC);
@@ -44,12 +61,58 @@ bool waylandPollInit(void)
LG_LOCK_INIT(wlWm.pollLock);
LG_LOCK_INIT(wlWm.pollFreeLock);
return wlWm.desktop->pollInit(wlWm.display);
#ifndef ENABLE_LIBDECOR
wlWm.displayFd = wl_display_get_fd(wlWm.display);
if (!waylandPollRegister(wlWm.displayFd, waylandDisplayCallback, NULL, EPOLLIN))
{
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
return false;
}
#endif
return true;
}
void waylandWait(unsigned int time)
{
wlWm.desktop->pollWait(wlWm.display, wlWm.epollFd, time);
#ifdef ENABLE_LIBDECOR
libdecor_dispatch(wlWm.libdecor, 0);
#else
while (wl_display_prepare_read(wlWm.display))
wl_display_dispatch_pending(wlWm.display);
wl_display_flush(wlWm.display);
#endif
struct epoll_event events[EPOLL_EVENTS];
int count;
if ((count = epoll_wait(wlWm.epollFd, events, EPOLL_EVENTS, time)) < 0)
{
if (errno != EINTR)
DEBUG_INFO("epoll failed: %s", strerror(errno));
#ifndef ENABLE_LIBDECOR
wl_display_cancel_read(wlWm.display);
#endif
return;
}
#ifndef ENABLE_LIBDECOR
bool sawDisplay = false;
#endif
for (int i = 0; i < count; ++i) {
struct WaylandPoll * poll = events[i].data.ptr;
if (!poll->removed)
poll->callback(events[i].events, poll->opaque);
#ifndef ENABLE_LIBDECOR
if (poll->fd == wlWm.displayFd)
sawDisplay = true;
#endif
}
#ifndef ENABLE_LIBDECOR
if (!sawDisplay)
wl_display_cancel_read(wlWm.display);
#endif
INTERLOCKED_SECTION(wlWm.pollFreeLock,
{
struct WaylandPoll * node;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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;
@@ -109,17 +108,10 @@ void waylandPresentationFrame(void)
return;
struct FrameData * data = malloc(sizeof(*data));
if (!data)
{
DEBUG_ERROR("out of memory");
return;
}
if (clock_gettime(wlWm.clkId, &data->sent))
{
DEBUG_ERROR("clock_gettime failed: %s\n", strerror(errno));
free(data);
return;
}
struct wp_presentation_feedback * feedback = wp_presentation_feedback(wlWm.presentation, wlWm.surface);

View File

@@ -1,71 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(wayland_protocol LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(WAYLAND REQUIRED IMPORTED_TARGET
wayland-client
wayland-cursor
xkbcommon
)
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
add_library(wayland_protocol STATIC
)
macro(wayland_generate protocol_file output_file)
add_custom_command(OUTPUT "${output_file}.h"
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h"
DEPENDS "${protocol_file}"
VERBATIM)
add_custom_command(OUTPUT "${output_file}.c"
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c"
DEPENDS "${protocol_file}"
VERBATIM)
target_sources(wayland_protocol 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(
"${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/stable/presentation-time/presentation-time.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-presentation-time-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/stable/viewporter/viewporter.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-viewporter-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol")
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")
target_link_libraries(wayland_protocol
PkgConfig::WAYLAND
)
target_include_directories(wayland_protocol
PUBLIC
"${CMAKE_BINARY_DIR}/wayland"
)

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -40,6 +40,13 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry,
wlWm.compositor = wl_registry_bind(wlWm.registry, name,
// we only need v3 to run, but v4 can use eglSwapBuffersWithDamageKHR
&wl_compositor_interface, version > 4 ? 4 : version);
#ifndef ENABLE_LIBDECOR
else if (!strcmp(interface, xdg_wm_base_interface.name))
wlWm.xdgWmBase = wl_registry_bind(wlWm.registry, name, &xdg_wm_base_interface, 1);
else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name))
wlWm.xdgDecorationManager = wl_registry_bind(wlWm.registry, name,
&zxdg_decoration_manager_v1_interface, 1);
#endif
else if (!strcmp(interface, wp_presentation_interface.name))
wlWm.presentation = wl_registry_bind(wlWm.registry, name,
&wp_presentation_interface, 1);
@@ -65,12 +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);
else if (wlWm.desktop->registryGlobalHandler(
data, registry, name, interface, version))
return;
}
static void registryGlobalRemoveHandler(void * data,

View File

@@ -0,0 +1,178 @@
/**
* Looking Glass
* Copyright © 2017-2021 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 <errno.h>
#include <stdbool.h>
#include <string.h>
#include <sys/epoll.h>
#include <libdecor.h>
#include <wayland-client.h>
#include "app.h"
#include "common/debug.h"
struct libdecor_configuration {
uint32_t serial;
bool has_window_state;
enum libdecor_window_state window_state;
bool has_size;
int window_width;
int window_height;
};
static void libdecorHandleError(struct libdecor * context, enum libdecor_error error,
const char *message)
{
DEBUG_ERROR("Got libdecor error (%d): %s", error, message);
}
static void libdecorFrameConfigure(struct libdecor_frame * frame,
struct libdecor_configuration * configuration, void * opaque)
{
if (!wlWm.configured)
{
xdg_surface_ack_configure(libdecor_frame_get_xdg_surface(frame), configuration->serial);
wlWm.configured = true;
return;
}
int width, height;
if (libdecor_configuration_get_content_size(configuration, frame, &width, &height))
{
wlWm.width = width;
wlWm.height = height;
struct libdecor_state * state = libdecor_state_new(wlWm.width, wlWm.height);
libdecor_frame_commit(wlWm.libdecorFrame, state, NULL);
libdecor_state_free(state);
}
enum libdecor_window_state windowState;
if (libdecor_configuration_get_window_state(configuration, &windowState))
wlWm.fullscreen = windowState & LIBDECOR_WINDOW_STATE_FULLSCREEN;
wlWm.needsResize = true;
wlWm.resizeSerial = configuration->serial;
app_invalidateWindow(true);
waylandStopWaitFrame();
}
static void libdecorFrameClose(struct libdecor_frame * frame, void * opaque)
{
app_handleCloseEvent();
}
static void libdecorFrameCommit(struct libdecor_frame * frame, void * opaque)
{
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
static struct libdecor_interface libdecorListener = {
libdecorHandleError,
};
static struct libdecor_frame_interface libdecorFrameListener = {
libdecorFrameConfigure,
libdecorFrameClose,
libdecorFrameCommit,
};
#pragma GCC diagnostic pop
static void libdecorCallback(uint32_t events, void * opaque)
{
libdecor_dispatch(wlWm.libdecor, 0);
}
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable)
{
wlWm.libdecor = libdecor_new(wlWm.display, &libdecorListener);
wlWm.libdecorFrame = libdecor_decorate(wlWm.libdecor, wlWm.surface, &libdecorFrameListener, NULL);
libdecor_frame_set_app_id(wlWm.libdecorFrame, "looking-glass-client");
libdecor_frame_set_title(wlWm.libdecorFrame, title);
libdecor_frame_map(wlWm.libdecorFrame);
if (resizable)
libdecor_frame_set_capabilities(wlWm.libdecorFrame, LIBDECOR_ACTION_RESIZE);
else
libdecor_frame_unset_capabilities(wlWm.libdecorFrame, LIBDECOR_ACTION_RESIZE);
while (!wlWm.configured)
libdecor_dispatch(wlWm.libdecor, 0);
if (!waylandPollRegister(libdecor_get_fd(wlWm.libdecor), libdecorCallback, NULL, EPOLLIN))
{
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
return false;
}
return true;
}
void waylandShellAckConfigureIfNeeded(void)
{
if (wlWm.resizeSerial)
{
xdg_surface_ack_configure(libdecor_frame_get_xdg_surface(wlWm.libdecorFrame), wlWm.resizeSerial);
wlWm.resizeSerial = 0;
}
}
void waylandSetFullscreen(bool fs)
{
if (fs)
libdecor_frame_set_fullscreen(wlWm.libdecorFrame, NULL);
else
libdecor_frame_unset_fullscreen(wlWm.libdecorFrame);
libdecor_frame_set_visibility(wlWm.libdecorFrame, !fs);
}
bool waylandGetFullscreen(void)
{
return wlWm.fullscreen;
}
void waylandMinimize(void)
{
libdecor_frame_set_minimized(wlWm.libdecorFrame);
}
void waylandShellResize(int w, int h)
{
if (!libdecor_frame_is_floating(wlWm.libdecorFrame))
return;
wlWm.width = w;
wlWm.height = h;
struct libdecor_state * state = libdecor_state_new(w, h);
libdecor_frame_commit(wlWm.libdecorFrame, state, NULL);
libdecor_state_free(state);
wlWm.needsResize = true;
app_invalidateWindow(true);
waylandStopWaitFrame();
}

View File

@@ -0,0 +1,160 @@
/**
* Looking Glass
* Copyright © 2017-2021 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 "app.h"
#include "common/debug.h"
// XDG WM base listeners.
static void xdgWmBasePing(void * data, struct xdg_wm_base * xdgWmBase, uint32_t serial)
{
xdg_wm_base_pong(xdgWmBase, serial);
}
static const struct xdg_wm_base_listener xdgWmBaseListener = {
.ping = xdgWmBasePing,
};
// XDG Surface listeners.
static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface,
uint32_t serial)
{
if (wlWm.configured)
{
wlWm.needsResize = true;
wlWm.resizeSerial = serial;
app_invalidateWindow(true);
waylandStopWaitFrame();
}
else
{
xdg_surface_ack_configure(xdgSurface, serial);
wlWm.configured = true;
}
}
static const struct xdg_surface_listener xdgSurfaceListener = {
.configure = xdgSurfaceConfigure,
};
// XDG Toplevel listeners.
static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel,
int32_t width, int32_t height, struct wl_array * states)
{
wlWm.width = width;
wlWm.height = height;
wlWm.fullscreen = false;
enum xdg_toplevel_state * state;
wl_array_for_each(state, states)
{
if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN)
wlWm.fullscreen = true;
}
}
static void xdgToplevelClose(void * data, struct xdg_toplevel * xdgToplevel)
{
app_handleCloseEvent();
}
static const struct xdg_toplevel_listener xdgToplevelListener = {
.configure = xdgToplevelConfigure,
.close = xdgToplevelClose,
};
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable)
{
if (!wlWm.xdgWmBase)
{
DEBUG_ERROR("Compositor missing xdg_wm_base, will not proceed");
return false;
}
xdg_wm_base_add_listener(wlWm.xdgWmBase, &xdgWmBaseListener, NULL);
wlWm.xdgSurface = xdg_wm_base_get_xdg_surface(wlWm.xdgWmBase, wlWm.surface);
xdg_surface_add_listener(wlWm.xdgSurface, &xdgSurfaceListener, NULL);
wlWm.xdgToplevel = xdg_surface_get_toplevel(wlWm.xdgSurface);
xdg_toplevel_add_listener(wlWm.xdgToplevel, &xdgToplevelListener, NULL);
xdg_toplevel_set_title(wlWm.xdgToplevel, title);
xdg_toplevel_set_app_id(wlWm.xdgToplevel, "looking-glass-client");
if (fullscreen)
xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL);
if (maximize)
xdg_toplevel_set_maximized(wlWm.xdgToplevel);
if (wlWm.xdgDecorationManager)
{
wlWm.xdgToplevelDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
wlWm.xdgDecorationManager, wlWm.xdgToplevel);
if (wlWm.xdgToplevelDecoration)
{
zxdg_toplevel_decoration_v1_set_mode(wlWm.xdgToplevelDecoration,
borderless ?
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE :
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
}
return true;
}
void waylandShellAckConfigureIfNeeded(void)
{
if (wlWm.resizeSerial)
{
xdg_surface_ack_configure(wlWm.xdgSurface, wlWm.resizeSerial);
wlWm.resizeSerial = 0;
}
}
void waylandSetFullscreen(bool fs)
{
if (fs)
xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL);
else
xdg_toplevel_unset_fullscreen(wlWm.xdgToplevel);
}
bool waylandGetFullscreen(void)
{
return wlWm.fullscreen;
}
void waylandMinimize(void)
{
xdg_toplevel_set_minimized(wlWm.xdgToplevel);
}
void waylandShellResize(int w, int h)
{
//TODO: Implement resize for XDG.
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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,13 +24,10 @@
#include <signal.h>
#include <string.h>
#include <wayland-client.h>
#include <sys/socket.h>
#include "common/debug.h"
#include "common/option.h"
#include "dynamic/wayland_desktops.h"
static struct Option waylandOptions[] =
{
{
@@ -71,69 +68,18 @@ static bool waylandProbe(void)
return getenv("WAYLAND_DISPLAY") != NULL;
}
static bool getCompositor(char * dst, size_t size)
{
int fd = wl_display_get_fd(wlWm.display);
struct ucred ucred;
socklen_t len = sizeof(struct ucred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1)
{
DEBUG_ERROR("Failed to get the pid of the socket");
return false;
}
char path[64];
snprintf(path, sizeof(path), "/proc/%d/comm", ucred.pid);
FILE *fp = fopen(path, "r");
if (!fp)
{
DEBUG_ERROR("Failed to open %s", path);
return false;
}
if (!fgets(dst, size, fp))
{
DEBUG_ERROR("Failed to read %s", path);
fclose(fp);
return false;
}
fclose(fp);
dst[strlen(dst) - 1] = 0;
return true;
}
static bool waylandInit(const LG_DSInitParams params)
{
memset(&wlWm, 0, sizeof(wlWm));
wlWm.desktop = WL_Desktops[0];
wlWm.display = wl_display_connect(NULL);
if (!wlWm.display)
return false;
// select the desktop interface based on the compositor process name
char compositor[1024];
if (getCompositor(compositor, sizeof(compositor)))
{
DEBUG_INFO("Compositor: %s", compositor);
for(int i = 0; i < WL_DESKTOP_COUNT; ++i)
if (strcmp(WL_Desktops[i]->compositor, compositor) == 0)
{
wlWm.desktop = WL_Desktops[i];
break;
}
}
else
DEBUG_WARN("Compositor: UNKNOWN");
DEBUG_INFO("Selected : %s", wlWm.desktop->name);
wl_list_init(&wlWm.surfaceOutputs);
wlWm.warpSupport = option_get_bool("wayland", "warpSupport");
wlWm.useFractionalScale = option_get_bool("wayland", "fractionScale");
wlWm.display = wl_display_connect(NULL);
wlWm.width = params.w;
wlWm.height = params.h;
if (!waylandPollInit())
return false;
@@ -143,9 +89,6 @@ static bool waylandInit(const LG_DSInitParams params)
if (!waylandRegistryInit())
return false;
if (!waylandActivationInit())
return false;
if (!waylandIdleInit())
return false;
@@ -158,9 +101,7 @@ static bool waylandInit(const LG_DSInitParams params)
if (!waylandInputInit())
return false;
wlWm.desktop->setSize(params.w, params.h);
if (!waylandWindowInit(params.title, params.appId, params.fullscreen, params.maximize,
params.borderless, params.resizable))
if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless, params.resizable))
return false;
if (!waylandEGLInit(params.w, params.h))
@@ -171,6 +112,9 @@ static bool waylandInit(const LG_DSInitParams params)
return false;
#endif
wlWm.width = params.w;
wlWm.height = params.h;
return true;
}
@@ -205,31 +149,8 @@ static bool waylandGetProp(LG_DSProperty prop, void * ret)
return false;
}
void waylandNeedsResize(void)
{
wlWm.needsResize = true;
app_invalidateWindow(true);
waylandStopWaitFrame();
}
static void waylandSetFullscreen(bool fs)
{
wlWm.desktop->setFullscreen(fs);
}
static bool waylandGetFullscreen(void)
{
return wlWm.desktop->getFullscreen();
}
static void waylandMinimize(void)
{
wlWm.desktop->minimize();
}
struct LG_DisplayServerOps LGDS_Wayland =
{
.name = "Wayland",
.setup = waylandSetup,
.probe = waylandProbe,
.earlyInit = waylandEarlyInit,
@@ -263,11 +184,9 @@ struct LG_DisplayServerOps LGDS_Wayland =
.uncapturePointer = waylandUncapturePointer,
.grabKeyboard = waylandGrabKeyboard,
.ungrabKeyboard = waylandUngrabKeyboard,
.getCharCode = waylandGetCharCode,
.warpPointer = waylandWarpPointer,
.realignPointer = waylandRealignPointer,
.isValidPointerPos = waylandIsValidPointerPos,
.requestActivation = waylandActivationRequestActivation,
.inhibitIdle = waylandInhibitIdle,
.uninhibitIdle = waylandUninhibitIdle,
.wait = waylandWait,

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -37,16 +37,16 @@
#include "common/countedbuffer.h"
#include "common/ringbuffer.h"
#include "interface/displayserver.h"
#include "interface/desktop.h"
#include "wayland-xdg-shell-client-protocol.h"
#include "wayland-presentation-time-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
#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);
@@ -99,8 +99,6 @@ struct WaylandDSState
bool pointerInSurface;
bool focusedOnSurface;
WL_DesktopOps * desktop;
struct wl_display * display;
struct wl_surface * surface;
struct wl_registry * registry;
@@ -108,9 +106,12 @@ struct WaylandDSState
struct wl_shm * shm;
struct wl_compositor * compositor;
int32_t width, height;
wl_fixed_t scale;
bool fractionalScale;
bool needsResize;
bool fullscreen;
uint32_t resizeSerial;
bool configured;
bool warpSupport;
double cursorX, cursorY;
@@ -131,6 +132,17 @@ struct WaylandDSState
RingBuffer photonTimings;
GraphHandle photonGraph;
#ifdef ENABLE_LIBDECOR
struct libdecor * libdecor;
struct libdecor_frame * libdecorFrame;
#else
struct xdg_wm_base * xdgWmBase;
struct xdg_surface * xdgSurface;
struct xdg_toplevel * xdgToplevel;
struct zxdg_decoration_manager_v1 * xdgDecorationManager;
struct zxdg_toplevel_decoration_v1 * xdgToplevelDecoration;
#endif
const char * cursorThemeName;
int cursorSize;
int cursorScale;
@@ -168,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;
@@ -221,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);
@@ -277,7 +282,6 @@ void waylandUncapturePointer(void);
void waylandRealignPointer(void);
void waylandWarpPointer(int x, int y, bool exiting);
void waylandGuestPointerUpdated(double x, double y, double localX, double localY);
int waylandGetCharCode(int key);
// output module
bool waylandOutputInit(void);
@@ -301,8 +305,16 @@ void waylandPresentationFree(void);
bool waylandRegistryInit(void);
void waylandRegistryFree(void);
// shell module
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable);
void waylandShellAckConfigureIfNeeded(void);
void waylandSetFullscreen(bool fs);
bool waylandGetFullscreen(void);
void waylandMinimize(void);
void waylandShellResize(int w, int h);
// window module
bool waylandWindowInit(const char * title, const char * appId, bool fullscreen, bool maximize, bool borderless, bool resizable);
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable);
void waylandWindowFree(void);
void waylandWindowUpdateScale(void);
void waylandSetWindowSize(int x, int y);
@@ -310,4 +322,3 @@ bool waylandIsValidPointerPos(int x, int y);
bool waylandWaitFrame(void);
void waylandSkipFrame(void);
void waylandStopWaitFrame(void);
void waylandNeedsResize(void);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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();
@@ -85,7 +79,7 @@ static const struct wl_surface_listener wlSurfaceListener = {
.leave = wlSurfaceLeaveHandler,
};
bool waylandWindowInit(const char * title, const char * appId, bool fullscreen, bool maximize, bool borderless, bool resizable)
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable)
{
wlWm.scale = wl_fixed_from_int(1);
@@ -112,8 +106,7 @@ bool waylandWindowInit(const char * title, const char * appId, bool fullscreen,
wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL);
if (!wlWm.desktop->shellInit(wlWm.display, wlWm.surface,
title, appId, fullscreen, maximize, borderless, resizable))
if (!waylandShellInit(title, fullscreen, maximize, borderless, resizable))
return false;
wl_surface_commit(wlWm.surface);
@@ -128,14 +121,12 @@ void waylandWindowFree(void)
void waylandSetWindowSize(int x, int y)
{
wlWm.desktop->shellResize(x, y);
waylandShellResize(x, y);
}
bool waylandIsValidPointerPos(int x, int y)
{
int width, height;
wlWm.desktop->getSize(&width, &height);
return x >= 0 && x < width && y >= 0 && y < height;
return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height;
}
static void frameHandler(void * opaque, struct wl_callback * callback, unsigned int data)

View File

@@ -1,37 +1,31 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.0)
project(displayserver_X11 LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(DISPLAYSERVER_X11 REQUIRED IMPORTED_TARGET
x11
xi
xfixes
xscrnsaver
xinerama
xcursor
xpresent
xkbcommon
x11
xi
xfixes
xscrnsaver
xinerama
xcursor
xpresent
)
add_library(displayserver_X11 STATIC
x11.c
atoms.c
clipboard.c
cursor.c
wm/default.c
wm/i3.c
x11.c
atoms.c
clipboard.c
)
add_definitions(-D GLX_GLXEXT_PROTOTYPES)
target_link_libraries(displayserver_X11
PkgConfig::DISPLAYSERVER_X11
lg_common
lg_resources
PkgConfig::DISPLAYSERVER_X11
lg_common
)
target_include_directories(displayserver_X11
PRIVATE
.
PRIVATE
src
)

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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,17 +28,14 @@
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) \
DEF_ATOM(_NET_WM_PID, True) \
DEF_ATOM(WM_DELETE_WINDOW, True) \
DEF_ATOM(_MOTIF_WM_HINTS, True) \
\

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -60,40 +60,40 @@ static void x11CBSelectionIncr(const XPropertyEvent e);
static void x11CBSelectionNotify(const XSelectionEvent e);
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e);
bool x11CBEventThread(const XEvent * xe)
bool x11CBEventThread(const XEvent xe)
{
switch(xe->type)
switch(xe.type)
{
case SelectionRequest:
x11CBSelectionRequest(xe->xselectionrequest);
x11CBSelectionRequest(xe.xselectionrequest);
return true;
case SelectionClear:
x11CBSelectionClear(xe->xselectionclear);
x11CBSelectionClear(xe.xselectionclear);
return true;
case SelectionNotify:
x11CBSelectionNotify(xe->xselection);
x11CBSelectionNotify(xe.xselection);
return true;
case PropertyNotify:
if (xe->xproperty.state != PropertyNewValue)
if (xe.xproperty.state != PropertyNewValue)
break;
if (xe->xproperty.atom == x11atoms.SEL_DATA)
if (xe.xproperty.atom == x11atoms.SEL_DATA)
{
if (x11cb.lowerBound == 0)
return true;
x11CBSelectionIncr(xe->xproperty);
x11CBSelectionIncr(xe.xproperty);
return true;
}
break;
default:
if (xe->type == x11.eventBase + XFixesSelectionNotify)
if (xe.type == x11.eventBase + XFixesSelectionNotify)
{
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)xe;
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&xe;
x11CBXFixesSelectionNotify(*sne);
return true;
}
@@ -103,7 +103,7 @@ bool x11CBEventThread(const XEvent * xe)
return false;
}
bool x11CBInit(void)
bool x11CBInit()
{
x11cb.aCurSelection = BadValue;
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
@@ -123,6 +123,8 @@ bool x11CBInit(void)
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-2025 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
@@ -26,9 +26,9 @@
#include "interface/displayserver.h"
bool x11CBEventThread(const XEvent * xe);
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,105 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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 "cursor.h"
#include "common/util.h"
#include <string.h>
#include <errno.h>
struct MemFile
{
const char * data;
int size;
int pos;
};
static int x11cursor_read(XcursorFile *file, unsigned char * buf, int len)
{
struct MemFile * f = (struct MemFile *)file->closure;
if (f->pos == f->size)
return 0;
len = min(f->size - f->pos, len);
memcpy(buf, f->data + f->pos, len);
f->pos += len;
return len;
}
static int x11cursor_write(XcursorFile *file, unsigned char * buf, int len)
{
errno = -EINVAL;
return -1;
}
static int x11cursor_seek(XcursorFile *file, long offset, int whence)
{
struct MemFile * f = (struct MemFile *)file->closure;
long target;
switch(whence)
{
case SEEK_SET:
target = offset;
break;
case SEEK_CUR:
target = f->pos + offset;
break;
case SEEK_END:
target = f->size + offset;
break;
default:
errno = -EINVAL;
return -1;
}
if (target < 0 || target > f->size)
{
errno = -EINVAL;
return -1;
}
f->pos = target;
return target;
}
XcursorImages * x11cursor_load(const char * cursor, int size)
{
struct MemFile closure =
{
.data = cursor,
.size = size,
.pos = 0
};
XcursorFile f =
{
.closure = &closure,
.read = x11cursor_read,
.write = x11cursor_write,
.seek = x11cursor_seek
};
return XcursorXcFileLoadAllImages(&f);
}

View File

@@ -1,71 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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_X11DS_WM_DEFAULT_
#define _H_X11DS_WM_DEFAULT_
#include "wm.h"
#include "x11.h"
#include "atoms.h"
static void wm_default_setup(void)
{
}
static bool wm_default_init(void)
{
return true;
}
static void wm_default_deinit(void)
{
}
static void wm_default_setFullscreen(bool enable)
{
XEvent e =
{
.xclient = {
.type = ClientMessage,
.send_event = true,
.message_type = x11atoms._NET_WM_STATE,
.format = 32,
.window = x11.window,
.data.l = {
enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE,
x11atoms._NET_WM_STATE_FULLSCREEN,
0
}
}
};
XSendEvent(x11.display, DefaultRootWindow(x11.display), False,
SubstructureNotifyMask | SubstructureRedirectMask, &e);
};
X11WM X11WM_Default =
{
.setup = wm_default_setup,
.init = wm_default_init,
.deinit = wm_default_deinit,
.setFullscreen = wm_default_setFullscreen
};
#endif

View File

@@ -1,193 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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_X11DS_WM_DEFAULT_
#define _H_X11DS_WM_DEFAULT_
#include "wm.h"
#include "x11.h"
#include "atoms.h"
#include "common/debug.h"
#include "common/option.h"
#include "common/util.h"
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
static struct Option options[] =
{
// app options
{
.module = "i3",
.name = "globalFullScreen",
.description = "Use i3's global full screen feature (spans all monitors)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{0}
};
struct i3state
{
int sock;
bool globalFullScreen;
};
static struct i3state i3;
static void wm_i3_setup(void)
{
option_register(options);
}
static bool wm_i3_init(void)
{
memset(&i3, 0, sizeof(i3));
i3.globalFullScreen = option_get_bool("i3", "globalFullScreen");
FILE * fd = popen("i3 --get-socketpath", "r");
if (!fd)
return false;
struct sockaddr_un addr = { .sun_family = AF_UNIX };
char * path = (char *)&addr.sun_path;
int pathLen;
if ((pathLen = fread(path, 1, sizeof(addr.sun_path), fd)) <= 0)
{
pclose(fd);
return false;
}
pclose(fd);
if(path[pathLen-1] == '\n')
--pathLen;
path[pathLen] = '\0';
i3.sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (i3.sock < 0)
{
DEBUG_ERROR("Failed to create socket for i3 IPC");
return false;
}
if (connect(i3.sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
DEBUG_ERROR("Failed to connect to the i3 IPC socket");
perror("connect");
goto err_socket;
}
DEBUG_INFO("i3 IPC Connected");
return true;
err_socket:
close(i3.sock);
return false;
}
static void wm_i3_deinit(void)
{
close(i3.sock);
}
static void wm_i3_setFullscreen(bool enable)
{
if (!i3.globalFullScreen)
{
X11WM_Default.setFullscreen(enable);
return;
}
struct i3Msg
{
char magic[6];
uint32_t length;
uint32_t type;
char payload[0];
}
__attribute__((packed));
#define I3_IPC_TYPE_RUN_COMMAND 0
char cmd[128];
int cmdLen = snprintf(cmd, sizeof(cmd),
"[id=%lu] fullscreen toggle global",
x11.window);
struct i3Msg *msg = alloca(sizeof(struct i3Msg) + cmdLen);
memcpy(msg->magic, "i3-ipc", 6);
msg->length = cmdLen;
msg->type = I3_IPC_TYPE_RUN_COMMAND;
memcpy(msg->payload, cmd, cmdLen);
int msgSize = sizeof(*msg) + msg->length;
char * buf = (char *)msg;
while(msgSize)
{
int wrote = write(i3.sock, buf, msgSize);
if (wrote <= 0)
{
DEBUG_WARN("i3 IPC communication failure");
return;
}
buf += wrote;
msgSize -= wrote;
}
if ((msgSize = read(i3.sock, msg, sizeof(*msg))) < 0)
{
DEBUG_WARN("i3 IPC read failure");
return;
}
if (memcmp(msg->magic, "i3-ipc", 6) != 0 ||
msg->type != I3_IPC_TYPE_RUN_COMMAND)
{
DEBUG_WARN("i3 IPC unexpected reply");
return;
}
// read and discard the payload
while(msg->length)
{
int len = read(i3.sock, cmd, min(msg->length, sizeof(cmd)));
if (len <= 0)
{
DEBUG_WARN("i3 IPC failed to read payload");
return;
}
msg->length -= len;
}
};
X11WM X11WM_i3 =
{
.setup = wm_i3_setup,
.init = wm_i3_init,
.deinit = wm_i3_deinit,
.setFullscreen = wm_i3_setFullscreen
};
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,11 +23,6 @@
#include "x11.h"
#include "atoms.h"
#include "clipboard.h"
#include "cursor.h"
#include "resources/icondata.h"
#include "resources/no-input-cursor/16.xcur.h"
#include "resources/no-input-cursor/32.xcur.h"
#include <string.h>
#include <unistd.h>
@@ -40,8 +35,6 @@
#include <X11/extensions/Xpresent.h>
#include <X11/Xcursor/Xcursor.h>
#include <xkbcommon/xkbcommon.h>
#include <GL/glx.h>
#include <GL/glxext.h>
@@ -57,6 +50,10 @@
#include "common/event.h"
#include "util.h"
#define _NET_WM_STATE_REMOVE 0
#define _NET_WM_STATE_ADD 1
#define _NET_WM_STATE_TOGGLE 2
struct X11DSState x11;
struct MwmHints
@@ -166,8 +163,6 @@ static void x11DoPresent(uint64_t msc)
static void x11Setup(void)
{
X11WM_Default.setup();
X11WM_i3 .setup();
}
static bool x11Probe(void)
@@ -194,14 +189,14 @@ static void x11CheckEWMHSupport(void)
if (XGetWindowProperty(x11.display, DefaultRootWindow(x11.display),
x11atoms._NET_SUPPORTING_WM_CHECK, 0, ~0L, False, XA_WINDOW,
&type, &fmt, &num, &bytes, &data) != Success || !data)
&type, &fmt, &num, &bytes, &data) != Success)
goto out;
Window * windowFromRoot = (Window *)data;
if (XGetWindowProperty(x11.display, *windowFromRoot,
x11atoms._NET_SUPPORTING_WM_CHECK, 0, ~0L, False, XA_WINDOW,
&type, &fmt, &num, &bytes, &data) != Success || !data)
&type, &fmt, &num, &bytes, &data) != Success)
goto out_root;
Window * windowFromChild = (Window *)data;
@@ -210,7 +205,7 @@ static void x11CheckEWMHSupport(void)
if (XGetWindowProperty(x11.display, DefaultRootWindow(x11.display),
x11atoms._NET_SUPPORTED, 0, ~0L, False, AnyPropertyType,
&type, &fmt, &num, &bytes, &data) != Success || !data)
&type, &fmt, &num, &bytes, &data) != Success)
goto out_child;
Atom * supported = (Atom *)data;
@@ -218,7 +213,7 @@ static void x11CheckEWMHSupport(void)
if (XGetWindowProperty(x11.display, *windowFromRoot,
x11atoms._NET_WM_NAME, 0, ~0L, False, AnyPropertyType,
&type, &fmt, &num, &bytes, &data) != Success || !data)
&type, &fmt, &num, &bytes, &data) != Success)
goto out_supported;
char * wmName = (char *)data;
@@ -229,11 +224,9 @@ static void x11CheckEWMHSupport(void)
x11.ewmhHasFocusEvent = true;
}
DEBUG_INFO("EWMH-compliant window manager detected: %s", wmName);
DEBUG_INFO("EWMH-complient window manager detected: %s", wmName);
x11.ewmhSupport = true;
if (strcmp(wmName, "i3") == 0)
x11.wm = &X11WM_i3;
XFree(wmName);
out_supported:
@@ -246,37 +239,17 @@ out:
return;
}
static int x11ErrorHandler(Display * display, XErrorEvent * error)
{
char errorText[1024];
XGetErrorText(display, error->error_code, errorText, sizeof(errorText));
DEBUG_ERROR("X11 Error: %s", errorText);
DEBUG_PRINT_BACKTRACE();
return 0;
}
static int x11IOErrorHandler(Display * display)
{
DEBUG_FATAL("Fatal X11 IO Error");
return 0;
}
static bool x11Init(const LG_DSInitParams params)
{
XIDeviceInfo *devinfo;
int count;
int event, error;
XSetErrorHandler(x11ErrorHandler);
XSetIOErrorHandler(x11IOErrorHandler);
memset(&x11, 0, sizeof(x11));
x11.xValuator = -1;
x11.yValuator = -1;
x11.display = XOpenDisplay(NULL);
x11.display = XOpenDisplay(NULL);
x11.jitRender = params.jitRender;
x11.wm = &X11WM_Default;
XSetWindowAttributes swa =
{
@@ -343,7 +316,7 @@ static bool x11Init(const LG_DSInitParams params)
XClassHint hint =
{
.res_name = strdup(params.title),
.res_class = strdup(params.appId)
.res_class = strdup("looking-glass-client")
};
XSetClassHint(x11.display, x11.window, &hint);
free(hint.res_name);
@@ -374,31 +347,6 @@ static bool x11Init(const LG_DSInitParams params)
// check for Extended Window Manager Hints support
x11CheckEWMHSupport();
if (!x11.wm->init())
{
x11.wm = &X11WM_Default;
if (!x11.wm->init())
{
DEBUG_ERROR("Failed to initialize the X11 window manager subsystem");
goto fail_window;
}
}
if (x11atoms._NET_WM_PID)
{
pid_t pid = getpid();
XChangeProperty(
x11.display,
x11.window,
x11atoms._NET_WM_PID,
XA_CARDINAL,
32,
PropModeReplace,
(unsigned char *)&pid,
1
);
}
if (params.borderless)
{
if (x11atoms._MOTIF_WM_HINTS)
@@ -449,14 +397,23 @@ static bool x11Init(const LG_DSInitParams params)
);
}
Atom wmState[3] = {0};
int wmStateCount = 0;
if (params.fullscreen)
{
x11.fullscreen = true;
wmState[wmStateCount++] = x11atoms._NET_WM_STATE_FULLSCREEN;
}
if (params.maximize)
{
Atom wmState[2] =
{
x11atoms._NET_WM_STATE_MAXIMIZED_HORZ,
x11atoms._NET_WM_STATE_MAXIMIZED_VERT
};
wmState[wmStateCount++] = x11atoms._NET_WM_STATE_MAXIMIZED_HORZ;
wmState[wmStateCount++] = x11atoms._NET_WM_STATE_MAXIMIZED_VERT;
}
if (wmStateCount)
{
XChangeProperty(
x11.display,
x11.window,
@@ -465,7 +422,7 @@ static bool x11Init(const LG_DSInitParams params)
32,
PropModeReplace,
(unsigned char *)&wmState,
2
wmStateCount
);
}
@@ -491,7 +448,7 @@ static bool x11Init(const LG_DSInitParams params)
if (XIQueryVersion(x11.display, &major, &minor) != Success)
{
DEBUG_ERROR("Failed to query the XInput version");
goto fail_window;
return false;
}
DEBUG_INFO("X11 XInput %d.%d in use", major, minor);
@@ -565,10 +522,6 @@ static bool x11Init(const LG_DSInitParams params)
goto fail_window;
}
XDisplayKeycodes(x11.display, &x11.minKeycode, &x11.maxKeycode);
x11.keysyms = XGetKeyboardMapping(x11.display, x11.minKeycode,
x11.maxKeycode - x11.minKeycode, &x11.symsPerKeycode);
XIFreeDeviceInfo(devinfo);
XQueryExtension(x11.display, "XInputExtension", &x11.xinputOp, &event, &error);
@@ -579,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 );
@@ -609,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 };
@@ -630,17 +575,29 @@ static bool x11Init(const LG_DSInitParams params)
XFreePixmap(x11.display, temp);
}
XcursorImages * images;
if (params.largeCursorDot)
images = x11cursor_load(b_no_input_cursor_32_xcur,
b_no_input_cursor_32_xcur_size);
else
images = x11cursor_load(b_no_input_cursor_16_xcur,
b_no_input_cursor_16_xcur_size);
/* create the square cursor */
{
static char data[] = { 0x07, 0x05, 0x07 };
static char mask[] = { 0xff, 0xff, 0xff };
x11.cursors[LG_POINTER_SQUARE] =
XcursorImagesLoadCursor(x11.display, images);
XcursorImagesDestroy(images);
Colormap cmap = DefaultColormap(x11.display, DefaultScreen(x11.display));
XColor colors[2] =
{
{ .pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(x11.display)) },
{ .pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(x11.display)) }
};
XQueryColors(x11.display, cmap, colors, 2);
Pixmap img = XCreateBitmapFromData(x11.display, x11.window, data, 3, 3);
Pixmap msk = XCreateBitmapFromData(x11.display, x11.window, mask, 3, 3);
x11.cursors[LG_POINTER_SQUARE] = XCreatePixmapCursor(x11.display, img, msk,
&colors[0], &colors[1], 1, 1);
XFreePixmap(x11.display, img);
XFreePixmap(x11.display, msk);
}
/* initialize the rest of the cursors */
const char * cursorLookup[LG_POINTER_COUNT] = {
@@ -698,9 +655,6 @@ static bool x11Init(const LG_DSInitParams params)
if (!params.center)
XMoveWindow(x11.display, x11.window, params.x, params.y);
if (params.fullscreen)
x11.doFullscreenOnExpose = true;
XSetLocaleModifiers(""); // Load XMODIFIERS
x11.xim = XOpenIM(x11.display, 0, 0, 0);
@@ -781,10 +735,6 @@ static void x11Free(void)
if (x11.cursors[i])
XFreeCursor(x11.display, x11.cursors[i]);
if (x11.keysyms)
XFree(x11.keysyms);
x11.wm->deinit();
XCloseDisplay(x11.display);
}
@@ -897,7 +847,7 @@ static int x11EventThread(void * unused)
XNextEvent(x11.display, &xe);
// call the clipboard handling code
if (x11CBEventThread(&xe))
if (x11CBEventThread(xe))
continue;
switch(xe.type)
@@ -946,11 +896,6 @@ static int x11EventThread(void * unused)
{
atomic_store(&x11.lastWMEvent, microtime());
x11.invalidateAll = true;
if (x11.doFullscreenOnExpose)
{
x11SetFullscreen(true);
x11.doFullscreenOnExpose = false;
}
break;
}
@@ -1077,28 +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 int x11GetCharCode(int detail)
{
detail += x11.minKeycode;
if (detail < x11.minKeycode || detail > x11.maxKeycode)
return 0;
KeySym sym = x11.keysyms[(detail - x11.minKeycode) *
x11.symsPerKeycode];
sym = xkb_keysym_to_upper(sym);
return xkb_keysym_to_utf32(sym);
}
static void x11XInputEvent(XGenericEventCookie *cookie)
{
static int button_state = 0;
@@ -1107,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;
}
@@ -1181,46 +1097,6 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
app_updateCursorPos(xie->event_x, xie->event_y);
app_handleEnterEvent(false);
x11.entered = false;
/**
* Because there is a race with the pointer ungrab the enter event for the
* next window is sometimes sent with the mode NotifyUngrab, unfortunatly
* some window managers such as i3 will ignore these which breaks focus
* follows mouse mode. To correct this we generate and send a normal
* EnterNotify event.
*/
int root_x, root_y, win_x, win_y;
Window root_win, child_win;
unsigned int mask;
XQueryPointer(x11.display, DefaultRootWindow(x11.display), &root_win,
&child_win, &root_x, &root_y, &win_x, &win_y, &mask);
int target_x, target_y;
Window target_root;
XTranslateCoordinates(x11.display, DefaultRootWindow(x11.display),
child_win, root_x, root_y, &target_x, &target_y, &target_root);
XEvent event;
memset(&event, 0, sizeof(event));
event.type = EnterNotify;
event.xcrossing.serial = 0;
event.xcrossing.send_event = True;
event.xcrossing.display = x11.display;
event.xcrossing.window = child_win;
event.xcrossing.root = root_win;
event.xcrossing.subwindow = child_win;
event.xcrossing.time = CurrentTime;
event.xcrossing.mode = NotifyNormal;
event.xcrossing.detail = NotifyNonlinear;
event.xcrossing.same_screen = True;
event.xcrossing.focus = False;
event.xcrossing.x = target_x;
event.xcrossing.y = target_y;
event.xcrossing.x_root = root_x;
event.xcrossing.y_root = root_y;
XSendEvent(x11.display, child_win, True, EnterWindowMask, &event);
XFlush(x11.display);
return;
}
@@ -1230,7 +1106,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
return;
XIDeviceEvent *device = cookie->data;
app_handleKeyPress(device->detail - x11.minKeycode);
app_handleKeyPress(device->detail - 8);
if (!x11.xic || !app_isOverlayMode())
return;
@@ -1280,7 +1156,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
return;
XIDeviceEvent *device = cookie->data;
app_handleKeyRelease(device->detail - x11.minKeycode);
app_handleKeyRelease(device->detail - 8);
if (!x11.xic || !app_isOverlayMode())
return;
@@ -1309,7 +1185,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
return;
XIRawEvent *raw = cookie->data;
app_handleKeyPress(raw->detail - x11.minKeycode);
app_handleKeyPress(raw->detail - 8);
return;
}
@@ -1319,7 +1195,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
return;
XIRawEvent *raw = cookie->data;
app_handleKeyRelease(raw->detail - x11.minKeycode);
app_handleKeyRelease(raw->detail - 8);
return;
}
@@ -1923,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);
@@ -1970,7 +1824,24 @@ static void x11SetFullscreen(bool fs)
if (x11.fullscreen == fs)
return;
x11.wm->setFullscreen(fs);
XEvent e =
{
.xclient = {
.type = ClientMessage,
.send_event = true,
.message_type = x11atoms._NET_WM_STATE,
.format = 32,
.window = x11.window,
.data.l = {
fs ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE,
x11atoms._NET_WM_STATE_FULLSCREEN,
0
}
}
};
XSendEvent(x11.display, DefaultRootWindow(x11.display), False,
SubstructureNotifyMask | SubstructureRedirectMask, &e);
}
static bool x11GetFullscreen(void)
@@ -1985,7 +1856,6 @@ static void x11Minimize(void)
struct LG_DisplayServerOps LGDS_X11 =
{
.name = "X11",
.setup = x11Setup,
.probe = x11Probe,
.earlyInit = x11EarlyInit,
@@ -2014,13 +1884,11 @@ struct LG_DisplayServerOps LGDS_X11 =
.ungrabPointer = x11UngrabPointer,
.capturePointer = x11CapturePointer,
.uncapturePointer = x11UncapturePointer,
.getCharCode = x11GetCharCode,
.grabKeyboard = x11GrabKeyboard,
.ungrabKeyboard = x11UngrabKeyboard,
.warpPointer = x11WarpPointer,
.realignPointer = x11RealignPointer,
.isValidPointerPos = x11IsValidPointerPos,
.requestActivation = x11RequestActivation,
.inhibitIdle = x11InhibitIdle,
.uninhibitIdle = x11UninhibitIdle,
.wait = x11Wait,

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,7 +33,6 @@
#include "interface/displayserver.h"
#include "common/thread.h"
#include "common/types.h"
#include "wm.h"
enum Modifiers
{
@@ -49,20 +48,11 @@ enum Modifiers
#define MOD_COUNT (MOD_SUPER_RIGHT + 1)
#define _NET_WM_STATE_REMOVE 0
#define _NET_WM_STATE_ADD 1
#define _NET_WM_STATE_TOGGLE 2
struct X11DSState
{
Display * display;
Window window;
XVisualInfo * visual;
X11WM * wm;
int minKeycode, maxKeycode;
int symsPerKeycode;
KeySym * keysyms;
//Extended Window Manager Hints
//ref: https://specifications.freedesktop.org/wm-spec/latest/
@@ -71,7 +61,6 @@ struct X11DSState
_Atomic(uint64_t) lastWMEvent;
bool invalidateAll;
bool doFullscreenOnExpose;
int xpresentOp;
bool jitRender;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -58,9 +58,9 @@ void app_resyncMouseBasic(void);
void app_handleButtonPress(int button);
void app_handleButtonRelease(int button);
void app_handleWheelMotion(double motion);
void app_handleKeyboardTyped(const char * typed);
void app_handleKeyPress(int scancode);
void app_handleKeyRelease(int scancode);
void app_handleKeyboardTyped(const char * typed);
void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super);
void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock);
void app_handleEnterEvent(bool entered);
@@ -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,31 +128,18 @@ 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
* @param charcode The charcode to register (used instead of sc if non zero)
* @param callback The function to be called when the combination is pressed
* @param opaque A pointer to be passed to the callback, may be NULL
* @retval A handle for the binding or NULL on failure.
* The caller is required to release the handle via `app_releaseKeybind` when it is no longer required
*/
KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback,
void * opaque, const char * description);
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description);
/**
* Release an existing key binding
@@ -175,20 +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);
/**
* Enable/disable the LG display
*/
void app_stopVideo(bool stop);
/**
* Enable/disable the spice display
*/
bool app_useSpiceDisplay(bool enable);
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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,51 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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 <stdbool.h>
/**
* initialize configuration options
*/
void evdev_earlyInit(void);
/**
* start the evdev layer
*/
bool evdev_start(void);
/**
* stop the evdev layer
*/
void evdev_stop(void);
/**
* grab the keyboard for exclusive access
*/
void evdev_grabKeyboard(void);
/**
* ungrab the keyboard
*/
void evdev_ungrabKeyboard(void);
/**
* returns true if input should only be processed by evdev
*/
bool evdev_isExclusive(void);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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-2025 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
@@ -84,14 +84,12 @@ LG_DSPointer;
typedef struct LG_DSInitParams
{
const char * title;
const char * appId;
int x, y, w, h;
bool center;
bool fullscreen;
bool resizable;
bool borderless;
bool maximize;
bool largeCursorDot;
// if true the renderer requires an OpenGL context
bool opengl;
@@ -112,8 +110,6 @@ typedef struct LGEvent LGEvent;
struct LG_DisplayServerOps
{
const char * name;
/* called before options are parsed, useful for registering options */
void (*setup)(void);
@@ -127,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
@@ -174,17 +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);
/* get the character code for the provided scancode */
int (*getCharCode)(int sc);
void (*capturePointer)();
void (*uncapturePointer)();
/* exiting = true if the warp is to leave the window */
void (*warpPointer)(int x, int y, bool exiting);
@@ -192,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);
@@ -256,7 +246,6 @@ struct LG_DisplayServerOps
DEBUG_ASSERT((x)->ungrabPointer ); \
DEBUG_ASSERT((x)->capturePointer ); \
DEBUG_ASSERT((x)->uncapturePointer ); \
DEBUG_ASSERT((x)->getCharCode ); \
DEBUG_ASSERT((x)->warpPointer ); \
DEBUG_ASSERT((x)->realignPointer ); \
DEBUG_ASSERT((x)->isValidPointerPos ); \

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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,8 +25,6 @@
#include "common/types.h"
#define TICK_RATE 25
struct LG_OverlayOps
{
/* internal name of the overlay for debugging */
@@ -45,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
@@ -65,15 +59,6 @@ struct LG_OverlayOps
int (*render)(void * udata, bool interactive, struct Rect * windowRects,
int maxRects);
/* called TICK_RATE 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-2025 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
@@ -27,22 +27,17 @@
#include "common/framebuffer.h"
#define IS_LG_RENDERER_VALID(x) \
((x)->getName && \
(x)->create && \
(x)->initialize && \
(x)->deinitialize && \
(x)->onRestart && \
(x)->onResize && \
(x)->onMouseShape && \
(x)->onMouseEvent && \
(x)->renderStartup && \
(x)->render && \
(x)->createTexture && \
(x)->freeTexture && \
(x)->spiceConfigure && \
(x)->spiceDrawFill && \
(x)->spiceDrawBitmap && \
(x)->spiceShow)
((x)->getName && \
(x)->create && \
(x)->initialize && \
(x)->deinitialize && \
(x)->onRestart && \
(x)->onResize && \
(x)->onMouseShape && \
(x)->onMouseEvent && \
(x)->renderStartup && \
(x)->needsRender && \
(x)->render)
typedef struct LG_RendererParams
{
@@ -71,15 +66,9 @@ LG_RendererRotate;
typedef struct LG_RendererFormat
{
FrameType type; // frame type
bool hdr; // if the frame is HDR or not
bool hdrPQ; // if the HDR content is PQ mapped
unsigned int screenWidth; // actual width of the host
unsigned int screenHeight; // actual height of the host
unsigned int dataWidth; // the width of the packed data
unsigned int dataHeight; // the height of the packed data
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)
@@ -150,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 */
@@ -167,36 +156,15 @@ typedef struct LG_RendererOps
* Context: renderThread */
bool (*renderStartup)(LG_Renderer * renderer, bool useDMA);
/* returns if the render method must be called even if nothing has changed.
* Context: renderThread */
bool (*needsRender)(LG_Renderer * renderer);
/* called to render the scene
* Context: renderThread */
bool (*render)(LG_Renderer * renderer, LG_RendererRotate rotate,
const bool newFrame, const bool invalidateWindow,
void (*preSwap)(void * udata), void * udata);
/* called to create a texture from the specified 32-bit RGB image data. This
* method is for use with Dear ImGui
* Context: renderThread */
void * (*createTexture)(LG_Renderer * renderer,
int width, int height, uint8_t * data);
/* called to free a texture previously created by createTexture. This method
* is for use with Dear ImGui
* Context: renderThread */
void (*freeTexture)(LG_Renderer * renderer, void * texture);
/* setup the spice display */
void (*spiceConfigure)(LG_Renderer * renderer, int width, int height);
/* draw a filled rect on the spice display with the specified color */
void (*spiceDrawFill)(LG_Renderer * renderer, int x, int y, int width,
int height, uint32_t color);
/* draw an image on the spice display, data is RGBA32 */
void (*spiceDrawBitmap)(LG_Renderer * renderer, int x, int y, int width,
int height, int stride, uint8_t * data, bool topDown);
/* show the spice display */
void (*spiceShow)(LG_Renderer * renderer, bool show);
}
LG_RendererOps;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,22 +18,16 @@
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _H_X11DS_WM_
#define _H_X11DS_WM_
#include <stdint.h>
#include <stdbool.h>
struct ll;
typedef struct X11WM
{
void (*setup)(void);
bool (*init)(void);
void (*deinit)(void);
void (*setFullscreen)(bool enable);
}
X11WM;
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);
extern X11WM X11WM_Default;
extern X11WM X11WM_i3;
#endif
void ll_reset (struct ll * list);
bool ll_walk (struct ll * list, void ** data);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -27,23 +27,9 @@
typedef struct ImVec2 ImVec2;
typedef struct
{
void * tex;
int width;
int height;
}
OverlayImage;
void overlayGetImGuiRect(struct Rect * rect);
ImVec2 * overlayGetScreenSize(void);
void overlayTextURL(const char * url, const char * text);
void overlayTextMaybeURL(const char * text, bool wrapped);
// create a texture from a SVG and scale it to fit the supplied width & height
bool overlayLoadSVG(const char * data, unsigned int size, OverlayImage * image,
int width, int height);
void overlayFreeImage(OverlayImage * image);
#endif

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,4 +1,4 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.0)
project(renderers LANGUAGES C)
set(RENDERER_H "${CMAKE_BINARY_DIR}/include/dynamic/renderers.h")

View File

@@ -1,118 +1,122 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.0)
project(renderer_EGL LANGUAGES C CXX)
find_package(PkgConfig)
pkg_check_modules(RENDERER_EGL REQUIRED IMPORTED_TARGET
egl
gl
egl
gl
)
pkg_check_modules(RENDERER_EGL_OPT IMPORTED_TARGET
wayland-egl
wayland-egl
)
find_program(AWK NAMES gawk mawk original-awk awk)
if(AWK MATCHES ".+-NOTFOUND")
message(FATAL_ERROR "FATAL: some known version of awk couldn't be found (${AWK}).")
message(FATAL_ERROR "FATAL: some known version of awk couldn't be found (${AWK}).")
else()
message(STATUS "Using awk: ${AWK}")
message(STATUS "Using awk: ${AWK}")
endif()
include(MakeObject)
function(build_shaders header_dir)
file(GLOB headers "${header_dir}/*.h")
set(EGL_SHADER_PROCESSED)
foreach(shader ${ARGN})
set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${shader}")
add_custom_command(OUTPUT "${out_f}"
COMMAND "${AWK}" -f "${CMAKE_CURRENT_SOURCE_DIR}/glsl.include.awk"
"${CMAKE_CURRENT_SOURCE_DIR}/${shader}" > "${out_f}"
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${shader}"
DEPENDS ${headers}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shader"
COMMENT "Preprocessing shader ${shader}"
VERBATIM
)
endforeach()
file(GLOB headers "${header_dir}/*.h")
set(EGL_SHADER_PROCESSED)
foreach(shader ${ARGN})
set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${shader}")
add_custom_command(OUTPUT "${out_f}"
COMMAND "${AWK}" -f "${CMAKE_CURRENT_SOURCE_DIR}/glsl.include.awk"
"${CMAKE_CURRENT_SOURCE_DIR}/${shader}" > "${out_f}"
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${shader}"
DEPENDS ${headers}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shader"
COMMENT "Preprocessing shader ${shader}"
VERBATIM
)
endforeach()
set(CMAKE_CURRENT_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
make_object(
EGL_SHADER
${ARGN}
)
set(CMAKE_CURRENT_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
make_object(
EGL_SHADER
${ARGN}
)
set(EGL_SHADER_OBJS "${EGL_SHADER_OBJS}" PARENT_SCOPE)
set(EGL_SHADER_INCS "${EGL_SHADER_INCS}" PARENT_SCOPE)
set(EGL_SHADER_OBJS "${EGL_SHADER_OBJS}" PARENT_SCOPE)
set(EGL_SHADER_INCS "${EGL_SHADER_INCS}" PARENT_SCOPE)
endfunction()
build_shaders(
shader
shader/desktop.vert
shader/desktop_rgb.frag
shader/cursor.vert
shader/cursor_rgb.frag
shader/cursor_mono.frag
shader/damage.vert
shader/damage.frag
shader/basic.vert
shader/convert_24bit.frag
shader/ffx_cas.frag
shader/ffx_fsr1_easu.frag
shader/ffx_fsr1_rcas.frag
shader/downscale.frag
shader/downscale_lanczos2.frag
shader/downscale_linear.frag
shader
shader/desktop.vert
shader/desktop_rgb.frag
shader/cursor.vert
shader/cursor_rgb.frag
shader/cursor_mono.frag
shader/damage.vert
shader/damage.frag
shader/splash_bg.vert
shader/splash_bg.frag
shader/splash_logo.vert
shader/splash_logo.frag
shader/basic.vert
shader/ffx_cas.frag
shader/ffx_fsr1_easu.frag
shader/ffx_fsr1_rcas.frag
shader/downscale.frag
shader/downscale_lanczos2.frag
shader/downscale_linear.frag
)
make_defines(
"${CMAKE_CURRENT_SOURCE_DIR}/shader/desktop_rgb.frag"
"${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h"
"${CMAKE_CURRENT_SOURCE_DIR}/shader/desktop_rgb.frag"
"${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h"
)
add_library(renderer_EGL STATIC
egl.c
egldebug.c
shader.c
texture_util.c
texture.c
texture_buffer.c
texture_framebuffer.c
texture_dmabuf.c
model.c
desktop.c
desktop_rects.c
cursor.c
damage.c
framebuffer.c
postprocess.c
ffx.c
filter.c
filter_24bit.c
filter_ffx_cas.c
filter_ffx_fsr1.c
filter_downscale.c
${EGL_SHADER_OBJS}
"${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h"
${PROJECT_TOP}/repos/cimgui/imgui/backends/imgui_impl_opengl3.cpp
egl.c
egldebug.c
shader.c
texture_util.c
texture.c
texture_buffer.c
texture_framebuffer.c
texture_dmabuf.c
model.c
desktop.c
desktop_rects.c
cursor.c
draw.c
splash.c
damage.c
framebuffer.c
postprocess.c
ffx.c
filter.c
filter_ffx_cas.c
filter_ffx_fsr1.c
filter_downscale.c
${EGL_SHADER_OBJS}
"${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h"
${PROJECT_TOP}/repos/cimgui/imgui/backends/imgui_impl_opengl3.cpp
)
target_compile_definitions(renderer_EGL PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 IMGUI_IMPL_OPENGL_ES3)
target_link_libraries(renderer_EGL
PkgConfig::RENDERER_EGL
lg_common
PkgConfig::RENDERER_EGL
lg_common
cimgui
cimgui
)
if(RENDERER_EGL_OPT_FOUND)
target_link_libraries(renderer_EGL
PkgConfig::RENDERER_EGL_OPT
)
target_link_libraries(renderer_EGL
PkgConfig::RENDERER_EGL_OPT
)
endif()
target_include_directories(renderer_EGL
PRIVATE
src
${EGL_SHADER_INCS}
PRIVATE
src
${EGL_SHADER_INCS}
)

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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;
@@ -87,7 +84,7 @@ static bool cursorTexInit(struct CursorTex * t,
const char * vertex_code , size_t vertex_size,
const char * fragment_code, size_t fragment_size)
{
if (!egl_textureInit(&t->texture, NULL, EGL_TEXTYPE_BUFFER))
if (!egl_textureInit(&t->texture, NULL, EGL_TEXTYPE_BUFFER, false))
{
DEBUG_ERROR("Failed to initialize the cursor texture");
return false;
@@ -100,28 +97,34 @@ static bool cursorTexInit(struct CursorTex * t,
}
if (!egl_shaderCompile(t->shader,
vertex_code, vertex_size, fragment_code, fragment_size, false, NULL))
vertex_code, vertex_size, fragment_code, fragment_size))
{
DEBUG_ERROR("Failed to compile the cursor shader");
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, cursor->width, sizeof(xor[0]));
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor, true);
}
// fall through
// fall through
case LG_CURSOR_COLOR:
{
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
cursor->width, cursor->height, cursor->width, cursor->stride);
egl_textureUpdate(cursor->norm.texture, data, true);
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,17 +276,14 @@ 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, cursor->width, sizeof(and[0]));
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
cursor->width, cursor->height, cursor->width, sizeof(xor[0]));
egl_textureUpdate(cursor->norm.texture, (uint8_t *)and, true);
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor, true);
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-2025 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-2025 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
@@ -76,8 +76,7 @@ bool egl_damageInit(EGL_Damage ** damage)
if (!egl_shaderCompile((*damage)->shader,
b_shader_damage_vert, b_shader_damage_vert_size,
b_shader_damage_frag, b_shader_damage_frag_size,
false, NULL))
b_shader_damage_frag, b_shader_damage_frag_size))
{
DEBUG_ERROR("Failed to compile the damage shader");
return false;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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,14 +46,9 @@ struct DesktopShader
EGL_Shader * shader;
GLint uTransform;
GLint uDesktopSize;
GLint uSamplerType;
GLint uScaleAlgo;
GLint uNVGain;
GLint uCBMode;
GLint uIsHDR;
GLint uMapHDRtoSDR;
GLint uMapHDRGain;
GLint uMapHDRPQ;
};
struct EGL_Desktop
@@ -62,20 +57,15 @@ struct EGL_Desktop
EGLDisplay * display;
EGL_Texture * texture;
struct DesktopShader dmaShader, shader;
GLuint sampler;
struct DesktopShader shader;
EGL_DesktopRects * mesh;
CountedBuffer * matrix;
// internals
int width, height;
bool hdr;
bool hdrPQ;
LG_RendererRotate rotate;
bool useSpice;
int spiceWidth, spiceHeight;
EGL_Texture * spiceTexture;
// scale algorithm
int scaleAlgo;
@@ -89,11 +79,6 @@ struct EGL_Desktop
bool useDMA;
LG_RendererFormat format;
// map HDR content to SDR
bool mapHDRtoSDR;
int peakLuminance;
int maxCLL;
EGL_PostProcess * pp;
_Atomic(bool) processFrame;
};
@@ -104,8 +89,7 @@ void toggleNV(int key, void * opaque);
static bool egl_initDesktopShader(
struct DesktopShader * shader,
const char * vertex_code , size_t vertex_size,
const char * fragment_code, size_t fragment_size,
bool useDMA
const char * fragment_code, size_t fragment_size
)
{
if (!egl_shaderInit(&shader->shader))
@@ -113,21 +97,16 @@ static bool egl_initDesktopShader(
if (!egl_shaderCompile(shader->shader,
vertex_code , vertex_size,
fragment_code, fragment_size,
useDMA, NULL))
fragment_code, fragment_size))
{
return false;
}
shader->uDesktopSize = egl_shaderGetUniform(shader->shader, "desktopSize" );
shader->uTransform = egl_shaderGetUniform(shader->shader, "transform" );
shader->uScaleAlgo = egl_shaderGetUniform(shader->shader, "scaleAlgo" );
shader->uNVGain = egl_shaderGetUniform(shader->shader, "nvGain" );
shader->uCBMode = egl_shaderGetUniform(shader->shader, "cbMode" );
shader->uIsHDR = egl_shaderGetUniform(shader->shader, "isHDR" );
shader->uMapHDRtoSDR = egl_shaderGetUniform(shader->shader, "mapHDRtoSDR" );
shader->uMapHDRGain = egl_shaderGetUniform(shader->shader, "mapHDRGain" );
shader->uMapHDRPQ = egl_shaderGetUniform(shader->shader, "mapHDRPQ" );
shader->uTransform = egl_shaderGetUniform(shader->shader, "transform" );
shader->uDesktopSize = egl_shaderGetUniform(shader->shader, "desktopSize");
shader->uScaleAlgo = egl_shaderGetUniform(shader->shader, "scaleAlgo" );
shader->uNVGain = egl_shaderGetUniform(shader->shader, "nvGain" );
shader->uCBMode = egl_shaderGetUniform(shader->shader, "cbMode" );
return true;
}
@@ -147,12 +126,21 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display,
desktop->display = display;
if (!egl_textureInit(&desktop->texture, display,
useDMA ? EGL_TEXTYPE_DMABUF : EGL_TEXTYPE_FRAMEBUFFER))
useDMA ? EGL_TEXTYPE_DMABUF : EGL_TEXTYPE_FRAMEBUFFER, true))
{
DEBUG_ERROR("Failed to initialize the desktop texture");
return false;
}
if (!egl_initDesktopShader(
&desktop->shader,
b_shader_desktop_vert , b_shader_desktop_vert_size,
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size))
{
DEBUG_ERROR("Failed to initialize the generic desktop shader");
return false;
}
if (!egl_desktopRectsInit(&desktop->mesh, maxRects))
{
DEBUG_ERROR("Failed to initialize the desktop mesh");
@@ -166,28 +154,7 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display,
return false;
}
if (!egl_initDesktopShader(
&desktop->shader,
b_shader_desktop_vert , b_shader_desktop_vert_size,
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size,
false))
{
DEBUG_ERROR("Failed to initialize the desktop shader");
return false;
}
if (useDMA)
if (!egl_initDesktopShader(
&desktop->dmaShader,
b_shader_desktop_vert , b_shader_desktop_vert_size,
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size,
true))
{
DEBUG_ERROR("Failed to initialize the desktop DMA shader");
return false;
}
app_registerKeybind(0, 'N', toggleNV, desktop,
app_registerKeybind(KEY_N, toggleNV, desktop,
"Toggle night vision mode");
desktop->nvMax = option_get_int("egl", "nvGainMax");
@@ -196,19 +163,12 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display,
desktop->scaleAlgo = option_get_int("egl", "scale" );
desktop->useDMA = useDMA;
desktop->mapHDRtoSDR = option_get_bool("egl", "mapHDRtoSDR" );
desktop->peakLuminance = option_get_int ("egl", "peakLuminance");
desktop->maxCLL = option_get_int ("egl", "maxCLL" );
if (!egl_postProcessInit(&desktop->pp))
{
DEBUG_ERROR("Failed to initialize the post process manager");
return false;
}
// this MUST be first
egl_postProcessAdd(desktop->pp, &egl_filter24bitOps);
egl_postProcessAdd(desktop->pp, &egl_filterDownscaleOps);
egl_postProcessAdd(desktop->pp, &egl_filterFFXCASOps );
egl_postProcessAdd(desktop->pp, &egl_filterFFXFSR1Ops );
@@ -242,12 +202,10 @@ void egl_desktopFree(EGL_Desktop ** desktop)
if (!*desktop)
return;
egl_textureFree (&(*desktop)->texture );
egl_textureFree (&(*desktop)->spiceTexture );
egl_shaderFree (&(*desktop)->shader .shader);
egl_shaderFree (&(*desktop)->dmaShader.shader);
egl_desktopRectsFree(&(*desktop)->mesh );
countedBufferRelease(&(*desktop)->matrix );
egl_textureFree (&(*desktop)->texture );
egl_shaderFree (&(*desktop)->shader.shader);
egl_desktopRectsFree(&(*desktop)->mesh );
countedBufferRelease(&(*desktop)->matrix );
egl_postProcessFree(&(*desktop)->pp);
@@ -270,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();
@@ -293,28 +250,6 @@ void egl_desktopConfigUI(EGL_Desktop * desktop)
}
igSliderInt("##nvgain", &desktop->nvGain, 0, desktop->nvMax, format, 0);
igPopItemWidth();
bool mapHDRtoSDR = desktop->mapHDRtoSDR;
int peakLuminance = desktop->peakLuminance;
int maxCLL = desktop->maxCLL;
igSeparator();
igCheckbox("Map HDR content to SDR", &mapHDRtoSDR);
igSliderInt("Peak Luminance", &peakLuminance, 1, 10000,
"%d nits",
ImGuiInputTextFlags_CharsDecimal);
igSliderInt("Max content light level", &maxCLL, 1, 10000,
"%d nits", ImGuiInputTextFlags_CharsDecimal);
if (mapHDRtoSDR != desktop->mapHDRtoSDR ||
peakLuminance != desktop->peakLuminance ||
maxCLL != desktop->maxCLL)
{
desktop->mapHDRtoSDR = mapHDRtoSDR;
desktop->peakLuminance = max(1, peakLuminance);
desktop->maxCLL = max(1, maxCLL);
app_invalidateWindow(true);
}
}
bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
@@ -340,46 +275,41 @@ bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
pixFmt = EGL_PF_RGBA16F;
break;
case FRAME_TYPE_BGR_32:
pixFmt = EGL_PF_BGR_32;
break;
case FRAME_TYPE_RGB_24:
pixFmt = EGL_PF_RGB_24;
break;
default:
DEBUG_ERROR("Unsupported frame format");
return false;
}
desktop->width = format.frameWidth;
desktop->height = format.frameHeight;
desktop->hdr = format.hdr;
desktop->hdrPQ = format.hdrPQ;
desktop->width = format.width;
desktop->height = format.height;
if (!egl_textureSetup(
desktop->texture,
pixFmt,
desktop->format.dataWidth,
desktop->format.dataHeight,
desktop->format.stride,
desktop->format.pitch
format.width,
format.height,
format.pitch
))
{
DEBUG_ERROR("Failed to setup the desktop texture");
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;
}
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd,
const FrameDamageRect * damageRects, int damageRectsCount)
{
if (likely(desktop->useDMA && dmaFd >= 0))
if (desktop->useDMA && dmaFd >= 0)
{
if (likely(egl_textureUpdateFromDMA(desktop->texture, frame, dmaFd)))
if (egl_textureUpdateFromDMA(desktop->texture, frame, dmaFd))
{
atomic_store(&desktop->processFrame, true);
return true;
@@ -405,7 +335,7 @@ bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dma
egl_textureFree(&desktop->texture);
if (!egl_textureInit(&desktop->texture, desktop->display,
EGL_TEXTYPE_FRAMEBUFFER))
EGL_TEXTYPE_FRAMEBUFFER, true))
{
DEBUG_ERROR("Failed to initialize the desktop texture");
return false;
@@ -415,8 +345,8 @@ bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dma
return false;
}
if (likely(egl_textureUpdateFromFrame(desktop->texture, frame,
damageRects, damageRectsCount)))
if (egl_textureUpdateFromFrame(desktop->texture, frame,
damageRects, damageRectsCount))
{
atomic_store(&desktop->processFrame, true);
return true;
@@ -435,30 +365,11 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
LG_RendererRotate rotate, const struct DamageRects * rects)
{
EGL_Texture * tex;
int width, height;
bool dma;
if (unlikely(desktop->useSpice))
{
tex = desktop->spiceTexture;
width = desktop->spiceWidth;
height = desktop->spiceHeight;
dma = false;
}
else
{
tex = desktop->texture;
width = desktop->width;
height = desktop->height;
dma = desktop->useDMA;
}
if (unlikely(outputWidth == 0 || outputHeight == 0))
if (outputWidth == 0 && outputHeight == 0)
DEBUG_FATAL("outputWidth || outputHeight == 0");
enum EGL_TexStatus status;
if (unlikely((status = egl_textureProcess(tex)) != EGL_TEX_STATUS_OK))
if ((status = egl_textureProcess(desktop->texture)) != EGL_TEX_STATUS_OK)
{
if (status != EGL_TEX_STATUS_NOTREADY)
DEBUG_ERROR("Failed to process the desktop texture");
@@ -467,32 +378,26 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
int scaleAlgo = EGL_SCALE_NEAREST;
egl_desktopRectsMatrix((float *)desktop->matrix->data,
width, height, x, y, scaleX, scaleY, rotate);
egl_desktopRectsUpdate(desktop->mesh, rects, width, height);
desktop->width, desktop->height, x, y, scaleX, scaleY, rotate);
egl_desktopRectsUpdate(desktop->mesh, rects, desktop->width, desktop->height);
if (atomic_exchange(&desktop->processFrame, false) ||
egl_postProcessConfigModified(desktop->pp))
egl_postProcessRun(desktop->pp, tex, desktop->mesh,
width, height, outputWidth, outputHeight, dma);
egl_postProcessRun(desktop->pp, desktop->texture, desktop->mesh,
desktop->width, desktop->height, outputWidth, outputHeight);
unsigned int finalSizeX, finalSizeY;
EGL_Texture * texture = egl_postProcessGetOutput(desktop->pp,
GLuint texture = egl_postProcessGetOutput(desktop->pp,
&finalSizeX, &finalSizeY);
if (unlikely(!texture))
{
texture = tex;
finalSizeX = width;
finalSizeY = height;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
egl_resetViewport(desktop->egl);
glActiveTexture(GL_TEXTURE0);
egl_textureBind(texture);
glBindTexture(GL_TEXTURE_2D, texture);
glBindSampler(0, desktop->sampler);
if (finalSizeX > width || finalSizeY > height)
if (finalSizeX > desktop->width || finalSizeY > desktop->height)
scaleType = EGL_DESKTOP_DOWNSCALE;
switch (desktop->scaleAlgo)
@@ -515,13 +420,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
scaleAlgo = desktop->scaleAlgo;
}
const struct DesktopShader * shader =
desktop->useDMA && texture == desktop->texture ?
&desktop->dmaShader : &desktop->shader;
const float mapHDRGain =
(float)desktop->maxCLL / desktop->peakLuminance;
const struct DesktopShader * shader = &desktop->shader;
EGL_Uniform uniforms[] =
{
{
@@ -532,7 +431,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
{
.type = EGL_UNIFORM_TYPE_2F,
.location = shader->uDesktopSize,
.f = { width, height },
.f = { desktop->width, desktop->height },
},
{
.type = EGL_UNIFORM_TYPE_M3x2FV,
@@ -549,26 +448,6 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
.type = EGL_UNIFORM_TYPE_1I,
.location = shader->uCBMode,
.f = { desktop->cbMode }
},
{
.type = EGL_UNIFORM_TYPE_1I,
.location = shader->uIsHDR,
.i = { desktop->hdr }
},
{
.type = EGL_UNIFORM_TYPE_1I,
.location = shader->uMapHDRtoSDR,
.i = { desktop->mapHDRtoSDR }
},
{
.type = EGL_UNIFORM_TYPE_1F,
.location = shader->uMapHDRGain,
.f = { mapHDRGain }
},
{
.type = EGL_UNIFORM_TYPE_1I,
.location = shader->uMapHDRPQ,
.f = { desktop->hdrPQ }
}
};
@@ -578,61 +457,3 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height)
{
if (!desktop->spiceTexture)
if (!egl_textureInit(&desktop->spiceTexture, desktop->display,
EGL_TEXTYPE_BUFFER_MAP))
{
DEBUG_ERROR("Failed to initialize the spice desktop texture");
return;
}
if (!egl_textureSetup(
desktop->spiceTexture,
EGL_PF_BGRA,
width,
height,
width,
width * 4
))
{
DEBUG_ERROR("Failed to setup the spice desktop texture");
return;
}
desktop->spiceWidth = width;
desktop->spiceHeight = height;
}
void egl_desktopSpiceDrawFill(EGL_Desktop * desktop, int x, int y, int width,
int height, uint32_t color)
{
/* this is a fairly hacky way to do this, but since it's only for the fallback
* spice display it's not really an issue */
uint32_t line[width];
for(int x = 0; x < width; ++x)
line[x] = color;
for(int dy = 0; dy < height; ++dy)
egl_textureUpdateRect(desktop->spiceTexture,
x, y + dy, width, 1, width, sizeof(line), (uint8_t *)line, false);
atomic_store(&desktop->processFrame, true);
}
void egl_desktopSpiceDrawBitmap(EGL_Desktop * desktop, int x, int y, int width,
int height, int stride, uint8_t * data, bool topDown)
{
egl_textureUpdateRect(desktop->spiceTexture,
x, y, width, height, width, stride, data, topDown);
atomic_store(&desktop->processFrame, true);
}
void egl_desktopSpiceShow(EGL_Desktop * desktop, bool show)
{
desktop->useSpice = show;
atomic_store(&desktop->processFrame, true);
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -50,10 +50,3 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
unsigned int outputHeight, const float x, const float y,
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
LG_RendererRotate rotate, const struct DamageRects * rects);
void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height);
void egl_desktopSpiceDrawFill(EGL_Desktop * desktop, int x, int y, int width,
int height, uint32_t color);
void egl_desktopSpiceDrawBitmap(EGL_Desktop * desktop, int x, int y, int width,
int height, int stride, uint8_t * data, bool topDown);
void egl_desktopSpiceShow(EGL_Desktop * desktop, bool show);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -32,10 +32,6 @@
struct EGL_DesktopRects
{
GLfloat * lastVertices;
int lastVerticesCount;
int lastVerticesSize;
GLuint buffers[2];
GLuint vao;
int count;
@@ -92,7 +88,6 @@ void egl_desktopRectsFree(EGL_DesktopRects ** rects_)
glDeleteVertexArrays(1, &rects->vao);
glDeleteBuffers(2, rects->buffers);
free(rects->lastVertices);
free(rects);
*rects_ = NULL;
}
@@ -118,8 +113,7 @@ void egl_desktopRectsUpdate(EGL_DesktopRects * rects, const struct DamageRects *
return;
}
const int count = (!data || data->count < 0 ? 1 : data->count) * 8;
GLfloat vertices[count];
GLfloat vertices[(!data || data->count < 0 ? 1 : data->count) * 8];
if (!data || data->count < 0)
{
FrameDamageRect full = {
@@ -137,30 +131,6 @@ void egl_desktopRectsUpdate(EGL_DesktopRects * rects, const struct DamageRects *
rectToVertices(vertices + i * 8, data->rects + i);
}
// check if the value actually changed and needs updating
if (count == rects->lastVerticesCount &&
memcmp(rects->lastVertices, vertices, sizeof(GLfloat) * count) == 0)
return;
// ensure the local storage is large enough
if (count > rects->lastVerticesSize)
{
if (rects->lastVertices)
free(rects->lastVertices);
rects->lastVertices = malloc(sizeof(GLfloat) * count);
if (!rects->lastVertices)
{
DEBUG_ERROR("out of memory");
return;
}
rects->lastVerticesSize = count;
}
// copy the last value for later comparison
rects->lastVerticesCount = count;
memcpy(rects->lastVertices, vertices, sizeof(GLfloat) * count);
glBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]);
glBufferSubData(GL_ARRAY_BUFFER, 0, rects->count * 8 * sizeof(GLfloat), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
@@ -296,7 +266,7 @@ bool egl_screenToDesktop(struct FrameDamageRect * output, const double matrix[6]
void egl_desktopRectsRender(EGL_DesktopRects * rects)
{
if (unlikely(!rects->count))
if (!rects->count)
return;
glBindVertexArray(rects->vao);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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

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

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,12 +18,12 @@
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _H_X11DS_CURSOR_
#define _H_X11DS_CURSOR_
#pragma once
#include <X11/Xlib.h>
#include <X11/Xcursor/Xcursor.h>
#include "model.h"
XcursorImages * x11cursor_load(const char * cursor, int size);
void egl_drawTorus(EGL_Model * model, unsigned int pts, float x, float y,
float inner, float outer);
#endif
void egl_drawTorusArc(EGL_Model * model, unsigned int pts, float x, float y,
float inner, float outer, float s, float e);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,7 +20,6 @@
#include "interface/renderer.h"
#include "common/util.h"
#include "common/debug.h"
#include "common/KVMFR.h"
#include "common/option.h"
@@ -40,15 +39,18 @@
#include <math.h>
#include <string.h>
#include "app.h"
#include "egl_dynprocs.h"
#include "model.h"
#include "shader.h"
#include "damage.h"
#include "desktop.h"
#include "cursor.h"
#include "splash.h"
#include "postprocess.h"
#include "util.h"
#define SPLASH_FADE_TIME 1000000
#define MAX_BUFFER_AGE 3
#define DESKTOP_DAMAGE_COUNT 4
#define MAX_ACCUMULATED_DAMAGE ((KVMFR_MAX_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2) * MAX_BUFFER_AGE)
@@ -76,11 +78,15 @@ struct Inst
EGL_Desktop * desktop; // the desktop
EGL_Cursor * cursor; // the mouse cursor
EGL_Splash * splash; // the splash screen
EGL_Damage * damage; // the damage display
bool imgui; // if imgui was initialized
LG_RendererFormat format;
bool formatValid;
bool start;
uint64_t waitFadeTime;
bool waitDone;
int width, height;
float uiScale;
@@ -97,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;
@@ -118,9 +122,6 @@ struct Inst
RingBuffer importTimings;
GraphHandle importGraph;
bool showSpice;
int spiceWidth, spiceHeight;
};
static struct Option egl_options[] =
@@ -196,34 +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
},
{
.module = "egl",
.name = "mapHDRtoSDR",
.description = "Map HDR content to the SDR color space",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "egl",
.name = "peakLuminance",
.description = "The peak luminance level in nits for HDR to SDR mapping",
.type = OPTION_TYPE_INT,
.value.x_int = 250,
},
{
.module = "egl",
.name = "maxCLL",
.description = "Maximum content light level in nits for HDR to SDR mapping",
.type = OPTION_TYPE_INT,
.value.x_int = 10000,
},
{0}
};
@@ -276,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;
@@ -297,10 +269,12 @@ static void egl_deinitialize(LG_Renderer * renderer)
if (this->imgui)
ImGui_ImplOpenGL3_Shutdown();
app_unregisterGraph(this->importGraph);
ringbuffer_free(&this->importTimings);
egl_desktopFree(&this->desktop);
egl_cursorFree (&this->cursor);
egl_splashFree (&this->splash);
egl_damageFree (&this->damage);
LG_LOCK_FREE(this->lock);
@@ -339,6 +313,7 @@ static void egl_onRestart(LG_Renderer * renderer)
eglDestroyContext(this->display, this->frameContext);
this->frameContext = NULL;
this->start = false;
INTERLOCKED_SECTION(this->desktopDamageLock, {
this->desktopDamage[this->desktopDamageIdx].count = -1;
@@ -347,17 +322,6 @@ static void egl_onRestart(LG_Renderer * renderer)
static void egl_calc_mouse_size(struct Inst * this)
{
if (this->showSpice)
{
this->mouseScaleX = 2.0f / this->spiceWidth;
this->mouseScaleY = 2.0f / this->spiceHeight;
egl_cursorSetSize(this->cursor,
(this->mouseWidth * (1.0f / this->spiceWidth )) * this->scaleX,
(this->mouseHeight * (1.0f / this->spiceHeight)) * this->scaleY
);
return;
}
if (!this->formatValid)
return;
@@ -367,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:
@@ -407,19 +371,6 @@ static void egl_calc_mouse_size(struct Inst * this)
static void egl_calc_mouse_state(struct Inst * this)
{
if (this->showSpice)
{
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
);
return;
}
if (!this->formatValid)
return;
@@ -430,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;
@@ -442,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;
}
@@ -459,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;
}
@@ -502,8 +449,8 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
if (destRect.valid)
{
this->translateX = -1.0f + (((this->destRect.w / 2) + this->destRect.x) * 2) / (float)this->width;
this->translateY = 1.0f - (((this->destRect.h / 2) + this->destRect.y) * 2) / (float)this->height;
this->translateX = 1.0f - (((this->destRect.w / 2) + this->destRect.x) * 2) / (float)this->width;
this->translateY = 1.0f - (((this->destRect.h / 2) + this->destRect.y) * 2) / (float)this->height;
this->scaleX = (float)this->destRect.w / (float)this->width;
this->scaleY = (float)this->destRect.h / (float)this->height;
this->viewportWidth = this->destRect.w;
@@ -518,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;
@@ -535,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);
@@ -561,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;
}
@@ -581,7 +514,7 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
this->formatValid = true;
/* this event runs in a second thread so we need to init it here */
if (unlikely(!this->frameContext))
if (!this->frameContext)
{
static EGLint attrs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
@@ -601,14 +534,8 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
}
}
if (likely(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, {
@@ -624,27 +551,23 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int d
struct Inst * this = UPCAST(struct Inst, renderer);
uint64_t start = nanotime();
if (unlikely(!egl_desktopUpdate(
this->desktop, frame, dmaFd, damageRects, damageRectsCount)))
if (!egl_desktopUpdate(this->desktop, frame, dmaFd, damageRects, damageRectsCount))
{
DEBUG_INFO("Failed to to update the desktop");
return false;
}
ringbuffer_push(this->importTimings, &(float){ (nanotime() - start) * 1e-6f });
this->start = true;
INTERLOCKED_SECTION(this->desktopDamageLock, {
struct DesktopDamage * damage = this->desktopDamage + this->desktopDamageIdx;
if (unlikely(
damage->count == -1 ||
damageRectsCount == 0 ||
damage->count + damageRectsCount >= KVMFR_MAX_DAMAGE_RECTS))
{
if (damage->count == -1 || damageRectsCount == 0 ||
damage->count + damageRectsCount >= KVMFR_MAX_DAMAGE_RECTS)
damage->count = -1;
}
else
{
memcpy(damage->rects + damage->count, damageRects,
damageRectsCount * sizeof(FrameDamageRect));
memcpy(damage->rects + damage->count, damageRects, damageRectsCount * sizeof(FrameDamageRect));
damage->count += damageRectsCount;
}
});
@@ -776,7 +699,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
EGLint attr[] =
{
EGL_BUFFER_SIZE , 30,
EGL_BUFFER_SIZE , 24,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0,
EGL_SAMPLES , maxSamples,
@@ -813,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;
@@ -829,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)
@@ -867,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);
@@ -916,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)
@@ -939,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)
{
@@ -971,6 +871,12 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
return false;
}
if (!egl_splashInit(&this->splash))
{
DEBUG_ERROR("Failed to initialize the splash screen");
return false;
}
if (!egl_damageInit(&this->damage))
{
DEBUG_ERROR("Failed to initialize the damage display");
@@ -989,6 +895,12 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
return true;
}
static bool egl_needsRender(LG_Renderer * renderer)
{
struct Inst * this = UPCAST(struct Inst, renderer);
return !this->waitDone;
}
inline static EGLint egl_bufferAge(struct Inst * this)
{
if (!this->hasBufferAge)
@@ -1015,26 +927,21 @@ inline static void renderLetterBox(struct Inst * this)
if (hLB)
{
// left
glScissor(0, 0, this->destRect.x, this->height);
glScissor(0.0f, 0.0f, this->destRect.x + 0.5f, this->height + 0.5f);
glClear(GL_COLOR_BUFFER_BIT);
// right
float x2 = this->destRect.x + this->destRect.w;
glScissor(x2, 0, this->width - x2, this->height);
glScissor(x2 - 0.5f, 0.0f, this->width - x2 + 1.0f, this->height + 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
if (vLB)
{
// top
glScissor(0, this->height - this->destRect.y,
this->width, this->destRect.y);
glScissor(0.0f, 0.0f, this->width + 0.5f, this->destRect.y + 0.5f);
glClear(GL_COLOR_BUFFER_BIT);
// bottom
int y2 = this->destRect.y + this->destRect.h;
glScissor(0, 0, this->width, this->height - y2);
float y2 = this->destRect.y + this->destRect.h;
glScissor(0.0f, y2 - 0.5f, this->width + 1.0f, this->height - y2 + 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
@@ -1048,9 +955,8 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
{
struct Inst * this = UPCAST(struct Inst, renderer);
EGLint bufferAge = egl_bufferAge(this);
bool renderAll = invalidateWindow || this->hadOverlay ||
bufferAge <= 0 || bufferAge > MAX_BUFFER_AGE ||
this->showSpice;
bool renderAll = invalidateWindow || !this->start || this->hadOverlay ||
bufferAge <= 0 || bufferAge > MAX_BUFFER_AGE;
bool hasOverlay = false;
struct CursorState cursorState = { .visible = false };
@@ -1063,14 +969,14 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
accumulated->count = 0;
INTERLOCKED_SECTION(this->desktopDamageLock, {
if (likely(!renderAll))
if (!renderAll)
{
for (int i = 0; i < bufferAge; ++i)
{
struct DesktopDamage * damage = this->desktopDamage +
IDX_AGO(this->desktopDamageIdx, i, DESKTOP_DAMAGE_COUNT);
if (unlikely(damage->count < 0))
if (damage->count < 0)
{
renderAll = true;
break;
@@ -1083,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),
};
}
}
@@ -1094,11 +1000,10 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
this->desktopDamage[this->desktopDamageIdx].count = 0;
});
if (likely(!renderAll))
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);
@@ -1108,7 +1013,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
int count = this->overlayHistoryCount[idx];
struct Rect * damage = this->overlayHistory[idx];
if (unlikely(count < 0))
if (count < 0)
{
renderAll = true;
break;
@@ -1117,16 +1022,15 @@ 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
);
}
accumulated->count = rectsMergeOverlapping(accumulated->rects,
accumulated->count);
accumulated->count = rectsMergeOverlapping(accumulated->rects, accumulated->count);
}
++this->overlayHistoryIdx;
if (likely(this->destRect.w > 0 && this->destRect.h > 0))
if (this->start && this->destRect.w > 0 && this->destRect.h > 0)
{
if (egl_desktopRender(this->desktop,
this->destRect.w, this->destRect.h,
@@ -1134,6 +1038,14 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
this->scaleX , this->scaleY ,
this->scaleType , rotate, renderAll ? NULL : accumulated))
{
if (!this->waitFadeTime)
{
if (!this->params.quickSplash)
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
else
this->waitDone = true;
}
cursorState = egl_cursorRender(this->cursor,
(this->format.rotate + rotate) % LG_ROTATE_MAX,
this->width, this->height);
@@ -1144,39 +1056,70 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
renderLetterBox(this);
hasOverlay |=
egl_damageRender(this->damage, rotate, newFrame ? desktopDamage : NULL) |
invalidateWindow;
if (!this->waitDone)
{
float a = 1.0f;
if (!this->waitFadeTime)
a = 1.0f;
else
{
uint64_t t = microtime();
if (t > this->waitFadeTime)
this->waitDone = true;
else
{
uint64_t delta = this->waitFadeTime - t;
a = 1.0f / SPLASH_FADE_TIME * delta;
}
}
if (!this->waitDone)
{
egl_splashRender(this->splash, a, this->splashRatio);
hasOverlay = true;
}
}
else if (!this->start)
{
egl_splashRender(this->splash, 1.0f, this->splashRatio);
hasOverlay = true;
}
hasOverlay |= egl_damageRender(this->damage, rotate, newFrame ? desktopDamage : NULL);
hasOverlay |= invalidateWindow;
struct Rect damage[KVMFR_MAX_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2];
int damageIdx = app_renderOverlay(damage, MAX_OVERLAY_RECTS);
if (unlikely(damageIdx != 0))
switch (damageIdx)
{
if (damageIdx == -1)
case 0: // no overlay
break;
case -1: // full damage
hasOverlay = true;
// fallthrough
default:
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData());
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData());
for (int i = 0; i < damageIdx; ++i)
damage[i].y = this->height - damage[i].y - damage[i].h;
for (int i = 0; i < damageIdx; ++i)
damage[i].y = this->height - damage[i].y - damage[i].h;
}
if (likely(damageIdx >= 0 && cursorState.visible))
if (damageIdx >= 0 && cursorState.visible)
damage[damageIdx++] = cursorState.rect;
int overlayHistoryIdx = this->overlayHistoryIdx % DESKTOP_DAMAGE_COUNT;
if (unlikely(hasOverlay))
if (hasOverlay)
this->overlayHistoryCount[overlayHistoryIdx] = -1;
else
{
if (unlikely(damageIdx > 0))
memcpy(this->overlayHistory[overlayHistoryIdx],
damage, damageIdx * sizeof(struct Rect));
if (damageIdx > 0)
memcpy(this->overlayHistory[overlayHistoryIdx], damage, damageIdx * sizeof(struct Rect));
this->overlayHistoryCount[overlayHistoryIdx] = damageIdx;
}
if (unlikely(!hasOverlay && !this->hadOverlay))
if (!hasOverlay && !this->hadOverlay)
{
if (this->cursorLast.visible)
damage[damageIdx++] = this->cursorLast.rect;
@@ -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);
@@ -1203,78 +1145,10 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
this->cursorLast = cursorState;
preSwap(udata);
app_eglSwapBuffers(this->display, this->surface, damage,
this->noSwapDamage ? 0 : damageIdx);
app_eglSwapBuffers(this->display, this->surface, damage, this->noSwapDamage ? 0 : damageIdx);
return true;
}
static void * egl_createTexture(LG_Renderer * renderer,
int width, int height, uint8_t * data)
{
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
width,
height,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
data);
glBindTexture(GL_TEXTURE_2D, 0);
return (void*)(intptr_t)tex;
}
static void egl_freeTexture(LG_Renderer * renderer, void * texture)
{
GLuint tex = (GLuint)(intptr_t)texture;
glDeleteTextures(1, &tex);
}
static void egl_spiceConfigure(LG_Renderer * renderer, int width, int height)
{
struct Inst * this = UPCAST(struct Inst, renderer);
this->spiceWidth = width;
this->spiceHeight = height;
egl_desktopSpiceConfigure(this->desktop, width, height);
}
static void egl_spiceDrawFill(LG_Renderer * renderer, int x, int y, int width,
int height, uint32_t color)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_desktopSpiceDrawFill(this->desktop, x, y, width, height, color);
}
static void egl_spiceDrawBitmap(LG_Renderer * renderer, int x, int y, int width,
int height, int stride, uint8_t * data, bool topDown)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_desktopSpiceDrawBitmap(this->desktop, x, y, width, height, stride,
data, topDown);
}
static void egl_spiceShow(LG_Renderer * renderer, bool show)
{
struct Inst * this = UPCAST(struct Inst, renderer);
this->showSpice = show;
egl_calc_mouse_size(this);
egl_desktopSpiceShow(this->desktop, show);
}
struct LG_RendererOps LGR_EGL =
{
.getName = egl_getName,
@@ -1290,12 +1164,6 @@ struct LG_RendererOps LGR_EGL =
.onFrameFormat = egl_onFrameFormat,
.onFrame = egl_onFrame,
.renderStartup = egl_renderStartup,
.render = egl_render,
.createTexture = egl_createTexture,
.freeTexture = egl_freeTexture,
.spiceConfigure = egl_spiceConfigure,
.spiceDrawFill = egl_spiceDrawFill,
.spiceDrawBitmap = egl_spiceDrawBitmap,
.spiceShow = egl_spiceShow
.needsRender = egl_needsRender,
.render = egl_render
};

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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-2025 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-2025 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,8 +25,6 @@
typedef enum EGL_TexType
{
EGL_TEXTYPE_BUFFER,
EGL_TEXTYPE_BUFFER_MAP,
EGL_TEXTYPE_BUFFER_STREAM,
EGL_TEXTYPE_FRAMEBUFFER,
EGL_TEXTYPE_DMABUF
}
@@ -37,10 +35,7 @@ typedef enum EGL_PixelFormat
EGL_PF_RGBA,
EGL_PF_BGRA,
EGL_PF_RGBA10,
EGL_PF_RGBA16F,
EGL_PF_BGR_32,
EGL_PF_RGB_24,
EGL_PF_RGB_24_32
EGL_PF_RGBA16F
}
EGL_PixelFormat;
@@ -63,17 +58,13 @@ typedef struct EGL_TexSetup
/* the height of the texture in pixels */
size_t height;
/* the row length of the texture in pixels */
/* the stide of the texture in bytes */
size_t stride;
/* the row length of the texture in bytes */
size_t pitch;
}
EGL_TexSetup;
typedef enum EGL_FilterType
{
EGL_FILTER_TYPE_INTERNAL,
EGL_FILTER_TYPE_EFFECT,
EGL_FILTER_TYPE_UPSCALE,
EGL_FILTER_TYPE_DOWNSCALE

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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-2025 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-2025 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
@@ -68,21 +68,18 @@ typedef struct EGL_FilterOps
/* reads filter state from options */
void (*loadState)(EGL_Filter * filter);
/* set the input format of the filter
* useDMA will be true if the texture provided needs to use samplerExternalOES
*/
/* set the input format of the filter */
bool (*setup)(EGL_Filter * filter, enum EGL_PixelFormat pixFmt,
unsigned int width, unsigned int height,
unsigned int desktopWidth, unsigned int desktopHeight, bool useDMA);
unsigned int width, unsigned int height);
/* set the output resolution hint for the filter
* this is optional and only a hint */
void (*setOutputResHint)(EGL_Filter * filter,
unsigned int x, unsigned int y);
/* returns the output resolution and pixel format of the filter */
/* returns the output resolution of the filter */
void (*getOutputRes)(EGL_Filter * filter,
unsigned int *x, unsigned int *y, enum EGL_PixelFormat *pixFmt);
unsigned int *x, unsigned int *y);
/* prepare the shader for use
* A filter can return false to bypass it */
@@ -90,8 +87,8 @@ typedef struct EGL_FilterOps
/* runs the filter on the provided texture
* returns the processed texture as the output */
EGL_Texture * (*run)(EGL_Filter * filter, EGL_FilterRects * rects,
EGL_Texture * texture);
GLuint (*run)(EGL_Filter * filter, EGL_FilterRects * rects,
GLuint texture);
/* called when the filter output is no loger needed so it can release memory
* this is optional */
@@ -105,12 +102,6 @@ typedef struct EGL_Filter
}
EGL_Filter;
static inline void egl_filterEarlyInit(const EGL_FilterOps * ops)
{
if (ops->earlyInit)
ops->earlyInit();
}
static inline bool egl_filterInit(const EGL_FilterOps * ops, EGL_Filter ** filter)
{
if (!ops->init(filter))
@@ -128,30 +119,23 @@ static inline void egl_filterFree(EGL_Filter ** filter)
static inline bool egl_filterImguiConfig(EGL_Filter * filter)
{
if (filter->ops.imguiConfig)
return filter->ops.imguiConfig(filter);
return false;
return filter->ops.imguiConfig(filter);
}
static inline void egl_filterSaveState(EGL_Filter * filter)
{
if (filter->ops.saveState)
filter->ops.saveState(filter);
filter->ops.saveState(filter);
}
static inline void egl_filterLoadState(EGL_Filter * filter)
{
if (filter->ops.loadState)
filter->ops.loadState(filter);
filter->ops.loadState(filter);
}
static inline bool egl_filterSetup(EGL_Filter * filter,
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height,
unsigned int desktopWidth, unsigned int desktopHeight,
bool useDMA)
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height)
{
return filter->ops.setup(filter, pixFmt, width, height,
desktopWidth, desktopHeight, useDMA);
return filter->ops.setup(filter, pixFmt, width, height);
}
static inline void egl_filterSetOutputResHint(EGL_Filter * filter,
@@ -162,9 +146,9 @@ static inline void egl_filterSetOutputResHint(EGL_Filter * filter,
}
static inline void egl_filterGetOutputRes(EGL_Filter * filter,
unsigned int *x, unsigned int *y, enum EGL_PixelFormat *pixFmt)
unsigned int *x, unsigned int *y)
{
return filter->ops.getOutputRes(filter, x, y, pixFmt);
return filter->ops.getOutputRes(filter, x, y);
}
static inline bool egl_filterPrepare(EGL_Filter * filter)
@@ -172,8 +156,8 @@ static inline bool egl_filterPrepare(EGL_Filter * filter)
return filter->ops.prepare(filter);
}
static inline EGL_Texture * egl_filterRun(EGL_Filter * filter,
EGL_FilterRects * rects, EGL_Texture * texture)
static inline GLuint egl_filterRun(EGL_Filter * filter,
EGL_FilterRects * rects, GLuint texture)
{
return filter->ops.run(filter, rects, texture);
}

View File

@@ -1,221 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2025 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 "filter.h"
#include "framebuffer.h"
#include <math.h>
#include "common/array.h"
#include "common/debug.h"
#include "common/option.h"
#include "cimgui.h"
#include "basic.vert.h"
#include "convert_24bit.frag.h"
typedef struct EGL_Filter24bit
{
EGL_Filter base;
bool enable;
EGL_PixelFormat format;
int useDMA;
unsigned int width, height;
unsigned int desktopWidth, desktopHeight;
bool prepared;
EGL_Uniform uOutputSize;
EGL_Shader * shader;
EGL_Framebuffer * fb;
GLuint sampler[2];
}
EGL_Filter24bit;
static bool egl_filter24bitInit(EGL_Filter ** filter)
{
EGL_Filter24bit * this = calloc(1, sizeof(*this));
if (!this)
{
DEBUG_ERROR("Failed to allocate ram");
return false;
}
this->useDMA = -1;
if (!egl_shaderInit(&this->shader))
{
DEBUG_ERROR("Failed to initialize the shader");
goto error_this;
}
if (!egl_framebufferInit(&this->fb))
{
DEBUG_ERROR("Failed to initialize the framebuffer");
goto error_shader;
}
glGenSamplers(ARRAY_LENGTH(this->sampler), this->sampler);
glSamplerParameteri(this->sampler[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glSamplerParameteri(this->sampler[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this->sampler[0], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glSamplerParameteri(this->sampler[0], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
glSamplerParameteri(this->sampler[1], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(this->sampler[1], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(this->sampler[1], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glSamplerParameteri(this->sampler[1], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
*filter = &this->base;
return true;
error_shader:
egl_shaderFree(&this->shader);
error_this:
free(this);
return false;
}
static void egl_filter24bitFree(EGL_Filter * filter)
{
EGL_Filter24bit * this = UPCAST(EGL_Filter24bit, filter);
egl_shaderFree(&this->shader);
egl_framebufferFree(&this->fb);
glDeleteSamplers(ARRAY_LENGTH(this->sampler), this->sampler);
free(this);
}
static bool egl_filter24bitSetup(EGL_Filter * filter,
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height,
unsigned int desktopWidth, unsigned int desktopHeight,
bool useDMA)
{
EGL_Filter24bit * this = UPCAST(EGL_Filter24bit, filter);
if (pixFmt != EGL_PF_BGR_32 && pixFmt != EGL_PF_RGB_24_32)
return false;
if (this->useDMA != useDMA || this->format != pixFmt)
{
EGL_ShaderDefine defines[] =
{
{"OUTPUT", pixFmt == EGL_PF_BGR_32 ? "fragColor.bgra" : "fragColor.rgba" },
{0}
};
if (!egl_shaderCompile(this->shader,
b_shader_basic_vert , b_shader_basic_vert_size,
b_shader_convert_24bit_frag, b_shader_convert_24bit_frag_size,
useDMA, defines)
)
{
DEBUG_ERROR("Failed to compile the shader");
return false;
}
this->uOutputSize.type = EGL_UNIFORM_TYPE_2F;
this->uOutputSize.location =
egl_shaderGetUniform(this->shader, "outputSize");
this->useDMA = useDMA;
this->prepared = false;
}
if (this->prepared &&
this->width == width &&
this->height == height &&
this->desktopWidth == desktopWidth &&
this->desktopHeight == desktopHeight)
return true;
if (!egl_framebufferSetup(this->fb, EGL_PF_BGRA, desktopWidth, desktopHeight))
return false;
this->format = pixFmt;
this->width = width;
this->height = height;
this->desktopWidth = desktopWidth;
this->desktopHeight = desktopHeight;
this->prepared = false;
return true;
}
static void egl_filter24bitGetOutputRes(EGL_Filter * filter,
unsigned int *width, unsigned int *height, enum EGL_PixelFormat *pixFmt)
{
EGL_Filter24bit * this = UPCAST(EGL_Filter24bit, filter);
*width = this->desktopWidth;
*height = this->desktopHeight;
*pixFmt = EGL_PF_BGRA;
}
static bool egl_filter24bitPrepare(EGL_Filter * filter)
{
EGL_Filter24bit * this = UPCAST(EGL_Filter24bit, filter);
if (this->prepared)
return true;
this->uOutputSize.f[0] = this->desktopWidth;
this->uOutputSize.f[1] = this->desktopHeight;
egl_shaderSetUniforms(this->shader, &this->uOutputSize, 1);
this->prepared = true;
return true;
}
static EGL_Texture * egl_filter24bitRun(EGL_Filter * filter,
EGL_FilterRects * rects, EGL_Texture * texture)
{
EGL_Filter24bit * this = UPCAST(EGL_Filter24bit, filter);
egl_framebufferBind(this->fb);
glActiveTexture(GL_TEXTURE0);
egl_textureBind(texture);
glBindSampler(0, this->sampler[0]);
egl_shaderUse(this->shader);
egl_filterRectsRender(this->shader, rects);
return egl_framebufferGetTexture(this->fb);
}
EGL_FilterOps egl_filter24bitOps =
{
.id = "24bit",
.name = "24bit",
.type = EGL_FILTER_TYPE_INTERNAL,
.earlyInit = NULL,
.init = egl_filter24bitInit,
.free = egl_filter24bitFree,
.imguiConfig = NULL,
.saveState = NULL,
.loadState = NULL,
.setup = egl_filter24bitSetup,
.getOutputRes = egl_filter24bitGetOutputRes,
.prepare = egl_filter24bitPrepare,
.run = egl_filter24bitRun
};

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -60,7 +60,6 @@ typedef struct EGL_FilterDownscale
EGL_Shader * lanczos2;
DownscaleFilter filter;
int useDMA;
enum EGL_PixelFormat pixFmt;
unsigned int width, height;
float pixelSize;
@@ -158,26 +157,55 @@ static bool egl_filterDownscaleInit(EGL_Filter ** filter)
return false;
}
this->useDMA = -1;
if (!egl_shaderInit(&this->nearest))
{
DEBUG_ERROR("Failed to initialize the shader");
goto error_this;
}
if (!egl_shaderCompile(this->nearest,
b_shader_basic_vert , b_shader_basic_vert_size,
b_shader_downscale_frag, b_shader_downscale_frag_size)
)
{
DEBUG_ERROR("Failed to compile the shader");
goto error_shader;
}
if (!egl_shaderInit(&this->linear))
{
DEBUG_ERROR("Failed to initialize the shader");
goto error_this;
}
if (!egl_shaderCompile(this->linear,
b_shader_basic_vert, b_shader_basic_vert_size,
b_shader_downscale_linear_frag, b_shader_downscale_linear_frag_size)
)
{
DEBUG_ERROR("Failed to compile the shader");
goto error_shader;
}
if (!egl_shaderInit(&this->lanczos2))
{
DEBUG_ERROR("Failed to initialize the shader");
goto error_this;
}
if (!egl_shaderCompile(this->lanczos2,
b_shader_basic_vert, b_shader_basic_vert_size,
b_shader_downscale_lanczos2_frag, b_shader_downscale_lanczos2_frag_size)
)
{
DEBUG_ERROR("Failed to compile the shader");
goto error_shader;
}
this->uNearest.type = EGL_UNIFORM_TYPE_3F;
this->uNearest.location =
egl_shaderGetUniform(this->nearest, "uConfig");
if (!egl_framebufferInit(&this->fb))
{
DEBUG_ERROR("Failed to initialize the framebuffer");
@@ -240,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;
@@ -298,9 +326,7 @@ static bool egl_filterDownscaleImguiConfig(EGL_Filter * filter)
}
static bool egl_filterDownscaleSetup(EGL_Filter * filter,
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height,
unsigned int desktopWidth, unsigned int desktopHeight,
bool useDMA)
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height)
{
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
@@ -310,48 +336,6 @@ static bool egl_filterDownscaleSetup(EGL_Filter * filter,
if (!this->enable)
return false;
if (this->useDMA != useDMA)
{
if (!egl_shaderCompile(this->nearest,
b_shader_basic_vert , b_shader_basic_vert_size,
b_shader_downscale_frag,
b_shader_downscale_frag_size,
useDMA, NULL)
)
{
DEBUG_ERROR("Failed to compile the shader");
return false;
}
if (!egl_shaderCompile(this->linear,
b_shader_basic_vert, b_shader_basic_vert_size,
b_shader_downscale_linear_frag,
b_shader_downscale_linear_frag_size,
useDMA, NULL)
)
{
DEBUG_ERROR("Failed to compile the shader");
return false;
}
if (!egl_shaderCompile(this->lanczos2,
b_shader_basic_vert, b_shader_basic_vert_size,
b_shader_downscale_lanczos2_frag,
b_shader_downscale_lanczos2_frag_size,
useDMA, NULL)
)
{
DEBUG_ERROR("Failed to compile the shader");
return false;
}
this->uNearest.type = EGL_UNIFORM_TYPE_3F;
this->uNearest.location =
egl_shaderGetUniform(this->nearest, "uConfig");
this->useDMA = useDMA;
}
if (this->prepared &&
pixFmt == this->pixFmt &&
this->width == width &&
@@ -370,12 +354,11 @@ static bool egl_filterDownscaleSetup(EGL_Filter * filter,
}
static void egl_filterDownscaleGetOutputRes(EGL_Filter * filter,
unsigned int *width, unsigned int *height, enum EGL_PixelFormat *pixFmt)
unsigned int *width, unsigned int *height)
{
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
*width = this->width;
*height = this->height;
*pixFmt = this->pixFmt;
}
static bool egl_filterDownscalePrepare(EGL_Filter * filter)
@@ -402,15 +385,15 @@ static bool egl_filterDownscalePrepare(EGL_Filter * filter)
return true;
}
static EGL_Texture * egl_filterDownscaleRun(EGL_Filter * filter,
EGL_FilterRects * rects, EGL_Texture * texture)
static GLuint egl_filterDownscaleRun(EGL_Filter * filter,
EGL_FilterRects * rects, GLuint texture)
{
EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter);
egl_framebufferBind(this->fb);
glActiveTexture(GL_TEXTURE0);
egl_textureBind(texture);
glBindTexture(GL_TEXTURE_2D, texture);
EGL_Shader * shader;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -37,7 +37,6 @@ typedef struct EGL_FilterFFXCAS
EGL_Shader * shader;
bool enable;
int useDMA;
enum EGL_PixelFormat pixFmt;
unsigned int width, height;
float sharpness;
@@ -107,14 +106,21 @@ static bool egl_filterFFXCASInit(EGL_Filter ** filter)
return false;
}
this->useDMA = -1;
if (!egl_shaderInit(&this->shader))
{
DEBUG_ERROR("Failed to initialize the shader");
goto error_this;
}
if (!egl_shaderCompile(this->shader,
b_shader_basic_vert , b_shader_basic_vert_size,
b_shader_ffx_cas_frag, b_shader_ffx_cas_frag_size)
)
{
DEBUG_ERROR("Failed to compile the shader");
goto error_shader;
}
this->consts = countedBufferNew(8 * sizeof(GLuint));
if (!this->consts)
{
@@ -122,6 +128,12 @@ static bool egl_filterFFXCASInit(EGL_Filter ** filter)
goto error_shader;
}
egl_shaderSetUniforms(this->shader, &(EGL_Uniform) {
.type = EGL_UNIFORM_TYPE_4UIV,
.location = egl_shaderGetUniform(this->shader, "uConsts"),
.v = this->consts,
}, 1);
egl_filterFFXCASLoadState(&this->base);
if (!egl_framebufferInit(&this->fb))
@@ -208,36 +220,13 @@ static bool egl_filterFFXCASImguiConfig(EGL_Filter * filter)
}
static bool egl_filterFFXCASSetup(EGL_Filter * filter,
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height,
unsigned int desktopWidth, unsigned int desktopHeight,
bool useDMA)
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height)
{
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
if (!this->enable)
return false;
if (this->useDMA != useDMA)
{
if (!egl_shaderCompile(this->shader,
b_shader_basic_vert , b_shader_basic_vert_size,
b_shader_ffx_cas_frag, b_shader_ffx_cas_frag_size,
useDMA, NULL)
)
{
DEBUG_ERROR("Failed to compile the shader");
return false;
}
egl_shaderSetUniforms(this->shader, &(EGL_Uniform) {
.type = EGL_UNIFORM_TYPE_4UIV,
.location = egl_shaderGetUniform(this->shader, "uConsts"),
.v = this->consts,
}, 1);
this->useDMA = useDMA;
}
if (pixFmt == this->pixFmt && this->width == width && this->height == height)
return true;
@@ -254,12 +243,11 @@ static bool egl_filterFFXCASSetup(EGL_Filter * filter,
}
static void egl_filterFFXCASGetOutputRes(EGL_Filter * filter,
unsigned int *width, unsigned int *height, enum EGL_PixelFormat *pixFmt)
unsigned int *width, unsigned int *height)
{
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
*width = this->width;
*height = this->height;
*pixFmt = this->pixFmt;
}
static bool egl_filterFFXCASPrepare(EGL_Filter * filter)
@@ -274,15 +262,15 @@ static bool egl_filterFFXCASPrepare(EGL_Filter * filter)
return true;
}
static EGL_Texture * egl_filterFFXCASRun(EGL_Filter * filter,
EGL_FilterRects * rects, EGL_Texture * texture)
static GLuint egl_filterFFXCASRun(EGL_Filter * filter,
EGL_FilterRects * rects, GLuint texture)
{
EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter);
egl_framebufferBind(this->fb);
glActiveTexture(GL_TEXTURE0);
egl_textureBind(texture);
glBindTexture(GL_TEXTURE_2D, texture);
glBindSampler(0, this->sampler);
egl_shaderUse(this->shader);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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 @@ typedef struct EGL_FilterFFXFSR1
CountedBuffer * consts;
EGL_Uniform easuUniform[2], rcasUniform;
int useDMA;
enum EGL_PixelFormat pixFmt;
unsigned int width, height;
unsigned int inWidth, inHeight;
@@ -110,8 +109,6 @@ static bool egl_filterFFXFSR1Init(EGL_Filter ** filter)
return false;
}
this->useDMA = -1;
if (!egl_shaderInit(&this->easu))
{
DEBUG_ERROR("Failed to initialize the Easu shader");
@@ -124,10 +121,18 @@ static bool egl_filterFFXFSR1Init(EGL_Filter ** filter)
goto error_esau;
}
if (!egl_shaderCompile(this->easu,
b_shader_basic_vert , b_shader_basic_vert_size,
b_shader_ffx_fsr1_easu_frag, b_shader_ffx_fsr1_easu_frag_size)
)
{
DEBUG_ERROR("Failed to compile the Easu shader");
goto error_rcas;
}
if (!egl_shaderCompile(this->rcas,
b_shader_basic_vert , b_shader_basic_vert_size,
b_shader_ffx_fsr1_rcas_frag, b_shader_ffx_fsr1_rcas_frag_size,
false, NULL)
b_shader_ffx_fsr1_rcas_frag, b_shader_ffx_fsr1_rcas_frag_size)
)
{
DEBUG_ERROR("Failed to compile the Rcas shader");
@@ -143,6 +148,14 @@ static bool egl_filterFFXFSR1Init(EGL_Filter ** filter)
egl_filterFFXFSR1LoadState(&this->base);
this->easuUniform[0].type = EGL_UNIFORM_TYPE_4UIV;
this->easuUniform[0].location =
egl_shaderGetUniform(this->easu, "uConsts");
this->easuUniform[0].v = this->consts;
this->easuUniform[1].type = EGL_UNIFORM_TYPE_2F;
this->easuUniform[1].location =
egl_shaderGetUniform(this->easu, "uOutRes");
this->rcasUniform.type = EGL_UNIFORM_TYPE_4UI;
this->rcasUniform.location = egl_shaderGetUniform(this->rcas, "uConsts");
rcasUpdateUniform(this);
@@ -322,38 +335,13 @@ static void egl_filterFFXFSR1SetOutputResHint(EGL_Filter * filter,
}
static bool egl_filterFFXFSR1Setup(EGL_Filter * filter,
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height,
unsigned int desktopWidth, unsigned int desktopHeight,
bool useDMA)
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height)
{
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
if (!this->enable)
return false;
if (this->useDMA != useDMA)
{
if (!egl_shaderCompile(this->easu,
b_shader_basic_vert , b_shader_basic_vert_size,
b_shader_ffx_fsr1_easu_frag, b_shader_ffx_fsr1_easu_frag_size,
useDMA, NULL)
)
{
DEBUG_ERROR("Failed to compile the Easu shader");
return false;
}
this->easuUniform[0].type = EGL_UNIFORM_TYPE_4UIV;
this->easuUniform[0].location =
egl_shaderGetUniform(this->easu, "uConsts");
this->easuUniform[0].v = this->consts;
this->easuUniform[1].type = EGL_UNIFORM_TYPE_2F;
this->easuUniform[1].location =
egl_shaderGetUniform(this->easu, "uOutRes");
this->useDMA = useDMA;
}
this->active = this->width > width && this->height > height;
if (!this->active)
return false;
@@ -383,12 +371,11 @@ static bool egl_filterFFXFSR1Setup(EGL_Filter * filter,
}
static void egl_filterFFXFSR1GetOutputRes(EGL_Filter * filter,
unsigned int *width, unsigned int *height, enum EGL_PixelFormat *pixFmt)
unsigned int *width, unsigned int *height)
{
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
*width = this->width;
*height = this->height;
*pixFmt = this->pixFmt;
}
static bool egl_filterFFXFSR1Prepare(EGL_Filter * filter)
@@ -408,15 +395,15 @@ static bool egl_filterFFXFSR1Prepare(EGL_Filter * filter)
return true;
}
static EGL_Texture * egl_filterFFXFSR1Run(EGL_Filter * filter,
EGL_FilterRects * rects, EGL_Texture * texture)
static GLuint egl_filterFFXFSR1Run(EGL_Filter * filter,
EGL_FilterRects * rects, GLuint texture)
{
EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter);
// pass 1, Easu
egl_framebufferBind(this->easuFb);
glActiveTexture(GL_TEXTURE0);
egl_textureBind(texture);
glBindTexture(GL_TEXTURE_2D, texture);
glBindSampler(0, this->sampler);
egl_shaderUse(this->easu);
egl_filterRectsRender(this->easu, rects);
@@ -425,7 +412,7 @@ static EGL_Texture * egl_filterFFXFSR1Run(EGL_Filter * filter,
// pass 2, Rcas
egl_framebufferBind(this->rcasFb);
glActiveTexture(GL_TEXTURE0);
egl_textureBind(texture);
glBindTexture(GL_TEXTURE_2D, texture);
glBindSampler(0, this->sampler);
egl_shaderUse(this->rcas);
egl_filterRectsRender(this->rcas, rects);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,7 +20,6 @@
#pragma once
extern EGL_FilterOps egl_filter24bitOps;
extern EGL_FilterOps egl_filterDownscaleOps;
extern EGL_FilterOps egl_filterFFXCASOps;
extern EGL_FilterOps egl_filterFFXFSR1Ops;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -40,10 +40,9 @@ bool egl_framebufferInit(EGL_Framebuffer ** fb)
return false;
}
if (!egl_textureInit(&this->tex, NULL, EGL_TEXTYPE_BUFFER))
if (!egl_textureInit(&this->tex, NULL, EGL_TEXTYPE_BUFFER, false))
{
DEBUG_ERROR("Failed to initialize the texture");
free(this);
return false;
}
@@ -65,14 +64,14 @@ void egl_framebufferFree(EGL_Framebuffer ** fb)
bool egl_framebufferSetup(EGL_Framebuffer * this, enum EGL_PixelFormat pixFmt,
unsigned int width, unsigned int height)
{
if (!egl_textureSetup(this->tex, pixFmt, width, height, 0, 0))
if (!egl_textureSetup(this->tex, pixFmt, width, height, 0))
{
DEBUG_ERROR("Failed to setup the texture");
return false;
}
GLuint tex;
egl_textureGet(this->tex, &tex, NULL, NULL, NULL);
egl_textureGet(this->tex, &tex, NULL, NULL);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -101,7 +100,9 @@ void egl_framebufferBind(EGL_Framebuffer * this)
glViewport(0, 0, this->tex->format.width, this->tex->format.height);
}
EGL_Texture * egl_framebufferGetTexture(EGL_Framebuffer * this)
GLuint egl_framebufferGetTexture(EGL_Framebuffer * this)
{
return this->tex;
GLuint output;
egl_textureGet(this->tex, &output, NULL, NULL);
return output;
}

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -32,4 +32,4 @@ bool egl_framebufferSetup(EGL_Framebuffer * this, enum EGL_PixelFormat pixFmt,
void egl_framebufferBind(EGL_Framebuffer * this);
EGL_Texture * egl_framebufferGetTexture(EGL_Framebuffer * this);
GLuint egl_framebufferGetTexture(EGL_Framebuffer * this);

View File

@@ -10,4 +10,4 @@ function process(line, second) {
}
}
{ process($0, $2) } END { print "\0"; }
{ process($0, $2) }

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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-2025 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-2025 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
@@ -48,8 +48,8 @@ static const EGL_FilterOps * EGL_Filters[] =
struct EGL_PostProcess
{
Vector filters, internalFilters;
EGL_Texture * output;
Vector filters;
GLuint output;
unsigned int outputX, outputY;
_Atomic(bool) modified;
@@ -74,22 +74,14 @@ 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);
for (int i = 0; i < ARRAY_LENGTH(EGL_Filters); ++i)
egl_filterEarlyInit(EGL_Filters[i]);
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);
}
@@ -464,8 +441,7 @@ static void configUI(void * opaque, int * id)
static size_t mouseIdx = -1;
static bool moving = false;
static size_t moveIdx = 0;
bool doMove = false;
bool doMove = false;
ImVec2 window, pos;
igGetWindowPos(&window);
@@ -480,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;
@@ -519,23 +495,16 @@ static void configUI(void * opaque, int * id)
{
EGL_Filter * tmp = filters[moveIdx];
if (mouseIdx > moveIdx) // moving down
memmove(
filters + moveIdx,
filters + moveIdx + 1,
(mouseIdx - moveIdx) * sizeof(EGL_Filter *));
memmove(filters + moveIdx, filters + moveIdx + 1, (mouseIdx - moveIdx) * sizeof(EGL_Filter *));
else // moving up
memmove(
filters + mouseIdx + 1,
filters + mouseIdx,
(moveIdx - mouseIdx) * sizeof(EGL_Filter *));
memmove(filters + mouseIdx + 1, filters + mouseIdx, (moveIdx - mouseIdx) * sizeof(EGL_Filter *));
filters[mouseIdx] = tmp;
}
if (redraw)
{
atomic_store(&this->modified, true);
app_invalidateWindow(true);
app_invalidateWindow(false);
}
}
@@ -548,24 +517,16 @@ bool egl_postProcessInit(EGL_PostProcess ** pp)
return false;
}
if (!vector_create(&this->filters,
sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters)))
if (!vector_create(&this->filters, sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters)))
{
DEBUG_ERROR("Failed to allocate the filter list");
goto error_this;
}
if (!vector_create(&this->internalFilters,
sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters)))
{
DEBUG_ERROR("Failed to allocate the filter list");
goto error_filters;
}
if (!egl_desktopRectsInit(&this->rects, 1))
{
DEBUG_ERROR("Failed to initialize the desktop rects");
goto error_internal;
goto error_filters;
}
loadPresetList(this);
@@ -575,9 +536,6 @@ bool egl_postProcessInit(EGL_PostProcess ** pp)
*pp = this;
return true;
error_internal:
vector_destroy(&this->internalFilters);
error_filters:
vector_destroy(&this->filters);
@@ -598,10 +556,6 @@ void egl_postProcessFree(EGL_PostProcess ** pp)
egl_filterFree(filter);
vector_destroy(&this->filters);
vector_forEachRef(filter, &this->internalFilters)
egl_filterFree(filter);
vector_destroy(&this->internalFilters);
free(this->presetDir);
if (this->presets)
stringlist_free(&this->presets);
@@ -618,10 +572,7 @@ bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops)
if (!egl_filterInit(ops, &filter))
return false;
if (ops->type == EGL_FILTER_TYPE_INTERNAL)
vector_push(&this->internalFilters, &filter);
else
vector_push(&this->filters, &filter);
vector_push(&this->filters, &filter);
return true;
}
@@ -632,7 +583,7 @@ bool egl_postProcessConfigModified(EGL_PostProcess * this)
bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
EGL_DesktopRects * rects, int desktopWidth, int desktopHeight,
unsigned int targetX, unsigned int targetY, bool useDMA)
unsigned int targetX, unsigned int targetY)
{
if (targetX == 0 && targetY == 0)
DEBUG_FATAL("targetX || targetY == 0");
@@ -640,11 +591,8 @@ bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
EGL_Filter * lastFilter = NULL;
unsigned int sizeX, sizeY;
//TODO: clean this up
GLuint _unused;
EGL_PixelFormat pixFmt;
if (egl_textureGet(tex, &_unused,
&sizeX, &sizeY, &pixFmt) != EGL_TEX_STATUS_OK)
GLuint texture;
if (egl_textureGet(tex, &texture, &sizeX, &sizeY) != EGL_TEX_STATUS_OK)
return false;
if (atomic_exchange(&this->modified, false))
@@ -665,36 +613,22 @@ bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
};
EGL_Filter * filter;
EGL_Texture * texture = tex;
const Vector * lists[] =
vector_forEach(filter, &this->filters)
{
&this->internalFilters,
&this->filters,
NULL
};
egl_filterSetOutputResHint(filter, targetX, targetY);
for(const Vector ** filters = lists; *filters; ++filters)
vector_forEach(filter, *filters)
{
egl_filterSetOutputResHint(filter, targetX, targetY);
if (!egl_filterSetup(filter, tex->format.pixFmt, sizeX, sizeY) ||
!egl_filterPrepare(filter))
continue;
if (!egl_filterSetup(filter, pixFmt, sizeX, sizeY,
desktopWidth, desktopHeight, useDMA) ||
!egl_filterPrepare(filter))
continue;
texture = egl_filterRun(filter, &filterRects, texture);
egl_filterGetOutputRes(filter, &sizeX, &sizeY);
texture = egl_filterRun(filter, &filterRects, texture);
egl_filterGetOutputRes(filter, &sizeX, &sizeY, &pixFmt);
if (lastFilter)
egl_filterRelease(lastFilter);
if (lastFilter)
egl_filterRelease(lastFilter);
lastFilter = filter;
// the first filter to run will convert to a normal texture
useDMA = false;
}
lastFilter = filter;
}
this->output = texture;
this->outputX = sizeX;
@@ -702,7 +636,7 @@ bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
return true;
}
EGL_Texture * egl_postProcessGetOutput(EGL_PostProcess * this,
GLuint egl_postProcessGetOutput(EGL_PostProcess * this,
unsigned int * outputX, unsigned int * outputY)
{
*outputX = this->outputX;

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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
@@ -41,7 +41,7 @@ bool egl_postProcessConfigModified(EGL_PostProcess * this);
* targetX/Y is the final target output dimension hint if scalers are present */
bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
EGL_DesktopRects * rects, int desktopWidth, int desktopHeight,
unsigned int targetX, unsigned int targetY, bool useDMA);
unsigned int targetX, unsigned int targetY);
EGL_Texture * egl_postProcessGetOutput(EGL_PostProcess * this,
GLuint egl_postProcessGetOutput(EGL_PostProcess * this,
unsigned int * outputX, unsigned int * outputY);

View File

@@ -1,6 +1,6 @@
/**
* Looking Glass
* Copyright © 2017-2025 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,7 +20,6 @@
#include "shader.h"
#include "common/debug.h"
#include "common/stringutils.h"
#include "util.h"
#include <stdlib.h>
@@ -65,9 +64,7 @@ void egl_shaderFree(EGL_Shader ** shader)
*shader = NULL;
}
bool egl_shaderLoad(EGL_Shader * this,
const char * vertex_file, const char * fragment_file, bool useDMA,
const EGL_ShaderDefine * defines)
bool egl_shaderLoad(EGL_Shader * this, const char * vertex_file, const char * fragment_file)
{
char * vertex_code, * fragment_code;
size_t vertex_size, fragment_size;
@@ -89,16 +86,13 @@ bool egl_shaderLoad(EGL_Shader * this,
DEBUG_INFO("Loaded fragment shader: %s", fragment_file);
bool ret = egl_shaderCompile(this,
vertex_code, vertex_size, fragment_code, fragment_size,
useDMA, defines);
bool ret = egl_shaderCompile(this, vertex_code, vertex_size, fragment_code, fragment_size);
free(vertex_code);
free(fragment_code);
return ret;
}
static bool shaderCompile(EGL_Shader * this, const char * vertex_code,
bool egl_shaderCompile(EGL_Shader * this, const char * vertex_code,
size_t vertex_size, const char * fragment_code, size_t fragment_size)
{
if (this->hasShader)
@@ -125,15 +119,10 @@ static bool 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);
@@ -156,15 +145,10 @@ static bool 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);
@@ -210,149 +194,6 @@ static bool shaderCompile(EGL_Shader * this, const char * vertex_code,
return true;
}
bool egl_shaderCompile(EGL_Shader * this, const char * vertex_code,
size_t vertex_size, const char * fragment_code, size_t fragment_size,
bool useDMA, const EGL_ShaderDefine * defines)
{
bool result = false;
char * processed = NULL;
char * newCode = NULL;
if (useDMA)
{
const char search[] = "sampler2D";
const char replace[] = "samplerExternalOES";
const char * offset = NULL;
int instances = 0;
while((offset = memsearch(
fragment_code, fragment_size,
search , sizeof(search)-1,
offset)))
{
++instances;
offset += sizeof(search)-1;
}
const int diff = (sizeof(replace) - sizeof(search)) * instances;
const int newLen = fragment_size + diff;
newCode = malloc(newLen + 1);
if (!newCode)
{
DEBUG_ERROR("Out of memory");
goto exit;
}
const char * src = fragment_code;
char * dst = newCode;
for(int i = 0; i < instances; ++i)
{
const char * pos = strstr(src, search);
const int offset = pos - src;
memcpy(dst, src, offset);
dst += offset;
src = pos + sizeof(search)-1;
memcpy(dst, replace, sizeof(replace)-1);
dst += sizeof(replace)-1;
}
const int final = fragment_size - (src - fragment_code);
memcpy(dst, src, final);
dst[final] = '\0';
fragment_code = newCode;
fragment_size = newLen;
}
if (defines)
{
// find the end of any existing lines starting with #
bool newLine = true;
bool skip = false;
int insertPos = 0;
for(int i = 0; i < fragment_size; ++i)
{
if (skip)
{
if (fragment_code[i] == '\n')
skip = false;
continue;
}
switch(fragment_code[i])
{
case '\n':
newLine = true;
continue;
case ' ':
case '\t':
case '\r':
continue;
case '#':
if (newLine)
{
skip = true;
continue;
}
//fallthrough
default:
newLine = false;
break;
}
if (!newLine)
{
insertPos = i;
if (insertPos > 0)
--insertPos;
break;
}
}
int processedLen = fragment_size;
const char * defineFormat = "#define %s %s\n";
for(const EGL_ShaderDefine * define = defines; define->name; ++define)
processedLen += snprintf(NULL, 0, defineFormat, define->name, define->value);
processed = malloc(processedLen);
if (!processed)
{
DEBUG_ERROR("Out of memory");
goto exit;
}
memcpy(processed, fragment_code, insertPos);
int offset = insertPos;
for(const EGL_ShaderDefine * define = defines; define->name; ++define)
offset += sprintf(processed + offset, defineFormat,
define->name, define->value);
memcpy(
processed + offset,
fragment_code + insertPos,
fragment_size - insertPos);
fragment_code = processed;
fragment_size = processedLen;
}
result = shaderCompile(this,
vertex_code , vertex_size,
fragment_code, fragment_size);
exit:
free(processed);
free(newCode);
return result;
}
void egl_shaderSetUniforms(EGL_Shader * this, EGL_Uniform * uniforms, int count)
{
egl_shaderFreeUniforms(this);
@@ -360,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-2025 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
@@ -93,22 +93,14 @@ typedef struct EGL_Uniform
}
EGL_Uniform;
typedef struct EGL_ShaderDefine
{
const char * name;
const char * value;
}
EGL_ShaderDefine;
bool egl_shaderInit(EGL_Shader ** shader);
void egl_shaderFree(EGL_Shader ** shader);
bool egl_shaderLoad(EGL_Shader * model, const char * vertex_file,
const char * fragment_file, bool useDMA, const EGL_ShaderDefine * defines);
const char * fragment_file);
bool egl_shaderCompile(EGL_Shader * model, const char * vertex_code,
size_t vertex_size, const char * fragment_code, size_t fragment_size,
bool useDMA, const EGL_ShaderDefine * defines);
size_t vertex_size, const char * fragment_code, size_t fragment_size);
void egl_shaderSetUniforms(EGL_Shader * shader, EGL_Uniform * uniforms,
int count);

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