pax_global_header00006660000000000000000000000064141117713220014511gustar00rootroot0000000000000052 comment=b4abd6d070ffdca3a86210f551fb7c8db4fd0e0f looking-glass-0+b4+dfsg.1/000077500000000000000000000000001411177132200153425ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/.gitattributes000066400000000000000000000001371411177132200202360ustar00rootroot00000000000000* text=auto eol=lf *.sln text eol=crlf *.vcxproj text eol=crlf *.vcxproj.filters text eol=crlf looking-glass-0+b4+dfsg.1/.github/000077500000000000000000000000001411177132200167025ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/.github/FUNDING.yml000066400000000000000000000011001411177132200205070ustar00rootroot00000000000000# These are supported funding model platforms github: gnif patreon: gnif open_collective: # Replace with a single Open Collective username ko_fi: lookingglass tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] looking-glass-0+b4+dfsg.1/.github/issue_template.md000066400000000000000000000044271411177132200222560ustar00rootroot00000000000000### Issues are for Bug Reports and Feature Requests Only! If you are looking for help or support please use one of the following methods Create a New Topic on the Level1Tech's forum under the Looking Glass category: * https://forum.level1techs.com/c/software/lookingGlass/142 Ask for help in the Looking Glass discord server * https://discord.gg/52SMupxkvt *Issues that are not bug reports or feature requests will be closed & ignored* ### Errors that are not bugs Some errors generated by the LG client are not bugs, but rather issues with your system's configuration and/or timing. Please do not report these, but rather use one of the above resources to ask for advice/help. * `LGMP_ERR_QUEUE_UNSUBSCRIBED` - Failure to heed advice on things such as using `isolcpus` and CPU pinning may result in this message, especially if you are over-taxing your CPU. * `Could not create an SDL window: *` - Failure to create a SDL window is not an issue with Looking Glass but rather a more substantial issue with your system, such as missing hardware support for the RGBA32 pixmap format, or missing required OpenGL EGL features. * `The host application is not compatible with this client` - The Looking Glass Host application in Windows is the incorrect version and is not compatible, you need to make sure you run matching versions of both the host and client applications. ### Bug Report Required Information The entire (not truncated) output from the client application (if applicable). To obtain this run `looking-glass-client` in a terminal. ``` PASTE CLIENT OUTPUT HERE ``` The entire (not truncated) log file from the host application (if applicable). Normally, this is found on the guest system at: %ProgramData%\Looking Glass (host)\looking-glass-host.txt This log may be quite long, please delete the file first and then proceed to launch the host and reproduce the issue so that the log only contains the pertinent information. ``` PASTE HOST LOG FILE CONTENTS HERE ``` If the client is unexpectedly exiting without a backtrace, please provide one via gdb with the command `thread apply all bt`. If you are unsure how to do this please watch the video below on how to perform a Debug build and generate this backtrace. https://www.youtube.com/watch?v=EqxxJK9Yo64 ``` PASTE FULL BACKTRACE HERE ``` looking-glass-0+b4+dfsg.1/.github/workflows/000077500000000000000000000000001411177132200207375ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/.github/workflows/build.yml000066400000000000000000000067071411177132200225730ustar00rootroot00000000000000name: build on: [push, pull_request] jobs: client: runs-on: ubuntu-20.04 strategy: matrix: cc: [gcc, clang] build_type: - Release - Debug steps: - uses: actions/checkout@v1 with: submodules: recursive - name: Update apt run: | sudo apt-get update - name: Install client dependencies run: | sudo apt-get install \ binutils-dev \ libsdl2-dev libsdl2-ttf-dev \ libspice-protocol-dev nettle-dev \ libx11-dev libxss-dev libxi-dev \ wayland-protocols - name: Configure client env: CC: /usr/bin/${{ matrix.cc }} run: | mkdir client/build cd client/build cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_SDL=ON .. - name: Build client run: | cd client/build make -j$(nproc) module: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 with: submodules: recursive - name: Build kernel module run: | cd module make host-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 with: submodules: recursive - name: Update apt run: | sudo apt-get update - name: Install Linux host dependencies run: | sudo apt-get install binutils-dev libgl1-mesa-dev libxcb-xfixes0-dev - name: Configure Linux host run: | mkdir host/build cd host/build cmake .. - name: Build Linux host run: | cd host/build make -j$(nproc) host-windows-cross: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v1 with: submodules: recursive - name: Update apt run: | sudo apt-get update - name: Install Windows host cross-compile dependencies run: | sudo apt-get install gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 nsis - name: Configure Windows host for cross-compile run: | mkdir host/build cd host/build cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake .. - name: Cross-compile Windows host run: | cd host/build make -j$(nproc) - name: Build Windows host installer run: | cd host/build makensis platform/Windows/installer.nsi host-windows-native: runs-on: windows-latest steps: - uses: actions/checkout@v1 with: submodules: recursive - name: Configure Windows host for native MinGW-w64 run: | mkdir host\build cd host\build cmake -G "MinGW Makefiles" .. - name: Build Windows host on native MinGW-w64 run: | cd host\build mingw32-make "-j$([Environment]::ProcessorCount)" - name: Build Windows host installer run: | cd host\build makensis platform\Windows\installer.nsi obs: runs-on: ubuntu-latest strategy: matrix: cc: [gcc, clang] steps: - uses: actions/checkout@v1 with: submodules: recursive - name: Update apt run: | sudo apt-get update - name: Install obs plugin dependencies run: | sudo apt-get install binutils-dev libobs-dev libgl1-mesa-dev - name: Configure obs plugin run: | mkdir obs/build cd obs/build CC=/usr/bin/${{ matrix.cc }} cmake .. - name: Build obs plugin run: | cd obs/build make -j$(nproc) looking-glass-0+b4+dfsg.1/.github/workflows/pr-check.yml000066400000000000000000000013001411177132200231500ustar00rootroot00000000000000name: pr-check on: pull_request jobs: authors: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Check AUTHORS file 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 "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." exit 1 fi looking-glass-0+b4+dfsg.1/.gitignore000066400000000000000000000001611411177132200173300ustar00rootroot00000000000000module/*.ko module/*.o module/*.mod.c module/.* module/Module.symvers module/modules.order *.a *.o *.exe */build looking-glass-0+b4+dfsg.1/AUTHORS000066400000000000000000000047641411177132200164250ustar00rootroot00000000000000The following authors have licensed all their contributions to the Looking Glass project under the terms detailed in LICENSE: Geoffrey McRae (gnif) Guanzhong Chen (quantum5) Tudor Brindus (Xyene) Jonathan Rubenstein (JJRcop) arcnmx (arcnmx) TheCakeIsNaOH (TheCakeIsNaOH) NamoDev (NamoDev) feltcat <58396817+feltcat@users.noreply.github.com> (feltcat) Ali Abdel-Qader Jack Karamanian Mikko Rasa (DataBeaver) Omar Pakker (Omar007) Yvan da Silva (YvanDaSilva) r4m0n (r4m0n) Łukasz Kostka A.J. Ruckman (ajruckman) Aaron (mcd1992) Alam Arias (alama) Alexander Olofsson (ananace) Andrew Sheldon (aqxa1) Andy Chun (noneucat) Arti Zirk Ash <5615358+ash-hat@users.noreply.github.com> (ash-hat) Dominik Csapak (flumm) Frediano Ziglio Luke Brown (Dynamic-Gravity) Marius Barbu (mbarbu) Max Sistemich (mafrasi2) Michael Golisch (mgolisch) Michał Zając Netboy3 <1472804+netboy3@users.noreply.github.com> (netboy3) Patrick Steinhardt Paul Götzinger (pagdot) Rikard Falkeborn (rikardfalkeborn) Rokas Kupstys (rokups) Samuel Bowman (SamuelBowman) Txanton (tbejos) Tyler Watson Xiretza (Xiretza) aspen (aspenluxxxy) camr0 (camr0) chrsm (chrsm) commander kotori (cmdrkotori) eater <=@eater.me> (the-eater) fishery (fisherwise) four0four (four0four) jmossman (jmossman) jonpas (jonpas) orcephrye (orcephrye) thejavascriptman (thejavascriptman) vroad <396351+vroad@users.noreply.github.com> (vroad) williamvds (williamvds) looking-glass-0+b4+dfsg.1/CONTRIBUTORS000066400000000000000000000023731411177132200172270ustar00rootroot00000000000000Level1Techs (level1techs.com) Provided a motherboard and CPU cooler and publicizing this project. Sapphire Technologies (www.sapphiretech.com) Provided an AMD Vega 56 PLE Australia (ple.com.au) Provided hardware at cost pricing Members of the community that donated the funding to obtain the remaining hardware. Tom Kopra Josh Strawbridge mercipher _ Jiri Belsky Brad Anstis Marcin Skarbek Fabian Tschopp Dolf Andringa Maxime Poulin Mike D always FlOoReD Matt Wirth Justin Phillips Jan Killius Tyler Watson Viesturs Kols Cooper YANG Srdjan Rosic Matthew Fisk Tyll Jungke Kimmo Huoso Oliver Masur Mark Tomlin Jason Simon Nicolas Avrutin Alex Karypidis Niklas Bloedorn Bruno Z Dennis Keitzel Martin Eisig August Örnhall Timothy Viola David Reddick david gerdeman Christopher Swenson Benjamin Klettbach Chadd Bland Peder Madsen Zachary Piper Declan Scott Thomas A Neal Stein Luke Brown Andy Bright Kyle Farwell Jai Mu Martin Lilleøen Daniel hauger Michael Hillman Andreas Jacobsen NikkyAi Michael Lindman And another 41 people that wish to remain anonymous. Thank you everyone for making this project possible. - Geoffrey McRae Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. looking-glass-0+b4+dfsg.1/README.md000066400000000000000000000012561411177132200166250ustar00rootroot00000000000000# Looking Glass An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough. * Project Website: https://looking-glass.io * Documentation: https://looking-glass.io/docs ## Documentation ❕❕❕ **IMPORTANT** ❕❕❕ This project contains submodules that must be checked out if building from the git repository! If you are not a developer and just want to compile Looking Glass, please download the source archive from the website instead: https://looking-glass.io/downloads Source code for the documentation can be found in the `/doc` directory. You may view this locally as HTML by running `make html` with `python3-sphinx` installed. looking-glass-0+b4+dfsg.1/client/000077500000000000000000000000001411177132200166205ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/CMakeLists.txt000066400000000000000000000075541411177132200213730ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(looking-glass-client C) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") include(GNUInstallDirs) include(CheckCCompilerFlag) include(FeatureSummary) option(OPTIMIZE_FOR_NATIVE "Build with -march=native" ON) if(OPTIMIZE_FOR_NATIVE) CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE) if(COMPILER_SUPPORTS_MARCH_NATIVE) add_compile_options("-march=native") endif() else() add_compile_options("-march=nehalem" "-mtune=generic") endif() option(ENABLE_OPENGL "Enable the OpenGL renderer" ON) add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.") option(ENABLE_EGL "Enable the EGL renderer" ON) add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.") option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON) add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.") option(ENABLE_ASAN "Build with AddressSanitizer" OFF) add_feature_info(ENABLE_ASAN ENABLE_ASAN "AddressSanitizer support.") option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF) add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.") option(ENABLE_SDL "Build with SDL support" OFF) add_feature_info(ENABLE_SDL ENABLE_SDL "SDL support.") option(ENABLE_X11 "Build with X11 support" ON) add_feature_info(ENABLE_X11 ENABLE_X11 "X11 support.") option(ENABLE_WAYLAND "Build with Wayland support" ON) add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.") option(ENABLE_LIBDECOR "Build with libdecor support" OFF) add_feature_info(ENABLE_LIBDECOR ENABLE_LIBDECOR "libdecor support.") if (NOT ENABLE_SDL AND NOT ENABLE_X11 AND NOT ENABLE_WAYLAND) message(FATAL_ERROR "One of ENABLE_SDL, ENABLE_X11, or ENABLE_WAYLAND must be on") endif() add_compile_options( "-Wall" "-Wextra" "-Wno-sign-compare" "-Wno-unused-parameter" "$<$:-Wimplicit-fallthrough=2>" "-Werror" "-Wfatal-errors" "-ffast-math" "-fdata-sections" "-ffunction-sections" "$<$:-O0;-g3;-ggdb>" ) set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack") set(CMAKE_C_STANDARD 11) if (ENABLE_OPENGL) add_definitions(-D ENABLE_OPENGL) endif() if (ENABLE_EGL) add_definitions(-D ENABLE_EGL) endif() if(ENABLE_ASAN) add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address") set(EXE_FLAGS "${EXE_FLAGS} -fno-omit-frame-pointer -fsanitize=address") endif() if(ENABLE_UBSAN) add_compile_options("-fsanitize=undefined") set(EXE_FLAGS "${EXE_FLAGS} -fsanitize=undefined") endif() find_package(GMP) add_definitions(-D ATOMIC_LOCKING) add_definitions(-D GL_GLEXT_PROTOTYPES) get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE) add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/version.c ${CMAKE_BINARY_DIR}/_version.c COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P ${PROJECT_TOP}/version.cmake ) include_directories( ${PROJECT_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include ${GMP_INCLUDE_DIR} ) link_libraries( ${GMP_LIBRARIES} ${CMAKE_DL_LIBS} rt m ) set(SOURCES ${CMAKE_BINARY_DIR}/version.c src/main.c src/core.c src/app.c src/config.c src/keybind.c src/lg-renderer.c src/ll.c src/util.c src/clipboard.c src/kb.c src/egl_dynprocs.c ) add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" ) add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" ) add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice") add_subdirectory(displayservers) add_subdirectory(renderers) add_subdirectory(fonts) add_executable(looking-glass-client ${SOURCES}) target_link_libraries(looking-glass-client ${EXE_FLAGS} lg_common displayservers lgmp purespice renderers fonts ) install(TARGETS looking-glass-client RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT binary) feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES) looking-glass-0+b4+dfsg.1/client/DEBUGGING.md000066400000000000000000000020471411177132200204400ustar00rootroot00000000000000# Debugging the Looking Glass Client If you are asked to provide debugging information to resolve an issue please follow the following procedure. ## If you're experiencing a crash: Run the program under the `gdb` debugger (you may need to install gdb), for example: gdb ./looking-glass-client If you need to set any arguments, do so now by running `set args ARGS`, for example: set args -F -k Now start the program by typing `r`. When the application crashes you will be dumped back into the debugger, the application may appear to be frozen. Run the following command: thread apply all bt Once you have this information please pastebin the log from looking-glass as well as the information resulting from this command. ## If you're experencing high CPU load and/or poor performance. The steps here are identical to the above, except instead of waiting for the program to crash, in the debugger press `CTRL+C` while the program is exhibiting the problem, then run `thread apply all bt` and pastebin your log and the results of the command. looking-glass-0+b4+dfsg.1/client/cmake/000077500000000000000000000000001411177132200177005ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/cmake/FindGMP.cmake000066400000000000000000000012501411177132200221240ustar00rootroot00000000000000# Try to find the GMP librairies # GMP_FOUND - system has GMP lib # GMP_INCLUDE_DIR - the GMP include directory # GMP_LIBRARIES - Libraries needed to use GMP if (GMP_INCLUDE_DIR AND GMP_LIBRARIES) # Already in cache, be silent set(GMP_FIND_QUIETLY TRUE) endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES) find_path(GMP_INCLUDE_DIR NAMES gmp.h ) find_library(GMP_LIBRARIES NAMES gmp libgmp ) find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx ) MESSAGE(STATUS "GMP libs: " ${GMP_LIBRARIES} " " ${GMPXX_LIBRARIES} ) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES) mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES) looking-glass-0+b4+dfsg.1/client/cmake/MakeObject.cmake000066400000000000000000000032731411177132200227130ustar00rootroot00000000000000function(make_object out_var) set(result) set(result_h) 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 "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.o") string(REGEX REPLACE "[/.]" "_" sym_in ${in_f}) add_custom_command(OUTPUT ${out_f} 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}_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 ${in_f} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Creating object from ${in_f}" VERBATIM ) file(WRITE ${out_h} "extern const char b_${sym_in}[];\n") file(APPEND ${out_h} "extern const char b_${sym_in}_end[];\n") file(APPEND ${out_h} "#define b_${sym_in}_size (b_${sym_in}_end - b_${sym_in})\n") get_filename_component(h_dir ${out_h} DIRECTORY) list(APPEND result_h ${h_dir}) list(APPEND result ${out_f}) endforeach() list(REMOVE_DUPLICATES result_h) set(${out_var}_OBJS "${result}" PARENT_SCOPE) set(${out_var}_INCS "${result_h}" PARENT_SCOPE) endfunction() function(make_defines in_file out_file) add_custom_command(OUTPUT ${out_file} COMMAND grep "^#define" "${in_file}" > "${out_file}" DEPENDS ${in_file} COMMENT "Creating #defines from ${in_file}" ) endfunction() looking-glass-0+b4+dfsg.1/client/displayservers/000077500000000000000000000000001411177132200216775ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/displayservers/CMakeLists.txt000066400000000000000000000034041411177132200244400ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(displayservers LANGUAGES C) set(DISPLAYSERVER_H "${CMAKE_BINARY_DIR}/include/dynamic/displayservers.h") set(DISPLAYSERVER_C "${CMAKE_BINARY_DIR}/src/displayservers.c") file(WRITE ${DISPLAYSERVER_H} "#include \"interface/displayserver.h\"\n\n") file(APPEND ${DISPLAYSERVER_H} "extern struct LG_DisplayServerOps * LG_DisplayServers[];\n\n") file(WRITE ${DISPLAYSERVER_C} "#include \"interface/displayserver.h\"\n\n") file(APPEND ${DISPLAYSERVER_C} "#include \n\n") set(DISPLAYSERVERS "_") set(DISPLAYSERVERS_LINK "_") function(add_displayserver name) set(DISPLAYSERVERS "${DISPLAYSERVERS};${name}" PARENT_SCOPE) set(DISPLAYSERVERS_LINK "${DISPLAYSERVERS_LINK};displayserver_${name}" PARENT_SCOPE) add_subdirectory(${name}) endfunction() # Add/remove displayservers here! if (ENABLE_WAYLAND) add_displayserver(Wayland) endif() if (ENABLE_X11) add_displayserver(X11) endif() # SDL must be last as it's the fallback implemntation if (ENABLE_SDL) add_displayserver(SDL) endif() list(REMOVE_AT DISPLAYSERVERS 0) list(REMOVE_AT DISPLAYSERVERS_LINK 0) list(LENGTH DISPLAYSERVERS DISPLAYSERVER_COUNT) file(APPEND ${DISPLAYSERVER_H} "#define LG_DISPLAYSERVER_COUNT ${DISPLAYSERVER_COUNT}\n") foreach(displayserver ${DISPLAYSERVERS}) file(APPEND ${DISPLAYSERVER_C} "extern struct LG_DisplayServerOps LGDS_${displayserver};\n") endforeach() file(APPEND ${DISPLAYSERVER_C} "\nconst struct LG_DisplayServerOps * LG_DisplayServers[] =\n{\n") foreach(displayserver ${DISPLAYSERVERS}) file(APPEND ${DISPLAYSERVER_C} " &LGDS_${displayserver},\n") endforeach() file(APPEND ${DISPLAYSERVER_C} " NULL\n};") add_library(displayservers STATIC ${DISPLAYSERVER_C}) target_link_libraries(displayservers ${DISPLAYSERVERS_LINK}) looking-glass-0+b4+dfsg.1/client/displayservers/SDL/000077500000000000000000000000001411177132200223215ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/displayservers/SDL/CMakeLists.txt000066400000000000000000000007771411177132200250740ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(displayserver_SDL LANGUAGES C) find_package(PkgConfig) pkg_check_modules(DISPLAYSERVER_SDL_PKGCONFIG REQUIRED sdl2 ) add_library(displayserver_SDL STATIC sdl.c ) target_link_libraries(displayserver_SDL ${DISPLAYSERVER_SDL_PKGCONFIG_LIBRARIES} ${DISPLAYSERVER_SDL_OPT_PKGCONFIG_LIBRARIES} lg_common ) target_include_directories(displayserver_SDL PRIVATE src ${DISPLAYSERVER_SDL_PKGCONFIG_INCLUDE_DIRS} ${DISPLAYSERVER_SDL_OPT_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b4+dfsg.1/client/displayservers/SDL/kb.h000066400000000000000000000275661411177132200231060ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include //FIXME: this should be made static once config.c is no longer using SDL //scancodes uint32_t sdl_to_xfree86[SDL_NUM_SCANCODES] = { [SDL_SCANCODE_UNKNOWN] /* = USB 0 */ = KEY_RESERVED, [SDL_SCANCODE_A] /* = USB 4 */ = KEY_A, [SDL_SCANCODE_B] /* = USB 5 */ = KEY_B, [SDL_SCANCODE_C] /* = USB 6 */ = KEY_C, [SDL_SCANCODE_D] /* = USB 7 */ = KEY_D, [SDL_SCANCODE_E] /* = USB 8 */ = KEY_E, [SDL_SCANCODE_F] /* = USB 9 */ = KEY_F, [SDL_SCANCODE_G] /* = USB 10 */ = KEY_G, [SDL_SCANCODE_H] /* = USB 11 */ = KEY_H, [SDL_SCANCODE_I] /* = USB 12 */ = KEY_I, [SDL_SCANCODE_J] /* = USB 13 */ = KEY_J, [SDL_SCANCODE_K] /* = USB 14 */ = KEY_K, [SDL_SCANCODE_L] /* = USB 15 */ = KEY_L, [SDL_SCANCODE_M] /* = USB 16 */ = KEY_M, [SDL_SCANCODE_N] /* = USB 17 */ = KEY_N, [SDL_SCANCODE_O] /* = USB 18 */ = KEY_O, [SDL_SCANCODE_P] /* = USB 19 */ = KEY_P, [SDL_SCANCODE_Q] /* = USB 20 */ = KEY_Q, [SDL_SCANCODE_R] /* = USB 21 */ = KEY_R, [SDL_SCANCODE_S] /* = USB 22 */ = KEY_S, [SDL_SCANCODE_T] /* = USB 23 */ = KEY_T, [SDL_SCANCODE_U] /* = USB 24 */ = KEY_U, [SDL_SCANCODE_V] /* = USB 25 */ = KEY_V, [SDL_SCANCODE_W] /* = USB 26 */ = KEY_W, [SDL_SCANCODE_X] /* = USB 27 */ = KEY_X, [SDL_SCANCODE_Y] /* = USB 28 */ = KEY_Y, [SDL_SCANCODE_Z] /* = USB 29 */ = KEY_Z, [SDL_SCANCODE_1] /* = USB 30 */ = KEY_1, [SDL_SCANCODE_2] /* = USB 31 */ = KEY_2, [SDL_SCANCODE_3] /* = USB 32 */ = KEY_3, [SDL_SCANCODE_4] /* = USB 33 */ = KEY_4, [SDL_SCANCODE_5] /* = USB 34 */ = KEY_5, [SDL_SCANCODE_6] /* = USB 35 */ = KEY_6, [SDL_SCANCODE_7] /* = USB 36 */ = KEY_7, [SDL_SCANCODE_8] /* = USB 37 */ = KEY_8, [SDL_SCANCODE_9] /* = USB 38 */ = KEY_9, [SDL_SCANCODE_0] /* = USB 39 */ = KEY_0, [SDL_SCANCODE_RETURN] /* = USB 40 */ = KEY_ENTER, [SDL_SCANCODE_ESCAPE] /* = USB 41 */ = KEY_ESC, [SDL_SCANCODE_BACKSPACE] /* = USB 42 */ = KEY_BACKSPACE, [SDL_SCANCODE_TAB] /* = USB 43 */ = KEY_TAB, [SDL_SCANCODE_SPACE] /* = USB 44 */ = KEY_SPACE, [SDL_SCANCODE_MINUS] /* = USB 45 */ = KEY_MINUS, [SDL_SCANCODE_EQUALS] /* = USB 46 */ = KEY_EQUAL, [SDL_SCANCODE_LEFTBRACKET] /* = USB 47 */ = KEY_LEFTBRACE, [SDL_SCANCODE_RIGHTBRACKET] /* = USB 48 */ = KEY_RIGHTBRACE, [SDL_SCANCODE_BACKSLASH] /* = USB 49 */ = KEY_BACKSLASH, [SDL_SCANCODE_SEMICOLON] /* = USB 51 */ = KEY_SEMICOLON, [SDL_SCANCODE_APOSTROPHE] /* = USB 52 */ = KEY_APOSTROPHE, [SDL_SCANCODE_GRAVE] /* = USB 53 */ = KEY_GRAVE, [SDL_SCANCODE_COMMA] /* = USB 54 */ = KEY_COMMA, [SDL_SCANCODE_PERIOD] /* = USB 55 */ = KEY_DOT, [SDL_SCANCODE_SLASH] /* = USB 56 */ = KEY_SLASH, [SDL_SCANCODE_CAPSLOCK] /* = USB 57 */ = KEY_CAPSLOCK, [SDL_SCANCODE_F1] /* = USB 58 */ = KEY_F1, [SDL_SCANCODE_F2] /* = USB 59 */ = KEY_F2, [SDL_SCANCODE_F3] /* = USB 60 */ = KEY_F3, [SDL_SCANCODE_F4] /* = USB 61 */ = KEY_F4, [SDL_SCANCODE_F5] /* = USB 62 */ = KEY_F5, [SDL_SCANCODE_F6] /* = USB 63 */ = KEY_F6, [SDL_SCANCODE_F7] /* = USB 64 */ = KEY_F7, [SDL_SCANCODE_F8] /* = USB 65 */ = KEY_F8, [SDL_SCANCODE_F9] /* = USB 66 */ = KEY_F9, [SDL_SCANCODE_F10] /* = USB 67 */ = KEY_F10, [SDL_SCANCODE_F11] /* = USB 68 */ = KEY_F11, [SDL_SCANCODE_F12] /* = USB 69 */ = KEY_F12, [SDL_SCANCODE_PRINTSCREEN] /* = USB 70 */ = KEY_SYSRQ, [SDL_SCANCODE_SCROLLLOCK] /* = USB 71 */ = KEY_SCROLLLOCK, [SDL_SCANCODE_PAUSE] /* = USB 72 */ = KEY_PAUSE, [SDL_SCANCODE_INSERT] /* = USB 73 */ = KEY_INSERT, [SDL_SCANCODE_HOME] /* = USB 74 */ = KEY_HOME, [SDL_SCANCODE_PAGEUP] /* = USB 75 */ = KEY_PAGEUP, [SDL_SCANCODE_DELETE] /* = USB 76 */ = KEY_DELETE, [SDL_SCANCODE_END] /* = USB 77 */ = KEY_END, [SDL_SCANCODE_PAGEDOWN] /* = USB 78 */ = KEY_PAGEDOWN, [SDL_SCANCODE_RIGHT] /* = USB 79 */ = KEY_RIGHT, [SDL_SCANCODE_LEFT] /* = USB 80 */ = KEY_LEFT, [SDL_SCANCODE_DOWN] /* = USB 81 */ = KEY_DOWN, [SDL_SCANCODE_UP] /* = USB 82 */ = KEY_UP, [SDL_SCANCODE_NUMLOCKCLEAR] /* = USB 83 */ = KEY_NUMLOCK, [SDL_SCANCODE_KP_DIVIDE] /* = USB 84 */ = KEY_KPSLASH, [SDL_SCANCODE_KP_MULTIPLY] /* = USB 85 */ = KEY_KPASTERISK, [SDL_SCANCODE_KP_MINUS] /* = USB 86 */ = KEY_KPMINUS, [SDL_SCANCODE_KP_PLUS] /* = USB 87 */ = KEY_KPPLUS, [SDL_SCANCODE_KP_ENTER] /* = USB 88 */ = KEY_KPENTER, [SDL_SCANCODE_KP_1] /* = USB 89 */ = KEY_KP1, [SDL_SCANCODE_KP_2] /* = USB 90 */ = KEY_KP2, [SDL_SCANCODE_KP_3] /* = USB 91 */ = KEY_KP3, [SDL_SCANCODE_KP_4] /* = USB 92 */ = KEY_KP4, [SDL_SCANCODE_KP_5] /* = USB 93 */ = KEY_KP5, [SDL_SCANCODE_KP_6] /* = USB 94 */ = KEY_KP6, [SDL_SCANCODE_KP_7] /* = USB 95 */ = KEY_KP7, [SDL_SCANCODE_KP_8] /* = USB 96 */ = KEY_KP8, [SDL_SCANCODE_KP_9] /* = USB 97 */ = KEY_KP9, [SDL_SCANCODE_KP_0] /* = USB 98 */ = KEY_KP0, [SDL_SCANCODE_KP_PERIOD] /* = USB 99 */ = KEY_KPDOT, [SDL_SCANCODE_NONUSBACKSLASH] /* = USB 100 */ = KEY_102ND, [SDL_SCANCODE_APPLICATION] /* = USB 101 */ = KEY_COMPOSE, [SDL_SCANCODE_POWER] /* = USB 102 */ = KEY_POWER, [SDL_SCANCODE_KP_EQUALS] /* = USB 103 */ = KEY_KPEQUAL, [SDL_SCANCODE_F13] /* = USB 104 */ = KEY_CONFIG, [SDL_SCANCODE_F14] /* = USB 105 */ = KEY_F14, [SDL_SCANCODE_F15] /* = USB 106 */ = KEY_F15, [SDL_SCANCODE_F16] /* = USB 107 */ = KEY_F16, [SDL_SCANCODE_F17] /* = USB 108 */ = KEY_F17, [SDL_SCANCODE_F18] /* = USB 109 */ = KEY_F18, [SDL_SCANCODE_F19] /* = USB 110 */ = KEY_F19, [SDL_SCANCODE_F20] /* = USB 111 */ = KEY_F20, [SDL_SCANCODE_HELP] /* = USB 117 */ = KEY_HELP, [SDL_SCANCODE_MENU] /* = USB 118 */ = KEY_MENU, [SDL_SCANCODE_STOP] /* = USB 120 */ = KEY_CANCEL, [SDL_SCANCODE_AGAIN] /* = USB 121 */ = KEY_AGAIN, [SDL_SCANCODE_UNDO] /* = USB 122 */ = KEY_UNDO, [SDL_SCANCODE_CUT] /* = USB 123 */ = KEY_CUT, [SDL_SCANCODE_COPY] /* = USB 124 */ = KEY_COPY, [SDL_SCANCODE_PASTE] /* = USB 125 */ = KEY_PASTE, [SDL_SCANCODE_FIND] /* = USB 126 */ = KEY_FIND, [SDL_SCANCODE_MUTE] /* = USB 127 */ = KEY_MUTE, [SDL_SCANCODE_VOLUMEUP] /* = USB 128 */ = KEY_VOLUMEUP, [SDL_SCANCODE_VOLUMEDOWN] /* = USB 129 */ = KEY_VOLUMEDOWN, [SDL_SCANCODE_KP_COMMA] /* = USB 133 */ = KEY_KPCOMMA, [SDL_SCANCODE_INTERNATIONAL1] /* = USB 135 */ = KEY_RO, [SDL_SCANCODE_INTERNATIONAL2] /* = USB 136 */ = KEY_KATAKANAHIRAGANA, [SDL_SCANCODE_INTERNATIONAL3] /* = USB 137 */ = KEY_YEN, [SDL_SCANCODE_INTERNATIONAL4] /* = USB 138 */ = KEY_HENKAN, [SDL_SCANCODE_INTERNATIONAL5] /* = USB 139 */ = KEY_MUHENKAN, [SDL_SCANCODE_LANG1] /* = USB 144 */ = KEY_HANGEUL, [SDL_SCANCODE_LANG2] /* = USB 145 */ = KEY_HANJA, [SDL_SCANCODE_SYSREQ] /* = USB 154 */ = KEY_RIGHTSHIFT, [SDL_SCANCODE_CANCEL] /* = USB 155 */ = KEY_STOP, [SDL_SCANCODE_KP_LEFTPAREN] /* = USB 182 */ = KEY_KPLEFTPAREN, [SDL_SCANCODE_KP_RIGHTPAREN] /* = USB 183 */ = KEY_KPRIGHTPAREN, [SDL_SCANCODE_KP_PLUSMINUS] /* = USB 215 */ = KEY_KPPLUSMINUS, [SDL_SCANCODE_LCTRL] /* = USB 224 */ = KEY_LEFTCTRL, [SDL_SCANCODE_LSHIFT] /* = USB 225 */ = KEY_LEFTSHIFT, [SDL_SCANCODE_LALT] /* = USB 226 */ = KEY_LEFTALT, [SDL_SCANCODE_LGUI] /* = USB 227 */ = KEY_LEFTMETA, [SDL_SCANCODE_RCTRL] /* = USB 228 */ = KEY_RIGHTCTRL, [SDL_SCANCODE_RSHIFT] /* = USB 229 */ = KEY_RIGHTSHIFT, [SDL_SCANCODE_RALT] /* = USB 230 */ = KEY_RIGHTALT, [SDL_SCANCODE_RGUI] /* = USB 231 */ = KEY_RIGHTMETA, [SDL_SCANCODE_MODE] /* = USB 257 */ = KEY_ZENKAKUHANKAKU, [SDL_SCANCODE_AUDIONEXT] /* = USB 258 */ = KEY_NEXTSONG, [SDL_SCANCODE_AUDIOPREV] /* = USB 259 */ = KEY_PREVIOUSSONG, [SDL_SCANCODE_AUDIOSTOP] /* = USB 260 */ = KEY_STOPCD, [SDL_SCANCODE_AUDIOPLAY] /* = USB 261 */ = KEY_PLAYPAUSE, [SDL_SCANCODE_MEDIASELECT] /* = USB 263 */ = KEY_MEDIA, [SDL_SCANCODE_WWW] /* = USB 264 */ = KEY_WWW, [SDL_SCANCODE_MAIL] /* = USB 265 */ = KEY_MAIL, [SDL_SCANCODE_CALCULATOR] /* = USB 266 */ = KEY_CALC, [SDL_SCANCODE_COMPUTER] /* = USB 267 */ = KEY_COMPUTER, [SDL_SCANCODE_AC_SEARCH] /* = USB 268 */ = KEY_SEARCH, [SDL_SCANCODE_AC_HOME] /* = USB 269 */ = KEY_HOMEPAGE, [SDL_SCANCODE_AC_BACK] /* = USB 270 */ = KEY_BACK, [SDL_SCANCODE_AC_FORWARD] /* = USB 271 */ = KEY_FORWARD, [SDL_SCANCODE_AC_REFRESH] /* = USB 273 */ = KEY_REFRESH, [SDL_SCANCODE_AC_BOOKMARKS] /* = USB 274 */ = KEY_BOOKMARKS, [SDL_SCANCODE_BRIGHTNESSDOWN] /* = USB 275 */ = KEY_BRIGHTNESSDOWN, [SDL_SCANCODE_BRIGHTNESSUP] /* = USB 276 */ = KEY_BRIGHTNESSUP, [SDL_SCANCODE_DISPLAYSWITCH] /* = USB 277 */ = KEY_SWITCHVIDEOMODE, [SDL_SCANCODE_KBDILLUMTOGGLE] /* = USB 278 */ = KEY_KBDILLUMTOGGLE, [SDL_SCANCODE_KBDILLUMDOWN] /* = USB 279 */ = KEY_KBDILLUMDOWN, [SDL_SCANCODE_KBDILLUMUP] /* = USB 280 */ = KEY_KBDILLUMUP, [SDL_SCANCODE_EJECT] /* = USB 281 */ = KEY_EJECTCD, [SDL_SCANCODE_SLEEP] /* = USB 282 */ = KEY_SLEEP, [SDL_SCANCODE_APP1] /* = USB 283 */ = KEY_PROG1, [SDL_SCANCODE_APP2] /* = USB 284 */ = KEY_PROG2, [SDL_SCANCODE_AUDIOREWIND] /* = USB 285 */ = KEY_REWIND, [SDL_SCANCODE_AUDIOFASTFORWARD] /* = USB 286 */ = KEY_FASTFORWARD, }; looking-glass-0+b4+dfsg.1/client/displayservers/SDL/sdl.c000066400000000000000000000313661411177132200232600ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "interface/displayserver.h" #include #include #ifdef ENABLE_EGL #include #endif #if defined(SDL_VIDEO_DRIVER_WAYLAND) #include #endif #include "app.h" #include "kb.h" #include "egl_dynprocs.h" #include "common/types.h" #include "common/debug.h" #include "util.h" struct SDLDSState { SDL_Window * window; SDL_Cursor * cursor; EGLNativeWindowType wlDisplay; bool keyboardGrabbed; bool pointerGrabbed; bool exiting; }; static struct SDLDSState sdl; /* forwards */ static int sdlEventFilter(void * userdata, SDL_Event * event); static void sdlSetup(void) { } static bool sdlProbe(void) { return true; } static bool sdlEarlyInit(void) { return true; } static bool sdlInit(const LG_DSInitParams params) { memset(&sdl, 0, sizeof(sdl)); // Allow screensavers for now: we will enable and disable as needed. SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); if (SDL_Init(SDL_INIT_VIDEO) < 0) { DEBUG_ERROR("SDL_Init Failed"); return false; } #ifdef ENABLE_OPENGL if (params.opengl) { SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE , 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8); } #endif sdl.window = SDL_CreateWindow( params.title, params.center ? SDL_WINDOWPOS_CENTERED : params.x, params.center ? SDL_WINDOWPOS_CENTERED : params.y, params.w, params.h, ( SDL_WINDOW_HIDDEN | (params.resizable ? SDL_WINDOW_RESIZABLE : 0) | (params.borderless ? SDL_WINDOW_BORDERLESS : 0) | (params.maximize ? SDL_WINDOW_MAXIMIZED : 0) | (params.opengl ? SDL_WINDOW_OPENGL : 0) ) ); if (sdl.window == NULL) { DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError()); goto fail_init; } const uint8_t data[4] = {0xf, 0x9, 0x9, 0xf}; const uint8_t mask[4] = {0xf, 0xf, 0xf, 0xf}; sdl.cursor = SDL_CreateCursor(data, mask, 8, 4, 4, 0); SDL_SetCursor(sdl.cursor); SDL_ShowWindow(sdl.window); SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); if (params.fullscreen) SDL_SetWindowFullscreen(sdl.window, SDL_WINDOW_FULLSCREEN_DESKTOP); if (!params.center) SDL_SetWindowPosition(sdl.window, params.x, params.y); // ensure mouse acceleration is identical in server mode SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE); SDL_SetEventFilter(sdlEventFilter, NULL); return true; fail_init: SDL_Quit(); return false; } static void sdlStartup(void) { } static void sdlShutdown(void) { } static void sdlFree(void) { SDL_DestroyWindow(sdl.window); if (sdl.cursor) SDL_FreeCursor(sdl.cursor); if (sdl.window) SDL_DestroyWindow(sdl.window); SDL_Quit(); } static bool sdlGetProp(LG_DSProperty prop, void * ret) { return false; } #ifdef ENABLE_EGL static EGLDisplay sdlGetEGLDisplay(void) { SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); if (!SDL_GetWindowWMInfo(sdl.window, &wminfo)) { DEBUG_ERROR("SDL_GetWindowWMInfo failed"); return EGL_NO_DISPLAY; } EGLNativeDisplayType native; EGLenum platform; switch(wminfo.subsystem) { case SDL_SYSWM_X11: native = (EGLNativeDisplayType)wminfo.info.x11.display; platform = EGL_PLATFORM_X11_KHR; break; #if defined(SDL_VIDEO_DRIVER_WAYLAND) case SDL_SYSWM_WAYLAND: native = (EGLNativeDisplayType)wminfo.info.wl.display; platform = EGL_PLATFORM_WAYLAND_KHR; break; #endif default: DEBUG_ERROR("Unsupported subsystem"); return EGL_NO_DISPLAY; } const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS); if (util_hasGLExt(early_exts, "EGL_KHR_platform_base") && g_egl_dynProcs.eglGetPlatformDisplay) { DEBUG_INFO("Using eglGetPlatformDisplay"); return g_egl_dynProcs.eglGetPlatformDisplay(platform, native, NULL); } if (util_hasGLExt(early_exts, "EGL_EXT_platform_base") && g_egl_dynProcs.eglGetPlatformDisplayEXT) { DEBUG_INFO("Using eglGetPlatformDisplayEXT"); return g_egl_dynProcs.eglGetPlatformDisplayEXT(platform, native, NULL); } DEBUG_INFO("Using eglGetDisplay"); return eglGetDisplay(native); } static EGLNativeWindowType sdlGetEGLNativeWindow(void) { SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); if (!SDL_GetWindowWMInfo(sdl.window, &wminfo)) { DEBUG_ERROR("SDL_GetWindowWMInfo failed"); return 0; } switch(wminfo.subsystem) { case SDL_SYSWM_X11: return (EGLNativeWindowType)wminfo.info.x11.window; #if defined(SDL_VIDEO_DRIVER_WAYLAND) case SDL_SYSWM_WAYLAND: { if (sdl.wlDisplay) return sdl.wlDisplay; int width, height; SDL_GetWindowSize(sdl.window, &width, &height); sdl.wlDisplay = (EGLNativeWindowType)wl_egl_window_create( wminfo.info.wl.surface, width, height); return sdl.wlDisplay; } #endif default: DEBUG_ERROR("Unsupported subsystem"); return 0; } } static void sdlEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { eglSwapBuffers(display, surface); } #endif //ENABLE_EGL #ifdef ENABLE_OPENGL static LG_DSGLContext sdlGLCreateContext(void) { return (LG_DSGLContext)SDL_GL_CreateContext(sdl.window); } static void sdlGLDeleteContext(LG_DSGLContext context) { SDL_GL_DeleteContext((SDL_GLContext)context); } static void sdlGLMakeCurrent(LG_DSGLContext context) { SDL_GL_MakeCurrent(sdl.window, (SDL_GLContext)context); } static void sdlGLSetSwapInterval(int interval) { SDL_GL_SetSwapInterval(interval); } static void sdlGLSwapBuffers(void) { SDL_GL_SwapWindow(sdl.window); } #endif //ENABLE_OPENGL static int sdlEventFilter(void * userdata, SDL_Event * event) { switch(event->type) { case SDL_QUIT: app_handleCloseEvent(); break; case SDL_MOUSEMOTION: // stop motion events during the warp out of the window if (sdl.exiting) break; app_updateCursorPos(event->motion.x, event->motion.y); app_handleMouseRelative(event->motion.xrel, event->motion.yrel, event->motion.xrel, event->motion.yrel); break; case SDL_MOUSEBUTTONDOWN: { int button = event->button.button; if (button > 3) button += 2; app_handleButtonPress(button); break; } case SDL_MOUSEBUTTONUP: { int button = event->button.button; if (button > 3) button += 2; app_handleButtonRelease(button); break; } case SDL_MOUSEWHEEL: { int button = event->wheel.y > 0 ? 4 : 5; app_handleButtonPress(button); app_handleButtonRelease(button); break; } case SDL_KEYDOWN: { SDL_Scancode sc = event->key.keysym.scancode; app_handleKeyPress(sdl_to_xfree86[sc]); break; } case SDL_KEYUP: { SDL_Scancode sc = event->key.keysym.scancode; app_handleKeyRelease(sdl_to_xfree86[sc]); break; } case SDL_WINDOWEVENT: switch(event->window.event) { case SDL_WINDOWEVENT_ENTER: app_handleEnterEvent(true); break; case SDL_WINDOWEVENT_LEAVE: sdl.exiting = false; app_handleEnterEvent(false); break; case SDL_WINDOWEVENT_FOCUS_GAINED: app_handleFocusEvent(true); break; case SDL_WINDOWEVENT_FOCUS_LOST: app_handleFocusEvent(false); break; case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_RESIZED: { struct Border border; SDL_GetWindowBordersSize( sdl.window, &border.top, &border.left, &border.bottom, &border.right ); app_handleResizeEvent( event->window.data1, event->window.data2, 1, border); break; } case SDL_WINDOWEVENT_MOVED: app_updateWindowPos(event->window.data1, event->window.data2); break; case SDL_WINDOWEVENT_CLOSE: app_handleCloseEvent(); break; } break; } return 0; } static void sdlShowPointer(bool show) { SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE); } static void sdlGrabPointer(void) { SDL_SetWindowGrab(sdl.window, SDL_TRUE); SDL_SetRelativeMouseMode(SDL_TRUE); sdl.pointerGrabbed = true; } static void sdlUngrabPointer(void) { SDL_SetWindowGrab(sdl.window, SDL_FALSE); SDL_SetRelativeMouseMode(SDL_FALSE); sdl.pointerGrabbed = false; } static void sdlGrabKeyboard(void) { if (sdl.pointerGrabbed) SDL_SetWindowGrab(sdl.window, SDL_FALSE); else { DEBUG_WARN("SDL does not support grabbing only the keyboard, grabbing all"); sdl.pointerGrabbed = true; } SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); SDL_SetWindowGrab(sdl.window, SDL_TRUE); sdl.keyboardGrabbed = true; } static void sdlUngrabKeyboard(void) { SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0"); SDL_SetWindowGrab(sdl.window, SDL_FALSE); if (sdl.pointerGrabbed) SDL_SetWindowGrab(sdl.window, SDL_TRUE); sdl.keyboardGrabbed = false; } static void sdlWarpPointer(int x, int y, bool exiting) { if (sdl.exiting) return; sdl.exiting = exiting; // if exiting turn off relative mode if (exiting) SDL_SetRelativeMouseMode(SDL_FALSE); // issue the warp SDL_WarpMouseInWindow(sdl.window, x, y); } static void sdlRealignPointer(void) { app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); } static bool sdlIsValidPointerPos(int x, int y) { const int displays = SDL_GetNumVideoDisplays(); for(int i = 0; i < displays; ++i) { SDL_Rect r; SDL_GetDisplayBounds(i, &r); if ((x >= r.x && x < r.x + r.w) && (y >= r.y && y < r.y + r.h)) return true; } return false; } static void sdlInhibitIdle(void) { SDL_DisableScreenSaver(); } static void sdlUninhibitIdle(void) { SDL_EnableScreenSaver(); } static void sdlWait(unsigned int time) { SDL_WaitEventTimeout(NULL, time); } static void sdlSetWindowSize(int x, int y) { SDL_SetWindowSize(sdl.window, x, y); } static void sdlSetFullscreen(bool fs) { SDL_SetWindowFullscreen(sdl.window, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); } static bool sdlGetFullscreen(void) { return (SDL_GetWindowFlags(sdl.window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; } static void sdlMinimize(void) { SDL_MinimizeWindow(sdl.window); } struct LG_DisplayServerOps LGDS_SDL = { .setup = sdlSetup, .probe = sdlProbe, .earlyInit = sdlEarlyInit, .init = sdlInit, .startup = sdlStartup, .shutdown = sdlShutdown, .free = sdlFree, .getProp = sdlGetProp, #ifdef ENABLE_EGL .getEGLDisplay = sdlGetEGLDisplay, .getEGLNativeWindow = sdlGetEGLNativeWindow, .eglSwapBuffers = sdlEGLSwapBuffers, #endif #ifdef ENABLE_OPENGL .glCreateContext = sdlGLCreateContext, .glDeleteContext = sdlGLDeleteContext, .glMakeCurrent = sdlGLMakeCurrent, .glSetSwapInterval = sdlGLSetSwapInterval, .glSwapBuffers = sdlGLSwapBuffers, #endif .showPointer = sdlShowPointer, .grabPointer = sdlGrabPointer, .ungrabPointer = sdlUngrabPointer, .capturePointer = sdlGrabPointer, .uncapturePointer = sdlUngrabPointer, .grabKeyboard = sdlGrabKeyboard, .ungrabKeyboard = sdlUngrabKeyboard, .warpPointer = sdlWarpPointer, .realignPointer = sdlRealignPointer, .isValidPointerPos = sdlIsValidPointerPos, .inhibitIdle = sdlInhibitIdle, .uninhibitIdle = sdlUninhibitIdle, .wait = sdlWait, .setWindowSize = sdlSetWindowSize, .setFullscreen = sdlSetFullscreen, .getFullscreen = sdlGetFullscreen, .minimize = sdlMinimize, /* SDL does not have clipboard support */ .cbInit = NULL, }; looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/000077500000000000000000000000001411177132200232765ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/CMakeLists.txt000066400000000000000000000063311411177132200260410ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(displayserver_Wayland LANGUAGES C) find_package(PkgConfig) pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED wayland-client ) #pkg_check_modules(DISPLAYSERVER_Wayland_OPT_PKGCONFIG #) set(displayserver_Wayland_SHELL_SRC "") if (ENABLE_LIBDECOR) pkg_check_modules(DISPLAYSERVER_Wayland_LIBDECOR REQUIRED libdecor-0.1 ) list(APPEND DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES ${DISPLAYSERVER_Wayland_LIBDECOR_LIBRARIES}) list(APPEND DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS ${DISPLAYSERVER_Wayland_LIBDECOR_INCLUDE_DIRS}) list(APPEND displayserver_Wayland_SHELL_SRC shell_libdecor.c) add_definitions(-D ENABLE_LIBDECOR) else() list(APPEND displayserver_Wayland_SHELL_SRC shell_xdg.c) endif() add_library(displayserver_Wayland STATIC clipboard.c cursor.c gl.c idle.c input.c output.c poll.c state.c registry.c wayland.c window.c ${displayserver_Wayland_SHELL_SRC} ) target_link_libraries(displayserver_Wayland ${DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES} ${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES} lg_common ) target_include_directories(displayserver_Wayland PRIVATE src ${DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS} ${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_INCLUDE_DIRS} ) find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner) pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15) pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir) macro(wayland_generate protocol_file output_file) add_custom_command(OUTPUT "${output_file}.h" COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h" DEPENDS "${protocol_file}" VERBATIM) add_custom_command(OUTPUT "${output_file}.c" COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c" DEPENDS "${protocol_file}" VERBATIM) target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c") endmacro() file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland") include_directories("${CMAKE_BINARY_DIR}/wayland") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-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") looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/clipboard.c000066400000000000000000000340301411177132200254010ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include #include #include #include #include #include "app.h" #include "common/debug.h" struct DataOffer { bool isSelfCopy; char * mimetypes[LG_CLIPBOARD_DATA_NONE]; }; static const char * textMimetypes[] = { "text/plain", "text/plain;charset=utf-8", "TEXT", "STRING", "UTF8_STRING", NULL, }; static const char * pngMimetypes[] = { "image/png", NULL, }; static const char * bmpMimetypes[] = { "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap", NULL, }; static const char * tiffMimetypes[] = { "image/tiff", NULL, }; static const char * jpegMimetypes[] = { "image/jpeg", NULL, }; static const char ** cbTypeToMimetypes(enum LG_ClipboardData type) { switch (type) { case LG_CLIPBOARD_DATA_TEXT: return textMimetypes; case LG_CLIPBOARD_DATA_PNG: return pngMimetypes; case LG_CLIPBOARD_DATA_BMP: return bmpMimetypes; case LG_CLIPBOARD_DATA_TIFF: return tiffMimetypes; case LG_CLIPBOARD_DATA_JPEG: return jpegMimetypes; default: DEBUG_ERROR("invalid clipboard type"); abort(); } } static bool containsMimetype(const char ** mimetypes, const char * needle) { for (const char ** mimetype = mimetypes; *mimetype; mimetype++) if (!strcmp(needle, *mimetype)) return true; return false; } static bool mimetypeEndswith(const char * mimetype, const char * what) { size_t mimetypeLen = strlen(mimetype); size_t whatLen = strlen(what); if (mimetypeLen < whatLen) return false; return !strcmp(mimetype + mimetypeLen - whatLen, what); } static bool isTextMimetype(const char * mimetype) { if (containsMimetype(textMimetypes, mimetype)) return true; if (!strcmp(mimetype, "text/ico")) return false; char * text = "text/"; if (!strncmp(mimetype, text, strlen(text))) return true; if (mimetypeEndswith(mimetype, "script") || mimetypeEndswith(mimetype, "xml") || mimetypeEndswith(mimetype, "yaml")) return true; if (strstr(mimetype, "json")) return true; return false; } static enum LG_ClipboardData mimetypeToCbType(const char * mimetype) { if (isTextMimetype(mimetype)) return LG_CLIPBOARD_DATA_TEXT; if (containsMimetype(pngMimetypes, mimetype)) return LG_CLIPBOARD_DATA_PNG; if (containsMimetype(bmpMimetypes, mimetype)) return LG_CLIPBOARD_DATA_BMP; if (containsMimetype(tiffMimetypes, mimetype)) return LG_CLIPBOARD_DATA_TIFF; if (containsMimetype(jpegMimetypes, mimetype)) return LG_CLIPBOARD_DATA_JPEG; return LG_CLIPBOARD_DATA_NONE; } static bool isImageCbtype(enum LG_ClipboardData type) { switch (type) { case LG_CLIPBOARD_DATA_TEXT: return false; case LG_CLIPBOARD_DATA_PNG: case LG_CLIPBOARD_DATA_BMP: case LG_CLIPBOARD_DATA_TIFF: case LG_CLIPBOARD_DATA_JPEG: return true; default: DEBUG_ERROR("invalid clipboard type"); abort(); } } static bool hasAnyMimetype(char ** mimetypes) { for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (mimetypes[i]) return true; return false; } static bool hasImageMimetype(char ** mimetypes) { for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (isImageCbtype(i) && mimetypes[i]) return true; return false; } // Destination client handlers. static void dataOfferHandleOffer(void * opaque, struct wl_data_offer * offer, const char * mimetype) { struct DataOffer * data = opaque; if (!strcmp(mimetype, wlCb.lgMimetype)) { data->isSelfCopy = true; return; } enum LG_ClipboardData type = mimetypeToCbType(mimetype); if (type == LG_CLIPBOARD_DATA_NONE) return; // text/html represents rich text format, which is almost never desirable when // and should not be used when a plain text or image format is available. if ((isImageCbtype(type) || containsMimetype(textMimetypes, mimetype)) && data->mimetypes[LG_CLIPBOARD_DATA_TEXT] && strstr(data->mimetypes[LG_CLIPBOARD_DATA_TEXT], "html")) { free(data->mimetypes[LG_CLIPBOARD_DATA_TEXT]); data->mimetypes[LG_CLIPBOARD_DATA_TEXT] = NULL; } if (strstr(mimetype, "html") && hasImageMimetype(data->mimetypes)) return; if (data->mimetypes[type]) return; data->mimetypes[type] = strdup(mimetype); } static void dataOfferHandleSourceActions(void * data, struct wl_data_offer * offer, uint32_t sourceActions) { // Do nothing. } static void dataOfferHandleAction(void * data, struct wl_data_offer * offer, uint32_t dndAction) { // Do nothing. } static const struct wl_data_offer_listener dataOfferListener = { .offer = dataOfferHandleOffer, .source_actions = dataOfferHandleSourceActions, .action = dataOfferHandleAction, }; static void dataDeviceHandleDataOffer(void * data, struct wl_data_device * dataDevice, struct wl_data_offer * offer) { struct DataOffer * extra = calloc(1, sizeof(struct DataOffer)); if (!extra) { DEBUG_ERROR("Out of memory while handling clipboard"); abort(); } wl_data_offer_set_user_data(offer, extra); wl_data_offer_add_listener(offer, &dataOfferListener, extra); } static void dataDeviceHandleSelection(void * opaque, struct wl_data_device * dataDevice, struct wl_data_offer * offer) { if (!offer) { waylandCBInvalidate(); return; } struct DataOffer * extra = wl_data_offer_get_user_data(offer); if (!hasAnyMimetype(extra->mimetypes) || extra->isSelfCopy) { waylandCBInvalidate(); wl_data_offer_destroy(offer); return; } wlCb.offer = offer; for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) free(wlCb.mimetypes[i]); memcpy(wlCb.mimetypes, extra->mimetypes, sizeof(wlCb.mimetypes)); wl_data_offer_set_user_data(offer, NULL); free(extra); int idx = 0; enum LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE]; for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (wlCb.mimetypes[i]) types[idx++] = i; app_clipboardNotifyTypes(types, idx); } static void dataDeviceHandleEnter(void * data, struct wl_data_device * device, uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, wl_fixed_t syW, struct wl_data_offer * offer) { assert(wlCb.dndOffer == NULL); wlCb.dndOffer = offer; struct DataOffer * extra = wl_data_offer_get_user_data(offer); for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) free(extra->mimetypes[i]); free(extra); wl_data_offer_set_user_data(offer, NULL); wl_data_offer_set_actions(offer, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); } static void dataDeviceHandleMotion(void * data, struct wl_data_device * device, uint32_t time, wl_fixed_t sxW, wl_fixed_t syW) { // Do nothing. } static void dataDeviceHandleLeave(void * data, struct wl_data_device * device) { wl_data_offer_destroy(wlCb.dndOffer); wlCb.dndOffer = NULL; } static void dataDeviceHandleDrop(void * data, struct wl_data_device * device) { wl_data_offer_destroy(wlCb.dndOffer); wlCb.dndOffer = NULL; } static const struct wl_data_device_listener dataDeviceListener = { .data_offer = dataDeviceHandleDataOffer, .selection = dataDeviceHandleSelection, .enter = dataDeviceHandleEnter, .motion = dataDeviceHandleMotion, .leave = dataDeviceHandleLeave, .drop = dataDeviceHandleDrop, }; bool waylandCBInit(void) { memset(&wlCb, 0, sizeof(wlCb)); if (!wlWm.dataDeviceManager) { DEBUG_ERROR("Missing wl_data_device_manager interface (version 3+)"); return false; } wlCb.dataDevice = wl_data_device_manager_get_data_device( wlWm.dataDeviceManager, wlWm.seat); if (!wlCb.dataDevice) { DEBUG_ERROR("Failed to get data device"); return false; } wl_data_device_add_listener(wlCb.dataDevice, &dataDeviceListener, NULL); snprintf(wlCb.lgMimetype, sizeof(wlCb.lgMimetype), "application/x-looking-glass-copy;pid=%d", getpid()); return true; } static void clipboardReadCancel(struct ClipboardRead * data) { waylandPollUnregister(data->fd); close(data->fd); free(data->buf); free(data); wlCb.currentRead = NULL; } static void clipboardReadCallback(uint32_t events, void * opaque) { struct ClipboardRead * data = opaque; if (events & EPOLLERR) { clipboardReadCancel(data); return; } ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead); if (result < 0) { DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno)); clipboardReadCancel(data); return; } if (result == 0) { app_clipboardNotifySize(data->type, data->numRead); app_clipboardData(data->type, data->buf, data->numRead); clipboardReadCancel(data); return; } data->numRead += result; if (data->numRead >= data->size) { data->size *= 2; void * nbuf = realloc(data->buf, data->size); if (!nbuf) { DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno)); clipboardReadCancel(data); return; } data->buf = nbuf; } } void waylandCBInvalidate(void) { if (wlCb.currentRead) clipboardReadCancel(wlCb.currentRead); app_clipboardRelease(); if (wlCb.offer) wl_data_offer_destroy(wlCb.offer); wlCb.offer = NULL; } void waylandCBRequest(LG_ClipboardData type) { if (!wlCb.offer || !wlCb.mimetypes[type]) { app_clipboardRelease(); return; } if (wlCb.currentRead) clipboardReadCancel(wlCb.currentRead); int fds[2]; if (pipe(fds) < 0) { DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno)); abort(); } wl_data_offer_receive(wlCb.offer, wlCb.mimetypes[type], fds[1]); close(fds[1]); struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead)); if (!data) { DEBUG_ERROR("Failed to allocate memory to read clipboard"); close(fds[0]); return; } data->fd = fds[0]; data->size = 4096; data->numRead = 0; data->buf = malloc(data->size); data->offer = wlCb.offer; data->type = type; if (!data->buf) { DEBUG_ERROR("Failed to allocate memory to receive clipboard data"); close(data->fd); free(data); return; } if (!waylandPollRegister(data->fd, clipboardReadCallback, data, EPOLLIN)) { DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno)); close(data->fd); free(data->buf); free(data); } wlCb.currentRead = data; } struct ClipboardWrite { int fd; size_t pos; struct CountedBuffer * buffer; }; static void clipboardWriteCallback(uint32_t events, void * opaque) { struct ClipboardWrite * data = opaque; if (events & EPOLLERR) goto error; ssize_t written = write(data->fd, data->buffer->data + data->pos, data->buffer->size - data->pos); if (written < 0) { if (errno != EPIPE) DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno)); goto error; } data->pos += written; if (data->pos < data->buffer->size) return; error: waylandPollUnregister(data->fd); close(data->fd); countedBufferRelease(&data->buffer); free(data); } static void dataSourceHandleSend(void * data, struct wl_data_source * source, const char * mimetype, int fd) { struct WCBTransfer * transfer = (struct WCBTransfer *) data; if (containsMimetype(transfer->mimetypes, mimetype)) { struct ClipboardWrite * data = malloc(sizeof(struct ClipboardWrite)); if (!data) { DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite"); goto error; } data->fd = fd; data->pos = 0; data->buffer = transfer->data; countedBufferAddRef(transfer->data); waylandPollRegister(fd, clipboardWriteCallback, data, EPOLLOUT); return; } error: close(fd); } static void dataSourceHandleCancelled(void * data, struct wl_data_source * source) { struct WCBTransfer * transfer = (struct WCBTransfer *) data; countedBufferRelease(&transfer->data); free(transfer); wl_data_source_destroy(source); } static const struct wl_data_source_listener dataSourceListener = { .send = dataSourceHandleSend, .cancelled = dataSourceHandleCancelled, }; static void waylandCBReplyFn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size) { struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer)); if (!transfer) { DEBUG_ERROR("Out of memory when allocating WCBTransfer"); return; } transfer->mimetypes = cbTypeToMimetypes(type); transfer->data = countedBufferNew(size); if (!transfer->data) { DEBUG_ERROR("Out of memory when allocating clipboard buffer"); free(transfer); return; } memcpy(transfer->data->data, data, size); struct wl_data_source * source = wl_data_device_manager_create_data_source(wlWm.dataDeviceManager); wl_data_source_add_listener(source, &dataSourceListener, transfer); for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++) wl_data_source_offer(source, *mimetype); wl_data_source_offer(source, wlCb.lgMimetype); wl_data_device_set_selection(wlCb.dataDevice, source, wlWm.keyboardEnterSerial); } void waylandCBNotice(LG_ClipboardData type) { wlCb.haveRequest = true; wlCb.type = type; app_clipboardRequest(waylandCBReplyFn, NULL); } void waylandCBRelease(void) { wlCb.haveRequest = false; } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/cursor.c000066400000000000000000000055141411177132200247640ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #define _GNU_SOURCE #include "wayland.h" #include #include #include #include #include #include #include "common/debug.h" static const uint32_t cursorBitmap[] = { 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, }; static struct wl_buffer * createCursorBuffer(void) { int fd = memfd_create("lg-cursor", 0); if (fd < 0) { DEBUG_ERROR("Failed to create cursor shared memory: %d", errno); return NULL; } struct wl_buffer * result = NULL; if (ftruncate(fd, sizeof cursorBitmap) < 0) { DEBUG_ERROR("Failed to ftruncate cursor shared memory: %d", errno); goto fail; } void * shm_data = mmap(NULL, sizeof cursorBitmap, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (shm_data == MAP_FAILED) { DEBUG_ERROR("Failed to map memory for cursor: %d", errno); goto fail; } struct wl_shm_pool * pool = wl_shm_create_pool(wlWm.shm, fd, sizeof cursorBitmap); result = wl_shm_pool_create_buffer(pool, 0, 4, 4, 16, WL_SHM_FORMAT_XRGB8888); wl_shm_pool_destroy(pool); memcpy(shm_data, cursorBitmap, sizeof cursorBitmap); munmap(shm_data, sizeof cursorBitmap); fail: close(fd); return result; } bool waylandCursorInit(void) { if (!wlWm.compositor) { DEBUG_ERROR("Compositor missing wl_compositor, will not proceed"); return false; } wlWm.cursorBuffer = createCursorBuffer(); if (wlWm.cursorBuffer) { wlWm.cursor = wl_compositor_create_surface(wlWm.compositor); wl_surface_attach(wlWm.cursor, wlWm.cursorBuffer, 0, 0); wl_surface_commit(wlWm.cursor); } return true; } void waylandCursorFree(void) { if (wlWm.cursor) wl_surface_destroy(wlWm.cursor); if (wlWm.cursorBuffer) wl_buffer_destroy(wlWm.cursorBuffer); } void waylandShowPointer(bool show) { wlWm.showPointer = show; wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, show ? wlWm.cursor : NULL, 0, 0); } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/gl.c000066400000000000000000000141401411177132200240440ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include #include "app.h" #include "common/debug.h" #include "util.h" #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) #include "egl_dynprocs.h" bool waylandEGLInit(int w, int h) { wlWm.eglWindow = wl_egl_window_create(wlWm.surface, w, h); if (!wlWm.eglWindow) { DEBUG_ERROR("Failed to create EGL window"); return false; } return true; } EGLDisplay waylandGetEGLDisplay(void) { EGLNativeDisplayType native = (EGLNativeDisplayType) wlWm.display; const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS); if (util_hasGLExt(early_exts, "EGL_KHR_platform_wayland") && g_egl_dynProcs.eglGetPlatformDisplay) { DEBUG_INFO("Using eglGetPlatformDisplay"); return g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native, NULL); } if (util_hasGLExt(early_exts, "EGL_EXT_platform_wayland") && g_egl_dynProcs.eglGetPlatformDisplayEXT) { DEBUG_INFO("Using eglGetPlatformDisplayEXT"); return g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, native, NULL); } DEBUG_INFO("Using eglGetDisplay"); return eglGetDisplay(native); } void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { if (!wlWm.eglSwapWithDamageInit) { const char *exts = eglQueryString(display, EGL_EXTENSIONS); wlWm.eglSwapWithDamageInit = true; if (wl_proxy_get_version((struct wl_proxy *) wlWm.surface) < 4) DEBUG_INFO("Swapping buffers with damage: not supported, need wl_compositor v4"); else if (util_hasGLExt(exts, "EGL_KHR_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageKHR) { wlWm.eglSwapWithDamage = g_egl_dynProcs.eglSwapBuffersWithDamageKHR; DEBUG_INFO("Using EGL_KHR_swap_buffers_with_damage"); } else if (util_hasGLExt(exts, "EGL_EXT_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageEXT) { wlWm.eglSwapWithDamage = g_egl_dynProcs.eglSwapBuffersWithDamageEXT; DEBUG_INFO("Using EGL_EXT_swap_buffers_with_damage"); } else DEBUG_INFO("Swapping buffers with damage: not supported"); } if (wlWm.eglSwapWithDamage && count) { if (count * 4 > wlWm.eglDamageRectCount) { free(wlWm.eglDamageRects); wlWm.eglDamageRects = malloc(sizeof(EGLint) * count * 4); if (!wlWm.eglDamageRects) DEBUG_FATAL("Out of memory"); wlWm.eglDamageRectCount = count * 4; } for (int i = 0; i < count; ++i) { wlWm.eglDamageRects[i*4+0] = damage[i].x; wlWm.eglDamageRects[i*4+1] = damage[i].y; wlWm.eglDamageRects[i*4+2] = damage[i].w; wlWm.eglDamageRects[i*4+3] = damage[i].h; } wlWm.eglSwapWithDamage(display, surface, wlWm.eglDamageRects, count); } else eglSwapBuffers(display, surface); if (wlWm.needsResize) { wl_egl_window_resize(wlWm.eglWindow, wlWm.width * wlWm.scale, wlWm.height * wlWm.scale, 0, 0); wl_surface_set_buffer_scale(wlWm.surface, wlWm.scale); struct wl_region * region = wl_compositor_create_region(wlWm.compositor); wl_region_add(region, 0, 0, wlWm.width, wlWm.height); wl_surface_set_opaque_region(wlWm.surface, region); wl_region_destroy(region); app_handleResizeEvent(wlWm.width, wlWm.height, wlWm.scale, (struct Border) {0, 0, 0, 0}); wlWm.needsResize = false; } waylandShellAckConfigureIfNeeded(); } #endif #ifdef ENABLE_EGL EGLNativeWindowType waylandGetEGLNativeWindow(void) { return (EGLNativeWindowType) wlWm.eglWindow; } #endif #ifdef ENABLE_OPENGL bool waylandOpenGLInit(void) { EGLint attr[] = { EGL_BUFFER_SIZE , 24, EGL_CONFORMANT , EGL_OPENGL_BIT, EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT, EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, EGL_RED_SIZE , 8, EGL_GREEN_SIZE , 8, EGL_BLUE_SIZE , 8, EGL_SAMPLE_BUFFERS , 0, EGL_SAMPLES , 0, EGL_NONE }; wlWm.glDisplay = waylandGetEGLDisplay(); int maj, min; if (!eglInitialize(wlWm.glDisplay, &maj, &min)) { DEBUG_ERROR("Unable to initialize EGL"); return false; } if (wlWm.glDisplay == EGL_NO_DISPLAY) { DEBUG_ERROR("Failed to get EGL display (eglError: 0x%x)", eglGetError()); return false; } EGLint num_config; if (!eglChooseConfig(wlWm.glDisplay, attr, &wlWm.glConfig, 1, &num_config)) { DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError()); return false; } wlWm.glSurface = eglCreateWindowSurface(wlWm.glDisplay, wlWm.glConfig, wlWm.eglWindow, NULL); if (wlWm.glSurface == EGL_NO_SURFACE) { DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError()); return false; } return true; } LG_DSGLContext waylandGLCreateContext(void) { eglBindAPI(EGL_OPENGL_API); return eglCreateContext(wlWm.glDisplay, wlWm.glConfig, EGL_NO_CONTEXT, NULL); } void waylandGLDeleteContext(LG_DSGLContext context) { eglDestroyContext(wlWm.glDisplay, context); } void waylandGLMakeCurrent(LG_DSGLContext context) { eglMakeCurrent(wlWm.glDisplay, wlWm.glSurface, wlWm.glSurface, context); } void waylandGLSetSwapInterval(int interval) { eglSwapInterval(wlWm.glDisplay, interval); } void waylandGLSwapBuffers(void) { waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface, NULL, 0); } #endif looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/idle.c000066400000000000000000000031751411177132200243650ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include "common/debug.h" bool waylandIdleInit(void) { if (!wlWm.idleInhibitManager) DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will " "not be able to suppress idle states"); return true; } void waylandIdleFree(void) { if (wlWm.idleInhibitManager) { waylandUninhibitIdle(); zwp_idle_inhibit_manager_v1_destroy(wlWm.idleInhibitManager); } } void waylandInhibitIdle(void) { if (wlWm.idleInhibitManager && !wlWm.idleInhibitor) wlWm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( wlWm.idleInhibitManager, wlWm.surface); } void waylandUninhibitIdle(void) { if (wlWm.idleInhibitor) { zwp_idle_inhibitor_v1_destroy(wlWm.idleInhibitor); wlWm.idleInhibitor = NULL; } } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/input.c000066400000000000000000000321731411177132200246070ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include #include "app.h" #include "common/debug.h" // Mouse-handling listeners. static void pointerMotionHandler(void * data, struct wl_pointer * pointer, uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW) { wlWm.cursorX = wl_fixed_to_double(sxW); wlWm.cursorY = wl_fixed_to_double(syW); app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); if (!wlWm.warpSupport && !wlWm.relativePointer) app_handleMouseBasic(); } static void pointerEnterHandler(void * data, struct wl_pointer * pointer, uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, wl_fixed_t syW) { if (surface != wlWm.surface) return; wlWm.pointerInSurface = true; app_handleEnterEvent(true); wl_pointer_set_cursor(pointer, serial, wlWm.showPointer ? wlWm.cursor : NULL, 0, 0); wlWm.pointerEnterSerial = serial; wlWm.cursorX = wl_fixed_to_double(sxW); wlWm.cursorY = wl_fixed_to_double(syW); app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); if (wlWm.warpSupport) { app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); return; } if (wlWm.relativePointer) return; app_resyncMouseBasic(); app_handleMouseBasic(); } static void pointerLeaveHandler(void * data, struct wl_pointer * pointer, uint32_t serial, struct wl_surface * surface) { if (surface != wlWm.surface) return; wlWm.pointerInSurface = false; app_handleEnterEvent(false); } static void pointerAxisHandler(void * data, struct wl_pointer * pointer, uint32_t serial, uint32_t axis, wl_fixed_t value) { int button = value > 0 ? 5 /* SPICE_MOUSE_BUTTON_DOWN */ : 4 /* SPICE_MOUSE_BUTTON_UP */; app_handleButtonPress(button); app_handleButtonRelease(button); } static int mapWaylandToSpiceButton(uint32_t button) { switch (button) { case BTN_LEFT: return 1; // SPICE_MOUSE_BUTTON_LEFT case BTN_MIDDLE: return 2; // SPICE_MOUSE_BUTTON_MIDDLE case BTN_RIGHT: return 3; // SPICE_MOUSE_BUTTON_RIGHT case BTN_SIDE: return 6; // SPICE_MOUSE_BUTTON_SIDE case BTN_EXTRA: return 7; // SPICE_MOUSE_BUTTON_EXTRA } return 0; // SPICE_MOUSE_BUTTON_INVALID } static void pointerButtonHandler(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW) { button = mapWaylandToSpiceButton(button); if (stateW == WL_POINTER_BUTTON_STATE_PRESSED) app_handleButtonPress(button); else app_handleButtonRelease(button); } static const struct wl_pointer_listener pointerListener = { .enter = pointerEnterHandler, .leave = pointerLeaveHandler, .motion = pointerMotionHandler, .button = pointerButtonHandler, .axis = pointerAxisHandler, }; static void relativePointerMotionHandler(void * data, struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo, wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW, wl_fixed_t dyUnaccelW) { wlWm.cursorX += wl_fixed_to_double(dxW); wlWm.cursorY += wl_fixed_to_double(dyW); app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); app_handleMouseRelative( wl_fixed_to_double(dxW), wl_fixed_to_double(dyW), wl_fixed_to_double(dxUnaccelW), wl_fixed_to_double(dyUnaccelW)); } static const struct zwp_relative_pointer_v1_listener relativePointerListener = { .relative_motion = relativePointerMotionHandler, }; // Keyboard-handling listeners. static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard, uint32_t format, int fd, uint32_t size) { close(fd); } static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, struct wl_surface * surface, struct wl_array * keys) { if (surface != wlWm.surface) return; wlWm.focusedOnSurface = true; app_handleFocusEvent(true); wlWm.keyboardEnterSerial = serial; uint32_t * key; wl_array_for_each(key, keys) app_handleKeyPress(*key); } static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, struct wl_surface * surface) { if (surface != wlWm.surface) return; wlWm.focusedOnSurface = false; waylandCBInvalidate(); app_handleFocusEvent(false); } static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { if (!wlWm.focusedOnSurface) return; if (state == WL_KEYBOARD_KEY_STATE_PRESSED) app_handleKeyPress(key); else app_handleKeyRelease(key); } static void keyboardModifiersHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { // Do nothing. } static const struct wl_keyboard_listener keyboardListener = { .keymap = keyboardKeymapHandler, .enter = keyboardEnterHandler, .leave = keyboardLeaveHandler, .key = keyboardKeyHandler, .modifiers = keyboardModifiersHandler, }; // Seat-handling listeners. static void handlePointerCapability(uint32_t capabilities) { bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; if (!hasPointer && wlWm.pointer) { wl_pointer_destroy(wlWm.pointer); wlWm.pointer = NULL; } else if (hasPointer && !wlWm.pointer) { wlWm.pointer = wl_seat_get_pointer(wlWm.seat); wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL); } } static void handleKeyboardCapability(uint32_t capabilities) { bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; if (!hasKeyboard && wlWm.keyboard) { wl_keyboard_destroy(wlWm.keyboard); wlWm.keyboard = NULL; } else if (hasKeyboard && !wlWm.keyboard) { wlWm.keyboard = wl_seat_get_keyboard(wlWm.seat); wl_keyboard_add_listener(wlWm.keyboard, &keyboardListener, NULL); } } static void seatCapabilitiesHandler(void * data, struct wl_seat * seat, uint32_t capabilities) { wlWm.capabilities = capabilities; handlePointerCapability(capabilities); handleKeyboardCapability(capabilities); } static void seatNameHandler(void * data, struct wl_seat * seat, const char * name) { // Do nothing. } static const struct wl_seat_listener seatListener = { .capabilities = seatCapabilitiesHandler, .name = seatNameHandler, }; bool waylandInputInit(void) { if (!wlWm.seat) { DEBUG_ERROR("Compositor missing wl_seat, will not proceed"); return false; } if (wlWm.warpSupport && (!wlWm.relativePointerManager || !wlWm.pointerConstraints)) { DEBUG_WARN("Cursor warp is requested, but cannot be honoured due to lack " "of zwp_relative_pointer_manager_v1 or zwp_pointer_constraints_v1"); wlWm.warpSupport = false; } if (!wlWm.relativePointerManager) DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, " "mouse will not be captured"); if (!wlWm.pointerConstraints) DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse " "will not be captured"); if (!wlWm.keyboardInhibitManager) DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by " "compositor, keyboard will not be grabbed"); wl_seat_add_listener(wlWm.seat, &seatListener, NULL); wl_display_roundtrip(wlWm.display); if (wlWm.warpSupport) { wlWm.relativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( wlWm.relativePointerManager, wlWm.pointer); zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, &relativePointerListener, NULL); } LG_LOCK_INIT(wlWm.confineLock); return true; } void waylandInputFree(void) { waylandUngrabPointer(); LG_LOCK_FREE(wlWm.confineLock); wl_pointer_destroy(wlWm.pointer); wl_keyboard_destroy(wlWm.keyboard); wl_seat_destroy(wlWm.seat); } void waylandGrabPointer(void) { if (!wlWm.relativePointerManager || !wlWm.pointerConstraints) return; if (!wlWm.warpSupport && !wlWm.relativePointer) { wlWm.relativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( wlWm.relativePointerManager, wlWm.pointer); zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, &relativePointerListener, NULL); } INTERLOCKED_SECTION(wlWm.confineLock, { if (!wlWm.confinedPointer) { wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); } }); } void waylandUngrabPointer(void) { INTERLOCKED_SECTION(wlWm.confineLock, { if (wlWm.confinedPointer) { zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); wlWm.confinedPointer = NULL; } }); if (!wlWm.warpSupport) { if (!wlWm.relativePointer) { wlWm.relativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( wlWm.relativePointerManager, wlWm.pointer); zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, &relativePointerListener, NULL); } app_resyncMouseBasic(); app_handleMouseBasic(); } } void waylandCapturePointer(void) { if (!wlWm.warpSupport) { waylandGrabPointer(); return; } INTERLOCKED_SECTION(wlWm.confineLock, { if (wlWm.confinedPointer) { zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); wlWm.confinedPointer = NULL; } wlWm.lockedPointer = zwp_pointer_constraints_v1_lock_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); }); } void waylandUncapturePointer(void) { INTERLOCKED_SECTION(wlWm.confineLock, { if (wlWm.lockedPointer) { zwp_locked_pointer_v1_destroy(wlWm.lockedPointer); wlWm.lockedPointer = NULL; } /* we need to ungrab the pointer on the following conditions when exiting capture mode: * - if warp is not supported, exit via window edge detection will never work * as the cursor can not be warped out of the window when we release it. * - if the format is invalid as we do not know where the guest cursor is, * which also breaks edge detection. * - if the user has opted to use captureInputOnly mode. */ if (!wlWm.warpSupport || !app_isFormatValid() || app_isCaptureOnlyMode()) { waylandUngrabPointer(); } else { wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); } }); } void waylandGrabKeyboard(void) { if (wlWm.keyboardInhibitManager && !wlWm.keyboardInhibitor) { wlWm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts( wlWm.keyboardInhibitManager, wlWm.surface, wlWm.seat); } } void waylandUngrabKeyboard(void) { if (wlWm.keyboardInhibitor) { zwp_keyboard_shortcuts_inhibitor_v1_destroy(wlWm.keyboardInhibitor); wlWm.keyboardInhibitor = NULL; } } void waylandWarpPointer(int x, int y, bool exiting) { if (!wlWm.pointerInSurface || wlWm.lockedPointer) return; INTERLOCKED_SECTION(wlWm.confineLock, { if (wlWm.lockedPointer) { LG_UNLOCK(wlWm.confineLock); return; } if (x < 0) x = 0; else if (x >= wlWm.width) x = wlWm.width - 1; if (y < 0) y = 0; 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); if (wlWm.confinedPointer) { zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region); wl_surface_commit(wlWm.surface); zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, NULL); } else { struct zwp_confined_pointer_v1 * confine; confine = zwp_pointer_constraints_v1_confine_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); wl_surface_commit(wlWm.surface); zwp_confined_pointer_v1_destroy(confine); } wl_surface_commit(wlWm.surface); wl_region_destroy(region); }); } void waylandRealignPointer(void) { if (!wlWm.warpSupport) app_resyncMouseBasic(); } void waylandGuestPointerUpdated(double x, double y, double localX, double localY) { if (!wlWm.warpSupport || !wlWm.pointerInSurface || wlWm.lockedPointer) return; waylandWarpPointer((int) localX, (int) localY, false); } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/output.c000066400000000000000000000064711411177132200250120ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include "common/debug.h" static void outputGeometryHandler(void * data, struct wl_output * output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char * make, const char * model, int32_t output_transform) { // Do nothing. } static void outputModeHandler(void * data, struct wl_output * wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { // Do nothing. } static void outputDoneHandler(void * data, struct wl_output * output) { // Do nothing. } static void outputScaleHandler(void * opaque, struct wl_output * output, int32_t scale) { struct WaylandOutput * node = opaque; node->scale = scale; waylandWindowUpdateScale(); } static const struct wl_output_listener outputListener = { .geometry = outputGeometryHandler, .mode = outputModeHandler, .done = outputDoneHandler, .scale = outputScaleHandler, }; bool waylandOutputInit(void) { wl_list_init(&wlWm.outputs); return true; } void waylandOutputFree(void) { struct WaylandOutput * node; struct WaylandOutput * temp; wl_list_for_each_safe(node, temp, &wlWm.outputs, link) { if (node->version >= 3) wl_output_release(node->output); wl_list_remove(&node->link); free(node); } } void waylandOutputBind(uint32_t name, uint32_t version) { struct WaylandOutput * node = malloc(sizeof(struct WaylandOutput)); if (!node) return; if (version < 2) { DEBUG_WARN("wl_output version too old: expected >= 2, got %d", version); return; } node->name = name; node->scale = 0; node->version = version; node->output = wl_registry_bind(wlWm.registry, name, &wl_output_interface, version >= 3 ? 3 : 2); if (!node->output) { DEBUG_ERROR("Failed to bind to wl_output %u\n", name); free(node); return; } wl_output_add_listener(node->output, &outputListener, node); wl_list_insert(&wlWm.outputs, &node->link); } void waylandOutputTryUnbind(uint32_t name) { struct WaylandOutput * node; wl_list_for_each(node, &wlWm.outputs, link) { if (node->name == name) { if (node->version >= 3) wl_output_release(node->output); wl_list_remove(&node->link); free(node); break; } } } int32_t waylandOutputGetScale(struct wl_output * output) { struct WaylandOutput * node; wl_list_for_each(node, &wlWm.outputs, link) if (node->output == output) return node->scale; return 0; } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/poll.c000066400000000000000000000104151411177132200244110ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include #include #include "common/debug.h" #include "common/locking.h" #define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait 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); } bool waylandPollInit(void) { wlWm.epollFd = epoll_create1(EPOLL_CLOEXEC); if (wlWm.epollFd < 0) { DEBUG_ERROR("Failed to initialize epoll: %s", strerror(errno)); return false; } wl_list_init(&wlWm.poll); wl_list_init(&wlWm.pollFree); LG_LOCK_INIT(wlWm.pollLock); LG_LOCK_INIT(wlWm.pollFreeLock); 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; } return true; } void waylandWait(unsigned int time) { while (wl_display_prepare_read(wlWm.display)) wl_display_dispatch_pending(wlWm.display); wl_display_flush(wlWm.display); 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)); wl_display_cancel_read(wlWm.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 == wlWm.displayFd) sawDisplay = true; } if (!sawDisplay) wl_display_cancel_read(wlWm.display); INTERLOCKED_SECTION(wlWm.pollFreeLock, { struct WaylandPoll * node; struct WaylandPoll * temp; wl_list_for_each_safe(node, temp, &wlWm.pollFree, link) { wl_list_remove(&node->link); free(node); } }); } static void waylandPollRemoveNode(struct WaylandPoll * node) { INTERLOCKED_SECTION(wlWm.pollLock, { wl_list_remove(&node->link); }); } bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events) { struct WaylandPoll * node = malloc(sizeof(struct WaylandPoll)); if (!node) return false; node->fd = fd; node->removed = false; node->callback = callback; node->opaque = opaque; INTERLOCKED_SECTION(wlWm.pollLock, { wl_list_insert(&wlWm.poll, &node->link); }); if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) { .events = events, .data = (epoll_data_t) { .ptr = node }, }) < 0) { waylandPollRemoveNode(node); free(node); return false; } return true; } bool waylandPollUnregister(int fd) { struct WaylandPoll * node = NULL; INTERLOCKED_SECTION(wlWm.pollLock, { wl_list_for_each(node, &wlWm.poll, link) { if (node->fd == fd) break; } }); if (!node) { DEBUG_ERROR("Attempt to unregister a fd that was not registered: %d", fd); return false; } node->removed = true; if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0) { DEBUG_ERROR("Failed to unregistered from epoll: %s", strerror(errno)); return false; } waylandPollRemoveNode(node); INTERLOCKED_SECTION(wlWm.pollFreeLock, { wl_list_insert(&wlWm.pollFree, &node->link); }); return true; } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/registry.c000066400000000000000000000073221411177132200253160ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include "common/debug.h" static void registryGlobalHandler(void * data, struct wl_registry * registry, uint32_t name, const char * interface, uint32_t version) { if (!strcmp(interface, wl_output_interface.name)) waylandOutputBind(name, version); else if (!strcmp(interface, wl_seat_interface.name) && !wlWm.seat) wlWm.seat = wl_registry_bind(wlWm.registry, name, &wl_seat_interface, 1); else if (!strcmp(interface, wl_shm_interface.name)) wlWm.shm = wl_registry_bind(wlWm.registry, name, &wl_shm_interface, 1); else if (!strcmp(interface, wl_compositor_interface.name) && version >= 3) 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, zwp_relative_pointer_manager_v1_interface.name)) wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name, &zwp_relative_pointer_manager_v1_interface, 1); else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name)) wlWm.pointerConstraints = wl_registry_bind(wlWm.registry, name, &zwp_pointer_constraints_v1_interface, 1); else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name)) wlWm.keyboardInhibitManager = wl_registry_bind(wlWm.registry, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); else if (!strcmp(interface, wl_data_device_manager_interface.name) && version >= 3) wlWm.dataDeviceManager = wl_registry_bind(wlWm.registry, name, &wl_data_device_manager_interface, 3); else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name)) wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name, &zwp_idle_inhibit_manager_v1_interface, 1); } static void registryGlobalRemoveHandler(void * data, struct wl_registry * registry, uint32_t name) { waylandOutputTryUnbind(name); } static const struct wl_registry_listener registryListener = { .global = registryGlobalHandler, .global_remove = registryGlobalRemoveHandler, }; bool waylandRegistryInit(void) { wlWm.registry = wl_display_get_registry(wlWm.display); if (!wlWm.registry) { DEBUG_ERROR("Unable to find wl_registry"); return false; } wl_registry_add_listener(wlWm.registry, ®istryListener, NULL); wl_display_roundtrip(wlWm.display); return true; } void waylandRegistryFree(void) { wl_registry_destroy(wlWm.registry); } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/shell_libdecor.c000066400000000000000000000072331411177132200264210ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include #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) { int width, height; if (libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { wlWm.width = width; wlWm.height = height; } enum libdecor_window_state windowState; if (libdecor_configuration_get_window_state(configuration, &windowState)) wlWm.fullscreen = windowState & LIBDECOR_WINDOW_STATE_FULLSCREEN; struct libdecor_state * state = libdecor_state_new(wlWm.width, wlWm.height); libdecor_frame_commit(frame, state, wlWm.configured ? NULL : configuration); libdecor_state_free(state); if (wlWm.configured) { wlWm.needsResize = true; wlWm.resizeSerial = configuration->serial; } else wlWm.configured = true; } 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 bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless) { 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); while (!wlWm.configured) wl_display_roundtrip(wlWm.display); 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); } bool waylandGetFullscreen(void) { return wlWm.fullscreen; } void waylandMinimize(void) { libdecor_frame_set_minimized(wlWm.libdecorFrame); } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/shell_xdg.c000066400000000000000000000077151411177132200254250ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #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; } 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) { 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); } looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/state.c000066400000000000000000000016021411177132200245610ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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" struct WaylandDSState wlWm; struct WCBState wlCb; looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/wayland.c000066400000000000000000000114451411177132200251060ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #define _GNU_SOURCE #include "wayland.h" #include #include #include #include "common/debug.h" #include "common/option.h" static struct Option waylandOptions[] = { { .module = "wayland", .name = "warpSupport", .description = "Enable cursor warping", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, {0} }; static bool waylandEarlyInit(void) { // Request to receive EPIPE instead of SIGPIPE when one end of a pipe // disconnects while a write is pending. This is useful to the Wayland // clipboard backend, where an arbitrary application is on the other end of // that pipe. signal(SIGPIPE, SIG_IGN); return true; } static void waylandSetup(void) { option_register(waylandOptions); } static bool waylandProbe(void) { return getenv("WAYLAND_DISPLAY") != NULL; } static bool waylandInit(const LG_DSInitParams params) { memset(&wlWm, 0, sizeof(wlWm)); wl_list_init(&wlWm.surfaceOutputs); wlWm.warpSupport = option_get_bool("wayland", "warpSupport"); wlWm.display = wl_display_connect(NULL); wlWm.width = params.w; wlWm.height = params.h; if (!waylandPollInit()) return false; if (!waylandOutputInit()) return false; if (!waylandRegistryInit()) return false; if (!waylandIdleInit()) return false; if (!waylandInputInit()) return false; if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless)) return false; if (!waylandEGLInit(params.w, params.h)) return false; if (!waylandCursorInit()) return false; #ifdef ENABLE_OPENGL if (params.opengl && !waylandOpenGLInit()) return false; #endif wlWm.width = params.w; wlWm.height = params.h; return true; } static void waylandStartup(void) { } static void waylandShutdown(void) { } static void waylandFree(void) { waylandIdleFree(); waylandWindowFree(); waylandInputFree(); waylandOutputFree(); waylandRegistryFree(); waylandCursorFree(); wl_display_disconnect(wlWm.display); } static bool waylandGetProp(LG_DSProperty prop, void * ret) { if (prop == LG_DS_WARP_SUPPORT) { *(enum LG_DSWarpSupport*)ret = wlWm.warpSupport ? LG_DS_WARP_SURFACE : LG_DS_WARP_NONE; return true; } return false; } struct LG_DisplayServerOps LGDS_Wayland = { .setup = waylandSetup, .probe = waylandProbe, .earlyInit = waylandEarlyInit, .init = waylandInit, .startup = waylandStartup, .shutdown = waylandShutdown, .free = waylandFree, .getProp = waylandGetProp, #ifdef ENABLE_EGL .getEGLDisplay = waylandGetEGLDisplay, .getEGLNativeWindow = waylandGetEGLNativeWindow, .eglSwapBuffers = waylandEGLSwapBuffers, #endif #ifdef ENABLE_OPENGL .glCreateContext = waylandGLCreateContext, .glDeleteContext = waylandGLDeleteContext, .glMakeCurrent = waylandGLMakeCurrent, .glSetSwapInterval = waylandGLSetSwapInterval, .glSwapBuffers = waylandGLSwapBuffers, #endif .guestPointerUpdated = waylandGuestPointerUpdated, .showPointer = waylandShowPointer, .grabPointer = waylandGrabPointer, .ungrabPointer = waylandUngrabPointer, .capturePointer = waylandCapturePointer, .uncapturePointer = waylandUncapturePointer, .grabKeyboard = waylandGrabKeyboard, .ungrabKeyboard = waylandUngrabKeyboard, .warpPointer = waylandWarpPointer, .realignPointer = waylandRealignPointer, .isValidPointerPos = waylandIsValidPointerPos, .inhibitIdle = waylandInhibitIdle, .uninhibitIdle = waylandUninhibitIdle, .wait = waylandWait, .setWindowSize = waylandSetWindowSize, .setFullscreen = waylandSetFullscreen, .getFullscreen = waylandGetFullscreen, .minimize = waylandMinimize, .cbInit = waylandCBInit, .cbNotice = waylandCBNotice, .cbRelease = waylandCBRelease, .cbRequest = waylandCBRequest }; looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/wayland.h000066400000000000000000000164661411177132200251230ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) # include # include # include #endif #include "egl_dynprocs.h" #include "common/locking.h" #include "common/countedbuffer.h" #include "interface/displayserver.h" #include "wayland-xdg-shell-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" typedef void (*WaylandPollCallback)(uint32_t events, void * opaque); struct WaylandPoll { int fd; bool removed; WaylandPollCallback callback; void * opaque; struct wl_list link; }; struct WaylandOutput { uint32_t name; int32_t scale; struct wl_output * output; uint32_t version; struct wl_list link; }; struct SurfaceOutput { struct wl_output * output; struct wl_list link; }; enum EGLSwapWithDamageState { SWAP_WITH_DAMAGE_UNKNOWN, SWAP_WITH_DAMAGE_UNSUPPORTED, SWAP_WITH_DAMAGE_KHR, SWAP_WITH_DAMAGE_EXT, }; struct WaylandDSState { bool pointerGrabbed; bool keyboardGrabbed; bool pointerInSurface; bool focusedOnSurface; struct wl_display * display; struct wl_surface * surface; struct wl_registry * registry; struct wl_seat * seat; struct wl_shm * shm; struct wl_compositor * compositor; int32_t width, height, scale; bool needsResize; bool fullscreen; uint32_t resizeSerial; bool configured; bool warpSupport; double cursorX, cursorY; #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) struct wl_egl_window * eglWindow; bool eglSwapWithDamageInit; eglSwapBuffersWithDamageKHR_t eglSwapWithDamage; EGLint * eglDamageRects; int eglDamageRectCount; #endif #ifdef ENABLE_OPENGL EGLDisplay glDisplay; EGLConfig glConfig; EGLSurface glSurface; #endif #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 struct wl_surface * cursor; struct wl_buffer * cursorBuffer; struct wl_data_device_manager * dataDeviceManager; uint32_t capabilities; struct wl_keyboard * keyboard; struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager; struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor; uint32_t keyboardEnterSerial; struct wl_pointer * pointer; struct zwp_relative_pointer_manager_v1 * relativePointerManager; struct zwp_pointer_constraints_v1 * pointerConstraints; struct zwp_relative_pointer_v1 * relativePointer; struct zwp_confined_pointer_v1 * confinedPointer; struct zwp_locked_pointer_v1 * lockedPointer; bool showPointer; uint32_t pointerEnterSerial; LG_Lock confineLock; struct zwp_idle_inhibit_manager_v1 * idleInhibitManager; struct zwp_idle_inhibitor_v1 * idleInhibitor; struct wl_list outputs; // WaylandOutput::link struct wl_list surfaceOutputs; // SurfaceOutput::link struct wl_list poll; // WaylandPoll::link struct wl_list pollFree; // WaylandPoll::link LG_Lock pollLock; LG_Lock pollFreeLock; int epollFd; int displayFd; }; struct WCBTransfer { struct CountedBuffer * data; const char ** mimetypes; }; struct ClipboardRead { int fd; size_t size; size_t numRead; uint8_t * buf; enum LG_ClipboardData type; struct wl_data_offer * offer; }; struct WCBState { struct wl_data_device * dataDevice; char lgMimetype[64]; char * mimetypes[LG_CLIPBOARD_DATA_NONE]; struct wl_data_offer * offer; struct wl_data_offer * dndOffer; bool haveRequest; LG_ClipboardData type; struct ClipboardRead * currentRead; }; extern struct WaylandDSState wlWm; extern struct WCBState wlCb; // clipboard module bool waylandCBInit(void); void waylandCBRequest(LG_ClipboardData type); void waylandCBNotice(LG_ClipboardData type); void waylandCBRelease(void); void waylandCBInvalidate(void); // cursor module bool waylandCursorInit(void); void waylandCursorFree(void); void waylandShowPointer(bool show); // gl module #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) bool waylandEGLInit(int w, int h); EGLDisplay waylandGetEGLDisplay(void); void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); #endif #ifdef ENABLE_EGL EGLNativeWindowType waylandGetEGLNativeWindow(void); #endif #ifdef ENABLE_OPENGL bool waylandOpenGLInit(void); LG_DSGLContext waylandGLCreateContext(void); void waylandGLDeleteContext(LG_DSGLContext context); void waylandGLMakeCurrent(LG_DSGLContext context); void waylandGLSetSwapInterval(int interval); void waylandGLSwapBuffers(void); #endif // idle module bool waylandIdleInit(void); void waylandIdleFree(void); void waylandInhibitIdle(void); void waylandUninhibitIdle(void); // input module bool waylandInputInit(void); void waylandInputFree(void); void waylandGrabKeyboard(void); void waylandGrabPointer(void); void waylandUngrabKeyboard(void); void waylandUngrabPointer(void); void waylandCapturePointer(void); void waylandUncapturePointer(void); void waylandRealignPointer(void); void waylandWarpPointer(int x, int y, bool exiting); void waylandGuestPointerUpdated(double x, double y, double localX, double localY); // output module bool waylandOutputInit(void); void waylandOutputFree(void); void waylandOutputBind(uint32_t name, uint32_t version); void waylandOutputTryUnbind(uint32_t name); int32_t waylandOutputGetScale(struct wl_output * output); // poll module bool waylandPollInit(void); void waylandWait(unsigned int time); bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events); bool waylandPollUnregister(int fd); // registry module bool waylandRegistryInit(void); void waylandRegistryFree(void); // shell module bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless); void waylandShellAckConfigureIfNeeded(void); void waylandSetFullscreen(bool fs); bool waylandGetFullscreen(void); void waylandMinimize(void); // window module bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless); void waylandWindowFree(void); void waylandWindowUpdateScale(void); void waylandSetWindowSize(int x, int y); bool waylandIsValidPointerPos(int x, int y); looking-glass-0+b4+dfsg.1/client/displayservers/Wayland/window.c000066400000000000000000000055651411177132200247640ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include "app.h" #include "common/debug.h" // Surface-handling listeners. void waylandWindowUpdateScale(void) { int32_t maxScale = 0; struct SurfaceOutput * node; wl_list_for_each(node, &wlWm.surfaceOutputs, link) { int32_t scale = waylandOutputGetScale(node->output); if (scale > maxScale) maxScale = scale; } if (maxScale) { wlWm.scale = maxScale; wlWm.needsResize = true; } } static void wlSurfaceEnterHandler(void * data, struct wl_surface * surface, struct wl_output * output) { struct SurfaceOutput * node = malloc(sizeof(struct SurfaceOutput)); node->output = output; wl_list_insert(&wlWm.surfaceOutputs, &node->link); waylandWindowUpdateScale(); } static void wlSurfaceLeaveHandler(void * data, struct wl_surface * surface, struct wl_output * output) { struct SurfaceOutput * node; wl_list_for_each(node, &wlWm.surfaceOutputs, link) if (node->output == output) { wl_list_remove(&node->link); break; } waylandWindowUpdateScale(); } static const struct wl_surface_listener wlSurfaceListener = { .enter = wlSurfaceEnterHandler, .leave = wlSurfaceLeaveHandler, }; bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless) { wlWm.scale = 1; if (!wlWm.compositor) { DEBUG_ERROR("Compositor missing wl_compositor (version 3+), will not proceed"); return false; } wlWm.surface = wl_compositor_create_surface(wlWm.compositor); if (!wlWm.surface) { DEBUG_ERROR("Failed to create wl_surface"); return false; } wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL); if (!waylandShellInit(title, fullscreen, maximize, borderless)) return false; wl_surface_commit(wlWm.surface); return true; } void waylandWindowFree(void) { wl_surface_destroy(wlWm.surface); } void waylandSetWindowSize(int x, int y) { // FIXME: implement. } bool waylandIsValidPointerPos(int x, int y) { return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height; } looking-glass-0+b4+dfsg.1/client/displayservers/X11/000077500000000000000000000000001411177132200222505ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/displayservers/X11/CMakeLists.txt000066400000000000000000000010011411177132200250000ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(displayserver_X11 LANGUAGES C) find_package(PkgConfig) pkg_check_modules(DISPLAYSERVER_X11_PKGCONFIG REQUIRED x11 xi xfixes xscrnsaver xinerama ) add_library(displayserver_X11 STATIC x11.c atoms.c clipboard.c ) add_definitions(-D GLX_GLXEXT_PROTOTYPES) target_link_libraries(displayserver_X11 ${DISPLAYSERVER_X11_PKGCONFIG_LIBRARIES} lg_common ) target_include_directories(displayserver_X11 PRIVATE src ${DISPLAYSERVER_X11_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b4+dfsg.1/client/displayservers/X11/atoms.c000066400000000000000000000020351411177132200235370ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "atoms.h" #include "x11.h" struct X11DSAtoms x11atoms = { 0 }; void X11AtomsInit(void) { #define DEF_ATOM(x, onlyIfExists) \ x11atoms.x = XInternAtom(x11.display, #x, onlyIfExists); DEF_ATOMS() #undef DEF_ATOM } looking-glass-0+b4+dfsg.1/client/displayservers/X11/atoms.h000066400000000000000000000032641411177132200235510ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_X11DS_ATOMS_ #define _H_X11DS_ATOMS_ #define DEF_ATOMS() \ DEF_ATOM(_NET_REQUEST_FRAME_EXTENTS, True) \ DEF_ATOM(_NET_FRAME_EXTENTS, True) \ DEF_ATOM(_NET_WM_BYPASS_COMPOSITOR, False) \ DEF_ATOM(_NET_WM_STATE, True) \ DEF_ATOM(_NET_WM_STATE_FULLSCREEN, True) \ DEF_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ, True) \ DEF_ATOM(_NET_WM_STATE_MAXIMIZED_VERT, 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(WM_DELETE_WINDOW, True) \ DEF_ATOM(_MOTIF_WM_HINTS, True) \ \ DEF_ATOM(CLIPBOARD, False) \ DEF_ATOM(TARGETS, False) \ DEF_ATOM(SEL_DATA, False) \ DEF_ATOM(INCR, False) #include #define DEF_ATOM(x, onlyIfExists) Atom x; struct X11DSAtoms { DEF_ATOMS() }; #undef DEF_ATOM extern struct X11DSAtoms x11atoms; void X11AtomsInit(void); #endif looking-glass-0+b4+dfsg.1/client/displayservers/X11/clipboard.c000066400000000000000000000233201411177132200243530ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "clipboard.h" #include "x11.h" #include "atoms.h" #include #include #include #include #include "app.h" #include "common/debug.h" struct X11ClipboardState { Atom aCurSelection; Atom aTypes[LG_CLIPBOARD_DATA_NONE]; LG_ClipboardData type; bool haveRequest; bool incrStart; unsigned int lowerBound; }; static const char * atomTypes[] = { "UTF8_STRING", "image/png", "image/bmp", "image/tiff", "image/jpeg" }; static struct X11ClipboardState x11cb; // forwards static void x11CBSelectionRequest(const XSelectionRequestEvent e); static void x11CBSelectionClear(const XSelectionClearEvent e); static void x11CBSelectionIncr(const XPropertyEvent e); static void x11CBSelectionNotify(const XSelectionEvent e); static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e); bool x11CBEventThread(const XEvent xe) { switch(xe.type) { case SelectionRequest: x11CBSelectionRequest(xe.xselectionrequest); return true; case SelectionClear: x11CBSelectionClear(xe.xselectionclear); return true; case SelectionNotify: x11CBSelectionNotify(xe.xselection); return true; case PropertyNotify: if (xe.xproperty.state != PropertyNewValue) break; if (xe.xproperty.atom == x11atoms.SEL_DATA) { if (x11cb.lowerBound == 0) return true; x11CBSelectionIncr(xe.xproperty); return true; } break; default: if (xe.type == x11.eventBase + XFixesSelectionNotify) { XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&xe; x11CBXFixesSelectionNotify(*sne); return true; } break; } return false; } bool x11CBInit() { x11cb.aCurSelection = BadValue; for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) { x11cb.aTypes[i] = XInternAtom(x11.display, atomTypes[i], False); if (x11cb.aTypes[i] == BadAlloc || x11cb.aTypes[i] == BadValue) { DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]); return false; } } // use xfixes to get clipboard change notifications if (!XFixesQueryExtension(x11.display, &x11.eventBase, &x11.errorBase)) { DEBUG_ERROR("failed to initialize xfixes"); return false; } XFixesSelectSelectionInput(x11.display, x11.window, XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask); XFixesSelectSelectionInput(x11.display, x11.window, x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask); return true; } static void x11CBReplyFn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size) { XEvent *s = (XEvent *)opaque; XChangeProperty( x11.display , s->xselection.requestor, s->xselection.property , s->xselection.target , 8, PropModeReplace, data, size); XSendEvent(x11.display, s->xselection.requestor, 0, 0, s); XFlush(x11.display); free(s); } static void x11CBSelectionRequest(const XSelectionRequestEvent e) { XEvent * s = (XEvent *)malloc(sizeof(XEvent)); s->xselection.type = SelectionNotify; s->xselection.requestor = e.requestor; s->xselection.selection = e.selection; s->xselection.target = e.target; s->xselection.property = e.property; s->xselection.time = e.time; if (!x11cb.haveRequest) goto nodata; // target list requested if (e.target == x11atoms.TARGETS) { Atom targets[2]; targets[0] = x11atoms.TARGETS; targets[1] = x11cb.aTypes[x11cb.type]; XChangeProperty( e.display, e.requestor, e.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, sizeof(targets) / sizeof(Atom)); goto send; } // look to see if we can satisfy the data type for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (x11cb.aTypes[i] == e.target && x11cb.type == i) { // request the data app_clipboardRequest(x11CBReplyFn, s); return; } nodata: // report no data s->xselection.property = None; send: XSendEvent(x11.display, e.requestor, 0, 0, s); XFlush(x11.display); free(s); } static void x11CBSelectionClear(const XSelectionClearEvent e) { if (e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD) return; x11cb.aCurSelection = BadValue; app_clipboardRelease(); return; } static void x11CBSelectionIncr(const XPropertyEvent e) { Atom type; int format; unsigned long itemCount, after; unsigned char *data; if (XGetWindowProperty( e.display, e.window, e.atom, 0, ~0L, // start and length True, // delete the property x11atoms.INCR, &type, &format, &itemCount, &after, &data) != Success) { DEBUG_INFO("GetProp Failed"); app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0); goto out; } LG_ClipboardData dataType; for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType) if (x11cb.aTypes[dataType] == type) break; if (dataType == LG_CLIPBOARD_DATA_NONE) { DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(x11.display, type)); x11cb.lowerBound = 0; app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0); goto out; } if (x11cb.incrStart) { app_clipboardNotifySize(dataType, x11cb.lowerBound); x11cb.incrStart = false; } XFree(data); data = NULL; if (XGetWindowProperty( e.display, e.window, e.atom, 0, ~0L, // start and length True, // delete the property type, &type, &format, &itemCount, &after, &data) != Success) { DEBUG_ERROR("XGetWindowProperty Failed"); app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0); goto out; } app_clipboardData(dataType, data, itemCount); x11cb.lowerBound -= itemCount; out: if (data) XFree(data); } static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e) { // check if the selection is valid and it isn't ourself if ((e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD) || e.owner == x11.window || e.owner == 0) { return; } // remember which selection we are working with x11cb.aCurSelection = e.selection; XConvertSelection( x11.display, e.selection, x11atoms.TARGETS, x11atoms.TARGETS, x11.window, CurrentTime); return; } static void x11CBSelectionNotify(const XSelectionEvent e) { if (e.property == None) return; Atom type; int format; unsigned long itemCount, after; unsigned char *data; if (XGetWindowProperty( e.display, e.requestor, e.property, 0, ~0L, // start and length True , // delete the property AnyPropertyType, &type, &format, &itemCount, &after, &data) != Success) { app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0); goto out; } if (type == x11atoms.INCR) { x11cb.incrStart = true; x11cb.lowerBound = *(unsigned int *)data; goto out; } // the target list if (e.property == x11atoms.TARGETS) { // the format is 32-bit and we must have data // this is technically incorrect however as it's // an array of padded 64-bit values if (!data || format != 32) goto out; int typeCount = 0; LG_ClipboardData types[itemCount]; // see if we support any of the targets listed const uint64_t * targets = (const uint64_t *)data; for(unsigned long i = 0; i < itemCount; ++i) { for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n) if (x11cb.aTypes[n] == targets[i]) types[typeCount++] = n; } app_clipboardNotifyTypes(types, typeCount); goto out; } if (e.property == x11atoms.SEL_DATA) { LG_ClipboardData dataType; for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType) if (x11cb.aTypes[dataType] == type) break; if (dataType == LG_CLIPBOARD_DATA_NONE) { DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(x11.display, type)); goto out; } app_clipboardData(dataType, data, itemCount); goto out; } out: if (data) XFree(data); } 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); } void x11CBRelease(void) { x11cb.haveRequest = false; XSetSelectionOwner(x11.display, XA_PRIMARY , None, CurrentTime); XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, None, CurrentTime); XFlush(x11.display); } void x11CBRequest(LG_ClipboardData type) { if (x11cb.aCurSelection == BadValue) return; XConvertSelection( x11.display, x11cb.aCurSelection, x11cb.aTypes[type], x11atoms.SEL_DATA, x11.window, CurrentTime); } looking-glass-0+b4+dfsg.1/client/displayservers/X11/clipboard.h000066400000000000000000000021731411177132200243630ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_X11DS_CLIPBOARD_ #define _H_X11DS_CLIPBOARD_ #include #include #include "interface/displayserver.h" bool x11CBEventThread(const XEvent xe); bool x11CBInit(); void x11CBNotice(LG_ClipboardData type); void x11CBRelease(void); void x11CBRequest(LG_ClipboardData type); #endif looking-glass-0+b4+dfsg.1/client/displayservers/X11/x11.c000066400000000000000000000733431411177132200230370ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "interface/displayserver.h" #include "x11.h" #include "atoms.h" #include "clipboard.h" #include #include #include #include #include #include #include #include #ifdef ENABLE_EGL #include #include "egl_dynprocs.h" #endif #include "app.h" #include "common/debug.h" #include "common/time.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 { unsigned long flags; unsigned long functions; unsigned long decorations; long input_mode; unsigned long status; }; enum { MWM_HINTS_FUNCTIONS = (1L << 0), MWM_HINTS_DECORATIONS = (1L << 1), MWM_FUNC_ALL = (1L << 0), MWM_FUNC_RESIZE = (1L << 1), MWM_FUNC_MOVE = (1L << 2), MWM_FUNC_MINIMIZE = (1L << 3), MWM_FUNC_MAXIMIZE = (1L << 4), MWM_FUNC_CLOSE = (1L << 5) }; // forwards static void x11SetFullscreen(bool fs); static int x11EventThread(void * unused); static void x11GenericEvent(XGenericEventCookie *cookie); static void x11Setup(void) { } static bool x11Probe(void) { return getenv("DISPLAY") != NULL; } static bool x11EarlyInit(void) { XInitThreads(); return true; } static bool x11Init(const LG_DSInitParams params) { XIDeviceInfo *devinfo; int count; int event, error; memset(&x11, 0, sizeof(x11)); x11.xValuator = -1; x11.yValuator = -1; x11.display = XOpenDisplay(NULL); XSetWindowAttributes swa = { .event_mask = StructureNotifyMask | PropertyChangeMask | ExposureMask }; unsigned long swaMask = CWEventMask; #ifdef ENABLE_OPENGL if (params.opengl) { GLint glXAttribs[] = { GLX_RGBA, GLX_DOUBLEBUFFER , GLX_DEPTH_SIZE , 24, GLX_STENCIL_SIZE , 0, GLX_RED_SIZE , 8, GLX_GREEN_SIZE , 8, GLX_BLUE_SIZE , 8, GLX_DEPTH_SIZE , 0, GLX_SAMPLE_BUFFERS, 0, GLX_SAMPLES , 0, None }; x11.visual = glXChooseVisual(x11.display, XDefaultScreen(x11.display), glXAttribs); if (!x11.visual) { DEBUG_ERROR("glXChooseVisual failed"); goto fail_display; } swa.colormap = XCreateColormap(x11.display, XDefaultRootWindow(x11.display), x11.visual->visual, AllocNone); swaMask |= CWColormap; } #endif x11.window = XCreateWindow( x11.display, XDefaultRootWindow(x11.display), params.x, params.y, params.w, params.h, 0, x11.visual ? x11.visual->depth : CopyFromParent, InputOutput, x11.visual ? x11.visual->visual : CopyFromParent, swaMask, &swa); if (!x11.window) { DEBUG_ERROR("XCreateWindow failed"); goto fail_display; } XStoreName(x11.display, x11.window, params.title); XClassHint hint = { .res_name = strdup(params.title), .res_class = strdup("looking-glass-client") }; XSetClassHint(x11.display, x11.window, &hint); free(hint.res_name); free(hint.res_class); XSizeHints *xsh = XAllocSizeHints(); if (params.center) { xsh->flags |= PWinGravity; xsh->win_gravity = 5; //Center } if (!params.resizable) { xsh->flags |= PMinSize | PMaxSize; xsh->min_width = params.w; xsh->max_width = params.w; xsh->min_height = params.h; xsh->max_height = params.h; } XSetWMNormalHints(x11.display, x11.window, xsh); XFree(xsh); X11AtomsInit(); XSetWMProtocols(x11.display, x11.window, &x11atoms.WM_DELETE_WINDOW, 1); if (params.borderless) { if (x11atoms._MOTIF_WM_HINTS) { const struct MwmHints hints = { .flags = MWM_HINTS_DECORATIONS, .decorations = 0 }; XChangeProperty( x11.display, x11.window, x11atoms._MOTIF_WM_HINTS, x11atoms._MOTIF_WM_HINTS, 32, PropModeReplace, (unsigned char *)&hints, 5 ); } else { // fallback to making a utility window, not ideal but better then nothing XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char *)&x11atoms._NET_WM_WINDOW_TYPE_UTILITY, 1 ); } } else { XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char *)&x11atoms._NET_WM_WINDOW_TYPE_NORMAL, 1 ); } Atom wmState[3] = {0}; int wmStateCount = 0; if (params.fullscreen) { x11.fullscreen = true; wmState[wmStateCount++] = x11atoms._NET_WM_STATE_FULLSCREEN; } if (params.maximize) { wmState[wmStateCount++] = x11atoms._NET_WM_STATE_MAXIMIZED_HORZ; wmState[wmStateCount++] = x11atoms._NET_WM_STATE_MAXIMIZED_VERT; } if (wmStateCount) { XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wmState, wmStateCount ); } if (x11atoms._NET_REQUEST_FRAME_EXTENTS) { XEvent reqevent = { .xclient = { .type = ClientMessage, .window = x11.window, .format = 32, .message_type = x11atoms._NET_REQUEST_FRAME_EXTENTS } }; XSendEvent(x11.display, DefaultRootWindow(x11.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &reqevent); } int major = 2; int minor = 0; if (XIQueryVersion(x11.display, &major, &minor) != Success) { DEBUG_ERROR("Failed to query the XInput version"); return false; } DEBUG_INFO("X11 XInput %d.%d in use", major, minor); devinfo = XIQueryDevice(x11.display, XIAllDevices, &count); if (!devinfo) { DEBUG_ERROR("XIQueryDevice failed"); goto fail_window; } Atom rel_x = XInternAtom(x11.display, "Rel X", True); Atom rel_y = XInternAtom(x11.display, "Rel Y", True); bool havePointer = false; bool haveKeyboard = false; for(int i = 0; i < count; ++i) { /* look for the master pointing device */ if (!havePointer && devinfo[i].use == XIMasterPointer) { for(int j = 0; j < devinfo[i].num_classes; ++j) { XIAnyClassInfo *cdevinfo = (XIAnyClassInfo *)(devinfo[i].classes[j]); if (cdevinfo->type == XIValuatorClass) { XIValuatorClassInfo *vdevinfo = (XIValuatorClassInfo *)cdevinfo; if (vdevinfo->label == rel_x || (!vdevinfo->label && vdevinfo->number == 0 && vdevinfo->mode == XIModeRelative)) x11.xValuator = vdevinfo->number; else if (vdevinfo->label == rel_y || (!vdevinfo->label && vdevinfo->number == 1 && vdevinfo->mode == XIModeRelative)) x11.yValuator = vdevinfo->number; } } if (x11.xValuator >= 0 && x11.yValuator >= 0) { havePointer = true; x11.pointerDev = devinfo[i].deviceid; } } /* look for the master keyboard device */ if (!haveKeyboard && devinfo[i].use == XIMasterKeyboard) for(int j = 0; j < devinfo[i].num_classes; ++j) { XIAnyClassInfo *cdevinfo = (XIAnyClassInfo *)(devinfo[i].classes[j]); if (cdevinfo->type == XIKeyClass) { haveKeyboard = true; x11.keyboardDev = devinfo[i].deviceid; break; } } } if (!havePointer) { DEBUG_ERROR("Failed to find the master pointing device"); XIFreeDeviceInfo(devinfo); goto fail_window; } if (!haveKeyboard) { DEBUG_ERROR("Failed to find the master keyboard device"); XIFreeDeviceInfo(devinfo); goto fail_window; } XIFreeDeviceInfo(devinfo); XQueryExtension(x11.display, "XInputExtension", &x11.xinputOp, &event, &error); XIEventMask eventmask; unsigned char mask[XIMaskLen(XI_LASTEVENT)] = { 0 }; eventmask.deviceid = XIAllMasterDevices; eventmask.mask_len = sizeof(mask); eventmask.mask = mask; XISetMask(mask, XI_FocusIn ); XISetMask(mask, XI_FocusOut ); XISetMask(mask, XI_Enter ); XISetMask(mask, XI_Leave ); XISetMask(mask, XI_Motion ); XISetMask(mask, XI_KeyPress ); XISetMask(mask, XI_KeyRelease); if (XISelectEvents(x11.display, x11.window, &eventmask, 1) != Success) { XFree(mask); DEBUG_ERROR("Failed to select the xinput events"); goto fail_window; } unsigned long value = 1; XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&value, 1 ); /* create the blank cursor */ { static char data[] = { 0x00 }; XColor dummy; Pixmap temp = XCreateBitmapFromData(x11.display, x11.window, data, 1, 1); x11.blankCursor = XCreatePixmapCursor(x11.display, temp, temp, &dummy, &dummy, 0, 0); XFreePixmap(x11.display, temp); } /* create the square cursor */ { static char data[] = { 0x07, 0x05, 0x07 }; static char mask[] = { 0xff, 0xff, 0xff }; 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.squareCursor = XCreatePixmapCursor(x11.display, img, msk, &colors[0], &colors[1], 1, 1); XFreePixmap(x11.display, img); XFreePixmap(x11.display, msk); } /* default to the square cursor */ XDefineCursor(x11.display, x11.window, x11.squareCursor); XMapWindow(x11.display, x11.window); XFlush(x11.display); if (!lgCreateThread("X11EventThread", x11EventThread, NULL, &x11.eventThread)) { DEBUG_ERROR("Failed to create the x11 event thread"); return false; } return true; fail_window: XDestroyWindow(x11.display, x11.window); fail_display: XCloseDisplay(x11.display); return false; } static void x11Startup(void) { } static void x11Shutdown(void) { } static void x11Free(void) { lgJoinThread(x11.eventThread, NULL); if (x11.window) XDestroyWindow(x11.display, x11.window); XFreeCursor(x11.display, x11.squareCursor); XFreeCursor(x11.display, x11.blankCursor); XCloseDisplay(x11.display); } static bool x11GetProp(LG_DSProperty prop, void *ret) { switch (prop) { case LG_DS_WARP_SUPPORT: *(enum LG_DSWarpSupport*)ret = LG_DS_WARP_SCREEN; return true; case LG_DS_MAX_MULTISAMPLE: { Display * dpy = XOpenDisplay(NULL); if (!dpy) return false; XVisualInfo queryTemplate; queryTemplate.screen = 0; int visualCount; int maxSamples = -1; XVisualInfo * visuals = XGetVisualInfo(dpy, VisualScreenMask, &queryTemplate, &visualCount); for (int i = 0; i < visualCount; i++) { XVisualInfo * visual = &visuals[i]; int res, supportsGL; // Some GLX visuals do not use GL, and these must be ignored in our search. if ((res = glXGetConfig(dpy, visual, GLX_USE_GL, &supportsGL)) != 0 || !supportsGL) continue; int sampleBuffers, samples; if ((res = glXGetConfig(dpy, visual, GLX_SAMPLE_BUFFERS, &sampleBuffers)) != 0) continue; // Will be 1 if this visual supports multisampling if (sampleBuffers != 1) continue; if ((res = glXGetConfig(dpy, visual, GLX_SAMPLES, &samples)) != 0) continue; // Track the largest number of samples supported if (samples > maxSamples) maxSamples = samples; } XFree(visuals); XCloseDisplay(dpy); *(int*)ret = maxSamples; return true; } default: return true; } } static int x11EventThread(void * unused) { fd_set in_fds; const int fd = ConnectionNumber(x11.display); while(app_isRunning()) { if (!XPending(x11.display)) { FD_ZERO(&in_fds); FD_SET(fd, &in_fds); struct timeval tv = { .tv_usec = 250000, .tv_sec = 0 }; int ret = select(fd + 1, &in_fds, NULL, NULL, &tv); if (ret == 0 || !XPending(x11.display)) continue; } XEvent xe; XNextEvent(x11.display, &xe); // call the clipboard handling code if (x11CBEventThread(xe)) continue; switch(xe.type) { case ClientMessage: if (xe.xclient.data.l[0] == x11atoms.WM_DELETE_WINDOW) app_handleCloseEvent(); continue; case ConfigureNotify: { int x, y; /* the window may have been re-parented so we need to translate to * ensure we get the screen top left position of the window */ Window child; XTranslateCoordinates( x11.display, x11.window, DefaultRootWindow(x11.display), 0, 0, &x, &y, &child); x11.rect.x = x; x11.rect.y = y; x11.rect.w = xe.xconfigure.width; x11.rect.h = xe.xconfigure.height; app_updateWindowPos(x, y); if (x11.fullscreen) { struct Border border = {0}; app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, border); } else app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, x11.border); break; } case GenericEvent: { XGenericEventCookie *cookie = (XGenericEventCookie*)&xe.xcookie; XGetEventData(x11.display, cookie); x11GenericEvent(cookie); XFreeEventData(x11.display, cookie); break; } case PropertyNotify: // ignore property events that are not for us if (xe.xproperty.display != x11.display || xe.xproperty.window != x11.window || xe.xproperty.state != PropertyNewValue) continue; if (xe.xproperty.atom == x11atoms._NET_WM_STATE) { Atom type; int fmt; unsigned long num, bytes; unsigned char *data; if (XGetWindowProperty(x11.display, x11.window, x11atoms._NET_WM_STATE, 0, ~0L, False, AnyPropertyType, &type, &fmt, &num, &bytes, &data) != Success) break; bool fullscreen = false; for(int i = 0; i < num; ++i) { Atom prop = ((Atom *)data)[i]; if (prop == x11atoms._NET_WM_STATE_FULLSCREEN) fullscreen = true; } x11.fullscreen = fullscreen; XFree(data); break; } if (xe.xproperty.atom == x11atoms._NET_FRAME_EXTENTS) { Atom type; int fmt; unsigned long num, bytes; unsigned char *data; if (XGetWindowProperty(x11.display, x11.window, x11atoms._NET_FRAME_EXTENTS, 0, 4, False, AnyPropertyType, &type, &fmt, &num, &bytes, &data) != Success) break; if (num >= 4) { long *cardinal = (long *)data; x11.border.left = cardinal[0]; x11.border.right = cardinal[1]; x11.border.top = cardinal[2]; x11.border.bottom = cardinal[3]; app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, x11.border); } XFree(data); break; } break; } } return 0; } static void x11GenericEvent(XGenericEventCookie *cookie) { static int button_state = 0; if (cookie->extension != x11.xinputOp) return; switch(cookie->evtype) { case XI_FocusIn: { if (x11.focused) return; XIFocusOutEvent *xie = cookie->data; if (xie->mode != XINotifyNormal && xie->mode != XINotifyWhileGrabbed && xie->mode != XINotifyUngrab) return; x11.focused = true; app_updateCursorPos(xie->event_x, xie->event_y); app_handleFocusEvent(true); return; } case XI_FocusOut: { if (!x11.focused) return; XIFocusOutEvent *xie = cookie->data; if (xie->mode != XINotifyNormal && xie->mode != XINotifyWhileGrabbed && xie->mode != XINotifyGrab) return; app_updateCursorPos(xie->event_x, xie->event_y); app_handleFocusEvent(false); x11.focused = false; return; } case XI_Enter: { XIEnterEvent *xie = cookie->data; if (x11.entered || xie->event != x11.window) return; app_updateCursorPos(xie->event_x, xie->event_y); app_handleEnterEvent(true); x11.entered = true; return; } case XI_Leave: { XILeaveEvent *xie = cookie->data; if (!x11.entered || xie->event != x11.window || button_state != 0 || app_isCaptureMode()) return; app_updateCursorPos(xie->event_x, xie->event_y); app_handleEnterEvent(false); x11.entered = false; return; } case XI_KeyPress: { if (!x11.focused || x11.keyboardGrabbed) return; XIDeviceEvent *device = cookie->data; app_handleKeyPress(device->detail - 8); return; } case XI_KeyRelease: { if (!x11.focused || x11.keyboardGrabbed) return; XIDeviceEvent *device = cookie->data; app_handleKeyRelease(device->detail - 8); return; } case XI_RawKeyPress: { if (!x11.focused) return; XIRawEvent *raw = cookie->data; app_handleKeyPress(raw->detail - 8); return; } case XI_RawKeyRelease: { if (!x11.focused) return; XIRawEvent *raw = cookie->data; app_handleKeyRelease(raw->detail - 8); return; } case XI_RawButtonPress: { if (!x11.focused || !x11.entered) return; XIRawEvent *raw = cookie->data; /* filter out duplicate events */ static Time prev_time = 0; static unsigned int prev_detail = 0; if (raw->time == prev_time && raw->detail == prev_detail) return; prev_time = raw->time; prev_detail = raw->detail; button_state |= (1 << raw->detail); app_handleButtonPress( raw->detail > 5 ? raw->detail - 2 : raw->detail); return; } case XI_RawButtonRelease: { if (!x11.focused || !x11.entered) return; XIRawEvent *raw = cookie->data; /* filter out duplicate events */ static Time prev_time = 0; static unsigned int prev_detail = 0; if (raw->time == prev_time && raw->detail == prev_detail) return; prev_time = raw->time; prev_detail = raw->detail; button_state &= ~(1 << raw->detail); app_handleButtonRelease( raw->detail > 5 ? raw->detail - 2 : raw->detail); return; } case XI_Motion: { XIDeviceEvent *device = cookie->data; app_updateCursorPos(device->event_x, device->event_y); if (!x11.pointerGrabbed) app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); return; } case XI_RawMotion: { if (!x11.focused || !x11.entered) return; XIRawEvent *raw = cookie->data; double raw_axis[2] = { 0 }; double axis[2] = { 0 }; /* select the active validators for the X & Y axis */ double *valuator = raw->valuators.values; double *r_value = raw->raw_values; bool has_axes = false; for(int i = 0; i < raw->valuators.mask_len * 8; ++i) { if (XIMaskIsSet(raw->valuators.mask, i)) { if (i == x11.xValuator) { raw_axis[0] = *r_value; axis [0] = *valuator; has_axes = true; } else if (i == x11.yValuator) { raw_axis[1] = *r_value; axis [1] = *valuator; has_axes = true; } ++valuator; ++r_value; } } /* filter out events with no axis data */ if (!has_axes) return; /* filter out duplicate events */ static Time prev_time = 0; static double prev_axis[2] = {0}; if (raw->time == prev_time && axis[0] == prev_axis[0] && axis[1] == prev_axis[1]) return; prev_time = raw->time; prev_axis[0] = axis[0]; prev_axis[1] = axis[1]; app_handleMouseRelative(axis[0], axis[1], raw_axis[0], raw_axis[1]); return; } } } #ifdef ENABLE_EGL static EGLDisplay x11GetEGLDisplay(void) { EGLDisplay ret; const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS); if (util_hasGLExt(early_exts, "EGL_KHR_platform_base") && g_egl_dynProcs.eglGetPlatformDisplay) { DEBUG_INFO("Using eglGetPlatformDisplay"); ret = g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, x11.display, NULL); } else if (util_hasGLExt(early_exts, "EGL_EXT_platform_base") && g_egl_dynProcs.eglGetPlatformDisplayEXT) { DEBUG_INFO("Using eglGetPlatformDisplayEXT"); ret = g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_KHR, x11.display, NULL); } else { DEBUG_INFO("Using eglGetDisplay"); ret = eglGetDisplay(x11.display); } return ret; } static EGLNativeWindowType x11GetEGLNativeWindow(void) { return (EGLNativeWindowType)x11.window; } static void x11EGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { eglSwapBuffers(display, surface); } #endif #ifdef ENABLE_OPENGL static LG_DSGLContext x11GLCreateContext(void) { return (LG_DSGLContext) glXCreateContext(x11.display, x11.visual, NULL, GL_TRUE); } static void x11GLDeleteContext(LG_DSGLContext context) { glXDestroyContext(x11.display, (GLXContext)context); } static void x11GLMakeCurrent(LG_DSGLContext context) { glXMakeCurrent(x11.display, x11.window, (GLXContext)context); } static void x11GLSetSwapInterval(int interval) { glXSwapIntervalEXT(x11.display, x11.window, interval); } static void x11GLSwapBuffers(void) { glXSwapBuffers(x11.display, x11.window); } #endif static void x11GuestPointerUpdated(double x, double y, double localX, double localY) { if (app_isCaptureMode() || !x11.entered) return; // avoid running too often static uint64_t last_warp = 0; uint64_t now = microtime(); if (now - last_warp < 10000) return; last_warp = now; XIWarpPointer( x11.display, x11.pointerDev, None, x11.window, 0, 0, 0, 0, localX, localY); XSync(x11.display, False); } static void x11ShowPointer(bool show) { if (show) XDefineCursor(x11.display, x11.window, x11.squareCursor); else XDefineCursor(x11.display, x11.window, x11.blankCursor); } static void x11PrintGrabError(const char * type, int dev, Status ret) { const char * errStr; switch(ret) { case AlreadyGrabbed : errStr = "AlreadyGrabbed" ; break; case GrabNotViewable: errStr = "GrabNotViewable"; break; case GrabFrozen : errStr = "GrabFrozen" ; break; case GrabInvalidTime: errStr = "GrabInvalidTime"; break; default: errStr = "Unknown"; break; } DEBUG_ERROR("XIGrabDevice failed for %s dev %d with 0x%x (%s)", type, dev, ret, errStr); } static void x11GrabPointer(void) { if (x11.pointerGrabbed) return; unsigned char mask_bits[XIMaskLen(XI_LASTEVENT)] = { 0 }; XIEventMask mask = { x11.pointerDev, sizeof(mask_bits), mask_bits }; XISetMask(mask.mask, XI_RawButtonPress ); XISetMask(mask.mask, XI_RawButtonRelease); XISetMask(mask.mask, XI_RawMotion ); XISetMask(mask.mask, XI_Motion ); XISetMask(mask.mask, XI_Enter ); XISetMask(mask.mask, XI_Leave ); Status ret = XIGrabDevice( x11.display, x11.pointerDev, x11.window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XINoOwnerEvents, &mask); if (ret != Success) { x11PrintGrabError("pointer", x11.pointerDev, ret); return; } x11.pointerGrabbed = true; } static void x11UngrabPointer(void) { if (!x11.pointerGrabbed) return; XIUngrabDevice(x11.display, x11.pointerDev, CurrentTime); XSync(x11.display, False); x11.pointerGrabbed = false; } static void x11CapturePointer(void) { x11GrabPointer(); } static void x11UncapturePointer(void) { /* we need to ungrab the pointer on the following conditions when exiting capture mode: * - if the format is invalid as we do not know where the guest cursor is, * which breaks edge detection as the cursor can not be warped out of the * window when we release it. * - if the user has opted to use captureInputOnly mode. */ if (!app_isFormatValid() || app_isCaptureOnlyMode()) x11UngrabPointer(); } static void x11GrabKeyboard(void) { if (x11.keyboardGrabbed) return; unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 }; XIEventMask mask = { x11.keyboardDev, sizeof(mask_bits), mask_bits }; XISetMask(mask.mask, XI_RawKeyPress ); XISetMask(mask.mask, XI_RawKeyRelease); Status ret = XIGrabDevice( x11.display, x11.keyboardDev, x11.window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XINoOwnerEvents, &mask); if (ret != Success) { x11PrintGrabError("keyboard", x11.keyboardDev, ret); return; } x11.keyboardGrabbed = true; } static void x11UngrabKeyboard(void) { if (!x11.keyboardGrabbed) return; XIUngrabDevice(x11.display, x11.keyboardDev, CurrentTime); XSync(x11.display, False); x11.keyboardGrabbed = false; } static void x11WarpPointer(int x, int y, bool exiting) { XIWarpPointer( x11.display, x11.pointerDev, None, x11.window, 0, 0, 0, 0, x, y); XSync(x11.display, False); } static void x11RealignPointer(void) { app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); } static bool x11IsValidPointerPos(int x, int y) { int screens; XineramaScreenInfo *xinerama = XineramaQueryScreens(x11.display, &screens); if(!xinerama) return true; bool ret = false; for(int i = 0; i < screens; ++i) if (x >= xinerama[i].x_org && x < xinerama[i].x_org + xinerama[i].width && y >= xinerama[i].y_org && y < xinerama[i].y_org + xinerama[i].height) { ret = true; break; } XFree(xinerama); return ret; } static void x11InhibitIdle(void) { XScreenSaverSuspend(x11.display, true); } static void x11UninhibitIdle(void) { XScreenSaverSuspend(x11.display, false); } static void x11Wait(unsigned int time) { usleep(time * 1000U); } static void x11SetWindowSize(int w, int h) { XResizeWindow(x11.display, x11.window, w, h); } static void x11SetFullscreen(bool fs) { if (x11.fullscreen == fs) return; 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) { return x11.fullscreen; } static void x11Minimize(void) { XIconifyWindow(x11.display, x11.window, XDefaultScreen(x11.display)); } struct LG_DisplayServerOps LGDS_X11 = { .setup = x11Setup, .probe = x11Probe, .earlyInit = x11EarlyInit, .init = x11Init, .startup = x11Startup, .shutdown = x11Shutdown, .free = x11Free, .getProp = x11GetProp, #ifdef ENABLE_EGL .getEGLDisplay = x11GetEGLDisplay, .getEGLNativeWindow = x11GetEGLNativeWindow, .eglSwapBuffers = x11EGLSwapBuffers, #endif #ifdef ENABLE_OPENGL .glCreateContext = x11GLCreateContext, .glDeleteContext = x11GLDeleteContext, .glMakeCurrent = x11GLMakeCurrent, .glSetSwapInterval = x11GLSetSwapInterval, .glSwapBuffers = x11GLSwapBuffers, #endif .guestPointerUpdated = x11GuestPointerUpdated, .showPointer = x11ShowPointer, .grabPointer = x11GrabPointer, .ungrabPointer = x11UngrabPointer, .capturePointer = x11CapturePointer, .uncapturePointer = x11UncapturePointer, .grabKeyboard = x11GrabKeyboard, .ungrabKeyboard = x11UngrabKeyboard, .warpPointer = x11WarpPointer, .realignPointer = x11RealignPointer, .isValidPointerPos = x11IsValidPointerPos, .inhibitIdle = x11InhibitIdle, .uninhibitIdle = x11UninhibitIdle, .wait = x11Wait, .setWindowSize = x11SetWindowSize, .setFullscreen = x11SetFullscreen, .getFullscreen = x11GetFullscreen, .minimize = x11Minimize, .cbInit = x11CBInit, .cbNotice = x11CBNotice, .cbRelease = x11CBRelease, .cbRequest = x11CBRequest }; looking-glass-0+b4+dfsg.1/client/displayservers/X11/x11.h000066400000000000000000000027211411177132200230340ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_X11DS_X11_ #define _H_X11DS_X11_ #include #include #include #include "common/thread.h" #include "common/types.h" struct X11DSState { Display * display; Window window; XVisualInfo * visual; int xinputOp; LGThread * eventThread; int pointerDev; int keyboardDev; int xValuator; int yValuator; bool pointerGrabbed; bool keyboardGrabbed; bool entered; bool focused; bool fullscreen; struct Rect rect; struct Border border; Cursor blankCursor; Cursor squareCursor; // XFixes vars int eventBase; int errorBase; }; extern struct X11DSState x11; #endif looking-glass-0+b4+dfsg.1/client/fonts/000077500000000000000000000000001411177132200177515ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/fonts/CMakeLists.txt000066400000000000000000000022121411177132200225060ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(fonts LANGUAGES C) set(FONT_H "${CMAKE_BINARY_DIR}/include/dynamic/fonts.h") set(FONT_C "${CMAKE_BINARY_DIR}/src/fonts.c") file(WRITE ${FONT_H} "#include \"interface/font.h\"\n\n") file(APPEND ${FONT_H} "extern LG_Font * LG_Fonts[];\n\n") file(WRITE ${FONT_C} "#include \"interface/font.h\"\n\n") file(APPEND ${FONT_C} "#include \n\n") set(FONTS "_") set(FONTS_LINK "_") function(add_font name) set(FONTS "${FONTS};${name}" PARENT_SCOPE) set(FONTS_LINK "${FONTS_LINK};font_${name}" PARENT_SCOPE) add_subdirectory(${name}) endfunction() # Add/remove fonts here! add_font(freetype) list(REMOVE_AT FONTS 0) list(REMOVE_AT FONTS_LINK 0) list(LENGTH FONTS FONT_COUNT) file(APPEND ${FONT_H} "#define LG_FONT_COUNT ${FONT_COUNT}\n") foreach(font ${FONTS}) file(APPEND ${FONT_C} "extern LG_Font LGF_${font};\n") endforeach() file(APPEND ${FONT_C} "\nconst LG_Font * LG_Fonts[] =\n{\n") foreach(font ${FONTS}) file(APPEND ${FONT_C} " &LGF_${font},\n") endforeach() file(APPEND ${FONT_C} " NULL\n};\n\n") add_library(fonts STATIC ${FONT_C}) target_link_libraries(fonts ${FONTS_LINK}) looking-glass-0+b4+dfsg.1/client/fonts/freetype/000077500000000000000000000000001411177132200215745ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/fonts/freetype/CMakeLists.txt000066400000000000000000000007751411177132200243450ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(font_freetype LANGUAGES C) find_package(PkgConfig) pkg_check_modules(FONT_FREETYPE_PKGCONFIG REQUIRED freetype2 fontconfig ) add_library(font_freetype STATIC src/freetype.c ) target_link_libraries(font_freetype ${FONT_FREETYPE_PKGCONFIG_LIBRARIES} lg_common ) target_include_directories(font_freetype PUBLIC $ $ PRIVATE src ${FONT_FREETYPE_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b4+dfsg.1/client/fonts/freetype/src/000077500000000000000000000000001411177132200223635ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/fonts/freetype/src/freetype.c000066400000000000000000000173361411177132200243640ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include "interface/font.h" #include "common/debug.h" #include #include FT_FREETYPE_H #include static int g_initCount = 0; static FcConfig * g_fontConfig = NULL; static FT_Library g_ft; struct Inst { FT_Face face; unsigned int height; }; static bool lgf_freetype_create(LG_FontObj * opaque, const char * font_name, unsigned int size) { bool ret = false; if (g_initCount == 0) { if (FT_Init_FreeType(&g_ft)) { DEBUG_ERROR("FT_Init_FreeType Failed"); goto fail; } g_fontConfig = FcInitLoadConfigAndFonts(); if (!g_fontConfig) { DEBUG_ERROR("FcInitLoadConfigAndFonts Failed"); goto fail_init; } } *opaque = malloc(sizeof(struct Inst)); if (!*opaque) { DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst)); goto fail_config; } memset(*opaque, 0, sizeof(struct Inst)); struct Inst * this = (struct Inst *)*opaque; if (!font_name) font_name = "FreeMono"; FcPattern * pat = FcNameParse((const FcChar8*)font_name); if (!pat) { DEBUG_ERROR("FCNameParse failed"); goto fail_opaque; } FcConfigSubstitute(g_fontConfig, pat, FcMatchPattern); FcDefaultSubstitute(pat); FcResult result; FcChar8 * file = NULL; FcPattern * match = FcFontMatch(g_fontConfig, pat, &result); if (!match) { DEBUG_ERROR("FcFontMatch Failed"); goto fail_parse; } if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch) { if (FT_New_Face(g_ft, (char *) file, 0, &this->face)) { DEBUG_ERROR("FT_New_Face Failed"); goto fail_match; } if (FT_Select_Charmap(this->face, ft_encoding_unicode)) { DEBUG_ERROR("FT_Select_Charmap failed"); FT_Done_Face(this->face); goto fail_match; } FT_Set_Pixel_Sizes(this->face, 0, size); this->height = size; } else { DEBUG_ERROR("Failed to locate the requested font: %s", font_name); goto fail_match; } ++g_initCount; ret = true; fail_match: FcPatternDestroy(match); fail_parse: FcPatternDestroy(pat); if (ret) return true; fail_opaque: free(this); *opaque = NULL; fail_config: if (g_initCount == 0) { FcConfigDestroy(g_fontConfig); g_fontConfig = NULL; } fail_init: if (g_initCount == 0) FT_Done_FreeType(g_ft); fail: return false; } static void lgf_freetype_destroy(LG_FontObj opaque) { struct Inst * this = (struct Inst *)opaque; if (this->face) FT_Done_Face(this->face); free(this); if (--g_initCount == 0) { FcConfigDestroy(g_fontConfig); g_fontConfig = NULL; FT_Done_FreeType(g_ft); } } // A very simple UTF-8 decoder that assumes the input is valid. static unsigned int utf8_decode(const char * str) { const unsigned char * ptr = (const unsigned char *) str; // Handle the 4 byte case: 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx. if ((*ptr & 0xf8) == 0xf0) return (ptr[0] & 0x07) << 18 | (ptr[1] & 0x3f) << 12 | (ptr[2] & 0x3f) << 6 | (ptr[3] & 0x3f); // Handle the 3 byte case: 1110 xxxx 10xx xxxx 10xx xxxx. else if ((*ptr & 0xf0) == 0xe0) return (ptr[0] & 0x0f) << 12 | (ptr[1] & 0x3f) << 6 | (ptr[2] & 0x3f); // Handle the 2 byte case: 110x xxxx 10xx xxxx. else if ((*ptr & 0xe0) == 0xc0) return (ptr[0] & 0x1f) << 6 | (ptr[1] & 0x3f); // Everything else is the 1 byte case. else return *ptr; } // Return the length of the current UTF-8 character. Assumes the input is valid. static unsigned int utf8_advance(const char * str) { const unsigned char * ptr = (const unsigned char *) str; // 4 byte case starts with 1111 0xxx. if ((*ptr & 0xf8) == 0xf0) return 4; // 3 byte case starts with 1110 xxxx. else if ((*ptr & 0xf0) == 0xe0) return 3; // 2 byte case starts with 110x xxxx. else if ((*ptr & 0xe0) == 0xc0) return 2; // Everything else is the 1 byte case. else return 1; } static LG_FontBitmap * lgf_freetype_render(LG_FontObj opaque, unsigned int fg_color, const char * text) { struct Inst * this = (struct Inst *)opaque; int width = 0; int row = 0; int rowWidth = 0; int topAscend = 0; int bottomDescend = 0; for (const char * ptr = text; *ptr; ptr += utf8_advance(ptr)) { unsigned int ch = utf8_decode(ptr); if (ch == '\n') { if (!ptr[1]) break; if (rowWidth > width) width = rowWidth; rowWidth = bottomDescend = 0; ++row; } else if (FT_Load_Char(this->face, ch, FT_LOAD_RENDER)) { DEBUG_ERROR("Failed to load character: %c", *ptr); return NULL; } else { FT_GlyphSlot glyph = this->face->glyph; rowWidth += glyph->advance.x / 64; int descend = glyph->bitmap.rows - glyph->bitmap_top; if (descend > bottomDescend) bottomDescend = descend; if (row == 0 && glyph->bitmap_top > topAscend) topAscend = glyph->bitmap_top; } } if (rowWidth > width) width = rowWidth; int height = topAscend + this->height * row + bottomDescend; uint32_t * pixels = calloc(width * height, sizeof(uint32_t)); if (!pixels) { DEBUG_ERROR("Failed to allocate memory for font pixels"); return NULL; } int baseline = topAscend; int x = 0; unsigned int r = (fg_color & 0xff000000) >> 24; unsigned int g = (fg_color & 0x00ff0000) >> 16; unsigned int b = (fg_color & 0x0000ff00) >> 8; uint32_t color = (r << 0) | (g << 8) | (b << 16); for (const char * ptr = text; *ptr; ptr += utf8_advance(ptr)) { unsigned int ch = utf8_decode(ptr); if (ch == '\n') { baseline += this->height; x = 0; } else if (FT_Load_Char(this->face, ch, FT_LOAD_RENDER)) { DEBUG_ERROR("Failed to load character: U+%x", ch); return NULL; } else { FT_GlyphSlot glyph = this->face->glyph; int start = baseline - glyph->bitmap_top; int pitch = width; if (glyph->bitmap.pitch < 0) { start += glyph->bitmap.rows - 1; pitch = -pitch; } for (int i = 0; i < glyph->bitmap.rows; ++i) for (int j = 0; j < glyph->bitmap.width; ++j) pixels[(start + i) * pitch + x + j + glyph->bitmap_left] = color | (uint32_t)glyph->bitmap.buffer[i * glyph->bitmap.pitch + j] << 24; x += glyph->advance.x / 64; } } LG_FontBitmap * out = malloc(sizeof(LG_FontBitmap)); if (!out) { free(pixels); DEBUG_ERROR("Failed to allocate memory for font bitmap"); return NULL; } out->width = width; out->height = height; out->bpp = 4; out->pixels = (uint8_t *) pixels; return out; } static void lgf_freetype_release(LG_FontObj opaque, LG_FontBitmap * font) { LG_FontBitmap * bitmap = (LG_FontBitmap *)font; free(bitmap->pixels); free(bitmap); } struct LG_Font LGF_freetype = { .name = "freetype", .create = lgf_freetype_create, .destroy = lgf_freetype_destroy, .render = lgf_freetype_render, .release = lgf_freetype_release }; looking-glass-0+b4+dfsg.1/client/include/000077500000000000000000000000001411177132200202435ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/include/app.h000066400000000000000000000077361411177132200212110ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_APP_ #define _H_LG_APP_ #include #include #include "common/types.h" #include "interface/displayserver.h" typedef enum LG_MsgAlert { LG_ALERT_INFO , LG_ALERT_SUCCESS, LG_ALERT_WARNING, LG_ALERT_ERROR } LG_MsgAlert; bool app_isRunning(void); bool app_inputEnabled(void); bool app_isCaptureMode(void); bool app_isCaptureOnlyMode(void); bool app_isFormatValid(void); void app_updateCursorPos(double x, double y); void app_updateWindowPos(int x, int y); void app_handleResizeEvent(int w, int h, double scale, const struct Border border); void app_handleMouseRelative(double normx, double normy, double rawx, double rawy); void app_handleMouseBasic(void); void app_resyncMouseBasic(void); void app_handleButtonPress(int button); void app_handleButtonRelease(int button); void app_handleKeyPress(int scancode); void app_handleKeyRelease(int scancode); void app_handleEnterEvent(bool entered); void app_handleFocusEvent(bool focused); void app_handleCloseEvent(void); void app_handleRenderEvent(const uint64_t timeUs); void app_setFullscreen(bool fs); bool app_getFullscreen(void); bool app_getProp(LG_DSProperty prop, void * ret); #ifdef ENABLE_EGL EGLDisplay app_getEGLDisplay(void); EGLNativeWindowType app_getEGLNativeWindow(void); void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); #endif #ifdef ENABLE_OPENGL LG_DSGLContext app_glCreateContext(void); void app_glDeleteContext(LG_DSGLContext context); void app_glMakeCurrent(LG_DSGLContext context); void app_glSetSwapInterval(int interval); void app_glSwapBuffers(void); #endif void app_clipboardRelease(void); void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count); void app_clipboardNotifySize(const LG_ClipboardData type, size_t size); void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size); void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque); /** * Show an alert on screen * @param type The alert type * param fmt The alert message format @ param ... formatted message values */ void app_alert(LG_MsgAlert type, const char * fmt, ...); typedef struct KeybindHandle * KeybindHandle; typedef void (*KeybindFn)(int sc, void * opaque); /** * Register a handler for the + combination * @param sc The scancode to register * @param callback The function to be called when the combination is pressed * @param opaque A pointer to be passed to the callback, may be NULL * @retval A handle for the binding or NULL on failure. * The caller is required to release the handle via `app_releaseKeybind` when it is no longer required */ KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description); /** * Release an existing key binding * @param handle A pointer to the keybind handle to release, may be NULL */ void app_releaseKeybind(KeybindHandle * handle); /** * Release all keybindings */ void app_releaseAllKeybinds(void); /** * Changes whether the help message is displayed or not. */ void app_showHelp(bool show); /** * Changes whether the FPS is displayed or not. */ void app_showFPS(bool showFPS); #endif looking-glass-0+b4+dfsg.1/client/include/egl_dynprocs.h000066400000000000000000000033221411177132200231040ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_EGL_DYNPROCS_ #define _H_LG_EGL_DYNPROCS_ #ifdef ENABLE_EGL #include #include typedef EGLDisplay (*eglGetPlatformDisplayEXT_t)(EGLenum platform, void *native_display, const EGLint *attrib_list); typedef void (*eglSwapBuffersWithDamageKHR_t)(EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects); typedef void (*glEGLImageTargetTexture2DOES_t)(GLenum target, GLeglImageOES image); struct EGLDynProcs { eglGetPlatformDisplayEXT_t eglGetPlatformDisplay; eglGetPlatformDisplayEXT_t eglGetPlatformDisplayEXT; eglSwapBuffersWithDamageKHR_t eglSwapBuffersWithDamageKHR; eglSwapBuffersWithDamageKHR_t eglSwapBuffersWithDamageEXT; glEGLImageTargetTexture2DOES_t glEGLImageTargetTexture2DOES; }; extern struct EGLDynProcs g_egl_dynProcs; void egl_dynProcsInit(void); #else #define egl_dynProcsInit(...) #endif #endif // _H_LG_EGL_DYNPROCS_ looking-glass-0+b4+dfsg.1/client/include/interface/000077500000000000000000000000001411177132200222035ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/include/interface/displayserver.h000066400000000000000000000146361411177132200252620ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_I_DISPLAYSERVER_ #define _H_I_DISPLAYSERVER_ #include #include #include "common/types.h" typedef enum LG_ClipboardData { LG_CLIPBOARD_DATA_TEXT = 0, LG_CLIPBOARD_DATA_PNG, LG_CLIPBOARD_DATA_BMP, LG_CLIPBOARD_DATA_TIFF, LG_CLIPBOARD_DATA_JPEG, LG_CLIPBOARD_DATA_NONE // enum max, not a data type } LG_ClipboardData; typedef enum LG_DSProperty { /** * returns the maximum number of samples supported * if not implemented LG assumes no multisample support * return data type: int */ LG_DS_MAX_MULTISAMPLE, /** * returns if the platform is warp capable * if not implemented LG assumes that the platform is warp capable * return data type: bool */ LG_DS_WARP_SUPPORT, } LG_DSProperty; enum LG_DSWarpSupport { LG_DS_WARP_NONE, LG_DS_WARP_SURFACE, LG_DS_WARP_SCREEN, }; typedef struct LG_DSInitParams { const char * title; int x, y, w, h; bool center; bool fullscreen; bool resizable; bool borderless; bool maximize; // if true the renderer requires an OpenGL context bool opengl; } LG_DSInitParams; typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size); typedef struct LG_DSGLContext * LG_DSGLContext; struct LG_DisplayServerOps { /* called before options are parsed, useful for registering options */ void (*setup)(void); /* return true if the selected ds is valid for the current platform */ bool (*probe)(void); /* called before anything has been initialized */ bool (*earlyInit)(void); /* called when it's time to create and show the application window */ bool (*init)(const LG_DSInitParams params); /* called at startup after window creation, renderer and SPICE is ready */ void (*startup)(); /* called just before final window destruction, before final free */ void (*shutdown)(); /* final free */ void (*free)(); /* * return a system specific property, returns false if unsupported or failure * if the platform does not support/implement the requested property the value * of `ret` must not be altered. */ bool (*getProp)(LG_DSProperty prop, void * ret); #ifdef ENABLE_EGL /* EGL support */ EGLDisplay (*getEGLDisplay)(void); EGLNativeWindowType (*getEGLNativeWindow)(void); void (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); #endif #ifdef ENABLE_OPENGL /* opengl platform specific methods */ LG_DSGLContext (*glCreateContext)(void); void (*glDeleteContext)(LG_DSGLContext context); void (*glMakeCurrent)(LG_DSGLContext context); void (*glSetSwapInterval)(int interval); void (*glSwapBuffers)(void); #endif /* dm specific cursor implementations */ void (*guestPointerUpdated)(double x, double y, double localX, double localY); void (*showPointer)(bool show); void (*grabKeyboard)(); void (*ungrabKeyboard)(); /* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */ void (*grabPointer)(); void (*ungrabPointer)(); /* (un)capturePointer is used do toggle special cursor tracking in capture mode */ void (*capturePointer)(); void (*uncapturePointer)(); /* exiting = true if the warp is to leave the window */ void (*warpPointer)(int x, int y, bool exiting); /* called when the client needs to realign the pointer. This should simply * call the appropriate app_handleMouse* method for the platform with zero * deltas */ void (*realignPointer)(); /* returns true if the position specified is actually valid */ bool (*isValidPointerPos)(int x, int y); /* called to disable/enable the screensaver */ void (*inhibitIdle)(); void (*uninhibitIdle)(); /* wait for the specified time without blocking UI processing/event loops */ void (*wait)(unsigned int time); /* get/set the window dimensions & state */ void (*setWindowSize)(int x, int y); bool (*getFullscreen)(void); void (*setFullscreen)(bool fs); void (*minimize)(void); /* clipboard support, optional, if not supported set to NULL */ bool (*cbInit)(void); void (*cbNotice)(LG_ClipboardData type); void (*cbRelease)(void); void (*cbRequest)(LG_ClipboardData type); }; #ifdef ENABLE_EGL #define ASSERT_EGL_FN(x) assert(x); #else #define ASSERT_EGL_FN(x) #endif #ifdef ENABLE_OPENGL #define ASSERT_OPENGL_FN(x) assert(x) #else #define ASSERT_OPENGL_FN(x) #endif #define ASSERT_LG_DS_VALID(x) \ assert((x)->setup ); \ assert((x)->probe ); \ assert((x)->earlyInit ); \ assert((x)->init ); \ assert((x)->startup ); \ assert((x)->shutdown ); \ assert((x)->free ); \ assert((x)->getProp ); \ ASSERT_EGL_FN((x)->getEGLDisplay ); \ ASSERT_EGL_FN((x)->getEGLNativeWindow ); \ ASSERT_EGL_FN((x)->eglSwapBuffers ); \ ASSERT_OPENGL_FN((x)->glCreateContext ); \ ASSERT_OPENGL_FN((x)->glDeleteContext ); \ ASSERT_OPENGL_FN((x)->glMakeCurrent ); \ ASSERT_OPENGL_FN((x)->glSetSwapInterval); \ ASSERT_OPENGL_FN((x)->glSwapBuffers ); \ assert((x)->guestPointerUpdated); \ assert((x)->showPointer ); \ assert((x)->grabPointer ); \ assert((x)->ungrabPointer ); \ assert((x)->capturePointer ); \ assert((x)->uncapturePointer ); \ assert((x)->warpPointer ); \ assert((x)->realignPointer ); \ assert((x)->isValidPointerPos ); \ assert((x)->inhibitIdle ); \ assert((x)->uninhibitIdle ); \ assert((x)->wait ); \ assert((x)->setWindowSize ); \ assert((x)->setFullscreen ); \ assert((x)->getFullscreen ); \ assert((x)->minimize ); #endif looking-glass-0+b4+dfsg.1/client/include/interface/font.h000066400000000000000000000031721411177132200233250ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include typedef void * LG_FontObj; typedef struct LG_FontBitmap { void * reserved; unsigned int width, height; unsigned int bpp; // bytes per pixel uint8_t * pixels; } LG_FontBitmap; typedef bool (* LG_FontCreate )(LG_FontObj * opaque, const char * font_name, unsigned int size); typedef void (* LG_FontDestroy )(LG_FontObj opaque); typedef LG_FontBitmap * (* LG_FontRender )(LG_FontObj opaque, unsigned int fg_color, const char * text); typedef void (* LG_FontRelease )(LG_FontObj opaque, LG_FontBitmap * bitmap); typedef struct LG_Font { // mandatory support const char * name; LG_FontCreate create; LG_FontDestroy destroy; LG_FontRender render; LG_FontRelease release; } LG_Font;looking-glass-0+b4+dfsg.1/client/include/interface/renderer.h000066400000000000000000000120231411177132200241600ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include #include "app.h" #include "common/KVMFR.h" #include "common/framebuffer.h" #define IS_LG_RENDERER_VALID(x) \ ((x)->get_name && \ (x)->create && \ (x)->initialize && \ (x)->deinitialize && \ (x)->on_restart && \ (x)->on_resize && \ (x)->on_mouse_shape && \ (x)->on_mouse_event && \ (x)->on_alert && \ (x)->on_help && \ (x)->on_show_fps && \ (x)->render_startup && \ (x)->render && \ (x)->update_fps) typedef struct LG_RendererParams { // TTF_Font * font; // TTF_Font * alertFont; bool quickSplash; } LG_RendererParams; typedef enum LG_RendererSupport { LG_SUPPORTS_DMABUF } LG_RendererSupport; typedef enum LG_RendererRotate { LG_ROTATE_0, LG_ROTATE_90, LG_ROTATE_180, LG_ROTATE_270 } LG_RendererRotate; // kept out of the enum so gcc doesn't warn when it's missing from a switch // statement. #define LG_ROTATE_MAX (LG_ROTATE_270+1) typedef struct LG_RendererFormat { FrameType type; // frame type unsigned int width; // image width unsigned int height; // image height unsigned int stride; // scanline width (zero if compresed) unsigned int pitch; // scanline bytes (or compressed size) unsigned int bpp; // bits per pixel (zero if compressed) LG_RendererRotate rotate; // guest rotation } LG_RendererFormat; typedef struct LG_RendererRect { bool valid; int x; int y; int w; int h; } LG_RendererRect; typedef enum LG_RendererCursor { LG_CURSOR_COLOR , LG_CURSOR_MONOCHROME , LG_CURSOR_MASKED_COLOR } LG_RendererCursor; // returns the friendly name of the renderer typedef const char * (* LG_RendererGetName)(); // called pre-creation to allow the renderer to register any options it might have typedef void (* LG_RendererSetup)(); typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params, bool * needsOpenGL); typedef bool (* LG_RendererInitialize )(void * opaque); typedef void (* LG_RendererDeInitialize )(void * opaque); typedef bool (* LG_RendererSupports )(void * opaque, LG_RendererSupport support); typedef void (* LG_RendererOnRestart )(void * opaque); typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate); typedef bool (* LG_RendererOnMouseShape )(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data); typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visible , const int x, const int y); typedef bool (* LG_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format, bool useDMA); typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD); typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag); typedef void (* LG_RendererOnHelp )(void * opaque, const char * message); typedef void (* LG_RendererOnShowFPS )(void * opaque, bool showFPS); typedef bool (* LG_RendererRenderStartup)(void * opaque); typedef bool (* LG_RendererRender )(void * opaque, LG_RendererRotate rotate); typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS); typedef struct LG_Renderer { LG_RendererGetName get_name; LG_RendererSetup setup; LG_RendererCreate create; LG_RendererInitialize initialize; LG_RendererDeInitialize deinitialize; LG_RendererSupports supports; LG_RendererOnRestart on_restart; LG_RendererOnResize on_resize; LG_RendererOnMouseShape on_mouse_shape; LG_RendererOnMouseEvent on_mouse_event; LG_RendererOnFrameFormat on_frame_format; LG_RendererOnFrame on_frame; LG_RendererOnAlert on_alert; LG_RendererOnHelp on_help; LG_RendererOnShowFPS on_show_fps; LG_RendererRenderStartup render_startup; LG_RendererRender render; LG_RendererUpdateFPS update_fps; } LG_Renderer; looking-glass-0+b4+dfsg.1/client/include/ll.h000066400000000000000000000024321411177132200210240ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 struct ll; struct ll * ll_new(); void ll_free (struct ll * list); void ll_push (struct ll * list, void * data); bool ll_shift (struct ll * list, void ** data); bool ll_peek_head(struct ll * list, void ** data); bool ll_peek_tail(struct ll * list, void ** data); unsigned int ll_count (struct ll * list); void ll_reset (struct ll * list); bool ll_walk (struct ll * list, void ** data); looking-glass-0+b4+dfsg.1/client/include/util.h000066400000000000000000000027531411177132200214000ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_UTIL_ #define _H_LG_UTIL_ #include #include #include "common/types.h" // reads the specified file into a new buffer // the callee must free the buffer bool util_fileGetContents(const char * filename, char ** buffer, size_t * length); void util_cursorToInt(double ex, double ey, int *x, int *y); bool util_guestCurToLocal(struct DoublePoint *local); void util_localCurToGuest(struct DoublePoint *guest); void util_rotatePoint(struct DoublePoint *point); bool util_hasGLExt(const char * exts, const char * ext); static inline double util_clamp(double x, double min, double max) { if (x < min) return min; if (x > max) return max; return x; } #endif looking-glass-0+b4+dfsg.1/client/renderers/000077500000000000000000000000001411177132200206115ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/renderers/CMakeLists.txt000066400000000000000000000026101411177132200233500ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(renderers LANGUAGES C) set(RENDERER_H "${CMAKE_BINARY_DIR}/include/dynamic/renderers.h") set(RENDERER_C "${CMAKE_BINARY_DIR}/src/renderers.c") file(WRITE ${RENDERER_H} "#include \"interface/renderer.h\"\n\n") file(APPEND ${RENDERER_H} "extern LG_Renderer * LG_Renderers[];\n\n") file(WRITE ${RENDERER_C} "#include \"interface/renderer.h\"\n\n") file(APPEND ${RENDERER_C} "#include \n\n") set(RENDERERS "_") set(RENDERERS_LINK "_") function(add_renderer name) set(RENDERERS "${RENDERERS};${name}" PARENT_SCOPE) set(RENDERERS_LINK "${RENDERERS_LINK};renderer_${name}" PARENT_SCOPE) add_subdirectory(${name}) endfunction() # Add/remove renderers here! if(ENABLE_EGL) add_renderer(EGL) endif() if (ENABLE_OPENGL) add_renderer(OpenGL) endif() list(REMOVE_AT RENDERERS 0) list(REMOVE_AT RENDERERS_LINK 0) list(LENGTH RENDERERS RENDERER_COUNT) file(APPEND ${RENDERER_H} "#define LG_RENDERER_COUNT ${RENDERER_COUNT}\n") foreach(renderer ${RENDERERS}) file(APPEND ${RENDERER_C} "extern LG_Renderer LGR_${renderer};\n") endforeach() file(APPEND ${RENDERER_C} "\nconst LG_Renderer * LG_Renderers[] =\n{\n") foreach(renderer ${RENDERERS}) file(APPEND ${RENDERER_C} " &LGR_${renderer},\n") endforeach() file(APPEND ${RENDERER_C} " NULL\n};") add_library(renderers STATIC ${RENDERER_C}) target_link_libraries(renderers ${RENDERERS_LINK}) looking-glass-0+b4+dfsg.1/client/renderers/EGL/000077500000000000000000000000001411177132200212205ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/renderers/EGL/CMakeLists.txt000066400000000000000000000024011411177132200237550ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(renderer_EGL LANGUAGES C) find_package(PkgConfig) pkg_check_modules(RENDERER_EGL_PKGCONFIG REQUIRED egl gl ) pkg_check_modules(RENDERER_EGL_OPT_PKGCONFIG wayland-egl ) include(MakeObject) make_object( EGL_SHADER shader/desktop.vert shader/desktop_rgb.frag shader/cursor.vert shader/cursor_rgb.frag shader/cursor_mono.frag shader/fps.vert shader/fps.frag shader/fps_bg.frag shader/help.vert shader/help.frag shader/help_bg.frag shader/alert.vert shader/alert.frag shader/alert_bg.frag shader/splash_bg.vert shader/splash_bg.frag shader/splash_logo.vert shader/splash_logo.frag ) make_defines( "${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.c model.c desktop.c cursor.c fps.c help.c draw.c splash.c alert.c ${EGL_SHADER_OBJS} "${EGL_SHADER_INCS}/desktop_rgb.def.h" ) target_link_libraries(renderer_EGL ${RENDERER_EGL_PKGCONFIG_LIBRARIES} ${RENDERER_EGL_OPT_PKGCONFIG_LIBRARIES} lg_common fonts ) target_include_directories(renderer_EGL PRIVATE src ${EGL_SHADER_INCS} ${RENDERER_EGL_PKGCONFIG_INCLUDE_DIRS} ${RENDERER_EGL_OPT_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b4+dfsg.1/client/renderers/EGL/alert.c000066400000000000000000000135001411177132200224720ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "alert.h" #include "common/debug.h" #include "common/locking.h" #include "texture.h" #include "shader.h" #include "model.h" #include #include // these headers are auto generated by cmake #include "alert.vert.h" #include "alert.frag.h" #include "alert_bg.frag.h" struct EGL_Alert { const LG_Font * font; LG_FontObj fontObj; EGL_Texture * texture; EGL_Shader * shader; EGL_Shader * shaderBG; EGL_Model * model; LG_Lock lock; bool update; LG_FontBitmap * bmp; bool ready; float width , height ; float bgWidth, bgHeight; float r, g, b, a; // uniforms GLint uScreen , uSize; GLint uScreenBG, uSizeBG, uColorBG; }; bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj) { *alert = (EGL_Alert *)malloc(sizeof(EGL_Alert)); if (!*alert) { DEBUG_ERROR("Failed to malloc EGL_Alert"); return false; } memset(*alert, 0, sizeof(EGL_Alert)); (*alert)->font = font; (*alert)->fontObj = fontObj; LG_LOCK_INIT((*alert)->lock); if (!egl_texture_init(&(*alert)->texture, NULL)) { DEBUG_ERROR("Failed to initialize the alert texture"); return false; } if (!egl_shader_init(&(*alert)->shader)) { DEBUG_ERROR("Failed to initialize the alert shader"); return false; } if (!egl_shader_init(&(*alert)->shaderBG)) { DEBUG_ERROR("Failed to initialize the alert bg shader"); return false; } if (!egl_shader_compile((*alert)->shader, b_shader_alert_vert, b_shader_alert_vert_size, b_shader_alert_frag, b_shader_alert_frag_size)) { DEBUG_ERROR("Failed to compile the alert shader"); return false; } if (!egl_shader_compile((*alert)->shaderBG, b_shader_alert_vert , b_shader_alert_vert_size, b_shader_alert_bg_frag, b_shader_alert_bg_frag_size)) { DEBUG_ERROR("Failed to compile the alert shader"); return false; } (*alert)->uSize = egl_shader_get_uniform_location((*alert)->shader , "size" ); (*alert)->uScreen = egl_shader_get_uniform_location((*alert)->shader , "screen"); (*alert)->uSizeBG = egl_shader_get_uniform_location((*alert)->shaderBG, "size" ); (*alert)->uScreenBG = egl_shader_get_uniform_location((*alert)->shaderBG, "screen"); (*alert)->uColorBG = egl_shader_get_uniform_location((*alert)->shaderBG, "color" ); if (!egl_model_init(&(*alert)->model)) { DEBUG_ERROR("Failed to initialize the alert model"); return false; } egl_model_set_default((*alert)->model); egl_model_set_texture((*alert)->model, (*alert)->texture); return true; } void egl_alert_free(EGL_Alert ** alert) { if (!*alert) return; egl_texture_free(&(*alert)->texture ); egl_shader_free (&(*alert)->shader ); egl_shader_free (&(*alert)->shaderBG); egl_model_free (&(*alert)->model ); free(*alert); *alert = NULL; } void egl_alert_set_color(EGL_Alert * alert, const uint32_t color) { alert->r = (1.0f / 0xff) * ((color >> 24) & 0xFF); alert->g = (1.0f / 0xff) * ((color >> 16) & 0xFF); alert->b = (1.0f / 0xff) * ((color >> 8) & 0xFF); alert->a = (1.0f / 0xff) * ((color >> 0) & 0xFF); } void egl_alert_set_text (EGL_Alert * alert, const char * str) { LG_LOCK(alert->lock); alert->bmp = alert->font->render(alert->fontObj, 0xffffff00, str); if (!alert->bmp) { alert->update = false; LG_UNLOCK(alert->lock); DEBUG_ERROR("Failed to render alert text"); return; } alert->update = true; LG_UNLOCK(alert->lock); } void egl_alert_set_font(EGL_Alert * alert, LG_Font * fontObj) { LG_LOCK(alert->lock); alert->fontObj = fontObj; LG_UNLOCK(alert->lock); } void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY) { if (alert->update) { LG_LOCK(alert->lock); egl_texture_setup( alert->texture, EGL_PF_BGRA, alert->bmp->width , alert->bmp->height, alert->bmp->width * alert->bmp->bpp, false, false ); egl_texture_update(alert->texture, alert->bmp->pixels); alert->width = alert->bgWidth = alert->bmp->width; alert->height = alert->bgHeight = alert->bmp->height; if (alert->bgWidth < 200) alert->bgWidth = 200; alert->bgHeight += 4; alert->ready = true; alert->font->release(alert->fontObj, alert->bmp); alert->update = false; alert->bmp = NULL; LG_UNLOCK(alert->lock); } if (!alert->ready) return; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // render the background first egl_shader_use(alert->shaderBG); glUniform2f(alert->uScreenBG, scaleX , scaleY ); glUniform2i(alert->uSizeBG , alert->bgWidth, alert->bgHeight); glUniform4f(alert->uColorBG , alert->r, alert->g, alert->b, alert->a); egl_model_render(alert->model); // render the texture over the background egl_shader_use(alert->shader); glUniform2f(alert->uScreen, scaleX , scaleY ); glUniform2i(alert->uSize , alert->width, alert->height); egl_model_render(alert->model); glDisable(GL_BLEND); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/alert.h000066400000000000000000000024551411177132200225060ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include "interface/font.h" typedef struct EGL_Alert EGL_Alert; bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj); void egl_alert_free(EGL_Alert ** alert); void egl_alert_set_color(EGL_Alert * alert, const uint32_t color); void egl_alert_set_text (EGL_Alert * alert, const char * str); void egl_alert_set_font (EGL_Alert * alert, LG_Font * fontObj); void egl_alert_render (EGL_Alert * alert, const float scaleX, const float scaleY);looking-glass-0+b4+dfsg.1/client/renderers/EGL/cursor.c000066400000000000000000000211441411177132200227030ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "cursor.h" #include "common/debug.h" #include "common/locking.h" #include "common/option.h" #include "texture.h" #include "shader.h" #include "model.h" #include #include // these headers are auto generated by cmake #include "cursor.vert.h" #include "cursor_rgb.frag.h" #include "cursor_mono.frag.h" struct CursorTex { struct EGL_Texture * texture; struct EGL_Shader * shader; GLuint uMousePos; GLuint uRotate; GLuint uCBMode; }; struct EGL_Cursor { LG_Lock lock; LG_RendererCursor type; int width; int height; int stride; uint8_t * data; size_t dataSize; bool update; // cursor state bool visible; float x, y, w, h; LG_RendererRotate rotate; int cbMode; struct CursorTex norm; struct CursorTex mono; struct EGL_Model * model; }; static bool egl_cursor_tex_init(struct CursorTex * t, const char * vertex_code , size_t vertex_size, const char * fragment_code, size_t fragment_size) { if (!egl_texture_init(&t->texture, NULL)) { DEBUG_ERROR("Failed to initialize the cursor texture"); return false; } if (!egl_shader_init(&t->shader)) { DEBUG_ERROR("Failed to initialize the cursor shader"); return false; } if (!egl_shader_compile(t->shader, vertex_code, vertex_size, fragment_code, fragment_size)) { DEBUG_ERROR("Failed to compile the cursor shader"); return false; } t->uMousePos = egl_shader_get_uniform_location(t->shader, "mouse" ); t->uRotate = egl_shader_get_uniform_location(t->shader, "rotate"); t->uCBMode = egl_shader_get_uniform_location(t->shader, "cbMode"); return true; } static inline void egl_cursor_tex_uniforms(EGL_Cursor * cursor, struct CursorTex * t, bool mono) { if (mono) { glUniform4f(t->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2); glUniform1i(t->uRotate , cursor->rotate); glUniform1i(t->uCBMode , cursor->cbMode); } else { glUniform4f(t->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h); glUniform1i(t->uRotate , cursor->rotate); glUniform1i(t->uCBMode , cursor->cbMode); } } static void egl_cursor_tex_free(struct CursorTex * t) { egl_texture_free(&t->texture); egl_shader_free (&t->shader ); }; bool egl_cursor_init(EGL_Cursor ** cursor) { *cursor = (EGL_Cursor *)malloc(sizeof(EGL_Cursor)); if (!*cursor) { DEBUG_ERROR("Failed to malloc EGL_Cursor"); return false; } memset(*cursor, 0, sizeof(EGL_Cursor)); LG_LOCK_INIT((*cursor)->lock); if (!egl_cursor_tex_init(&(*cursor)->norm, b_shader_cursor_vert , b_shader_cursor_vert_size, b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size)) return false; if (!egl_cursor_tex_init(&(*cursor)->mono, b_shader_cursor_vert , b_shader_cursor_vert_size, b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size)) return false; if (!egl_model_init(&(*cursor)->model)) { DEBUG_ERROR("Failed to initialize the cursor model"); return false; } egl_model_set_default((*cursor)->model); (*cursor)->cbMode = option_get_int("egl", "cbMode"); return true; } void egl_cursor_free(EGL_Cursor ** cursor) { if (!*cursor) return; LG_LOCK_FREE((*cursor)->lock); if ((*cursor)->data) free((*cursor)->data); egl_cursor_tex_free(&(*cursor)->norm); egl_cursor_tex_free(&(*cursor)->mono); egl_model_free(&(*cursor)->model); free(*cursor); *cursor = NULL; } bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data) { LG_LOCK(cursor->lock); cursor->type = type; cursor->width = width; cursor->height = (type == LG_CURSOR_MONOCHROME ? height / 2 : height); cursor->stride = stride; const size_t size = height * stride; if (size > cursor->dataSize) { if (cursor->data) free(cursor->data); cursor->data = (uint8_t *)malloc(size); if (!cursor->data) { DEBUG_ERROR("Failed to malloc buffer for cursor shape"); return false; } cursor->dataSize = size; } memcpy(cursor->data, data, size); cursor->update = true; LG_UNLOCK(cursor->lock); return true; } void egl_cursor_set_size(EGL_Cursor * cursor, const float w, const float h) { cursor->w = w; cursor->h = h; } void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y) { cursor->visible = visible; cursor->x = x; cursor->y = y; } struct CursorState egl_cursor_get_state(EGL_Cursor * cursor, int width, int height) { return (struct CursorState) { .visible = cursor->visible, .rect.x = (cursor->x * width + width) / 2, .rect.y = (-cursor->y * height + height) / 2 - cursor->h * height, .rect.w = cursor->w * width + 2, .rect.h = cursor->h * height + 2, }; } void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate) { if (!cursor->visible) return; if (cursor->update) { LG_LOCK(cursor->lock); cursor->update = false; uint8_t * data = cursor->data; switch(cursor->type) { case LG_CURSOR_MASKED_COLOR: // fall through case LG_CURSOR_COLOR: { egl_texture_setup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false, false); egl_texture_update(cursor->norm.texture, data); egl_model_set_texture(cursor->model, cursor->norm.texture); break; } case LG_CURSOR_MONOCHROME: { 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); const uint8_t * srcXor = srcAnd + cursor->stride * cursor->height; const uint8_t mask = 0x80 >> (x % 8); const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000; const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000; and[y * cursor->width + x] = andMask; xor[y * cursor->width + x] = xorMask; } egl_texture_setup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false); egl_texture_setup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false); egl_texture_update(cursor->norm.texture, (uint8_t *)and); egl_texture_update(cursor->mono.texture, (uint8_t *)xor); break; } } LG_UNLOCK(cursor->lock); } cursor->rotate = rotate; glEnable(GL_BLEND); switch(cursor->type) { case LG_CURSOR_MONOCHROME: { egl_shader_use(cursor->norm.shader); egl_cursor_tex_uniforms(cursor, &cursor->norm, true);; glBlendFunc(GL_ZERO, GL_SRC_COLOR); egl_model_set_texture(cursor->model, cursor->norm.texture); egl_model_render(cursor->model); egl_shader_use(cursor->mono.shader); egl_cursor_tex_uniforms(cursor, &cursor->mono, true);; glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); egl_model_set_texture(cursor->model, cursor->mono.texture); egl_model_render(cursor->model); break; } case LG_CURSOR_COLOR: { egl_shader_use(cursor->norm.shader); egl_cursor_tex_uniforms(cursor, &cursor->norm, false); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); egl_model_render(cursor->model); break; } case LG_CURSOR_MASKED_COLOR: { egl_shader_use(cursor->mono.shader); egl_cursor_tex_uniforms(cursor, &cursor->mono, false); glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); egl_model_render(cursor->model); break; } } glDisable(GL_BLEND); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/cursor.h000066400000000000000000000030731411177132200227110ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include "interface/renderer.h" typedef struct EGL_Cursor EGL_Cursor; struct CursorState { bool visible; struct Rect rect; }; bool egl_cursor_init(EGL_Cursor ** cursor); void egl_cursor_free(EGL_Cursor ** cursor); bool egl_cursor_set_shape( EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data); void egl_cursor_set_size(EGL_Cursor * cursor, const float x, const float y); void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y); struct CursorState egl_cursor_get_state(EGL_Cursor * cursor, int width, int height); void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate); looking-glass-0+b4+dfsg.1/client/renderers/EGL/desktop.c000066400000000000000000000207741411177132200230470ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "desktop.h" #include "common/debug.h" #include "common/option.h" #include "common/locking.h" #include "app.h" #include "texture.h" #include "shader.h" #include "model.h" #include #include // these headers are auto generated by cmake #include "desktop.vert.h" #include "desktop_rgb.frag.h" #include "desktop_rgb.def.h" struct DesktopShader { EGL_Shader * shader; GLint uDesktopPos; GLint uDesktopSize; GLint uRotate; GLint uScaleAlgo; GLint uNV, uNVGain; GLint uCBMode; }; struct EGL_Desktop { EGLDisplay * display; EGL_Texture * texture; struct DesktopShader * shader; // the active shader EGL_Model * model; // internals int width, height; LG_RendererRotate rotate; // shader instances struct DesktopShader shader_generic; // scale algorithm int scaleAlgo; // night vision int nvMax; int nvGain; // colorblind mode int cbMode; }; // forwards void egl_desktop_toggle_nv(int key, void * opaque); void egl_desktop_toggle_scale_algo(int key, void * opaque); static bool egl_init_desktop_shader( struct DesktopShader * shader, const char * vertex_code , size_t vertex_size, const char * fragment_code, size_t fragment_size ) { if (!egl_shader_init(&shader->shader)) return false; if (!egl_shader_compile(shader->shader, vertex_code , vertex_size, fragment_code, fragment_size)) { return false; } shader->uDesktopPos = egl_shader_get_uniform_location(shader->shader, "position" ); shader->uDesktopSize = egl_shader_get_uniform_location(shader->shader, "size" ); shader->uRotate = egl_shader_get_uniform_location(shader->shader, "rotate" ); shader->uScaleAlgo = egl_shader_get_uniform_location(shader->shader, "scaleAlgo"); shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" ); shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" ); shader->uCBMode = egl_shader_get_uniform_location(shader->shader, "cbMode" ); return true; } bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display) { *desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop)); if (!*desktop) { DEBUG_ERROR("Failed to malloc EGL_Desktop"); return false; } memset(*desktop, 0, sizeof(EGL_Desktop)); (*desktop)->display = display; if (!egl_texture_init(&(*desktop)->texture, display)) { DEBUG_ERROR("Failed to initialize the desktop texture"); return false; } if (!egl_init_desktop_shader( &(*desktop)->shader_generic, 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_model_init(&(*desktop)->model)) { DEBUG_ERROR("Failed to initialize the desktop model"); return false; } egl_model_set_default((*desktop)->model); egl_model_set_texture((*desktop)->model, (*desktop)->texture); app_registerKeybind(KEY_N, egl_desktop_toggle_nv, *desktop, "Toggle night vision mode"); app_registerKeybind(KEY_S, egl_desktop_toggle_scale_algo, *desktop, "Toggle scale algorithm"); (*desktop)->nvMax = option_get_int("egl", "nvGainMax"); (*desktop)->nvGain = option_get_int("egl", "nvGain" ); (*desktop)->cbMode = option_get_int("egl", "cbMode" ); (*desktop)->scaleAlgo = option_get_int("egl", "scale" ); return true; } void egl_desktop_toggle_nv(int key, void * opaque) { EGL_Desktop * desktop = (EGL_Desktop *)opaque; if (desktop->nvGain++ == desktop->nvMax) desktop->nvGain = 0; if (desktop->nvGain == 0) app_alert(LG_ALERT_INFO, "NV Disabled"); else if (desktop->nvGain == 1) app_alert(LG_ALERT_INFO, "NV Enabled"); else app_alert(LG_ALERT_INFO, "NV Gain + %d", desktop->nvGain - 1); } static const char * egl_desktop_scale_algo_name(int algorithm) { switch (algorithm) { case EGL_SCALE_AUTO: return "Automatic (downscale: linear, upscale: nearest)"; case EGL_SCALE_NEAREST: return "Nearest"; case EGL_SCALE_LINEAR: return "Linear"; default: return "(unknown)"; } } bool egl_desktop_scale_validate(struct Option * opt, const char ** error) { if (opt->value.x_int >= 0 && opt->value.x_int < EGL_SCALE_MAX) return true; *error = "Invalid scale algorithm number"; return false; } void egl_desktop_toggle_scale_algo(int key, void * opaque) { EGL_Desktop * desktop = (EGL_Desktop *)opaque; if (++desktop->scaleAlgo == EGL_SCALE_MAX) desktop->scaleAlgo = 0; app_alert(LG_ALERT_INFO, "Scale Algorithm %d: %s", desktop->scaleAlgo, egl_desktop_scale_algo_name(desktop->scaleAlgo)); } void egl_desktop_free(EGL_Desktop ** desktop) { if (!*desktop) return; egl_texture_free(&(*desktop)->texture ); egl_shader_free (&(*desktop)->shader_generic.shader); egl_model_free (&(*desktop)->model ); free(*desktop); *desktop = NULL; } bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA) { enum EGL_PixelFormat pixFmt; switch(format.type) { case FRAME_TYPE_BGRA: pixFmt = EGL_PF_BGRA; desktop->shader = &desktop->shader_generic; break; case FRAME_TYPE_RGBA: pixFmt = EGL_PF_RGBA; desktop->shader = &desktop->shader_generic; break; case FRAME_TYPE_RGBA10: pixFmt = EGL_PF_RGBA10; desktop->shader = &desktop->shader_generic; break; case FRAME_TYPE_RGBA16F: pixFmt = EGL_PF_RGBA16F; desktop->shader = &desktop->shader_generic; break; default: DEBUG_ERROR("Unsupported frame format"); return false; } desktop->width = format.width; desktop->height = format.height; if (!egl_texture_setup( desktop->texture, pixFmt, format.width, format.height, format.pitch, true, // streaming texture useDMA )) { DEBUG_ERROR("Failed to setup the desktop texture"); return false; } return true; } bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd) { if (dmaFd >= 0) { if (!egl_texture_update_from_dma(desktop->texture, frame, dmaFd)) return false; } else { if (!egl_texture_update_from_frame(desktop->texture, frame)) return false; } enum EGL_TexStatus status; if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK) { if (status != EGL_TEX_STATUS_NOTREADY) DEBUG_ERROR("Failed to process the desktop texture"); } return true; } bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType, LG_RendererRotate rotate) { if (!desktop->shader) return false; int scaleAlgo = EGL_SCALE_NEAREST; switch (desktop->scaleAlgo) { case EGL_SCALE_AUTO: switch (scaleType) { case EGL_DESKTOP_NOSCALE: case EGL_DESKTOP_UPSCALE: scaleAlgo = EGL_SCALE_NEAREST; break; case EGL_DESKTOP_DOWNSCALE: scaleAlgo = EGL_SCALE_LINEAR; break; } break; default: scaleAlgo = desktop->scaleAlgo; } const struct DesktopShader * shader = desktop->shader; egl_shader_use(shader->shader); glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY); glUniform1i(shader->uRotate , rotate); glUniform1i(shader->uScaleAlgo , scaleAlgo); glUniform2f(shader->uDesktopSize, desktop->width, desktop->height); if (desktop->nvGain) { glUniform1i(shader->uNV, 1); glUniform1f(shader->uNVGain, (float)desktop->nvGain); } else glUniform1i(shader->uNV, 0); glUniform1i(shader->uCBMode, desktop->cbMode); egl_model_render(desktop->model); return true; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/desktop.h000066400000000000000000000031111411177132200230360ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include "interface/renderer.h" typedef struct EGL_Desktop EGL_Desktop; enum EGL_DesktopScaleType { EGL_DESKTOP_NOSCALE, EGL_DESKTOP_UPSCALE, EGL_DESKTOP_DOWNSCALE, }; struct Option; bool egl_desktop_scale_validate(struct Option * opt, const char ** error); bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display); void egl_desktop_free(EGL_Desktop ** desktop); bool egl_desktop_setup (EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA); bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd); bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType, LG_RendererRotate rotate); looking-glass-0+b4+dfsg.1/client/renderers/EGL/draw.c000066400000000000000000000041371411177132200223260ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include void egl_draw_torus(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer) { GLfloat * v = (GLfloat *)malloc(sizeof(GLfloat) * (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_model_add_verticies(model, v, NULL, (pts + 1) * 2); free(v); } void egl_draw_torus_arc(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer, float s, float e) { GLfloat * v = (GLfloat *)malloc(sizeof(GLfloat) * (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_model_add_verticies(model, v, NULL, (pts + 1) * 2); free(v); }looking-glass-0+b4+dfsg.1/client/renderers/EGL/draw.h000066400000000000000000000020731411177132200223300ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include "model.h" void egl_draw_torus (EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer); void egl_draw_torus_arc(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer, float s, float e);looking-glass-0+b4+dfsg.1/client/renderers/EGL/egl.c000066400000000000000000000544521411177132200221450ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "interface/renderer.h" #include "common/debug.h" #include "common/option.h" #include "common/sysinfo.h" #include "common/time.h" #include "common/locking.h" #include "util.h" #include "dynamic/fonts.h" #include #include #include #include #include "app.h" #include "egl_dynprocs.h" #include "model.h" #include "shader.h" #include "desktop.h" #include "cursor.h" #include "fps.h" #include "splash.h" #include "alert.h" #include "help.h" #define SPLASH_FADE_TIME 1000000 #define ALERT_TIMEOUT 2000000 struct Options { bool vsync; bool doubleBuffer; }; struct Inst { bool dmaSupport; LG_RendererParams params; struct Options opt; EGLNativeWindowType nativeWind; EGLDisplay display; EGLConfig configs; EGLSurface surface; EGLContext context, frameContext; EGL_Desktop * desktop; // the desktop EGL_Cursor * cursor; // the mouse cursor EGL_FPS * fps; // the fps display EGL_Splash * splash; // the splash screen EGL_Alert * alert; // the alert display EGL_Help * help; // the help display LG_RendererFormat format; bool formatValid; bool start; uint64_t waitFadeTime; bool waitDone; bool showAlert; uint64_t alertTimeout; bool useCloseFlag; bool closeFlag; int width, height; float uiScale; LG_RendererRect destRect; LG_RendererRotate rotate; //client side rotation float translateX , translateY; float scaleX , scaleY; float splashRatio; float screenScaleX, screenScaleY; int viewportWidth, viewportHeight; enum EGL_DesktopScaleType scaleType; bool cursorVisible; int cursorX , cursorY; float mouseWidth , mouseHeight; float mouseScaleX, mouseScaleY; const LG_Font * font; LG_FontObj fontObj; unsigned fontSize; LG_FontObj helpFontObj; unsigned helpFontSize; bool cursorLastValid; struct CursorState cursorLast; }; static struct Option egl_options[] = { { .module = "egl", .name = "vsync", .description = "Enable vsync", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "egl", .name = "doubleBuffer", .description = "Enable double buffering", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "egl", .name = "multisample", .description = "Enable Multisampling", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "egl", .name = "nvGainMax", .description = "The maximum night vision gain", .type = OPTION_TYPE_INT, .value.x_int = 1 }, { .module = "egl", .name = "nvGain", .description = "The initial night vision gain at startup", .type = OPTION_TYPE_INT, .value.x_int = 0 }, { .module = "egl", .name = "cbMode", .description = "Color Blind Mode (0 = Off, 1 = Protanope, 2 = Deuteranope, 3 = Tritanope)", .type = OPTION_TYPE_INT, .value.x_int = 0 }, { .module = "egl", .name = "scale", .description = "Set the scale algorithm (0 = auto, 1 = nearest, 2 = linear)", .type = OPTION_TYPE_INT, .validator = egl_desktop_scale_validate, .value.x_int = 0 }, {0} }; void update_mouse_shape(struct Inst * this); const char * egl_get_name(void) { return "EGL"; } void egl_setup(void) { option_register(egl_options); } static bool egl_update_font(struct Inst * this) { unsigned size = round(16.0f * this->uiScale); if (size == this->fontSize) return true; LG_FontObj fontObj; if (!this->font->create(&fontObj, NULL, size)) { DEBUG_ERROR("Failed to create a font instance"); return false; } if (this->alert) egl_alert_set_font(this->alert, fontObj); if (this->fps) egl_fps_set_font(this->fps, fontObj); if (this->fontObj) this->font->destroy(this->fontObj); this->fontObj = fontObj; return true; } static bool egl_update_help_font(struct Inst * this) { unsigned size = round(14.0f * this->uiScale); if (size == this->helpFontSize) return true; LG_FontObj fontObj; if (!this->font->create(&fontObj, NULL, size)) { DEBUG_ERROR("Failed to create a font instance"); return false; } if (this->help) egl_help_set_font(this->help, fontObj); if (this->helpFontObj) this->font->destroy(this->helpFontObj); this->helpFontObj = fontObj; return true; } bool egl_create(void ** opaque, const LG_RendererParams params, bool * needsOpenGL) { // check if EGL is even available if (!eglQueryString(EGL_NO_DISPLAY, EGL_VERSION)) return false; // create our local storage *opaque = calloc(1, sizeof(struct Inst)); if (!*opaque) { DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst)); return false; } // safe off parameteres and init our default option values struct Inst * this = (struct Inst *)*opaque; memcpy(&this->params, ¶ms, sizeof(LG_RendererParams)); this->opt.vsync = option_get_bool("egl", "vsync"); this->opt.doubleBuffer = option_get_bool("egl", "doubleBuffer"); this->translateX = 0; this->translateY = 0; this->scaleX = 1.0f; this->scaleY = 1.0f; this->screenScaleX = 1.0f; this->screenScaleY = 1.0f; this->uiScale = 1.0; this->font = LG_Fonts[0]; if (!egl_update_font(this)) return false; if (!egl_update_help_font(this)) return false; *needsOpenGL = false; return true; } bool egl_initialize(void * opaque) { struct Inst * this = (struct Inst *)opaque; DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off"); return true; } void egl_deinitialize(void * opaque) { struct Inst * this = (struct Inst *)opaque; if (this->font) { if (this->fontObj) this->font->destroy(this->fontObj); if (this->helpFontObj) this->font->destroy(this->helpFontObj); } egl_desktop_free(&this->desktop); egl_cursor_free (&this->cursor); egl_fps_free (&this->fps ); egl_splash_free (&this->splash); egl_alert_free (&this->alert ); egl_help_free (&this->help); LG_LOCK_FREE(this->lock); eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (this->frameContext) eglDestroyContext(this->display, this->frameContext); if (this->context) eglDestroyContext(this->display, this->context); eglTerminate(this->display); free(this); } bool egl_supports(void * opaque, LG_RendererSupport flag) { struct Inst * this = (struct Inst *)opaque; switch(flag) { case LG_SUPPORTS_DMABUF: return this->dmaSupport; default: return false; } } void egl_on_restart(void * opaque) { struct Inst * this = (struct Inst *)opaque; eglDestroyContext(this->display, this->frameContext); this->frameContext = NULL; this->start = false; } static void egl_calc_mouse_size(struct Inst * this) { if (!this->formatValid) return; int w = 0, h = 0; switch(this->format.rotate) { case LG_ROTATE_0: case LG_ROTATE_180: this->mouseScaleX = 2.0f / this->format.width; this->mouseScaleY = 2.0f / this->format.height; w = this->format.width; h = this->format.height; break; case LG_ROTATE_90: case LG_ROTATE_270: this->mouseScaleX = 2.0f / this->format.height; this->mouseScaleY = 2.0f / this->format.width; w = this->format.height; h = this->format.width; break; default: assert(!"unreachable"); } switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: case LG_ROTATE_180: egl_cursor_set_size(this->cursor, (this->mouseWidth * (1.0f / w)) * this->scaleX, (this->mouseHeight * (1.0f / h)) * this->scaleY ); break; case LG_ROTATE_90: case LG_ROTATE_270: egl_cursor_set_size(this->cursor, (this->mouseWidth * (1.0f / w)) * this->scaleY, (this->mouseHeight * (1.0f / h)) * this->scaleX ); break; } } static void egl_calc_mouse_state(struct Inst * this) { if (!this->formatValid) return; switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: case LG_ROTATE_180: egl_cursor_set_state( this->cursor, this->cursorVisible, (((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX, (((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY ); break; case LG_ROTATE_90: case LG_ROTATE_270: egl_cursor_set_state( this->cursor, this->cursorVisible, (((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY, (((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX ); break; } } static void egl_update_scale_type(struct Inst * this) { int width = 0, height = 0; switch (this->rotate) { case LG_ROTATE_0: case LG_ROTATE_180: width = this->format.width; height = this->format.height; break; case LG_ROTATE_90: case LG_ROTATE_270: width = this->format.height; height = this->format.width; break; } if (width == this->viewportWidth || height == this->viewportHeight) this->scaleType = EGL_DESKTOP_NOSCALE; else if (width > this->viewportWidth || height > this->viewportHeight) this->scaleType = EGL_DESKTOP_DOWNSCALE; else this->scaleType = EGL_DESKTOP_UPSCALE; } void egl_on_resize(void * opaque, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate) { struct Inst * this = (struct Inst *)opaque; this->width = width * scale; this->height = height * scale; this->uiScale = (float) scale; this->rotate = rotate; this->destRect.x = destRect.x * scale; this->destRect.y = destRect.y * scale; this->destRect.w = destRect.w * scale; this->destRect.h = destRect.h * scale; glViewport(0, 0, this->width, this->height); 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->scaleX = (float)this->destRect.w / (float)this->width; this->scaleY = (float)this->destRect.h / (float)this->height; this->viewportWidth = this->destRect.w; this->viewportHeight = this->destRect.h; } egl_update_scale_type(this); egl_calc_mouse_size(this); this->splashRatio = (float)width / (float)height; this->screenScaleX = 1.0f / this->width; this->screenScaleY = 1.0f / this->height; egl_calc_mouse_state(this); egl_update_font(this); egl_update_help_font(this); this->cursorLastValid = false; } bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data) { struct Inst * this = (struct Inst *)opaque; if (!egl_cursor_set_shape(this->cursor, cursor, width, height, pitch, data)) { DEBUG_ERROR("Failed to update the cursor shape"); return false; } this->mouseWidth = width; this->mouseHeight = height; egl_calc_mouse_size(this); return true; } bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const int y) { struct Inst * this = (struct Inst *)opaque; this->cursorVisible = visible; this->cursorX = x; this->cursorY = y; egl_calc_mouse_state(this); return true; } bool egl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA) { struct Inst * this = (struct Inst *)opaque; memcpy(&this->format, &format, sizeof(LG_RendererFormat)); this->formatValid = true; /* this event runs in a second thread so we need to init it here */ if (!this->frameContext) { static EGLint attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; if (!(this->frameContext = eglCreateContext(this->display, this->configs, this->context, attrs))) { DEBUG_ERROR("Failed to create the frame context"); return false; } if (!eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->frameContext)) { DEBUG_ERROR("Failed to make the frame context current"); return false; } } egl_update_scale_type(this); return egl_desktop_setup(this->desktop, format, useDMA); } bool egl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd) { struct Inst * this = (struct Inst *)opaque; if (!egl_desktop_update(this->desktop, frame, dmaFd)) { DEBUG_INFO("Failed to to update the desktop"); return false; } this->start = true; this->cursorLastValid = false; return true; } void egl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag) { struct Inst * this = (struct Inst *)opaque; static const uint32_t colors[] = { 0x0000CCCC, // LG_ALERT_INFO 0x00CC00CC, // LG_ALERT_SUCCESS 0xCC7F00CC, // LG_ALERT_WARNING 0xFF0000CC // LG_ALERT_ERROR }; if (alert > LG_ALERT_ERROR || alert < 0) { DEBUG_ERROR("Invalid alert value"); return; } egl_alert_set_color(this->alert, colors[alert]); egl_alert_set_text (this->alert, message ); if (closeFlag) { this->useCloseFlag = true; *closeFlag = &this->closeFlag; } else { this->useCloseFlag = false; this->alertTimeout = microtime() + ALERT_TIMEOUT; } this->showAlert = true; this->cursorLastValid = false; } void egl_on_help(void * opaque, const char * message) { struct Inst * this = (struct Inst *)opaque; egl_help_set_text(this->help, message); this->cursorLastValid = false; } void egl_on_show_fps(void * opaque, bool showFPS) { struct Inst * this = (struct Inst *)opaque; egl_fps_set_display(this->fps, showFPS); } bool egl_render_startup(void * opaque) { struct Inst * this = (struct Inst *)opaque; this->nativeWind = app_getEGLNativeWindow(); if (!this->nativeWind) return false; this->display = app_getEGLDisplay(); if (this->display == EGL_NO_DISPLAY) return false; int maj, min; if (!eglInitialize(this->display, &maj, &min)) { DEBUG_ERROR("Unable to initialize EGL"); return false; } int maxSamples = 1; if (option_get_bool("egl", "multisample")) { if (app_getProp(LG_DS_MAX_MULTISAMPLE, &maxSamples) && maxSamples > 1) { if (maxSamples > 4) maxSamples = 4; DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples); } } EGLint attr[] = { EGL_BUFFER_SIZE , 24, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0, EGL_SAMPLES , maxSamples, EGL_NONE }; EGLint num_config; if (!eglChooseConfig(this->display, attr, &this->configs, 1, &num_config)) { DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError()); return false; } const EGLint surfattr[] = { EGL_RENDER_BUFFER, this->opt.doubleBuffer ? EGL_BACK_BUFFER : EGL_SINGLE_BUFFER, EGL_NONE }; this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, surfattr); if (this->surface == EGL_NO_SURFACE) { DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError()); return false; } EGLint ctxattr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; this->context = eglCreateContext(this->display, this->configs, EGL_NO_CONTEXT, ctxattr); if (this->context == EGL_NO_CONTEXT) { DEBUG_ERROR("Failed to create EGL context (eglError: 0x%x)", eglGetError()); return false; } EGLint rb = 0; eglQuerySurface(this->display, this->surface, EGL_RENDER_BUFFER, &rb); switch(rb) { case EGL_SINGLE_BUFFER: DEBUG_INFO("Single buffer mode"); break; case EGL_BACK_BUFFER: DEBUG_INFO("Back buffer mode"); break; default: DEBUG_WARN("Unknown render buffer mode: %d", rb); break; } eglMakeCurrent(this->display, this->surface, this->surface, this->context); const char *client_exts = eglQueryString(this->display, EGL_EXTENSIONS); const char *vendor = (const char *)glGetString(GL_VENDOR); DEBUG_INFO("EGL : %d.%d", maj, min); DEBUG_INFO("Vendor : %s", vendor); DEBUG_INFO("Renderer : %s", glGetString(GL_RENDERER)); DEBUG_INFO("Version : %s", glGetString(GL_VERSION )); DEBUG_INFO("EGL APIs : %s", eglQueryString(this->display, EGL_CLIENT_APIS)); DEBUG_INFO("Extensions: %s", client_exts); if (g_egl_dynProcs.glEGLImageTargetTexture2DOES) { if (util_hasGLExt(client_exts, "EGL_EXT_image_dma_buf_import")) { /* * As of version 455.45.01 NVidia started advertising support for this * feature, however even on the latest version 460.27.04 this is still * broken and does not work, until this is fixed and we have way to detect * this early just disable dma for all NVIDIA devices. * * ref: https://forums.developer.nvidia.com/t/egl-ext-image-dma-buf-import-broken-egl-bad-alloc-with-tons-of-free-ram/165552 */ if (strstr(vendor, "NVIDIA") != NULL) DEBUG_WARN("NVIDIA driver detected, ignoring broken DMA support"); else this->dmaSupport = true; } } else { DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled"); } eglSwapInterval(this->display, this->opt.vsync ? 1 : 0); if (!egl_desktop_init(&this->desktop, this->display)) { DEBUG_ERROR("Failed to initialize the desktop"); return false; } if (!egl_cursor_init(&this->cursor)) { DEBUG_ERROR("Failed to initialize the cursor"); return false; } if (!egl_fps_init(&this->fps, this->font, this->fontObj)) { DEBUG_ERROR("Failed to initialize the FPS display"); return false; } if (!egl_splash_init(&this->splash)) { DEBUG_ERROR("Failed to initialize the splash screen"); return false; } if (!egl_alert_init(&this->alert, this->font, this->fontObj)) { DEBUG_ERROR("Failed to initialize the alert display"); return false; } if (!egl_help_init(&this->help, this->font, this->helpFontObj)) { DEBUG_ERROR("Failed to initialize the alert display"); return false; } return true; } bool egl_render(void * opaque, LG_RendererRotate rotate) { struct Inst * this = (struct Inst *)opaque; glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); bool hasLastCursor = this->cursorLastValid; bool cursorRendered = false; struct CursorState cursorState; if (this->start && egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX , this->scaleY , this->scaleType , rotate)) { if (!this->waitFadeTime) { if (!this->params.quickSplash) this->waitFadeTime = microtime() + SPLASH_FADE_TIME; else this->waitDone = true; } cursorRendered = true; cursorState = egl_cursor_get_state(this->cursor, this->width, this->height); egl_cursor_render(this->cursor, (this->format.rotate + rotate) % LG_ROTATE_MAX); } 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_splash_render(this->splash, a, this->splashRatio); } else { if (!this->start) egl_splash_render(this->splash, 1.0f, this->splashRatio); } if (this->showAlert) { bool close = false; if (this->useCloseFlag) close = this->closeFlag; else if (this->alertTimeout < microtime()) close = true; if (close) { this->showAlert = false; this->cursorLastValid = false; } else egl_alert_render(this->alert, this->screenScaleX, this->screenScaleY); } struct Rect damage[2]; int damageIdx = 0; if (this->waitDone) { if (cursorRendered && hasLastCursor) { if (this->cursorLast.visible) damage[damageIdx++] = this->cursorLast.rect; if (cursorState.visible) damage[damageIdx++] = cursorState.rect; this->cursorLast = cursorState; } else if (cursorRendered) { this->cursorLast = cursorState; this->cursorLastValid = true; } } egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY); egl_help_render(this->help, this->screenScaleX, this->screenScaleY); app_eglSwapBuffers(this->display, this->surface, damage, damageIdx); return true; } void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS) { struct Inst * this = (struct Inst *)opaque; egl_fps_update(this->fps, avgUPS, avgFPS); this->cursorLastValid = false; } struct LG_Renderer LGR_EGL = { .get_name = egl_get_name, .setup = egl_setup, .create = egl_create, .initialize = egl_initialize, .deinitialize = egl_deinitialize, .supports = egl_supports, .on_restart = egl_on_restart, .on_resize = egl_on_resize, .on_mouse_shape = egl_on_mouse_shape, .on_mouse_event = egl_on_mouse_event, .on_frame_format = egl_on_frame_format, .on_frame = egl_on_frame, .on_alert = egl_on_alert, .on_help = egl_on_help, .on_show_fps = egl_on_show_fps, .render_startup = egl_render_startup, .render = egl_render, .update_fps = egl_update_fps }; looking-glass-0+b4+dfsg.1/client/renderers/EGL/egldebug.c000066400000000000000000000036061411177132200231470ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "egldebug.h" #include #include const char * egl_getErrorStr(void) { switch (eglGetError()) { case EGL_SUCCESS : return "EGL_SUCCESS"; case EGL_NOT_INITIALIZED : return "EGL_NOT_INITIALIZED"; case EGL_BAD_ACCESS : return "EGL_BAD_ACCESS"; case EGL_BAD_ALLOC : return "EGL_BAD_ALLOC"; case EGL_BAD_ATTRIBUTE : return "EGL_BAD_ATTRIBUTE"; case EGL_BAD_CONTEXT : return "EGL_BAD_CONTEXT"; case EGL_BAD_CONFIG : return "EGL_BAD_CONFIG"; case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; case EGL_BAD_DISPLAY : return "EGL_BAD_DISPLAY"; case EGL_BAD_SURFACE : return "EGL_BAD_SURFACE"; case EGL_BAD_MATCH : return "EGL_BAD_MATCH"; case EGL_BAD_PARAMETER : return "EGL_BAD_PARAMETER"; case EGL_BAD_NATIVE_PIXMAP : return "EGL_BAD_NATIVE_PIXMAP"; case EGL_BAD_NATIVE_WINDOW : return "EGL_BAD_NATIVE_WINDOW"; case EGL_CONTEXT_LOST : return "EGL_CONTEXT_LOST"; default : return "UNKNOWN"; } } looking-glass-0+b4+dfsg.1/client/renderers/EGL/egldebug.h000066400000000000000000000020651411177132200231520ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/debug.h" const char * egl_getErrorStr(void); #define DEBUG_EGL_WARN(fmt, ...) \ DEBUG_WARN(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr()) #define DEBUG_EGL_ERROR(fmt, ...) \ DEBUG_ERROR(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr()) looking-glass-0+b4+dfsg.1/client/renderers/EGL/fps.c000066400000000000000000000117111411177132200221550ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "fps.h" #include "common/debug.h" #include "texture.h" #include "shader.h" #include "model.h" #include #include // these headers are auto generated by cmake #include "fps.vert.h" #include "fps.frag.h" #include "fps_bg.frag.h" struct EGL_FPS { const LG_Font * font; LG_FontObj fontObj; EGL_Texture * texture; EGL_Shader * shader; EGL_Shader * shaderBG; EGL_Model * model; bool display; bool ready; int iwidth, iheight; float width, height; // uniforms GLint uScreen , uSize; GLint uScreenBG, uSizeBG; }; bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj) { *fps = (EGL_FPS *)malloc(sizeof(EGL_FPS)); if (!*fps) { DEBUG_ERROR("Failed to malloc EGL_FPS"); return false; } memset(*fps, 0, sizeof(EGL_FPS)); (*fps)->font = font; (*fps)->fontObj = fontObj; if (!egl_texture_init(&(*fps)->texture, NULL)) { DEBUG_ERROR("Failed to initialize the fps texture"); return false; } if (!egl_shader_init(&(*fps)->shader)) { DEBUG_ERROR("Failed to initialize the fps shader"); return false; } if (!egl_shader_init(&(*fps)->shaderBG)) { DEBUG_ERROR("Failed to initialize the fps bg shader"); return false; } if (!egl_shader_compile((*fps)->shader, b_shader_fps_vert, b_shader_fps_vert_size, b_shader_fps_frag, b_shader_fps_frag_size)) { DEBUG_ERROR("Failed to compile the fps shader"); return false; } if (!egl_shader_compile((*fps)->shaderBG, b_shader_fps_vert , b_shader_fps_vert_size, b_shader_fps_bg_frag, b_shader_fps_bg_frag_size)) { DEBUG_ERROR("Failed to compile the fps shader"); return false; } (*fps)->uSize = egl_shader_get_uniform_location((*fps)->shader , "size" ); (*fps)->uScreen = egl_shader_get_uniform_location((*fps)->shader , "screen"); (*fps)->uSizeBG = egl_shader_get_uniform_location((*fps)->shaderBG, "size" ); (*fps)->uScreenBG = egl_shader_get_uniform_location((*fps)->shaderBG, "screen"); if (!egl_model_init(&(*fps)->model)) { DEBUG_ERROR("Failed to initialize the fps model"); return false; } egl_model_set_default((*fps)->model); egl_model_set_texture((*fps)->model, (*fps)->texture); return true; } void egl_fps_free(EGL_FPS ** fps) { if (!*fps) return; egl_texture_free(&(*fps)->texture ); egl_shader_free (&(*fps)->shader ); egl_shader_free (&(*fps)->shaderBG); egl_model_free (&(*fps)->model ); free(*fps); *fps = NULL; } void egl_fps_set_display(EGL_FPS * fps, bool display) { fps->display = display; } void egl_fps_set_font(EGL_FPS * fps, LG_Font * fontObj) { fps->fontObj = fontObj; } void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS) { if (!fps->display) return; char str[128]; snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS); LG_FontBitmap * bmp = fps->font->render(fps->fontObj, 0xffffff00, str); if (!bmp) { DEBUG_ERROR("Failed to render fps text"); return; } if (fps->iwidth != bmp->width || fps->iheight != bmp->height) { fps->iwidth = bmp->width; fps->iheight = bmp->height; fps->width = (float)bmp->width; fps->height = (float)bmp->height; egl_texture_setup( fps->texture, EGL_PF_BGRA, bmp->width , bmp->height, bmp->width * bmp->bpp, false, false ); } egl_texture_update ( fps->texture, bmp->pixels ); fps->ready = true; fps->font->release(fps->fontObj, bmp); } void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY) { if (!fps->display || !fps->ready) return; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // render the background first egl_shader_use(fps->shaderBG); glUniform2f(fps->uScreenBG, scaleX , scaleY ); glUniform2f(fps->uSizeBG , fps->width, fps->height); egl_model_render(fps->model); // render the texture over the background egl_shader_use(fps->shader); glUniform2f(fps->uScreen, scaleX , scaleY ); glUniform2f(fps->uSize , fps->width, fps->height); egl_model_render(fps->model); glDisable(GL_BLEND); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/fps.h000066400000000000000000000024211411177132200221600ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include "interface/font.h" typedef struct EGL_FPS EGL_FPS; bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj); void egl_fps_free(EGL_FPS ** fps); void egl_fps_set_display(EGL_FPS * fps, bool display); void egl_fps_set_font (EGL_FPS * fps, LG_Font * fontObj); void egl_fps_update(EGL_FPS * fps, const float avgUPS, const float avgFPS); void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY);looking-glass-0+b4+dfsg.1/client/renderers/EGL/help.c000066400000000000000000000123001411177132200223100ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "help.h" #include "common/debug.h" #include "texture.h" #include "shader.h" #include "model.h" #include #include #include // these headers are auto generated by cmake #include "help.vert.h" #include "help.frag.h" #include "help_bg.frag.h" struct EGL_Help { const LG_Font * font; LG_FontObj fontObj; EGL_Texture * texture; EGL_Shader * shader; EGL_Shader * shaderBG; EGL_Model * model; _Atomic(LG_FontBitmap *) bmp; bool shouldRender; int iwidth, iheight; float width, height; // uniforms GLint uScreen , uSize; GLint uScreenBG, uSizeBG; }; bool egl_help_init(EGL_Help ** help, const LG_Font * font, LG_FontObj fontObj) { *help = (EGL_Help *)malloc(sizeof(EGL_Help)); if (!*help) { DEBUG_ERROR("Failed to malloc EGL_Help"); return false; } memset(*help, 0, sizeof(EGL_Help)); (*help)->font = font; (*help)->fontObj = fontObj; if (!egl_texture_init(&(*help)->texture, NULL)) { DEBUG_ERROR("Failed to initialize the help texture"); return false; } if (!egl_shader_init(&(*help)->shader)) { DEBUG_ERROR("Failed to initialize the help shader"); return false; } if (!egl_shader_init(&(*help)->shaderBG)) { DEBUG_ERROR("Failed to initialize the help bg shader"); return false; } if (!egl_shader_compile((*help)->shader, b_shader_help_vert, b_shader_help_vert_size, b_shader_help_frag, b_shader_help_frag_size)) { DEBUG_ERROR("Failed to compile the help shader"); return false; } if (!egl_shader_compile((*help)->shaderBG, b_shader_help_vert , b_shader_help_vert_size, b_shader_help_bg_frag, b_shader_help_bg_frag_size)) { DEBUG_ERROR("Failed to compile the help shader"); return false; } (*help)->uSize = egl_shader_get_uniform_location((*help)->shader , "size" ); (*help)->uScreen = egl_shader_get_uniform_location((*help)->shader , "screen"); (*help)->uSizeBG = egl_shader_get_uniform_location((*help)->shaderBG, "size" ); (*help)->uScreenBG = egl_shader_get_uniform_location((*help)->shaderBG, "screen"); if (!egl_model_init(&(*help)->model)) { DEBUG_ERROR("Failed to initialize the fps model"); return false; } egl_model_set_default((*help)->model); egl_model_set_texture((*help)->model, (*help)->texture); atomic_init(&(*help)->bmp, NULL); return true; } void egl_help_free(EGL_Help ** help) { if (!*help) return; egl_texture_free(&(*help)->texture ); egl_shader_free (&(*help)->shader ); egl_shader_free (&(*help)->shaderBG); egl_model_free (&(*help)->model ); free(*help); *help = NULL; } void egl_help_set_text(EGL_Help * help, const char * help_text) { LG_FontBitmap * bmp = NULL; if (help_text) { bmp = help->font->render(help->fontObj, 0xffffff00, help_text); if (!bmp) DEBUG_ERROR("Failed to render help text"); } else help->shouldRender = false; bmp = atomic_exchange(&help->bmp, bmp); if (bmp) { help->font->release(help->fontObj, bmp); } } void egl_help_set_font(EGL_Help * help, LG_FontObj fontObj) { help->fontObj = fontObj; } void egl_help_render(EGL_Help * help, const float scaleX, const float scaleY) { LG_FontBitmap * bmp = atomic_exchange(&help->bmp, NULL); if (bmp) { if (help->iwidth != bmp->width || help->iheight != bmp->height) { help->iwidth = bmp->width; help->iheight = bmp->height; help->width = (float)bmp->width; help->height = (float)bmp->height; egl_texture_setup( help->texture, EGL_PF_BGRA, bmp->width , bmp->height, bmp->width * bmp->bpp, false, false ); } egl_texture_update ( help->texture, bmp->pixels ); help->shouldRender = true; help->font->release(help->fontObj, bmp); } if (!help->shouldRender) return; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // render the background first egl_shader_use(help->shaderBG); glUniform2f(help->uScreenBG, scaleX , scaleY ); glUniform2f(help->uSizeBG , help->width, help->height); egl_model_render(help->model); // render the texture over the background egl_shader_use(help->shader); glUniform2f(help->uScreen, scaleX , scaleY ); glUniform2f(help->uSize , help->width, help->height); egl_model_render(help->model); glDisable(GL_BLEND); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/help.h000066400000000000000000000023341411177132200223230ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include "interface/font.h" typedef struct EGL_Help EGL_Help; bool egl_help_init(EGL_Help ** help, const LG_Font * font, LG_FontObj fontObj); void egl_help_free(EGL_Help ** help); void egl_help_set_text(EGL_Help * help, const char * help_text); void egl_help_set_font(EGL_Help * help, LG_FontObj fontObj); void egl_help_render(EGL_Help * help, const float scaleX, const float scaleY); looking-glass-0+b4+dfsg.1/client/renderers/EGL/model.c000066400000000000000000000122471411177132200224720ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "model.h" #include "shader.h" #include "texture.h" #include "common/debug.h" #include "ll.h" #include #include #include #include struct EGL_Model { bool rebuild; struct ll * verticies; size_t vertexCount; bool finish; bool hasBuffer; GLuint buffer; EGL_Shader * shader; EGL_Texture * texture; }; struct FloatList { GLfloat * v; GLfloat * u; size_t count; }; void update_uniform_bindings(EGL_Model * model); bool egl_model_init(EGL_Model ** model) { *model = (EGL_Model *)malloc(sizeof(EGL_Model)); if (!*model) { DEBUG_ERROR("Failed to malloc EGL_Model"); return false; } memset(*model, 0, sizeof(EGL_Model)); (*model)->verticies = ll_new(); return true; } void egl_model_free(EGL_Model ** model) { if (!*model) return; struct FloatList * fl; while(ll_shift((*model)->verticies, (void **)&fl)) { free(fl->u); free(fl->v); free(fl); } ll_free((*model)->verticies); if ((*model)->hasBuffer) glDeleteBuffers(1, &(*model)->buffer); free(*model); *model = NULL; } void egl_model_set_default(EGL_Model * model) { static const GLfloat square[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f }; static const GLfloat uvs[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f }; egl_model_add_verticies(model, square, uvs, 4); } void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count) { struct FloatList * fl = (struct FloatList *)malloc(sizeof(struct FloatList)); fl->count = count; fl->v = (GLfloat *)malloc(sizeof(GLfloat) * count * 3); fl->u = (GLfloat *)malloc(sizeof(GLfloat) * count * 2); memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3); if (uvs) memcpy(fl->u, uvs, sizeof(GLfloat) * count * 2); else memset(fl->u, 0 , sizeof(GLfloat) * count * 2); ll_push(model->verticies, fl); model->rebuild = true; model->vertexCount += count; } void egl_model_render(EGL_Model * model) { if (!model->vertexCount) return; if (model->rebuild) { if (model->hasBuffer) glDeleteBuffers(1, &model->buffer); /* create a buffer large enough */ glGenBuffers(1, &model->buffer); glBindBuffer(GL_ARRAY_BUFFER, model->buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * (model->vertexCount * 5), NULL, GL_STATIC_DRAW); GLintptr offset = 0; /* buffer the verticies */ struct FloatList * 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 */ 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; } glBindBuffer(GL_ARRAY_BUFFER, 0); model->rebuild = false; } /* bind the model buffer and setup the pointers */ glBindBuffer(GL_ARRAY_BUFFER, model->buffer); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat) * model->vertexCount * 3)); if (model->shader) egl_shader_use(model->shader); if (model->texture) egl_texture_bind(model->texture); /* draw the arrays */ GLint offset = 0; struct FloatList * fl; for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);) { glDrawArrays(GL_TRIANGLE_STRIP, offset, fl->count); offset += fl->count; } /* unbind and cleanup */ glBindTexture(GL_TEXTURE_2D, 0); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); glUseProgram(0); } void egl_model_set_shader(EGL_Model * model, EGL_Shader * shader) { model->shader = shader; update_uniform_bindings(model); } void egl_model_set_texture(EGL_Model * model, EGL_Texture * texture) { model->texture = texture; update_uniform_bindings(model); } void update_uniform_bindings(EGL_Model * model) { if (!model->shader || !model->texture) return; const int count = egl_texture_count(model->texture); egl_shader_associate_textures(model->shader, count); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/model.h000066400000000000000000000025541411177132200224770ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include "shader.h" #include "texture.h" #include typedef struct EGL_Model EGL_Model; bool egl_model_init(EGL_Model ** model); void egl_model_free(EGL_Model ** model); void egl_model_set_default (EGL_Model * model); void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count); void egl_model_set_shader (EGL_Model * model, EGL_Shader * shader); void egl_model_set_texture (EGL_Model * model, EGL_Texture * texture); void egl_model_render(EGL_Model * model);looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader.c000066400000000000000000000131471411177132200226400ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "shader.h" #include "common/debug.h" #include "util.h" #include #include #include struct EGL_Shader { bool hasShader; GLuint shader; }; bool egl_shader_init(EGL_Shader ** this) { *this = (EGL_Shader *)malloc(sizeof(EGL_Shader)); if (!*this) { DEBUG_ERROR("Failed to malloc EGL_Shader"); return false; } memset(*this, 0, sizeof(EGL_Shader)); return true; } void egl_shader_free(EGL_Shader ** this) { if (!*this) return; if ((*this)->hasShader) glDeleteProgram((*this)->shader); free(*this); *this = NULL; } bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * fragment_file) { char * vertex_code, * fragment_code; size_t vertex_size, fragment_size; if (!util_fileGetContents(vertex_file, &vertex_code, &vertex_size)) { DEBUG_ERROR("Failed to read vertex shader"); return false; } DEBUG_INFO("Loaded vertex shader: %s", vertex_file); if (!util_fileGetContents(fragment_file, &fragment_code, &fragment_size)) { DEBUG_ERROR("Failed to read fragment shader"); free(vertex_code); return false; } DEBUG_INFO("Loaded fragment shader: %s", fragment_file); bool ret = egl_shader_compile(this, vertex_code, vertex_size, fragment_code, fragment_size); free(vertex_code); free(fragment_code); return ret; } bool egl_shader_compile(EGL_Shader * this, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size) { if (this->hasShader) { glDeleteProgram(this->shader); this->hasShader = false; } GLint length; GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); length = vertex_size; glShaderSource(vertexShader, 1, (const char**)&vertex_code, &length); glCompileShader(vertexShader); GLint result = GL_FALSE; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result); if (result == GL_FALSE) { DEBUG_ERROR("Failed to compile vertex shader"); int logLength; glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { char *log = malloc(logLength + 1); glGetShaderInfoLog(vertexShader, logLength, NULL, log); log[logLength] = 0; DEBUG_ERROR("%s", log); free(log); } glDeleteShader(vertexShader); return false; } GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); length = fragment_size; glShaderSource(fragmentShader, 1, (const char**)&fragment_code, &length); glCompileShader(fragmentShader); glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result); if (result == GL_FALSE) { DEBUG_ERROR("Failed to compile fragment shader"); int logLength; glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { char *log = malloc(logLength + 1); glGetShaderInfoLog(fragmentShader, logLength, NULL, log); log[logLength] = 0; DEBUG_ERROR("%s", log); free(log); } glDeleteShader(fragmentShader); glDeleteShader(vertexShader ); return false; } this->shader = glCreateProgram(); glAttachShader(this->shader, vertexShader ); glAttachShader(this->shader, fragmentShader); glLinkProgram(this->shader); glGetProgramiv(this->shader, GL_LINK_STATUS, &result); if (result == GL_FALSE) { DEBUG_ERROR("Failed to link shader program"); int logLength; glGetProgramiv(this->shader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { char *log = malloc(logLength + 1); glGetProgramInfoLog(this->shader, logLength, NULL, log); log[logLength] = 0; DEBUG_ERROR("%s", log); free(log); } glDetachShader(this->shader, vertexShader ); glDetachShader(this->shader, fragmentShader); glDeleteShader(fragmentShader); glDeleteShader(vertexShader ); glDeleteProgram(this->shader ); return false; } glDetachShader(this->shader, vertexShader ); glDetachShader(this->shader, fragmentShader); glDeleteShader(fragmentShader); glDeleteShader(vertexShader ); this->hasShader = true; return true; } void egl_shader_use(EGL_Shader * this) { if (this->hasShader) glUseProgram(this->shader); else DEBUG_ERROR("Shader program has not been compiled"); } void egl_shader_associate_textures(EGL_Shader * this, const int count) { char name[] = "sampler1"; glUseProgram(this->shader); for(int i = 0; i < count; ++i, name[7]++) { GLint loc = glGetUniformLocation(this->shader, name); if (loc == -1) { DEBUG_WARN("Shader uniform location `%s` not found", name); continue; } glUniform1i(loc, i); } glUseProgram(0); } GLint egl_shader_get_uniform_location(EGL_Shader * this, const char * name) { if (!this->shader) { DEBUG_ERROR("Shader program has not been compiled"); return 0; } return glGetUniformLocation(this->shader, name); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader.h000066400000000000000000000026671411177132200226520ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include #include typedef struct EGL_Shader EGL_Shader; bool egl_shader_init(EGL_Shader ** shader); void egl_shader_free(EGL_Shader ** shader); bool egl_shader_load (EGL_Shader * model, const char * vertex_file, const char * fragment_file); bool egl_shader_compile(EGL_Shader * model, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size); void egl_shader_use (EGL_Shader * shader); void egl_shader_associate_textures(EGL_Shader * shader, const int count); GLint egl_shader_get_uniform_location(EGL_Shader * shader, const char * name);looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/000077500000000000000000000000001411177132200224665ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/alert.frag000066400000000000000000000002561411177132200244410ustar00rootroot00000000000000#version 300 es in highp vec2 uv; in highp vec2 sz; out highp vec4 color; uniform sampler2D sampler1; void main() { color = texelFetch(sampler1, ivec2(uv * sz), 0); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/alert.vert000066400000000000000000000006401411177132200244770ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; uniform vec2 screen; uniform ivec2 size; uniform vec4 color; out highp vec2 uv; out highp vec2 sz; out highp vec4 c; void main() { sz = vec2(size) + 0.5; gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; gl_Position.xy *= screen.xy * sz; uv = vertexUV; c = color; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/alert_bg.frag000066400000000000000000000001271411177132200251060ustar00rootroot00000000000000#version 300 es in highp vec4 c; out highp vec4 color; void main() { color = c; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/cursor.vert000066400000000000000000000013401411177132200247030ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; uniform vec4 mouse; uniform lowp int rotate; out highp vec2 uv; void main() { vec2 muv = vertexPosition_modelspace.xy; muv.x += 1.0f; muv.y -= 1.0f; muv.x *= mouse.z; muv.y *= mouse.w; muv.x += mouse.x; muv.y -= mouse.y; if (rotate == 0) // 0 { gl_Position.xy = muv; } else if (rotate == 1) // 90 { gl_Position.x = muv.y; gl_Position.y = -muv.x; } else if (rotate == 2) // 180 { gl_Position.x = -muv.x; gl_Position.y = -muv.y; } else if (rotate == 3) // 270 { gl_Position.x = -muv.y; gl_Position.y = muv.x; } gl_Position.w = 1.0; uv = vertexUV; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/cursor_mono.frag000066400000000000000000000003241411177132200256730ustar00rootroot00000000000000#version 300 es in highp vec2 uv; out highp vec4 color; uniform sampler2D sampler1; void main() { highp vec4 tmp = texture(sampler1, uv); if (tmp.rgb == vec3(0.0, 0.0, 0.0)) discard; color = tmp; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/cursor_rgb.frag000066400000000000000000000027351411177132200255050ustar00rootroot00000000000000#version 300 es in highp vec2 uv; out highp vec4 color; uniform sampler2D sampler1; uniform lowp int rotate; uniform int cbMode; void main() { color = texture(sampler1, uv); if (cbMode > 0) { highp float L = (17.8824000 * color.r) + (43.516100 * color.g) + (4.11935 * color.b); highp float M = (03.4556500 * color.r) + (27.155400 * color.g) + (3.86714 * color.b); highp float S = (00.0299566 * color.r) + (00.184309 * color.g) + (1.46709 * color.b); highp float l, m, s; if (cbMode == 1) // Protanope { l = 0.0f * L + 2.02344f * M + -2.52581f * S; m = 0.0f * L + 1.0f * M + 0.0f * S; s = 0.0f * L + 0.0f * M + 1.0f * S; } else if (cbMode == 2) // Deuteranope { l = 1.000000 * L + 0.0f * M + 0.00000 * S; m = 0.494207 * L + 0.0f * M + 1.24827 * S; s = 0.000000 * L + 0.0f * M + 1.00000 * S; } else if (cbMode == 3) // Tritanope { l = 1.000000 * L + 0.000000 * M + 0.0 * S; m = 0.000000 * L + 1.000000 * M + 0.0 * S; s = -0.395913 * L + 0.801109 * M + 0.0 * S; } highp vec4 error; error.r = ( 0.080944447900 * l) + (-0.13050440900 * m) + ( 0.116721066 * s); error.g = (-0.010248533500 * l) + ( 0.05401932660 * m) + (-0.113614708 * s); error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + ( 0.693511405 * s); error.a = 0.0; error = color - error; color.g += (error.r * 0.7) + (error.g * 1.0); color.b += (error.r * 0.7) + (error.b * 1.0); } } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/desktop.vert000066400000000000000000000006101411177132200250360ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; uniform vec4 position; out highp vec2 uv; void main() { gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; gl_Position.x -= position.x; gl_Position.y -= position.y; gl_Position.x *= position.z; gl_Position.y *= position.w; uv = vertexUV; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/desktop_rgb.frag000066400000000000000000000045471411177132200256440ustar00rootroot00000000000000#version 300 es #define EGL_SCALE_AUTO 0 #define EGL_SCALE_NEAREST 1 #define EGL_SCALE_LINEAR 2 #define EGL_SCALE_MAX 3 in highp vec2 uv; out highp vec4 color; uniform sampler2D sampler1; uniform int scaleAlgo; uniform highp vec2 size; uniform int rotate; uniform int nv; uniform highp float nvGain; uniform int cbMode; void main() { highp vec2 ruv; if (rotate == 0) // 0 { ruv = uv; } else if (rotate == 1) // 90 { ruv.x = uv.y; ruv.y = -uv.x + 1.0f; } else if (rotate == 2) // 180 { ruv.x = -uv.x + 1.0f; ruv.y = -uv.y + 1.0f; } else if (rotate == 3) // 270 { ruv.x = -uv.y + 1.0f; ruv.y = uv.x; } switch (scaleAlgo) { case EGL_SCALE_NEAREST: color = texelFetch(sampler1, ivec2(ruv * size), 0); break; case EGL_SCALE_LINEAR: color = texture(sampler1, ruv); break; } if (cbMode > 0) { highp float L = (17.8824000 * color.r) + (43.516100 * color.g) + (4.11935 * color.b); highp float M = (03.4556500 * color.r) + (27.155400 * color.g) + (3.86714 * color.b); highp float S = (00.0299566 * color.r) + (00.184309 * color.g) + (1.46709 * color.b); highp float l, m, s; if (cbMode == 1) // Protanope { l = 0.0f * L + 2.02344f * M + -2.52581f * S; m = 0.0f * L + 1.0f * M + 0.0f * S; s = 0.0f * L + 0.0f * M + 1.0f * S; } else if (cbMode == 2) // Deuteranope { l = 1.000000 * L + 0.0f * M + 0.00000 * S; m = 0.494207 * L + 0.0f * M + 1.24827 * S; s = 0.000000 * L + 0.0f * M + 1.00000 * S; } else if (cbMode == 3) // Tritanope { l = 1.000000 * L + 0.000000 * M + 0.0 * S; m = 0.000000 * L + 1.000000 * M + 0.0 * S; s = -0.395913 * L + 0.801109 * M + 0.0 * S; } highp vec4 error; error.r = ( 0.080944447900 * l) + (-0.13050440900 * m) + ( 0.116721066 * s); error.g = (-0.010248533500 * l) + ( 0.05401932660 * m) + (-0.113614708 * s); error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + ( 0.693511405 * s); error.a = 0.0; error = color - error; color.g += (error.r * 0.7) + (error.g * 1.0); color.b += (error.r * 0.7) + (error.b * 1.0); } if (nv == 1) { highp float lumi = 1.0 - (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b); color *= 1.0 + lumi; color *= nvGain; } color.a = 1.0; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/fps.frag000066400000000000000000000002111411177132200241110ustar00rootroot00000000000000#version 300 es in highp vec2 uv; out highp vec4 color; uniform sampler2D sampler1; void main() { color = texture(sampler1, uv); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/fps.vert000066400000000000000000000007511411177132200241630ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; uniform vec2 screen; uniform vec2 size; out highp vec2 uv; void main() { gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; gl_Position.xy *= screen.xy * size.xy; gl_Position.x -= 1.0 - (screen.x * size.x); gl_Position.y += 1.0 - (screen.y * size.y); gl_Position.x += screen.x * 10.0; gl_Position.y -= screen.y * 10.0; uv = vertexUV; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/fps_bg.frag000066400000000000000000000001341411177132200245650ustar00rootroot00000000000000#version 300 es out highp vec4 color; void main() { color = vec4(0.0, 0.0, 1.0, 0.5); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/help.frag000066400000000000000000000002111411177132200242510ustar00rootroot00000000000000#version 300 es in highp vec2 uv; out highp vec4 color; uniform sampler2D sampler1; void main() { color = texture(sampler1, uv); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/help.vert000066400000000000000000000007511411177132200243230ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; uniform vec2 screen; uniform vec2 size; out highp vec2 uv; void main() { gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; gl_Position.xy *= screen.xy * size.xy; gl_Position.x -= 1.0 - (screen.x * size.x); gl_Position.y -= 1.0 - (screen.y * size.y); gl_Position.x += screen.x * 10.0; gl_Position.y += screen.y * 10.0; uv = vertexUV; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/help_bg.frag000066400000000000000000000001341411177132200247250ustar00rootroot00000000000000#version 300 es out highp vec4 color; void main() { color = vec4(0.0, 0.0, 1.0, 0.5); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/splash_bg.frag000066400000000000000000000003551411177132200252740ustar00rootroot00000000000000#version 300 es in highp vec3 pos; out highp vec4 color; uniform sampler2D sampler1; void main() { highp float d = 1.0 - sqrt(pos.x * pos.x + pos.y * pos.y) / 2.0; color = vec4(0.234375 * d, 0.015625f * d, 0.425781f * d, 1); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/splash_bg.vert000066400000000000000000000003601411177132200253310ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; out highp vec3 pos; out highp float a; void main() { gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; pos = vertexPosition_modelspace; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/splash_logo.frag000066400000000000000000000001711411177132200256400ustar00rootroot00000000000000#version 300 es out highp vec4 color; uniform sampler2D sampler1; void main() { color = vec4(1.0, 1.0, 1.0, 1.0); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/shader/splash_logo.vert000066400000000000000000000003231411177132200257000ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; uniform float scale; void main() { gl_Position.xyz = vertexPosition_modelspace; gl_Position.y *= scale; gl_Position.w = 1.0; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/splash.c000066400000000000000000000111531411177132200226570ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "splash.h" #include "common/debug.h" #include "draw.h" #include "texture.h" #include "shader.h" #include "model.h" #include #include #include #include // these headers are auto generated by cmake #include "splash_bg.vert.h" #include "splash_bg.frag.h" #include "splash_logo.vert.h" #include "splash_logo.frag.h" struct EGL_Splash { EGL_Shader * bgShader; EGL_Model * bg; EGL_Shader * logoShader; EGL_Model * logo; // uniforms GLint uScale; }; bool egl_splash_init(EGL_Splash ** splash) { *splash = (EGL_Splash *)malloc(sizeof(EGL_Splash)); if (!*splash) { DEBUG_ERROR("Failed to malloc EGL_Splash"); return false; } memset(*splash, 0, sizeof(EGL_Splash)); if (!egl_shader_init(&(*splash)->bgShader)) { DEBUG_ERROR("Failed to initialize the splash bgShader"); return false; } if (!egl_shader_compile((*splash)->bgShader, b_shader_splash_bg_vert, b_shader_splash_bg_vert_size, b_shader_splash_bg_frag, b_shader_splash_bg_frag_size)) { DEBUG_ERROR("Failed to compile the splash bgShader"); return false; } if (!egl_model_init(&(*splash)->bg)) { DEBUG_ERROR("Failed to intiailize the splash bg model"); return false; } egl_model_set_default((*splash)->bg); if (!egl_shader_init(&(*splash)->logoShader)) { DEBUG_ERROR("Failed to initialize the splash logoShader"); return false; } if (!egl_shader_compile((*splash)->logoShader, b_shader_splash_logo_vert, b_shader_splash_logo_vert_size, b_shader_splash_logo_frag, b_shader_splash_logo_frag_size)) { DEBUG_ERROR("Failed to compile the splash logoShader"); return false; } (*splash)->uScale = egl_shader_get_uniform_location((*splash)->logoShader, "scale"); if (!egl_model_init(&(*splash)->logo)) { DEBUG_ERROR("Failed to intiailize the splash model"); return false; } /* build the splash model */ #define P(x) ((1.0f/800.0f)*(float)(x)) egl_draw_torus_arc((*splash)->logo, 30, P( 0 ), P(0), P(102), P(98), 0.0f, -M_PI); egl_draw_torus ((*splash)->logo, 30, P(-100), P(8), P(8 ), P(4 )); egl_draw_torus ((*splash)->logo, 30, P( 100), P(8), P(8 ), P(4 )); egl_draw_torus ((*splash)->logo, 60, P(0), P(0), P(83), P(79)); egl_draw_torus ((*splash)->logo, 60, P(0), P(0), P(67), P(63)); static const GLfloat lines[][12] = { { P( -2), P(-140), 0.0f, P( -2), P(-100), 0.0f, P( 2), P(-140), 0.0f, P( 2), P(-100), 0.0f }, { P(-26), P(-144), 0.0f, P(-26), P(-140), 0.0f, P( 26), P(-144), 0.0f, P( 26), P(-140), 0.0f }, { P(-40), P(-156), 0.0f, P(-40), P(-152), 0.0f, P( 40), P(-156), 0.0f, P( 40), P(-152), 0.0f } }; egl_model_add_verticies((*splash)->logo, lines[0], NULL, 4); egl_model_add_verticies((*splash)->logo, lines[1], NULL, 4); egl_model_add_verticies((*splash)->logo, lines[2], NULL, 4); egl_draw_torus_arc((*splash)->logo, 10, P(-26), P(-154), P(10), P(14), M_PI , -M_PI / 2.0); egl_draw_torus_arc((*splash)->logo, 10, P( 26), P(-154), P(10), P(14), M_PI / 2.0f, -M_PI / 2.0); #undef P return true; } void egl_splash_free(EGL_Splash ** splash) { if (!*splash) return; egl_model_free(&(*splash)->bg ); egl_model_free(&(*splash)->logo); egl_shader_free(&(*splash)->bgShader ); egl_shader_free(&(*splash)->logoShader); free(*splash); *splash = NULL; } void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY) { glEnable(GL_BLEND); glBlendColor(0, 0, 0, alpha); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); egl_shader_use(splash->bgShader); egl_model_render(splash->bg); egl_shader_use(splash->logoShader); glUniform1f(splash->uScale, scaleY); egl_model_render(splash->logo); glDisable(GL_BLEND); } looking-glass-0+b4+dfsg.1/client/renderers/EGL/splash.h000066400000000000000000000020371411177132200226650ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include typedef struct EGL_Splash EGL_Splash; bool egl_splash_init(EGL_Splash ** splash); void egl_splash_free(EGL_Splash ** splash); void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY);looking-glass-0+b4+dfsg.1/client/renderers/EGL/texture.c000066400000000000000000000377241411177132200231010ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "texture.h" #include "common/debug.h" #include "common/framebuffer.h" #include "egl_dynprocs.h" #include "egldebug.h" #include #include #include #include /** * the following comes from drm_fourcc.h and is included here to avoid the * external dependency for the few simple defines we need */ #define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \ ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) #define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4') #define DRM_FORMAT_ABGR8888 fourcc_code('A', 'B', '2', '4') #define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0') #define DRM_FORMAT_ABGR16161616F fourcc_code('A', 'B', '4', 'H') /* this must be a multiple of 2 */ #define BUFFER_COUNT 4 struct Buffer { bool hasPBO; GLuint pbo; void * map; GLsync sync; }; struct BufferState { _Atomic(uint8_t) w, u, s, d; }; struct EGL_Texture { EGLDisplay * display; enum EGL_PixelFormat pixFmt; size_t bpp; bool streaming; bool dma; bool ready; GLuint sampler; size_t width, height, stride, pitch; GLenum intFormat; GLenum format; GLenum dataType; unsigned int fourcc; size_t pboBufferSize; struct BufferState state; int bufferCount; GLuint tex[BUFFER_COUNT]; struct Buffer buf[BUFFER_COUNT]; size_t dmaImageCount; size_t dmaImageUsed; struct { int fd; EGLImage image; } * dmaImages; GLuint dmaFBO; GLuint dmaTex; }; bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display) { *texture = (EGL_Texture *)malloc(sizeof(EGL_Texture)); if (!*texture) { DEBUG_ERROR("Failed to malloc EGL_Texture"); return false; } memset(*texture, 0, sizeof(EGL_Texture)); (*texture)->display = display; return true; } void egl_texture_free(EGL_Texture ** texture) { if (!*texture) return; glDeleteSamplers(1, &(*texture)->sampler); for(int i = 0; i < (*texture)->bufferCount; ++i) { struct Buffer * b = &(*texture)->buf[i]; if (b->hasPBO) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, b->pbo); if ((*texture)->buf[i].map) { glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); (*texture)->buf[i].map = NULL; } glDeleteBuffers(1, &b->pbo); if (b->sync) glDeleteSync(b->sync); } } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glDeleteTextures((*texture)->bufferCount, (*texture)->tex); for (size_t i = 0; i < (*texture)->dmaImageUsed; ++i) eglDestroyImage((*texture)->display, (*texture)->dmaImages[i].image); free((*texture)->dmaImages); free(*texture); *texture = NULL; } static bool egl_texture_map(EGL_Texture * texture, uint8_t i) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo); texture->buf[i].map = glMapBufferRange( GL_PIXEL_UNPACK_BUFFER, 0, texture->pboBufferSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT ); if (!texture->buf[i].map) { DEBUG_EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize); } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); return texture->buf[i].map; } static void egl_texture_unmap(EGL_Texture * texture, uint8_t i) { if (!texture->buf[i].map) return; glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); texture->buf[i].map = NULL; } bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming, bool useDMA) { if (texture->streaming && !useDMA) { for(int i = 0; i < texture->bufferCount; ++i) { egl_texture_unmap(texture, i); if (texture->buf[i].hasPBO) { glDeleteBuffers(1, &texture->buf[i].pbo); texture->buf[i].hasPBO = false; } } } texture->pixFmt = pixFmt; texture->width = width; texture->height = height; texture->stride = stride; texture->streaming = streaming; texture->bufferCount = streaming ? BUFFER_COUNT : 1; texture->dma = useDMA; texture->ready = false; atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed); atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed); atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed); atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed); switch(pixFmt) { case EGL_PF_BGRA: texture->bpp = 4; texture->format = GL_BGRA; texture->intFormat = GL_BGRA; texture->dataType = GL_UNSIGNED_BYTE; texture->fourcc = DRM_FORMAT_ARGB8888; texture->pboBufferSize = height * stride; break; case EGL_PF_RGBA: texture->bpp = 4; texture->format = GL_RGBA; texture->intFormat = GL_BGRA; texture->dataType = GL_UNSIGNED_BYTE; texture->fourcc = DRM_FORMAT_ABGR8888; texture->pboBufferSize = height * stride; break; case EGL_PF_RGBA10: texture->bpp = 4; texture->format = GL_RGBA; texture->intFormat = GL_RGB10_A2; texture->dataType = GL_UNSIGNED_INT_2_10_10_10_REV; texture->fourcc = DRM_FORMAT_BGRA1010102; texture->pboBufferSize = height * stride; break; case EGL_PF_RGBA16F: texture->bpp = 8; texture->format = GL_RGBA; texture->intFormat = GL_RGBA16F; texture->dataType = GL_HALF_FLOAT; texture->fourcc = DRM_FORMAT_ABGR16161616F; texture->pboBufferSize = height * stride; break; default: DEBUG_ERROR("Unsupported pixel format"); return false; } texture->pitch = stride / texture->bpp; if (texture->tex[0]) glDeleteTextures(texture->bufferCount, texture->tex); glGenTextures(texture->bufferCount, texture->tex); if (!texture->sampler) { glGenSamplers(1, &texture->sampler); glSamplerParameteri(texture->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(texture->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(texture->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glSamplerParameteri(texture->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); } if (useDMA) { if (texture->dmaFBO) glDeleteFramebuffers(1, &texture->dmaFBO); if (texture->dmaTex) glDeleteTextures(1, &texture->dmaTex); glGenFramebuffers(1, &texture->dmaFBO); glGenTextures(1, &texture->dmaTex); for (size_t i = 0; i < texture->dmaImageUsed; ++i) eglDestroyImage(texture->display, texture->dmaImages[i].image); texture->dmaImageUsed = 0; return true; } for(int i = 0; i < texture->bufferCount; ++i) { glBindTexture(GL_TEXTURE_2D, texture->tex[i]); glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->width, texture->height, 0, texture->format, texture->dataType, NULL); } glBindTexture(GL_TEXTURE_2D, 0); if (!streaming) return true; for(int i = 0; i < texture->bufferCount; ++i) { glGenBuffers(1, &texture->buf[i].pbo); texture->buf[i].hasPBO = true; glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo); glBufferStorage( GL_PIXEL_UNPACK_BUFFER, texture->pboBufferSize, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT ); if (!egl_texture_map(texture, i)) return false; } return true; } static void egl_warn_slow(void) { static bool warnDone = false; if (!warnDone) { warnDone = true; DEBUG_BREAK(); DEBUG_WARN("The guest is providing updates faster then your computer can display them"); DEBUG_WARN("This is a hardware limitation, expect microstutters & frame skips"); DEBUG_BREAK(); } } bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer) { if (texture->streaming) { const uint8_t sw = atomic_load_explicit(&texture->state.w, memory_order_acquire); if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1)) { egl_warn_slow(); return true; } const uint8_t b = sw % BUFFER_COUNT; memcpy(texture->buf[b].map, buffer, texture->pboBufferSize); atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release); } else { glBindTexture(GL_TEXTURE_2D, texture->tex[0]); glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->pitch); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height, texture->format, texture->dataType, buffer); glBindTexture(GL_TEXTURE_2D, 0); } return true; } bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame) { if (!texture->streaming) return false; const uint8_t sw = atomic_load_explicit(&texture->state.w, memory_order_acquire); if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1)) { egl_warn_slow(); return true; } const uint8_t b = sw % BUFFER_COUNT; framebuffer_read( frame, texture->buf[b].map, texture->stride, texture->height, texture->width, texture->bpp, texture->stride ); atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release); return true; } bool egl_texture_update_from_dma(EGL_Texture * texture, const FrameBuffer * frame, const int dmaFd) { if (!texture->streaming) return false; const uint8_t sw = atomic_load_explicit(&texture->state.w, memory_order_acquire); if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1)) { egl_warn_slow(); return true; } EGLImage image = EGL_NO_IMAGE; for (int i = 0; i < texture->dmaImageUsed; ++i) { if (texture->dmaImages[i].fd == dmaFd) { image = texture->dmaImages[i].image; break; } } if (image == EGL_NO_IMAGE) { EGLAttrib const attribs[] = { EGL_WIDTH , texture->width, EGL_HEIGHT , texture->height, EGL_LINUX_DRM_FOURCC_EXT , texture->fourcc, EGL_DMA_BUF_PLANE0_FD_EXT , dmaFd, EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, EGL_DMA_BUF_PLANE0_PITCH_EXT , texture->stride, EGL_NONE , EGL_NONE }; /* create the image backed by the dma buffer */ image = eglCreateImage( texture->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL, attribs ); if (image == EGL_NO_IMAGE) { DEBUG_EGL_ERROR("Failed to create ELGImage for DMA transfer"); return false; } if (texture->dmaImageUsed == texture->dmaImageCount) { size_t newCount = texture->dmaImageCount * 2 + 2; void * new = realloc(texture->dmaImages, newCount * sizeof *texture->dmaImages); if (!new) { DEBUG_EGL_ERROR("Failed to allocate memory"); eglDestroyImage(texture->display, image); return false; } texture->dmaImageCount = newCount; texture->dmaImages = new; } const size_t index = texture->dmaImageUsed++; texture->dmaImages[index].fd = dmaFd; texture->dmaImages[index].image = image; } /* wait for completion */ framebuffer_wait(frame, texture->height * texture->stride); glBindTexture(GL_TEXTURE_2D, texture->dmaTex); g_egl_dynProcs.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); glBindFramebuffer(GL_FRAMEBUFFER, texture->dmaFBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->dmaTex, 0); glBindTexture(GL_TEXTURE_2D, texture->tex[0]); glCopyTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, 0, 0, texture->width, texture->height, 0); GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); switch (glClientWaitSync(fence, 0, 10000000)) // 10ms { case GL_ALREADY_SIGNALED: case GL_CONDITION_SATISFIED: break; case GL_TIMEOUT_EXPIRED: egl_warn_slow(); break; case GL_WAIT_FAILED: case GL_INVALID_VALUE: DEBUG_EGL_ERROR("glClientWaitSync failed"); } glDeleteSync(fence); atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release); return true; } enum EGL_TexStatus egl_texture_process(EGL_Texture * texture) { if (!texture->streaming) return EGL_TEX_STATUS_OK; const uint8_t su = atomic_load_explicit(&texture->state.u, memory_order_acquire); const uint8_t nextu = su + 1; if ( su == atomic_load_explicit(&texture->state.w, memory_order_acquire) || nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) || nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire)) return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY; const uint8_t b = su % BUFFER_COUNT; /* update the texture */ if (!texture->dma) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[b].pbo); glBindTexture(GL_TEXTURE_2D, texture->tex[b]); glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->pitch); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height, texture->format, texture->dataType, (const void *)0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); /* create a fence to prevent usage before the update is complete */ texture->buf[b].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); /* we must flush to ensure the sync is in the command buffer */ glFlush(); } texture->ready = true; atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release); return EGL_TEX_STATUS_OK; } enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture) { uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire); uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire); GLuint tex = texture->tex[0]; if (texture->streaming) { if (!texture->ready) return EGL_TEX_STATUS_NOTREADY; const uint8_t b = ss % BUFFER_COUNT; if (texture->dma) { ss = atomic_fetch_add_explicit(&texture->state.s, 1, memory_order_release) + 1; } else if (texture->buf[b].sync != 0) { switch(glClientWaitSync(texture->buf[b].sync, 0, 20000000)) // 20ms { case GL_ALREADY_SIGNALED: case GL_CONDITION_SATISFIED: glDeleteSync(texture->buf[b].sync); texture->buf[b].sync = 0; ss = atomic_fetch_add_explicit(&texture->state.s, 1, memory_order_release) + 1; break; case GL_TIMEOUT_EXPIRED: break; case GL_WAIT_FAILED: case GL_INVALID_VALUE: glDeleteSync(texture->buf[b].sync); texture->buf[b].sync = 0; DEBUG_EGL_ERROR("glClientWaitSync failed"); return EGL_TEX_STATUS_ERROR; } } if (ss != sd && ss != (uint8_t)(sd + 1)) sd = atomic_fetch_add_explicit(&texture->state.d, 1, memory_order_release) + 1; if (!texture->dma) tex = texture->tex[sd % BUFFER_COUNT]; } glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); glBindSampler(0, texture->sampler); return EGL_TEX_STATUS_OK; } int egl_texture_count(EGL_Texture * texture) { return 1; } looking-glass-0+b4+dfsg.1/client/renderers/EGL/texture.h000066400000000000000000000037241411177132200230770ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include "shader.h" #include "common/framebuffer.h" #include #include #include typedef struct EGL_Texture EGL_Texture; enum EGL_PixelFormat { EGL_PF_RGBA, EGL_PF_BGRA, EGL_PF_RGBA10, EGL_PF_RGBA16F, EGL_PF_YUV420 }; enum EGL_TexStatus { EGL_TEX_STATUS_NOTREADY, EGL_TEX_STATUS_OK, EGL_TEX_STATUS_ERROR }; bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display); void egl_texture_free(EGL_Texture ** tex); bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming, bool useDMA); bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer); bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame); bool egl_texture_update_from_dma (EGL_Texture * texture, const FrameBuffer * frmame, const int dmaFd); enum EGL_TexStatus egl_texture_process(EGL_Texture * texture); enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture); int egl_texture_count (EGL_Texture * texture); looking-glass-0+b4+dfsg.1/client/renderers/OpenGL/000077500000000000000000000000001411177132200217355ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/renderers/OpenGL/CMakeLists.txt000066400000000000000000000006311411177132200244750ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(renderer_Opengl LANGUAGES C) find_package(PkgConfig) pkg_check_modules(RENDERER_OPENGL_PKGCONFIG REQUIRED gl ) add_library(renderer_OpenGL STATIC opengl.c ) target_link_libraries(renderer_OpenGL ${RENDERER_OPENGL_PKGCONFIG_LIBRARIES} lg_common fonts ) target_include_directories(renderer_OpenGL PRIVATE src ${RENDERER_OPENGL_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b4+dfsg.1/client/renderers/OpenGL/opengl.c000066400000000000000000001072031411177132200233700ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "interface/renderer.h" #include #include #include #include #include #include #include #include #include "common/debug.h" #include "common/option.h" #include "common/framebuffer.h" #include "common/locking.h" #include "dynamic/fonts.h" #include "ll.h" #define BUFFER_COUNT 2 #define FPS_TEXTURE 0 #define MOUSE_TEXTURE 1 #define ALERT_TEXTURE 2 #define TEXTURE_COUNT 3 #define ALERT_TIMEOUT_FLAG ((uint64_t)-1) #define FADE_TIME 1000000 static struct Option opengl_options[] = { { .module = "opengl", .name = "mipmap", .description = "Enable mipmapping", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "opengl", .name = "vsync", .description = "Enable vsync", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "opengl", .name = "preventBuffer", .description = "Prevent the driver from buffering frames", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "opengl", .name = "amdPinnedMem", .description = "Use GL_AMD_pinned_memory if it is available", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, {0} }; struct IntPoint { int x; int y; }; struct IntRect { int x; int y; int w; int h; }; struct OpenGL_Options { bool mipmap; bool vsync; bool preventBuffer; bool amdPinnedMem; }; struct Alert { bool ready; bool useCloseFlag; LG_FontBitmap *text; float r, g, b, a; uint64_t timeout; bool closeFlag; }; struct Inst { LG_RendererParams params; struct OpenGL_Options opt; bool amdPinnedMemSupport; bool renderStarted; bool configured; bool reconfigure; LG_DSGLContext glContext; struct IntPoint window; float uiScale; _Atomic(bool) frameUpdate; const LG_Font * font; LG_FontObj fontObj, alertFontObj; LG_Lock formatLock; LG_RendererFormat format; GLuint intFormat; GLuint vboFormat; GLuint dataFormat; size_t texSize; size_t texPos; const FrameBuffer * frame; uint64_t drawStart; bool hasBuffers; GLuint vboID[BUFFER_COUNT]; uint8_t * texPixels[BUFFER_COUNT]; LG_Lock frameLock; bool texReady; int texWIndex, texRIndex; int texList; int fpsList; int mouseList; LG_RendererRect destRect; bool hasTextures, hasFrames; GLuint frames[BUFFER_COUNT]; GLsync fences[BUFFER_COUNT]; GLuint textures[TEXTURE_COUNT]; struct ll * alerts; int alertList; bool waiting; uint64_t waitFadeTime; bool waitDone; bool showFPS; bool fpsTexture; struct IntRect fpsRect; LG_Lock mouseLock; LG_RendererCursor mouseCursor; int mouseWidth; int mouseHeight; int mousePitch; uint8_t * mouseData; size_t mouseDataSize; bool mouseUpdate; bool newShape; LG_RendererCursor mouseType; bool mouseVisible; struct IntRect mousePos; }; static bool _check_gl_error(unsigned int line, const char * name); #define check_gl_error(name) _check_gl_error(__LINE__, name) enum ConfigStatus { CONFIG_STATUS_OK, CONFIG_STATUS_ERROR, CONFIG_STATUS_NOOP }; static void deconfigure(struct Inst * this); static enum ConfigStatus configure(struct Inst * this); static void update_mouse_shape(struct Inst * this, bool * newShape); static bool draw_frame(struct Inst * this); static void draw_mouse(struct Inst * this); static void render_wait(struct Inst * this); const char * opengl_get_name(void) { return "OpenGL"; } static void opengl_setup(void) { option_register(opengl_options); } bool opengl_create(void ** opaque, const LG_RendererParams params, bool * needsOpenGL) { // create our local storage *opaque = malloc(sizeof(struct Inst)); if (!*opaque) { DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst)); return false; } memset(*opaque, 0, sizeof(struct Inst)); struct Inst * this = (struct Inst *)*opaque; memcpy(&this->params, ¶ms, sizeof(LG_RendererParams)); this->opt.mipmap = option_get_bool("opengl", "mipmap" ); this->opt.vsync = option_get_bool("opengl", "vsync" ); this->opt.preventBuffer = option_get_bool("opengl", "preventBuffer"); this->opt.amdPinnedMem = option_get_bool("opengl", "amdPinnedMem" ); LG_LOCK_INIT(this->formatLock); LG_LOCK_INIT(this->frameLock ); LG_LOCK_INIT(this->mouseLock ); this->font = LG_Fonts[0]; if (!this->font->create(&this->fontObj, NULL, 14)) { DEBUG_ERROR("Unable to create the font renderer"); return false; } if (!this->font->create(&this->alertFontObj, NULL, 18)) { DEBUG_ERROR("Unable to create the font renderer"); return false; } this->alerts = ll_new(); *needsOpenGL = true; return true; } bool opengl_initialize(void * opaque) { struct Inst * this = (struct Inst *)opaque; if (!this) return false; this->waiting = true; this->waitDone = false; return true; } void opengl_deinitialize(void * opaque) { struct Inst * this = (struct Inst *)opaque; if (!this) return; if (this->renderStarted) { glDeleteLists(this->texList , BUFFER_COUNT); glDeleteLists(this->mouseList, 1); glDeleteLists(this->fpsList , 1); glDeleteLists(this->alertList, 1); } deconfigure(this); if (this->hasTextures) { glDeleteTextures(TEXTURE_COUNT, this->textures); this->hasTextures = false; } if (this->mouseData) free(this->mouseData); if (this->glContext) { app_glDeleteContext(this->glContext); this->glContext = NULL; } LG_LOCK_FREE(this->formatLock); LG_LOCK_FREE(this->frameLock ); LG_LOCK_FREE(this->mouseLock ); struct Alert * alert; while(ll_shift(this->alerts, (void **)&alert)) { if (alert->text) this->font->release(this->alertFontObj, alert->text); free(alert); } ll_free(this->alerts); if (this->font && this->fontObj) this->font->destroy(this->fontObj); free(this); } void opengl_on_restart(void * opaque) { struct Inst * this = (struct Inst *)opaque; this->waiting = true; } void opengl_on_resize(void * opaque, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate) { struct Inst * this = (struct Inst *)opaque; this->window.x = width * scale; this->window.y = height * scale; this->uiScale = (float) scale; if (destRect.valid) { this->destRect.valid = true; this->destRect.x = destRect.x * scale; this->destRect.y = destRect.y * scale; this->destRect.w = destRect.w * scale; this->destRect.h = destRect.h * scale; } // setup the projection matrix glViewport(0, 0, this->window.x, this->window.y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, this->window.x, this->window.y, 0, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (this->destRect.valid) { glTranslatef(this->destRect.x, this->destRect.y, 0.0f); glScalef( (float)this->destRect.w / (float)this->format.width, (float)this->destRect.h / (float)this->format.height, 1.0f ); } } bool opengl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data) { struct Inst * this = (struct Inst *)opaque; if (!this) return false; LG_LOCK(this->mouseLock); this->mouseCursor = cursor; this->mouseWidth = width; this->mouseHeight = height; this->mousePitch = pitch; const size_t size = height * pitch; if (size > this->mouseDataSize) { if (this->mouseData) free(this->mouseData); this->mouseData = (uint8_t *)malloc(size); this->mouseDataSize = size; } memcpy(this->mouseData, data, size); this->newShape = true; LG_UNLOCK(this->mouseLock); return true; } bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const int y) { struct Inst * this = (struct Inst *)opaque; if (!this) return false; if (this->mousePos.x == x && this->mousePos.y == y && this->mouseVisible == visible) return true; this->mouseVisible = visible; this->mousePos.x = x; this->mousePos.y = y; this->mouseUpdate = true; return false; } bool opengl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA) { struct Inst * this = (struct Inst *)opaque; LG_LOCK(this->formatLock); memcpy(&this->format, &format, sizeof(LG_RendererFormat)); this->reconfigure = true; LG_UNLOCK(this->formatLock); return true; } bool opengl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd) { struct Inst * this = (struct Inst *)opaque; LG_LOCK(this->frameLock); this->frame = frame; atomic_store_explicit(&this->frameUpdate, true, memory_order_release); LG_UNLOCK(this->frameLock); if (this->waiting) { this->waiting = false; if (!this->params.quickSplash) this->waitFadeTime = microtime() + FADE_TIME; else { glDisable(GL_MULTISAMPLE); this->waitDone = true; } } return true; } void opengl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag) { struct Inst * this = (struct Inst *)opaque; struct Alert * a = malloc(sizeof(struct Alert)); memset(a, 0, sizeof(struct Alert)); switch(alert) { case LG_ALERT_INFO: a->r = 0.0f; a->g = 0.0f; a->b = 0.8f; a->a = 0.8f; break; case LG_ALERT_SUCCESS: a->r = 0.0f; a->g = 0.8f; a->b = 0.0f; a->a = 0.8f; break; case LG_ALERT_WARNING: a->r = 0.8f; a->g = 0.5f; a->b = 0.0f; a->a = 0.8f; break; case LG_ALERT_ERROR: a->r = 1.0f; a->g = 0.0f; a->b = 0.0f; a->a = 0.8f; break; } if (!(a->text = this->font->render(this->alertFontObj, 0xffffff00, message))) { DEBUG_ERROR("Failed to render alert text"); free(a); return; } if (closeFlag) { a->useCloseFlag = true; *closeFlag = &a->closeFlag; } ll_push(this->alerts, a); } void opengl_on_help(void * opaque, const char * message) { // TODO: Implement this. } void opengl_on_show_fps(void * opaque, bool showFPS) { struct Inst * this = (struct Inst *)opaque; this->showFPS = showFPS; } void bitmap_to_texture(LG_FontBitmap * bitmap, GLuint texture) { glBindTexture(GL_TEXTURE_2D , texture ); glPixelStorei(GL_UNPACK_ALIGNMENT , 4 ); glPixelStorei(GL_UNPACK_ROW_LENGTH, bitmap->width); glTexImage2D( GL_TEXTURE_2D, 0, bitmap->bpp, bitmap->width, bitmap->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, bitmap->pixels ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); } bool opengl_render_startup(void * opaque) { struct Inst * this = (struct Inst *)opaque; this->glContext = app_glCreateContext(); if (!this->glContext) return false; app_glMakeCurrent(this->glContext); DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR )); DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER)); DEBUG_INFO("Version : %s", glGetString(GL_VERSION )); GLint n; glGetIntegerv(GL_NUM_EXTENSIONS, &n); for(GLint i = 0; i < n; ++i) { const GLubyte *ext = glGetStringi(GL_EXTENSIONS, i); if (strcmp((const char *)ext, "GL_AMD_pinned_memory") == 0) { if (this->opt.amdPinnedMem) { this->amdPinnedMemSupport = true; DEBUG_INFO("Using GL_AMD_pinned_memory"); } else DEBUG_INFO("GL_AMD_pinned_memory is available but not in use"); break; } } glEnable(GL_TEXTURE_2D); glEnable(GL_COLOR_MATERIAL); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); glEnable(GL_MULTISAMPLE); // generate lists for drawing this->texList = glGenLists(BUFFER_COUNT); this->mouseList = glGenLists(1); this->fpsList = glGenLists(1); this->alertList = glGenLists(1); // create the overlay textures glGenTextures(TEXTURE_COUNT, this->textures); if (check_gl_error("glGenTextures")) { LG_UNLOCK(this->formatLock); return false; } this->hasTextures = true; app_glSetSwapInterval(this->opt.vsync ? 1 : 0); this->renderStarted = true; return true; } bool opengl_render(void * opaque, LG_RendererRotate rotate) { struct Inst * this = (struct Inst *)opaque; if (!this) return false; switch(configure(this)) { case CONFIG_STATUS_ERROR: DEBUG_ERROR("configure failed"); return false; case CONFIG_STATUS_NOOP : case CONFIG_STATUS_OK : if (!draw_frame(this)) return false; } glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); if (this->waiting) render_wait(this); else { bool newShape; update_mouse_shape(this, &newShape); glCallList(this->texList + this->texRIndex); draw_mouse(this); if (!this->waitDone) render_wait(this); } if (this->showFPS && this->fpsTexture) glCallList(this->fpsList); struct Alert * alert; while(ll_peek_head(this->alerts, (void **)&alert)) { if (!alert->ready) { bitmap_to_texture(alert->text, this->textures[ALERT_TEXTURE]); glNewList(this->alertList, GL_COMPILE); const int p = 4; const int w = alert->text->width + p * 2; const int h = alert->text->height + p * 2; glTranslatef(-(w / 2), -(h / 2), 0.0f); glEnable(GL_BLEND); glDisable(GL_TEXTURE_2D); glColor4f(alert->r, alert->g, alert->b, alert->a); glBegin(GL_TRIANGLE_STRIP); glVertex2i(0, 0); glVertex2i(w, 0); glVertex2i(0, h); glVertex2i(w, h); glEnd(); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, this->textures[ALERT_TEXTURE]); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glTranslatef(p, p, 0.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 ); glTexCoord2f(1.0f, 0.0f); glVertex2i(alert->text->width, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , alert->text->height); glTexCoord2f(1.0f, 1.0f); glVertex2i(alert->text->width, alert->text->height); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_BLEND); glEndList(); if (!alert->useCloseFlag) alert->timeout = microtime() + 2*1000000; alert->ready = true; this->font->release(this->fontObj, alert->text); alert->text = NULL; alert->ready = true; } else { bool close = false; if (alert->useCloseFlag) close = alert->closeFlag; else if (alert->timeout < microtime()) close = true; if (close) { free(alert); ll_shift(this->alerts, NULL); continue; } } glPushMatrix(); glLoadIdentity(); glTranslatef(this->window.x / 2, this->window.y / 2, 0.0f); glScalef(this->uiScale, this->uiScale, 1.0f); glCallList(this->alertList); glPopMatrix(); break; } if (this->opt.preventBuffer) { app_glSwapBuffers(); glFinish(); } else app_glSwapBuffers(); this->mouseUpdate = false; return true; } void opengl_update_fps(void * opaque, const float avgUPS, const float avgFPS) { struct Inst * this = (struct Inst *)opaque; if (!this->showFPS) return; char str[128]; snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgUPS, avgFPS); LG_FontBitmap *textSurface = NULL; if (!(textSurface = this->font->render(this->fontObj, 0xffffff00, str))) DEBUG_ERROR("Failed to render text"); bitmap_to_texture(textSurface, this->textures[FPS_TEXTURE]); this->fpsRect.x = 5; this->fpsRect.y = 5; this->fpsRect.w = textSurface->width; this->fpsRect.h = textSurface->height; this->font->release(this->fontObj, textSurface); this->fpsTexture = true; glNewList(this->fpsList, GL_COMPILE); glEnable(GL_BLEND); glDisable(GL_TEXTURE_2D); glColor4f(0.0f, 0.0f, 1.0f, 0.5f); glBegin(GL_TRIANGLE_STRIP); glVertex2i(this->fpsRect.x , this->fpsRect.y ); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y ); glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h); glEnd(); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, this->textures[FPS_TEXTURE]); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f , 0.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y ); glTexCoord2f(1.0f , 0.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y ); glTexCoord2f(0.0f , 1.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h); glTexCoord2f(1.0f, 1.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_BLEND); glEndList(); } void draw_torus(float x, float y, float inner, float outer, unsigned int pts) { glBegin(GL_QUAD_STRIP); for (unsigned int i = 0; i <= pts; ++i) { float angle = (i / (float)pts) * M_PI * 2.0f; glVertex2f(x + (inner * cos(angle)), y + (inner * sin(angle))); glVertex2f(x + (outer * cos(angle)), y + (outer * sin(angle))); } glEnd(); } void draw_torus_arc(float x, float y, float inner, float outer, unsigned int pts, float s, float e) { glBegin(GL_QUAD_STRIP); for (unsigned int i = 0; i <= pts; ++i) { float angle = s + ((i / (float)pts) * e); glVertex2f(x + (inner * cos(angle)), y + (inner * sin(angle))); glVertex2f(x + (outer * cos(angle)), y + (outer * sin(angle))); } glEnd(); } static void render_wait(struct Inst * this) { float a; if (this->waiting) a = 1.0f; else { uint64_t t = microtime(); if (t > this->waitFadeTime) { glDisable(GL_MULTISAMPLE); this->waitDone = true; return; } uint64_t delta = this->waitFadeTime - t; a = 1.0f / FADE_TIME * delta; } glEnable(GL_BLEND); glPushMatrix(); glLoadIdentity(); glTranslatef(this->window.x / 2.0f, this->window.y / 2.0f, 0.0f); //draw the background gradient glBegin(GL_TRIANGLE_FAN); glColor4f(0.234375f, 0.015625f, 0.425781f, a); glVertex2f(0, 0); glColor4f(0, 0, 0, a); for (unsigned int i = 0; i <= 100; ++i) { float angle = (i / (float)100) * M_PI * 2.0f; glVertex2f(cos(angle) * this->window.x, sin(angle) * this->window.y); } glEnd(); // draw the logo glColor4f(1.0f, 1.0f, 1.0f, a); glScalef (2.0f, 2.0f, 1.0f); draw_torus ( 0, 0, 40, 42, 60); draw_torus ( 0, 0, 32, 34, 60); draw_torus (-50, -3, 2, 4, 30); draw_torus ( 50, -3, 2, 4, 30); draw_torus_arc( 0, 0, 51, 49, 60, 0.0f, M_PI); glBegin(GL_QUADS); glVertex2f(-1 , 50); glVertex2f(-1 , 76); glVertex2f( 1 , 76); glVertex2f( 1 , 50); glVertex2f(-14, 76); glVertex2f(-14, 78); glVertex2f( 14, 78); glVertex2f( 14, 76); glVertex2f(-21, 83); glVertex2f(-21, 85); glVertex2f( 21, 85); glVertex2f( 21, 83); glEnd(); draw_torus_arc(-14, 83, 5, 7, 10, M_PI , M_PI / 2.0f); draw_torus_arc( 14, 83, 5, 7, 10, M_PI * 1.5f, M_PI / 2.0f); //FIXME: draw the diagnoal marks on the circle glPopMatrix(); glDisable(GL_BLEND); } const LG_Renderer LGR_OpenGL = { .get_name = opengl_get_name, .setup = opengl_setup, .create = opengl_create, .initialize = opengl_initialize, .deinitialize = opengl_deinitialize, .on_restart = opengl_on_restart, .on_resize = opengl_on_resize, .on_mouse_shape = opengl_on_mouse_shape, .on_mouse_event = opengl_on_mouse_event, .on_frame_format = opengl_on_frame_format, .on_frame = opengl_on_frame, .on_alert = opengl_on_alert, .on_help = opengl_on_help, .on_show_fps = opengl_on_show_fps, .render_startup = opengl_render_startup, .render = opengl_render, .update_fps = opengl_update_fps }; static bool _check_gl_error(unsigned int line, const char * name) { GLenum error = glGetError(); if (error == GL_NO_ERROR) return false; const char * errStr; switch (error) { case GL_INVALID_ENUM: errStr = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: errStr = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: errStr = "GL_INVALID_OPERATION"; break; case GL_STACK_OVERFLOW: errStr = "GL_STACK_OVERFLOW"; break; case GL_STACK_UNDERFLOW: errStr = "GL_STACK_UNDERFLOW"; break; case GL_OUT_OF_MEMORY: errStr = "GL_OUT_OF_MEMORY"; break; case GL_TABLE_TOO_LARGE: errStr = "GL_TABLE_TOO_LARGE"; break; default: errStr = "unknown error"; } DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr); return true; } static enum ConfigStatus configure(struct Inst * this) { LG_LOCK(this->formatLock); if (!this->reconfigure) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_NOOP; } deconfigure(this); switch(this->format.type) { case FRAME_TYPE_BGRA: this->intFormat = GL_RGBA8; this->vboFormat = GL_BGRA; this->dataFormat = GL_UNSIGNED_BYTE; break; case FRAME_TYPE_RGBA: this->intFormat = GL_RGBA8; this->vboFormat = GL_RGBA; this->dataFormat = GL_UNSIGNED_BYTE; break; case FRAME_TYPE_RGBA10: this->intFormat = GL_RGB10_A2; this->vboFormat = GL_RGBA; this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV; break; case FRAME_TYPE_RGBA16F: this->intFormat = GL_RGB16F; this->vboFormat = GL_RGBA; this->dataFormat = GL_HALF_FLOAT; break; default: DEBUG_ERROR("Unknown/unsupported compression type"); return CONFIG_STATUS_ERROR; } // calculate the texture size in bytes this->texSize = this->format.height * this->format.pitch; this->texPos = 0; glGenBuffers(BUFFER_COUNT, this->vboID); if (check_gl_error("glGenBuffers")) { LG_UNLOCK(this->formatLock); return false; } this->hasBuffers = true; if (this->amdPinnedMemSupport) { const int pagesize = getpagesize(); for(int i = 0; i < BUFFER_COUNT; ++i) { this->texPixels[i] = aligned_alloc(pagesize, this->texSize); if (!this->texPixels[i]) { DEBUG_ERROR("Failed to allocate memory for texture"); return CONFIG_STATUS_ERROR; } memset(this->texPixels[i], 0, this->texSize); glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]); if (check_gl_error("glBindBuffer")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } glBufferData( GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->texSize, this->texPixels[i], GL_STREAM_DRAW ); if (check_gl_error("glBufferData")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } } glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0); } else { for(int i = 0; i < BUFFER_COUNT; ++i) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]); if (check_gl_error("glBindBuffer")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } glBufferData( GL_PIXEL_UNPACK_BUFFER, this->texSize, NULL, GL_STREAM_DRAW ); if (check_gl_error("glBufferData")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } // create the frame textures glGenTextures(BUFFER_COUNT, this->frames); if (check_gl_error("glGenTextures")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } this->hasFrames = true; for(int i = 0; i < BUFFER_COUNT; ++i) { // bind and create the new texture glBindTexture(GL_TEXTURE_2D, this->frames[i]); if (check_gl_error("glBindTexture")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } glTexImage2D( GL_TEXTURE_2D, 0, this->intFormat, this->format.width, this->format.height, 0, this->vboFormat, this->dataFormat, (void*)0 ); if (check_gl_error("glTexImage2D")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } // configure the texture glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // create the display lists glNewList(this->texList + i, GL_COMPILE); glBindTexture(GL_TEXTURE_2D, this->frames[i]); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 ); glTexCoord2f(1.0f, 0.0f); glVertex2i(this->format.width, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , this->format.height); glTexCoord2f(1.0f, 1.0f); glVertex2i(this->format.width, this->format.height); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glEndList(); } glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); this->drawStart = nanotime(); this->configured = true; this->reconfigure = false; LG_UNLOCK(this->formatLock); return CONFIG_STATUS_OK; } static void deconfigure(struct Inst * this) { if (this->hasFrames) { glDeleteTextures(BUFFER_COUNT, this->frames); this->hasFrames = false; } if (this->hasBuffers) { glDeleteBuffers(BUFFER_COUNT, this->vboID); this->hasBuffers = false; } if (this->amdPinnedMemSupport) { for(int i = 0; i < BUFFER_COUNT; ++i) { if (this->fences[i]) { glDeleteSync(this->fences[i]); this->fences[i] = NULL; } if (this->texPixels[i]) { free(this->texPixels[i]); this->texPixels[i] = NULL; } } } this->configured = false; } static void update_mouse_shape(struct Inst * this, bool * newShape) { LG_LOCK(this->mouseLock); *newShape = this->newShape; if (!this->newShape) { LG_UNLOCK(this->mouseLock); return; } const LG_RendererCursor cursor = this->mouseCursor; const int width = this->mouseWidth; const int height = this->mouseHeight; const int pitch = this->mousePitch; const uint8_t * data = this->mouseData; // tmp buffer for masked colour uint32_t tmp[width * height]; this->mouseType = cursor; switch(cursor) { case LG_CURSOR_MASKED_COLOR: for(int i = 0; i < width * height; ++i) { const uint32_t c = ((uint32_t *)data)[i]; tmp[i] = (c & ~0xFF000000) | (c & 0xFF000000 ? 0x0 : 0xFF000000); } data = (uint8_t *)tmp; // fall through to LG_CURSOR_COLOR // // technically we should also create an XOR texture from the data but this // usage seems very rare in modern software. case LG_CURSOR_COLOR: { glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]); glPixelStorei(GL_UNPACK_ALIGNMENT , 4 ); glPixelStorei(GL_UNPACK_ROW_LENGTH, width); glTexImage2D ( GL_TEXTURE_2D, 0 , GL_RGBA, width , height , 0 , GL_BGRA, // windows cursors are in BGRA format GL_UNSIGNED_BYTE, data ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); this->mousePos.w = width; this->mousePos.h = height; glNewList(this->mouseList, GL_COMPILE); glEnable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 ); glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , height); glTexCoord2f(1.0f, 1.0f); glVertex2i(width, height); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_BLEND); glEndList(); break; } case LG_CURSOR_MONOCHROME: { const int hheight = height / 2; uint32_t d[width * height]; for(int y = 0; y < hheight; ++y) for(int x = 0; x < width; ++x) { const uint8_t * srcAnd = data + (pitch * y) + (x / 8); const uint8_t * srcXor = srcAnd + pitch * hheight; const uint8_t mask = 0x80 >> (x % 8); const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000; const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000; d[y * width + x ] = andMask; d[y * width + x + width * hheight] = xorMask; } glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]); glPixelStorei(GL_UNPACK_ALIGNMENT , 4 ); glPixelStorei(GL_UNPACK_ROW_LENGTH, width); glTexImage2D ( GL_TEXTURE_2D, 0 , GL_RGBA, width , height , 0 , GL_RGBA, GL_UNSIGNED_BYTE, d ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); this->mousePos.w = width; this->mousePos.h = hheight; glNewList(this->mouseList, GL_COMPILE); glEnable(GL_COLOR_LOGIC_OP); glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]); glLogicOp(GL_AND); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 ); glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 ); glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , hheight); glTexCoord2f(1.0f, 0.5f); glVertex2i(width, hheight); glEnd(); glLogicOp(GL_XOR); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , 0 ); glTexCoord2f(1.0f, 0.5f); glVertex2i(width, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , hheight); glTexCoord2f(1.0f, 1.0f); glVertex2i(width, hheight); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_COLOR_LOGIC_OP); glEndList(); break; } } this->mouseUpdate = true; LG_UNLOCK(this->mouseLock); } static bool opengl_buffer_fn(void * opaque, const void * data, size_t size) { struct Inst * this = (struct Inst *)opaque; // update the buffer, this performs a DMA transfer if possible glBufferSubData( GL_PIXEL_UNPACK_BUFFER, this->texPos, size, data ); check_gl_error("glBufferSubData"); this->texPos += size; return true; } static bool draw_frame(struct Inst * this) { if (glIsSync(this->fences[this->texWIndex])) { switch(glClientWaitSync(this->fences[this->texWIndex], 0, GL_TIMEOUT_IGNORED)) { case GL_ALREADY_SIGNALED: break; case GL_CONDITION_SATISFIED: DEBUG_WARN("Had to wait for the sync"); break; case GL_TIMEOUT_EXPIRED: DEBUG_WARN("Timeout expired, DMA transfers are too slow!"); break; case GL_WAIT_FAILED: DEBUG_ERROR("Wait failed %d", glGetError()); break; } glDeleteSync(this->fences[this->texWIndex]); this->fences[this->texWIndex] = NULL; this->texRIndex = this->texWIndex; if (++this->texWIndex == BUFFER_COUNT) this->texWIndex = 0; } LG_LOCK(this->frameLock); if (!atomic_exchange_explicit(&this->frameUpdate, false, memory_order_acquire)) { LG_UNLOCK(this->frameLock); return true; } LG_LOCK(this->formatLock); glBindTexture(GL_TEXTURE_2D, this->frames[this->texWIndex]); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texWIndex]); const int bpp = this->format.bpp / 8; glPixelStorei(GL_UNPACK_ALIGNMENT , bpp); glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.width); this->texPos = 0; framebuffer_read_fn( this->frame, this->format.height, this->format.width, bpp, this->format.pitch, opengl_buffer_fn, this ); LG_UNLOCK(this->frameLock); // update the texture glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, this->format.width , this->format.height, this->vboFormat, this->dataFormat, (void*)0 ); if (check_gl_error("glTexSubImage2D")) { DEBUG_ERROR("texWIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu", this->texWIndex, this->format.width, this->format.height, this->vboFormat, this->texSize ); } // unbind the buffer glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); const bool mipmap = this->opt.mipmap && ( (this->format.width > this->destRect.w) || (this->format.height > this->destRect.h)); if (mipmap) { glGenerateMipmap(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } glBindTexture(GL_TEXTURE_2D, 0); // set a fence so we don't overwrite a buffer in use this->fences[this->texWIndex] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); LG_UNLOCK(this->formatLock); this->texReady = true; return true; } static void draw_mouse(struct Inst * this) { if (!this->mouseVisible) return; glPushMatrix(); glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f); glCallList(this->mouseList); glPopMatrix(); } looking-glass-0+b4+dfsg.1/client/src/000077500000000000000000000000001411177132200174075ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/client/src/app.c000066400000000000000000000322031411177132200203330ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "app.h" #include "main.h" #include "core.h" #include "util.h" #include "clipboard.h" #include "ll.h" #include "kb.h" #include "common/debug.h" #include "common/stringutils.h" #include #include #include bool app_isRunning(void) { return g_state.state == APP_STATE_RUNNING || g_state.state == APP_STATE_RESTART; } bool app_isCaptureMode(void) { return g_cursor.grab; } bool app_isCaptureOnlyMode(void) { return g_params.captureInputOnly; } bool app_isFormatValid(void) { return g_state.formatValid; } void app_updateCursorPos(double x, double y) { g_cursor.pos.x = x; g_cursor.pos.y = y; g_cursor.valid = true; } void app_handleFocusEvent(bool focused) { g_state.focused = focused; if (!core_inputEnabled()) { if (!focused && g_params.minimizeOnFocusLoss && app_getFullscreen()) g_state.ds->minimize(); return; } if (!focused) { core_setGrabQuiet(false); core_setCursorInView(false); if (g_params.releaseKeysOnFocusLoss) for (int key = 0; key < KEY_MAX; key++) if (g_state.keyDown[key]) app_handleKeyRelease(key); if (!g_params.showCursorDot) g_state.ds->showPointer(false); if (g_params.minimizeOnFocusLoss) g_state.ds->minimize(); } g_cursor.realign = true; g_state.ds->realignPointer(); } void app_handleEnterEvent(bool entered) { if (entered) { g_cursor.inWindow = true; if (!core_inputEnabled()) return; g_cursor.realign = true; } else { g_cursor.inWindow = false; core_setCursorInView(false); if (!core_inputEnabled()) return; if (!g_params.alwaysShowCursor) g_cursor.draw = false; g_cursor.redraw = true; } } void app_clipboardRelease(void) { if (!g_params.clipboardToVM) return; spice_clipboard_release(); } void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count) { if (!g_params.clipboardToVM) return; if (count == 0) { spice_clipboard_release(); return; } SpiceDataType conv[count]; for(int i = 0; i < count; ++i) conv[i] = cb_lgTypeToSpiceType(types[i]); spice_clipboard_grab(conv, count); } void app_clipboardNotifySize(const LG_ClipboardData type, size_t size) { if (!g_params.clipboardToVM) return; if (type == LG_CLIPBOARD_DATA_NONE) { spice_clipboard_release(); return; } g_state.cbType = cb_lgTypeToSpiceType(type); g_state.cbChunked = size > 0; g_state.cbXfer = size; spice_clipboard_data_start(g_state.cbType, size); } void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size) { if (!g_params.clipboardToVM) return; if (g_state.cbChunked && size > g_state.cbXfer) { DEBUG_ERROR("refusing to send more then cbXfer bytes for chunked xfer"); size = g_state.cbXfer; } if (!g_state.cbChunked) spice_clipboard_data_start(g_state.cbType, size); spice_clipboard_data(g_state.cbType, data, (uint32_t)size); g_state.cbXfer -= size; } void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque) { if (!g_params.clipboardToLocal) return; struct CBRequest * cbr = (struct CBRequest *)malloc(sizeof(struct CBRequest)); cbr->type = g_state.cbType; cbr->replyFn = replyFn; cbr->opaque = opaque; ll_push(g_state.cbRequestList, cbr); spice_clipboard_request(g_state.cbType); } void spiceClipboardNotice(const SpiceDataType type) { if (!g_params.clipboardToLocal) return; if (!g_state.cbAvailable) return; g_state.cbType = type; g_state.ds->cbNotice(cb_spiceTypeToLGType(type)); } void app_handleButtonPress(int button) { g_cursor.buttons |= (1U << button); if (!core_inputEnabled() || !g_cursor.inView) return; if (!spice_mouse_press(button)) DEBUG_ERROR("app_handleButtonPress: failed to send message"); } void app_handleButtonRelease(int button) { g_cursor.buttons &= ~(1U << button); if (!core_inputEnabled()) return; if (!spice_mouse_release(button)) DEBUG_ERROR("app_handleButtonRelease: failed to send message"); } void app_handleKeyPress(int sc) { if (sc == g_params.escapeKey && !g_state.escapeActive) { g_state.escapeActive = true; g_state.escapeTime = microtime(); g_state.escapeAction = -1; return; } if (g_state.escapeActive) { g_state.escapeAction = sc; return; } if (!core_inputEnabled()) return; if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA)) return; if (!g_state.keyDown[sc]) { uint32_t ps2 = xfree86_to_ps2[sc]; if (!ps2) return; if (spice_key_down(ps2)) g_state.keyDown[sc] = true; else { DEBUG_ERROR("app_handleKeyPress: failed to send message"); return; } } } void app_handleKeyRelease(int sc) { if (g_state.escapeActive) { if (g_state.escapeAction == -1) { if (!g_state.escapeHelp && g_params.useSpiceInput) core_setGrab(!g_cursor.grab); } else { KeybindHandle handle = g_state.bindings[sc]; if (handle) { handle->callback(sc, handle->opaque); return; } } if (sc == g_params.escapeKey) g_state.escapeActive = false; } if (!core_inputEnabled()) return; // avoid sending key up events when we didn't send a down if (!g_state.keyDown[sc]) return; if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA)) return; uint32_t ps2 = xfree86_to_ps2[sc]; if (!ps2) return; if (spice_key_up(ps2)) g_state.keyDown[sc] = false; else { DEBUG_ERROR("app_handleKeyRelease: failed to send message"); return; } } void app_handleMouseRelative(double normx, double normy, double rawx, double rawy) { if (g_cursor.grab) { if (g_params.rawMouse) core_handleMouseGrabbed(rawx, rawy); else core_handleMouseGrabbed(normx, normy); } else if (g_cursor.inWindow) core_handleMouseNormal(normx, normy); } // On some display servers normal cursor logic does not work due to the lack of // cursor warp support. Instead, we attempt a best-effort emulation which works // with a 1:1 mouse movement patch applied in the guest. For anything fancy, use // capture mode. void app_handleMouseBasic() { /* do not pass mouse events to the guest if we do not have focus */ if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused) return; if (!core_inputEnabled()) return; const bool inView = g_cursor.pos.x >= g_state.dstRect.x && g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w && g_cursor.pos.y >= g_state.dstRect.y && g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h; core_setCursorInView(inView); /* translate the current position to guest coordinate space */ struct DoublePoint guest; util_localCurToGuest(&guest); int x = (int) round(util_clamp(guest.x, 0, g_state.srcSize.x) - g_cursor.projected.x); int y = (int) round(util_clamp(guest.y, 0, g_state.srcSize.y) - g_cursor.projected.y); if (!x && !y) return; g_cursor.projected.x += x; g_cursor.projected.y += y; if (!spice_mouse_motion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } void app_resyncMouseBasic() { if (!g_cursor.guest.valid) return; g_cursor.projected.x = g_cursor.guest.x + g_cursor.guest.hx; g_cursor.projected.y = g_cursor.guest.y + g_cursor.guest.hy; } void app_updateWindowPos(int x, int y) { g_state.windowPos.x = x; g_state.windowPos.y = y; } void app_handleResizeEvent(int w, int h, double scale, const struct Border border) { memcpy(&g_state.border, &border, sizeof(border)); /* don't do anything else if the window dimensions have not changed */ if (g_state.windowW == w && g_state.windowH == h && g_state.windowScale == scale) return; g_state.windowW = w; g_state.windowH = h; g_state.windowCX = w / 2; g_state.windowCY = h / 2; g_state.windowScale = scale; core_updatePositionInfo(); if (core_inputEnabled()) { /* if the window is moved/resized causing a loss of focus while grabbed, it * makes it impossible to re-focus the window, so we quietly re-enter * capture if we were already in it */ if (g_cursor.grab) { core_setGrabQuiet(false); core_setGrabQuiet(true); } core_alignToGuest(); } } void app_handleCloseEvent(void) { if (!g_params.ignoreQuit || !g_cursor.inView) g_state.state = APP_STATE_SHUTDOWN; } void app_handleRenderEvent(const uint64_t timeUs) { if (!g_state.escapeActive) { if (g_state.escapeHelp) { g_state.escapeHelp = false; app_showHelp(false); } } else { if (!g_state.escapeHelp && timeUs - g_state.escapeTime > g_params.helpMenuDelayUs) { g_state.escapeHelp = true; app_showHelp(true); } } } void app_setFullscreen(bool fs) { g_state.ds->setFullscreen(fs); } bool app_getFullscreen(void) { return g_state.ds->getFullscreen(); } bool app_getProp(LG_DSProperty prop, void * ret) { return g_state.ds->getProp(prop, ret); } #ifdef ENABLE_EGL EGLDisplay app_getEGLDisplay(void) { return g_state.ds->getEGLDisplay(); } EGLNativeWindowType app_getEGLNativeWindow(void) { return g_state.ds->getEGLNativeWindow(); } void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { g_state.ds->eglSwapBuffers(display, surface, damage, count); } #endif #ifdef ENABLE_OPENGL LG_DSGLContext app_glCreateContext(void) { return g_state.ds->glCreateContext(); } void app_glDeleteContext(LG_DSGLContext context) { g_state.ds->glDeleteContext(context); } void app_glMakeCurrent(LG_DSGLContext context) { g_state.ds->glMakeCurrent(context); } void app_glSetSwapInterval(int interval) { g_state.ds->glSetSwapInterval(interval); } void app_glSwapBuffers(void) { g_state.ds->glSwapBuffers(); } #endif void app_alert(LG_MsgAlert type, const char * fmt, ...) { if (!g_state.lgr || !g_params.showAlerts) return; char * buffer; va_list args; va_start(args, fmt); valloc_sprintf(&buffer, fmt, args); va_end(args); g_state.lgr->on_alert( g_state.lgrData, type, buffer, NULL ); free(buffer); } KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description) { // don't allow duplicate binds if (g_state.bindings[sc]) { DEBUG_INFO("Key already bound"); return NULL; } KeybindHandle handle = (KeybindHandle)malloc(sizeof(struct KeybindHandle)); handle->sc = sc; handle->callback = callback; handle->opaque = opaque; g_state.bindings[sc] = handle; g_state.keyDescription[sc] = description; return handle; } void app_releaseKeybind(KeybindHandle * handle) { if (!*handle) return; g_state.bindings[(*handle)->sc] = NULL; free(*handle); *handle = NULL; } void app_releaseAllKeybinds(void) { for(int i = 0; i < KEY_MAX; ++i) if (g_state.bindings[i]) { free(g_state.bindings[i]); g_state.bindings[i] = NULL; } } static char * build_help_str() { size_t size = 50; size_t offset = 0; char * buffer = malloc(size); if (!buffer) return NULL; const char * escapeName = xfree86_to_display[g_params.escapeKey]; offset += snprintf(buffer, size, "%s %-10s Toggle capture mode\n", escapeName, ""); if (offset >= size) { DEBUG_ERROR("Help string somehow overflowed. This should be impossible."); return NULL; } for (int i = 0; i < KEY_MAX; ++i) { if (g_state.keyDescription[i]) { const char * keyName = xfree86_to_display[i]; const char * desc = g_state.keyDescription[i]; int needed = snprintf(buffer + offset, size - offset, "%s+%-10s %s\n", escapeName, keyName, desc); if (offset + needed < size) offset += needed; else { size = size * 2 + needed; void * new = realloc(buffer, size); if (!new) { free(buffer); DEBUG_ERROR("Out of memory when constructing help text"); return NULL; } buffer = new; offset += snprintf(buffer + offset, size - offset, "%s+%-10s %s\n", escapeName, keyName, desc); } } } return buffer; } void app_showHelp(bool show) { char * help = show ? build_help_str() : NULL; g_state.lgr->on_help(g_state.lgrData, help); free(help); } void app_showFPS(bool showFPS) { if (!g_state.lgr) return; g_state.lgr->on_show_fps(g_state.lgrData, showFPS); } looking-glass-0+b4+dfsg.1/client/src/clipboard.c000066400000000000000000000057661411177132200215300ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "clipboard.h" #include "main.h" #include "ll.h" #include "common/debug.h" LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type) { switch(type) { case SPICE_DATA_TEXT: return LG_CLIPBOARD_DATA_TEXT; break; case SPICE_DATA_PNG : return LG_CLIPBOARD_DATA_PNG ; break; case SPICE_DATA_BMP : return LG_CLIPBOARD_DATA_BMP ; break; case SPICE_DATA_TIFF: return LG_CLIPBOARD_DATA_TIFF; break; case SPICE_DATA_JPEG: return LG_CLIPBOARD_DATA_JPEG; break; default: DEBUG_ERROR("invalid spice data type"); return LG_CLIPBOARD_DATA_NONE; } } SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type) { switch(type) { case LG_CLIPBOARD_DATA_TEXT: return SPICE_DATA_TEXT; break; case LG_CLIPBOARD_DATA_PNG : return SPICE_DATA_PNG ; break; case LG_CLIPBOARD_DATA_BMP : return SPICE_DATA_BMP ; break; case LG_CLIPBOARD_DATA_TIFF: return SPICE_DATA_TIFF; break; case LG_CLIPBOARD_DATA_JPEG: return SPICE_DATA_JPEG; break; default: DEBUG_ERROR("invalid clipboard data type"); return SPICE_DATA_NONE; } } void cb_spiceNotice(const SpiceDataType type) { if (!g_params.clipboardToLocal) return; if (!g_state.cbAvailable) return; g_state.cbType = type; g_state.ds->cbNotice(cb_spiceTypeToLGType(type)); } void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size) { if (!g_params.clipboardToLocal) return; if (type == SPICE_DATA_TEXT) { // dos2unix uint8_t * p = buffer; uint32_t newSize = size; for(uint32_t i = 0; i < size; ++i) { uint8_t c = buffer[i]; if (c == '\r') { --newSize; continue; } *p++ = c; } size = newSize; } struct CBRequest * cbr; if (ll_shift(g_state.cbRequestList, (void **)&cbr)) { cbr->replyFn(cbr->opaque, cb_spiceTypeToLGType(type), buffer, size); free(cbr); } } void cb_spiceRelease(void) { if (!g_params.clipboardToLocal) return; if (g_state.cbAvailable) g_state.ds->cbRelease(); } void cb_spiceRequest(const SpiceDataType type) { if (!g_params.clipboardToVM) return; if (g_state.cbAvailable) g_state.ds->cbRequest(cb_spiceTypeToLGType(type)); } looking-glass-0+b4+dfsg.1/client/src/clipboard.h000066400000000000000000000022771411177132200215270ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "spice/spice.h" #include "interface/displayserver.h" LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type); SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type); void cb_spiceNotice(const SpiceDataType type); void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size); void cb_spiceRelease(void); void cb_spiceRequest(const SpiceDataType type); looking-glass-0+b4+dfsg.1/client/src/config.c000066400000000000000000000562741411177132200210360ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "main.h" #include "config.h" #include "kb.h" #include "common/option.h" #include "common/debug.h" #include "common/stringutils.h" #include #include #include #include // forwards static bool optRendererParse (struct Option * opt, const char * str); static StringList optRendererValues (struct Option * opt); static char * optRendererToString(struct Option * opt); static bool optPosParse (struct Option * opt, const char * str); static StringList optPosValues (struct Option * opt); static char * optPosToString (struct Option * opt); static bool optSizeParse (struct Option * opt, const char * str); static StringList optSizeValues (struct Option * opt); static char * optSizeToString (struct Option * opt); static bool optScancodeValidate(struct Option * opt, const char ** error); static char * optScancodeToString(struct Option * opt); static bool optRotateValidate (struct Option * opt, const char ** error); static void doLicense(); static struct Option options[] = { // app options { .module = "app", .name = "configFile", .description = "A file to read additional configuration from", .shortopt = 'C', .type = OPTION_TYPE_STRING, .value.x_string = NULL, }, { .module = "app", .name = "renderer", .description = "Specify the renderer to use", .shortopt = 'g', .type = OPTION_TYPE_CUSTOM, .parser = optRendererParse, .getValues = optRendererValues, .toString = optRendererToString }, { .module = "app", .name = "license", .description = "Show the license for this application and then terminate", .shortopt = 'l', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "app", .name = "cursorPollInterval", .description = "How often to check for a cursor update in microseconds", .type = OPTION_TYPE_INT, .value.x_int = 1000 }, { .module = "app", .name = "framePollInterval", .description = "How often to check for a frame update in microseconds", .type = OPTION_TYPE_INT, .value.x_int = 1000 }, { .module = "app", .name = "allowDMA", .description = "Allow direct DMA transfers if supported (see `README.md` in the `module` dir)", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, // window options { .module = "win", .name = "title", .description = "The window title", .type = OPTION_TYPE_STRING, .value.x_string = "Looking Glass (client)" }, { .module = "win", .name = "position", .description = "Initial window position at startup", .type = OPTION_TYPE_CUSTOM, .parser = optPosParse, .getValues = optPosValues, .toString = optPosToString }, { .module = "win", .name = "size", .description = "Initial window size at startup", .type = OPTION_TYPE_CUSTOM, .parser = optSizeParse, .getValues = optSizeValues, .toString = optSizeToString }, { .module = "win", .name = "autoResize", .description = "Auto resize the window to the guest", .shortopt = 'a', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "allowResize", .description = "Allow the window to be manually resized", .shortopt = 'n', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "keepAspect", .description = "Maintain the correct aspect ratio", .shortopt = 'r', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "forceAspect", .description = "Force the window to maintain the aspect ratio", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "dontUpscale", .description = "Never try to upscale the window", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "shrinkOnUpscale", .description = "Limit the window dimensions when dontUpscale is enabled", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "borderless", .description = "Borderless mode", .shortopt = 'd', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "fullScreen", .description = "Launch in fullscreen borderless mode", .shortopt = 'F', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "maximize", .description = "Launch window maximized", .shortopt = 'T', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "minimizeOnFocusLoss", .description = "Minimize window on focus loss", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "fpsMin", .description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)", .shortopt = 'K', .type = OPTION_TYPE_INT, .value.x_int = -1, }, { .module = "win", .name = "showFPS", .description = "Enable the FPS & UPS display", .shortopt = 'k', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "ignoreQuit", .description = "Ignore requests to quit (ie: Alt+F4)", .shortopt = 'Q', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "noScreensaver", .description = "Prevent the screensaver from starting", .shortopt = 'S', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "autoScreensaver", .description = "Prevent the screensaver from starting when guest requests it", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "alerts", .description = "Show on screen alert messages", .shortopt = 'q', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "quickSplash", .description = "Skip fading out the splash screen when a connection is established", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "rotate", .description = "Rotate the displayed image (0, 90, 180, 270)", .type = OPTION_TYPE_INT, .validator = optRotateValidate, .value.x_int = 0, }, // input options { .module = "input", .name = "grabKeyboard", .description = "Grab the keyboard in capture mode", .shortopt = 'G', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "grabKeyboardOnFocus", .description = "Grab the keyboard when focused", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "releaseKeysOnFocusLoss", .description = "On focus loss, send key up events to guest for all held keys", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "input", .name = "escapeKey", .description = "Specify the escape key, see for valid values", .shortopt = 'm', .type = OPTION_TYPE_INT, .value.x_int = KEY_SCROLLLOCK, .validator = optScancodeValidate, .toString = optScancodeToString, }, { .module = "input", .name = "ignoreWindowsKeys", .description = "Do not pass events for the windows keys to the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "input", .name = "hideCursor", .description = "Hide the local mouse cursor", .shortopt = 'M', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "mouseSens", .description = "Initial mouse sensitivity when in capture mode (-9 to 9)", .type = OPTION_TYPE_INT, .value.x_int = 0, }, { .module = "input", .name = "mouseSmoothing", .description = "Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing)", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "rawMouse", .description = "Use RAW mouse input when in capture mode (good for gaming)", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "input", .name = "mouseRedraw", .description = "Mouse movements trigger redraws (ignores FPS minimum)", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "autoCapture", .description = "Try to keep the mouse captured when needed", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "input", .name = "captureOnly", .description = "Only enable input via SPICE if in capture mode", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "input", .name = "helpMenuDelay", .description = "Show help menu after holding down the escape key for this many milliseconds", .type = OPTION_TYPE_INT, .value.x_int = 200 }, // spice options { .module = "spice", .name = "enable", .description = "Enable the built in SPICE client for input and/or clipboard support", .shortopt = 's', .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "host", .description = "The SPICE server host or UNIX socket", .shortopt = 'c', .type = OPTION_TYPE_STRING, .value.x_string = "127.0.0.1" }, { .module = "spice", .name = "port", .description = "The SPICE server port (0 = unix socket)", .shortopt = 'p', .type = OPTION_TYPE_INT, .value.x_int = 5900 }, { .module = "spice", .name = "input", .description = "Use SPICE to send keyboard and mouse input events to the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboard", .description = "Use SPICE to syncronize the clipboard contents with the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboardToVM", .description = "Allow the clipboard to be syncronized TO the VM", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboardToLocal", .description = "Allow the clipboard to be syncronized FROM the VM", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "scaleCursor", .description = "Scale cursor input position to screen size when up/down scaled", .shortopt = 'j', .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "captureOnStart", .description = "Capture mouse and keyboard on start", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "spice", .name = "alwaysShowCursor", .description = "Always show host cursor", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "spice", .name = "showCursorDot", .description = "Use a \"dot\" cursor when the window does not have focus", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, {0} }; void config_init(void) { g_params.center = true; g_params.w = 1024; g_params.h = 768; option_register(options); } bool config_load(int argc, char * argv[]) { // load any global options first struct stat st; if (stat("/etc/looking-glass-client.ini", &st) >= 0) { DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini"); if (!option_load("/etc/looking-glass-client.ini")) return false; } // load user's local options struct passwd * pw = getpwuid(getuid()); char * localFile; alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir); if (stat(localFile, &st) >= 0) { DEBUG_INFO("Loading config from: %s", localFile); if (!option_load(localFile)) { free(localFile); return false; } } free(localFile); // parse the command line arguments if (!option_parse(argc, argv)) return false; // if a file was specified to also load, do it const char * configFile = option_get_string("app", "configFile"); if (configFile) { DEBUG_INFO("Loading config from: %s", configFile); if (!option_load(configFile)) return false; } // validate the values are sane if (!option_validate()) return false; if (option_get_bool("app", "license")) { doLicense(); return false; } // setup the application params for the basic types g_params.cursorPollInterval = option_get_int ("app" , "cursorPollInterval"); g_params.framePollInterval = option_get_int ("app" , "framePollInterval" ); g_params.allowDMA = option_get_bool ("app" , "allowDMA" ); g_params.windowTitle = option_get_string("win", "title" ); g_params.autoResize = option_get_bool ("win", "autoResize" ); g_params.allowResize = option_get_bool ("win", "allowResize" ); g_params.keepAspect = option_get_bool ("win", "keepAspect" ); g_params.forceAspect = option_get_bool ("win", "forceAspect" ); g_params.dontUpscale = option_get_bool ("win", "dontUpscale" ); g_params.shrinkOnUpscale = option_get_bool ("win", "shrinkOnUpscale"); g_params.borderless = option_get_bool ("win", "borderless" ); g_params.fullscreen = option_get_bool ("win", "fullScreen" ); g_params.maximize = option_get_bool ("win", "maximize" ); g_params.fpsMin = option_get_int ("win", "fpsMin" ); g_params.showFPS = option_get_bool ("win", "showFPS" ); g_params.ignoreQuit = option_get_bool ("win", "ignoreQuit" ); g_params.noScreensaver = option_get_bool ("win", "noScreensaver" ); g_params.autoScreensaver = option_get_bool ("win", "autoScreensaver"); g_params.showAlerts = option_get_bool ("win", "alerts" ); g_params.quickSplash = option_get_bool ("win", "quickSplash" ); if (g_params.noScreensaver && g_params.autoScreensaver) { fprintf(stderr, "win:noScreensaver (-S) and win:autoScreensaver " "can't be used simultaneously\n"); return false; } switch(option_get_int("win", "rotate")) { case 0 : g_params.winRotate = LG_ROTATE_0 ; break; case 90 : g_params.winRotate = LG_ROTATE_90 ; break; case 180: g_params.winRotate = LG_ROTATE_180; break; case 270: g_params.winRotate = LG_ROTATE_270; break; } g_params.grabKeyboard = option_get_bool("input", "grabKeyboard" ); g_params.grabKeyboardOnFocus = option_get_bool("input", "grabKeyboardOnFocus" ); g_params.releaseKeysOnFocusLoss = option_get_bool("input", "releaseKeysOnFocusLoss"); g_params.escapeKey = option_get_int ("input", "escapeKey" ); g_params.ignoreWindowsKeys = option_get_bool("input", "ignoreWindowsKeys" ); g_params.hideMouse = option_get_bool("input", "hideCursor" ); g_params.mouseSens = option_get_int ("input", "mouseSens" ); g_params.mouseSmoothing = option_get_bool("input", "mouseSmoothing" ); g_params.rawMouse = option_get_bool("input", "rawMouse" ); g_params.mouseRedraw = option_get_bool("input", "mouseRedraw" ); g_params.autoCapture = option_get_bool("input", "autoCapture" ); g_params.captureInputOnly = option_get_bool("input", "captureOnly" ); g_params.helpMenuDelayUs = option_get_int("input", "helpMenuDelay") * (uint64_t) 1000; g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss"); if (option_get_bool("spice", "enable")) { g_params.spiceHost = option_get_string("spice", "host"); g_params.spicePort = option_get_int ("spice", "port"); g_params.useSpiceInput = option_get_bool("spice", "input" ); g_params.useSpiceClipboard = option_get_bool("spice", "clipboard"); if (g_params.useSpiceClipboard) { g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" ); g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal"); g_params.useSpiceClipboard = g_params.clipboardToVM || g_params.clipboardToLocal; } else { g_params.clipboardToVM = false; g_params.clipboardToLocal = false; } g_params.scaleMouseInput = option_get_bool("spice", "scaleCursor"); g_params.captureOnStart = option_get_bool("spice", "captureOnStart"); g_params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor"); g_params.showCursorDot = option_get_bool("spice", "showCursorDot"); } return true; } void config_free(void) { option_free(); } static void doLicense(void) { fprintf(stderr, "\n" "Looking Glass - KVM FrameRelay (KVMFR) Client\n" "Copyright(C) 2017-2021 Geoffrey McRae \n" "https://looking-glass.hostfission.com\n" "\n" "This program is free software; you can redistribute it and / or modify it under\n" "the terms of the GNU General Public License as published by the Free Software\n" "Foundation; either version 2 of the License, or (at your option) any later\n" "version.\n" "\n" "This program is distributed in the hope that it will be useful, but WITHOUT ANY\n" "WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n" "PARTICULAR PURPOSE.See the GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License along with\n" "this program; if not, write to the Free Software Foundation, Inc., 59 Temple\n" "Place, Suite 330, Boston, MA 02111 - 1307 USA\n" "\n" ); } static bool optRendererParse(struct Option * opt, const char * str) { if (!str) return false; if (strcasecmp(str, "auto") == 0) { g_params.forceRenderer = false; return true; } for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) if (strcasecmp(str, LG_Renderers[i]->get_name()) == 0) { g_params.forceRenderer = true; g_params.forceRendererIndex = i; return true; } return false; } static StringList optRendererValues(struct Option * opt) { StringList sl = stringlist_new(false); // this typecast is safe as the stringlist doesn't own the values for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) stringlist_push(sl, (char *)LG_Renderers[i]->get_name()); return sl; } static char * optRendererToString(struct Option * opt) { if (!g_params.forceRenderer) return strdup("auto"); if (g_params.forceRendererIndex >= LG_RENDERER_COUNT) return NULL; return strdup(LG_Renderers[g_params.forceRendererIndex]->get_name()); } static bool optPosParse(struct Option * opt, const char * str) { if (!str) return false; if (strcmp(str, "center") == 0) { g_params.center = true; return true; } if (sscanf(str, "%dx%d", &g_params.x, &g_params.y) == 2) { g_params.center = false; return true; } return false; } static StringList optPosValues(struct Option * opt) { StringList sl = stringlist_new(false); stringlist_push(sl, "center"); stringlist_push(sl, "x, ie: 100x100"); return sl; } static char * optPosToString(struct Option * opt) { if (g_params.center) return strdup("center"); int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y); char * str = malloc(len + 1); sprintf(str, "%dx%d", g_params.x, g_params.y); return str; } static bool optSizeParse(struct Option * opt, const char * str) { if (!str) return false; if (sscanf(str, "%dx%d", &g_params.w, &g_params.h) == 2) { if (g_params.w < 1 || g_params.h < 1) return false; return true; } return false; } static StringList optSizeValues(struct Option * opt) { StringList sl = stringlist_new(false); stringlist_push(sl, "x, ie: 100x100"); return sl; } static char * optSizeToString(struct Option * opt) { int len = snprintf(NULL, 0, "%dx%d", g_params.w, g_params.h); char * str = malloc(len + 1); sprintf(str, "%dx%d", g_params.w, g_params.h); return str; } static bool optScancodeValidate(struct Option * opt, const char ** error) { if (opt->value.x_int >= 0 && opt->value.x_int < KEY_MAX) return true; *error = "Out of range"; return false; } static char * optScancodeToString(struct Option * opt) { char * str; alloc_sprintf(&str, "%d = %s", opt->value.x_int, xfree86_to_str[opt->value.x_int]); return str; } static bool optRotateValidate(struct Option * opt, const char ** error) { switch(opt->value.x_int) { case 0: case 90: case 180: case 270: return true; } *error = "Rotation angle must be one of 0, 90, 180 or 270"; return false; } looking-glass-0+b4+dfsg.1/client/src/config.h000066400000000000000000000016351411177132200210320ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 void config_init(); bool config_load(int argc, char * argv[]); void config_free(); looking-glass-0+b4+dfsg.1/client/src/core.c000066400000000000000000000326201411177132200205060ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "core.h" #include "main.h" #include "app.h" #include "util.h" #include "common/time.h" #include "common/debug.h" #include #include #define RESIZE_TIMEOUT (10 * 1000) // 10ms bool core_inputEnabled(void) { return g_params.useSpiceInput && !g_state.ignoreInput && ((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly); } void core_setCursorInView(bool enable) { // if the state has not changed, don't do anything else if (g_cursor.inView == enable) return; if (enable && !g_state.focused) return; // do not allow the view to become active if any mouse buttons are being held, // this fixes issues with meta window resizing. if (enable && g_cursor.buttons) return; g_cursor.inView = enable; g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly) ? true : enable; g_cursor.redraw = true; /* if the display server does not support warp, then we can not operate in * always relative mode and we should not grab the pointer */ enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); g_cursor.warpState = enable ? WARP_STATE_ON : WARP_STATE_OFF; if (enable) { if (g_params.hideMouse) g_state.ds->showPointer(false); if (warpSupport != LG_DS_WARP_NONE && !g_params.captureInputOnly) g_state.ds->grabPointer(); if (g_params.grabKeyboardOnFocus) g_state.ds->grabKeyboard(); } else { if (g_params.hideMouse) g_state.ds->showPointer(true); if (warpSupport != LG_DS_WARP_NONE) g_state.ds->ungrabPointer(); g_state.ds->ungrabKeyboard(); } g_cursor.warpState = WARP_STATE_ON; } void core_setGrab(bool enable) { core_setGrabQuiet(enable); app_alert( g_cursor.grab ? LG_ALERT_SUCCESS : LG_ALERT_WARNING, g_cursor.grab ? "Capture Enabled" : "Capture Disabled" ); } void core_setGrabQuiet(bool enable) { /* we always do this so that at init the cursor is in the right state */ if (g_params.captureInputOnly && g_params.hideMouse) g_state.ds->showPointer(!enable); if (g_cursor.grab == enable) return; g_cursor.grab = enable; g_cursor.acc.x = 0.0; g_cursor.acc.y = 0.0; /* if the display server does not support warp we need to ungrab the pointer * here instead of in the move handler */ enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); if (enable) { core_setCursorInView(true); g_state.ignoreInput = false; if (g_params.grabKeyboard) g_state.ds->grabKeyboard(); g_state.ds->capturePointer(); } else { if (g_params.grabKeyboard) { if (!g_params.grabKeyboardOnFocus || !g_state.focused || g_params.captureInputOnly) g_state.ds->ungrabKeyboard(); } g_state.ds->uncapturePointer(); /* if exiting capture when input on capture only we need to align the local * cursor to the guest's location before it is shown. */ if (g_params.captureInputOnly || !g_params.hideMouse) core_alignToGuest(); } } bool core_warpPointer(int x, int y, bool exiting) { if (!g_cursor.inWindow && !exiting) return false; if (g_cursor.warpState == WARP_STATE_OFF) return false; if (exiting) g_cursor.warpState = WARP_STATE_OFF; if (g_cursor.pos.x == x && g_cursor.pos.y == y) return true; g_state.ds->warpPointer(x, y, exiting); return true; } void core_updatePositionInfo(void) { if (!g_state.haveSrcSize) goto done; float srcW = 0.0f; float srcH = 0.0f; switch(g_params.winRotate) { case LG_ROTATE_0: case LG_ROTATE_180: srcW = g_state.srcSize.x; srcH = g_state.srcSize.y; break; case LG_ROTATE_90: case LG_ROTATE_270: srcW = g_state.srcSize.y; srcH = g_state.srcSize.x; break; default: assert(!"unreachable"); } if (g_params.keepAspect) { const float srcAspect = srcH / srcW; const float wndAspect = (float)g_state.windowH / (float)g_state.windowW; bool force = true; if (g_params.dontUpscale && srcW <= g_state.windowW && srcH <= g_state.windowH) { force = false; g_state.dstRect.w = srcW; g_state.dstRect.h = srcH; g_state.dstRect.x = g_state.windowCX - srcW / 2; g_state.dstRect.y = g_state.windowCY - srcH / 2; } else if ((int)(wndAspect * 1000) == (int)(srcAspect * 1000)) { force = false; g_state.dstRect.w = g_state.windowW; g_state.dstRect.h = g_state.windowH; g_state.dstRect.x = 0; g_state.dstRect.y = 0; } else if (wndAspect < srcAspect) { g_state.dstRect.w = (float)g_state.windowH / srcAspect; g_state.dstRect.h = g_state.windowH; g_state.dstRect.x = (g_state.windowW >> 1) - (g_state.dstRect.w >> 1); g_state.dstRect.y = 0; } else { g_state.dstRect.w = g_state.windowW; g_state.dstRect.h = (float)g_state.windowW * srcAspect; g_state.dstRect.x = 0; g_state.dstRect.y = (g_state.windowH >> 1) - (g_state.dstRect.h >> 1); } if (g_params.dontUpscale && g_params.shrinkOnUpscale) { if (g_state.windowW > srcW) { force = true; g_state.dstRect.w = (int) (srcW + 0.5); } if (g_state.windowH > srcH) { force = true; g_state.dstRect.h = (int) (srcH + 0.5); } } if (force && g_params.forceAspect) { g_state.resizeTimeout = microtime() + RESIZE_TIMEOUT; g_state.resizeDone = false; } } else { g_state.dstRect.x = 0; g_state.dstRect.y = 0; g_state.dstRect.w = g_state.windowW; g_state.dstRect.h = g_state.windowH; } g_state.dstRect.valid = true; g_cursor.useScale = ( srcH != g_state.dstRect.h || srcW != g_state.dstRect.w || g_cursor.guest.dpiScale != 100); g_cursor.scale.x = (float)srcW / (float)g_state.dstRect.w; g_cursor.scale.y = (float)srcH / (float)g_state.dstRect.h; g_cursor.dpiScale = g_cursor.guest.dpiScale / 100.0f; if (!g_state.posInfoValid) { g_state.posInfoValid = true; g_state.ds->realignPointer(); } done: atomic_fetch_add(&g_state.lgrResize, 1); } void core_alignToGuest(void) { if (!g_cursor.guest.valid || !g_state.focused) return; struct DoublePoint local; if (util_guestCurToLocal(&local)) if (core_warpPointer(round(local.x), round(local.y), false)) core_setCursorInView(true); } bool core_isValidPointerPos(int x, int y) { return g_state.ds->isValidPointerPos(x, y); } bool core_startFrameThread(void) { if (g_state.frameThread) return true; g_state.stopVideo = false; if (!lgCreateThread("frameThread", main_frameThread, NULL, &g_state.frameThread)) { DEBUG_ERROR("frame create thread failed"); return false; } return true; } void core_stopFrameThread(void) { g_state.stopVideo = true; if (g_state.frameThread) lgJoinThread(g_state.frameThread, NULL); g_state.frameThread = NULL; } void core_handleGuestMouseUpdate(void) { struct DoublePoint localPos; if (!util_guestCurToLocal(&localPos)) return; g_state.ds->guestPointerUpdated( g_cursor.guest.x, g_cursor.guest.y, util_clamp(localPos.x, g_state.dstRect.x, g_state.dstRect.x + g_state.dstRect.w), util_clamp(localPos.y, g_state.dstRect.y, g_state.dstRect.y + g_state.dstRect.h) ); } void core_handleMouseGrabbed(double ex, double ey) { if (!core_inputEnabled()) return; int x, y; if (g_params.rawMouse && !g_cursor.sens) { /* raw unscaled input are always round numbers */ x = floor(ex); y = floor(ey); } else { /* apply sensitivity */ ex = (ex / 10.0) * (g_cursor.sens + 10); ey = (ey / 10.0) * (g_cursor.sens + 10); util_cursorToInt(ex, ey, &x, &y); } if (x == 0 && y == 0) return; if (!spice_mouse_motion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } static bool isInView(void) { return g_cursor.pos.x >= g_state.dstRect.x && g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w && g_cursor.pos.y >= g_state.dstRect.y && g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h; } void core_handleMouseNormal(double ex, double ey) { // prevent cursor handling outside of capture if the position is not known if (!g_cursor.guest.valid) return; if (!core_inputEnabled()) return; /* scale the movement to the guest */ if (g_cursor.useScale && g_params.scaleMouseInput) { ex *= g_cursor.scale.x; ey *= g_cursor.scale.y; } bool testExit = true; if (!g_cursor.inView) { const bool inView = isInView(); core_setCursorInView(inView); if (inView) g_cursor.realign = true; } /* nothing to do if we are outside the viewport */ if (!g_cursor.inView) return; /* * do not pass mouse events to the guest if we do not have focus, this must be * done after the inView test has been performed so that when focus is gained * we know if we should be drawing the cursor. */ if (!g_state.focused) return; /* if we have been instructed to realign */ if (g_cursor.realign) { g_cursor.realign = false; struct DoublePoint guest; util_localCurToGuest(&guest); /* add the difference to the offset */ ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx); ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy); /* don't test for an exit as we just entered, we can get into a enter/exit * loop otherwise */ testExit = false; } /* if we are in "autoCapture" and the delta was large don't test for exit */ if (g_params.autoCapture && (fabs(ex) > 20.0 / g_cursor.scale.x || fabs(ey) > 20.0 / g_cursor.scale.y)) testExit = false; /* if any buttons are held we should not allow exit to happen */ if (g_cursor.buttons) testExit = false; if (testExit) { enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); /* translate the move to the guests orientation */ struct DoublePoint move = {.x = ex, .y = ey}; util_rotatePoint(&move); /* translate the guests position to our coordinate space */ struct DoublePoint local; util_guestCurToLocal(&local); /* check if the move would push the cursor outside the guest's viewport */ if ( local.x + move.x < g_state.dstRect.x || local.y + move.y < g_state.dstRect.y || local.x + move.x >= g_state.dstRect.x + g_state.dstRect.w || local.y + move.y >= g_state.dstRect.y + g_state.dstRect.h) { local.x += move.x; local.y += move.y; const int tx = (local.x <= 0.0) ? floor(local.x) : ceil(local.x); const int ty = (local.y <= 0.0) ? floor(local.y) : ceil(local.y); switch (warpSupport) { case LG_DS_WARP_NONE: break; case LG_DS_WARP_SURFACE: g_state.ds->ungrabPointer(); core_warpPointer(tx, ty, true); if (!isInView() && tx >= 0 && tx < g_state.windowW && ty >= 0 && ty < g_state.windowH) core_setCursorInView(false); break; case LG_DS_WARP_SCREEN: if (core_isValidPointerPos( g_state.windowPos.x + g_state.border.left + tx, g_state.windowPos.y + g_state.border.top + ty)) { core_setCursorInView(false); /* preempt the window leave flag if the warp will leave our window */ if (tx < 0 || ty < 0 || tx > g_state.windowW || ty > g_state.windowH) g_cursor.inWindow = false; /* ungrab the pointer and move the local cursor to the exit point */ g_state.ds->ungrabPointer(); core_warpPointer(tx, ty, true); return; } } } else if (warpSupport == LG_DS_WARP_SURFACE && isInView()) { /* regrab the pointer in case the user did not move off the surface */ g_state.ds->grabPointer(); g_cursor.warpState = WARP_STATE_ON; } } int x, y; util_cursorToInt(ex, ey, &x, &y); if (x == 0 && y == 0) return; if (g_params.autoCapture) { g_cursor.delta.x += x; g_cursor.delta.y += y; if (fabs(g_cursor.delta.x) > 50.0 || fabs(g_cursor.delta.y) > 50.0) { g_cursor.delta.x = 0; g_cursor.delta.y = 0; } } else { /* assume the mouse will move to the location we attempt to move it to so we * avoid warp out of window issues. The cursorThread will correct this if * wrong after the movement has ocurred on the guest */ g_cursor.guest.x += x; g_cursor.guest.y += y; } if (!spice_mouse_motion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } looking-glass-0+b4+dfsg.1/client/src/core.h000066400000000000000000000025711411177132200205150ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_CORE_ #define _H_LG_CORE_ #include bool core_inputEnabled(void); void core_setCursorInView(bool enable); void core_setGrab(bool enable); void core_setGrabQuiet(bool enable); bool core_warpPointer(int x, int y, bool exiting); void core_updatePositionInfo(void); void core_alignToGuest(void); bool core_isValidPointerPos(int x, int y); bool core_startFrameThread(void); void core_stopFrameThread(void); void core_handleGuestMouseUpdate(void); void core_handleMouseGrabbed(double ex, double ey); void core_handleMouseNormal(double ex, double ey); #endif looking-glass-0+b4+dfsg.1/client/src/egl_dynprocs.c000066400000000000000000000030711411177132200222440ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifdef ENABLE_EGL #include "egl_dynprocs.h" struct EGLDynProcs g_egl_dynProcs = {0}; void egl_dynProcsInit(void) { g_egl_dynProcs.eglGetPlatformDisplay = (eglGetPlatformDisplayEXT_t) eglGetProcAddress("eglGetPlatformDisplay"); g_egl_dynProcs.eglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_t) eglGetProcAddress("eglGetPlatformDisplayEXT"); g_egl_dynProcs.glEGLImageTargetTexture2DOES = (glEGLImageTargetTexture2DOES_t) eglGetProcAddress("glEGLImageTargetTexture2DOES"); g_egl_dynProcs.eglSwapBuffersWithDamageKHR = (eglSwapBuffersWithDamageKHR_t) eglGetProcAddress("eglSwapBuffersWithDamageKHR"); g_egl_dynProcs.eglSwapBuffersWithDamageEXT = (eglSwapBuffersWithDamageKHR_t) eglGetProcAddress("eglSwapBuffersWithDamageEXT"); }; #endif looking-glass-0+b4+dfsg.1/client/src/kb.c000066400000000000000000000372331411177132200201570ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_KB_ #define _H_LG_KB_ #include "kb.h" const uint32_t xfree86_to_ps2[KEY_MAX] = { [KEY_RESERVED] /* = USB 0 */ = 0x000000, [KEY_ESC] /* = USB 41 */ = 0x000001, [KEY_1] /* = USB 30 */ = 0x000002, [KEY_2] /* = USB 31 */ = 0x000003, [KEY_3] /* = USB 32 */ = 0x000004, [KEY_4] /* = USB 33 */ = 0x000005, [KEY_5] /* = USB 34 */ = 0x000006, [KEY_6] /* = USB 35 */ = 0x000007, [KEY_7] /* = USB 36 */ = 0x000008, [KEY_8] /* = USB 37 */ = 0x000009, [KEY_9] /* = USB 38 */ = 0x00000A, [KEY_0] /* = USB 39 */ = 0x00000B, [KEY_MINUS] /* = USB 45 */ = 0x00000C, [KEY_EQUAL] /* = USB 46 */ = 0x00000D, [KEY_BACKSPACE] /* = USB 42 */ = 0x00000E, [KEY_TAB] /* = USB 43 */ = 0x00000F, [KEY_Q] /* = USB 20 */ = 0x000010, [KEY_W] /* = USB 26 */ = 0x000011, [KEY_E] /* = USB 8 */ = 0x000012, [KEY_R] /* = USB 21 */ = 0x000013, [KEY_T] /* = USB 23 */ = 0x000014, [KEY_Y] /* = USB 28 */ = 0x000015, [KEY_U] /* = USB 24 */ = 0x000016, [KEY_I] /* = USB 12 */ = 0x000017, [KEY_O] /* = USB 18 */ = 0x000018, [KEY_P] /* = USB 19 */ = 0x000019, [KEY_LEFTBRACE] /* = USB 47 */ = 0x00001A, [KEY_RIGHTBRACE] /* = USB 48 */ = 0x00001B, [KEY_ENTER] /* = USB 40 */ = 0x00001C, [KEY_LEFTCTRL] /* = USB 224 */ = 0x00001D, [KEY_A] /* = USB 4 */ = 0x00001E, [KEY_S] /* = USB 22 */ = 0x00001F, [KEY_D] /* = USB 7 */ = 0x000020, [KEY_F] /* = USB 9 */ = 0x000021, [KEY_G] /* = USB 10 */ = 0x000022, [KEY_H] /* = USB 11 */ = 0x000023, [KEY_J] /* = USB 13 */ = 0x000024, [KEY_K] /* = USB 14 */ = 0x000025, [KEY_L] /* = USB 15 */ = 0x000026, [KEY_SEMICOLON] /* = USB 51 */ = 0x000027, [KEY_APOSTROPHE] /* = USB 52 */ = 0x000028, [KEY_GRAVE] /* = USB 53 */ = 0x000029, [KEY_LEFTSHIFT] /* = USB 225 */ = 0x00002A, [KEY_BACKSLASH] /* = USB 49 */ = 0x00002B, [KEY_Z] /* = USB 29 */ = 0x00002C, [KEY_X] /* = USB 27 */ = 0x00002D, [KEY_C] /* = USB 6 */ = 0x00002E, [KEY_V] /* = USB 25 */ = 0x00002F, [KEY_B] /* = USB 5 */ = 0x000030, [KEY_N] /* = USB 17 */ = 0x000031, [KEY_M] /* = USB 16 */ = 0x000032, [KEY_COMMA] /* = USB 54 */ = 0x000033, [KEY_DOT] /* = USB 55 */ = 0x000034, [KEY_SLASH] /* = USB 56 */ = 0x000035, [KEY_RIGHTSHIFT] /* = USB 229 */ = 0x000036, [KEY_KPASTERISK] /* = USB 85 */ = 0x000037, [KEY_LEFTALT] /* = USB 226 */ = 0x000038, [KEY_SPACE] /* = USB 44 */ = 0x000039, [KEY_CAPSLOCK] /* = USB 57 */ = 0x00003A, [KEY_F1] /* = USB 58 */ = 0x00003B, [KEY_F2] /* = USB 59 */ = 0x00003C, [KEY_F3] /* = USB 60 */ = 0x00003D, [KEY_F4] /* = USB 61 */ = 0x00003E, [KEY_F5] /* = USB 62 */ = 0x00003F, [KEY_F6] /* = USB 63 */ = 0x000040, [KEY_F7] /* = USB 64 */ = 0x000041, [KEY_F8] /* = USB 65 */ = 0x000042, [KEY_F9] /* = USB 66 */ = 0x000043, [KEY_F10] /* = USB 67 */ = 0x000044, [KEY_NUMLOCK] /* = USB 83 */ = 0x000045, [KEY_SCROLLLOCK] /* = USB 71 */ = 0x000046, [KEY_KP7] /* = USB 95 */ = 0x000047, [KEY_KP8] /* = USB 96 */ = 0x000048, [KEY_KP9] /* = USB 97 */ = 0x000049, [KEY_KPMINUS] /* = USB 86 */ = 0x00004A, [KEY_KP4] /* = USB 92 */ = 0x00004B, [KEY_KP5] /* = USB 93 */ = 0x00004C, [KEY_KP6] /* = USB 94 */ = 0x00004D, [KEY_KPPLUS] /* = USB 87 */ = 0x00004E, [KEY_KP1] /* = USB 89 */ = 0x00004F, [KEY_KP2] /* = USB 90 */ = 0x000050, [KEY_KP3] /* = USB 91 */ = 0x000051, [KEY_KP0] /* = USB 98 */ = 0x000052, [KEY_KPDOT] /* = USB 99 */ = 0x000053, [KEY_102ND] /* = USB 100 */ = 0x000056, [KEY_F11] /* = USB 68 */ = 0x000057, [KEY_F12] /* = USB 69 */ = 0x000058, [KEY_RO] /* = USB 135 */ = 0x000073, [KEY_HENKAN] /* = USB 138 */ = 0x000079, [KEY_KATAKANAHIRAGANA] /* = USB 136 */ = 0x000070, [KEY_MUHENKAN] /* = USB 139 */ = 0x00007B, [KEY_KPENTER] /* = USB 88 */ = 0x00E01C, [KEY_RIGHTCTRL] /* = USB 228 */ = 0x00E01D, [KEY_KPSLASH] /* = USB 84 */ = 0x00E035, [KEY_SYSRQ] /* = USB 70 */ = 0x00E037, [KEY_RIGHTALT] /* = USB 230 */ = 0x00E038, [KEY_HOME] /* = USB 74 */ = 0x00E047, [KEY_UP] /* = USB 82 */ = 0x00E048, [KEY_PAGEUP] /* = USB 75 */ = 0x00E049, [KEY_LEFT] /* = USB 80 */ = 0x00E04B, [KEY_RIGHT] /* = USB 79 */ = 0x00E04D, [KEY_END] /* = USB 77 */ = 0x00E04F, [KEY_DOWN] /* = USB 81 */ = 0x00E050, [KEY_PAGEDOWN] /* = USB 78 */ = 0x00E051, [KEY_INSERT] /* = USB 73 */ = 0x00E052, [KEY_DELETE] /* = USB 76 */ = 0x00E053, [KEY_KPEQUAL] /* = USB 103 */ = 0x000059, [KEY_PAUSE] /* = USB 72 */ = 0x00E046, [KEY_KPCOMMA] /* = USB 133 */ = 0x00007E, [KEY_HANGEUL] /* = USB 144 */ = 0x0000F2, [KEY_HANJA] /* = USB 145 */ = 0x0000F1, [KEY_YEN] /* = USB 137 */ = 0x00007D, [KEY_LEFTMETA] /* = USB 227 */ = 0x00E05B, [KEY_RIGHTMETA] /* = USB 231 */ = 0x00E05C, [KEY_COMPOSE] /* = USB 101 */ = 0x00E05D, [KEY_F13] /* = USB 104 */ = 0x00005D, [KEY_F14] /* = USB 105 */ = 0x00005E, [KEY_F15] /* = USB 106 */ = 0x00005F, [KEY_PRINT] /* = USB 70 */ = 0x00E037, }; const char * xfree86_to_str[KEY_MAX] = { [KEY_RESERVED] = "KEY_RESERVED", [KEY_ESC] = "KEY_ESC", [KEY_1] = "KEY_1", [KEY_2] = "KEY_2", [KEY_3] = "KEY_3", [KEY_4] = "KEY_4", [KEY_5] = "KEY_5", [KEY_6] = "KEY_6", [KEY_7] = "KEY_7", [KEY_8] = "KEY_8", [KEY_9] = "KEY_9", [KEY_0] = "KEY_0", [KEY_MINUS] = "KEY_MINUS", [KEY_EQUAL] = "KEY_EQUAL", [KEY_BACKSPACE] = "KEY_BACKSPACE", [KEY_TAB] = "KEY_TAB", [KEY_Q] = "KEY_Q", [KEY_W] = "KEY_W", [KEY_E] = "KEY_E", [KEY_R] = "KEY_R", [KEY_T] = "KEY_T", [KEY_Y] = "KEY_Y", [KEY_U] = "KEY_U", [KEY_I] = "KEY_I", [KEY_O] = "KEY_O", [KEY_P] = "KEY_P", [KEY_LEFTBRACE] = "KEY_LEFTBRACE", [KEY_RIGHTBRACE] = "KEY_RIGHTBRACE", [KEY_ENTER] = "KEY_ENTER", [KEY_LEFTCTRL] = "KEY_LEFTCTRL", [KEY_A] = "KEY_A", [KEY_S] = "KEY_S", [KEY_D] = "KEY_D", [KEY_F] = "KEY_F", [KEY_G] = "KEY_G", [KEY_H] = "KEY_H", [KEY_J] = "KEY_J", [KEY_K] = "KEY_K", [KEY_L] = "KEY_L", [KEY_SEMICOLON] = "KEY_SEMICOLON", [KEY_APOSTROPHE] = "KEY_APOSTROPHE", [KEY_GRAVE] = "KEY_GRAVE", [KEY_LEFTSHIFT] = "KEY_LEFTSHIFT", [KEY_BACKSLASH] = "KEY_BACKSLASH", [KEY_Z] = "KEY_Z", [KEY_X] = "KEY_X", [KEY_C] = "KEY_C", [KEY_V] = "KEY_V", [KEY_B] = "KEY_B", [KEY_N] = "KEY_N", [KEY_M] = "KEY_M", [KEY_COMMA] = "KEY_COMMA", [KEY_DOT] = "KEY_DOT", [KEY_SLASH] = "KEY_SLASH", [KEY_RIGHTSHIFT] = "KEY_RIGHTSHIFT", [KEY_KPASTERISK] = "KEY_KPASTERISK", [KEY_LEFTALT] = "KEY_LEFTALT", [KEY_SPACE] = "KEY_SPACE", [KEY_CAPSLOCK] = "KEY_CAPSLOCK", [KEY_F1] = "KEY_F1", [KEY_F2] = "KEY_F2", [KEY_F3] = "KEY_F3", [KEY_F4] = "KEY_F4", [KEY_F5] = "KEY_F5", [KEY_F6] = "KEY_F6", [KEY_F7] = "KEY_F7", [KEY_F8] = "KEY_F8", [KEY_F9] = "KEY_F9", [KEY_F10] = "KEY_F10", [KEY_NUMLOCK] = "KEY_NUMLOCK", [KEY_SCROLLLOCK] = "KEY_SCROLLLOCK", [KEY_KP7] = "KEY_KP7", [KEY_KP8] = "KEY_KP8", [KEY_KP9] = "KEY_KP9", [KEY_KPMINUS] = "KEY_KPMINUS", [KEY_KP4] = "KEY_KP4", [KEY_KP5] = "KEY_KP5", [KEY_KP6] = "KEY_KP6", [KEY_KPPLUS] = "KEY_KPPLUS", [KEY_KP1] = "KEY_KP1", [KEY_KP2] = "KEY_KP2", [KEY_KP3] = "KEY_KP3", [KEY_KP0] = "KEY_KP0", [KEY_KPDOT] = "KEY_KPDOT", [KEY_102ND] = "KEY_102ND", [KEY_F11] = "KEY_F11", [KEY_F12] = "KEY_F12", [KEY_RO] = "KEY_RO", [KEY_HENKAN] = "KEY_HENKAN", [KEY_KATAKANAHIRAGANA] = "KEY_KATAKANAHIRAGANA", [KEY_MUHENKAN] = "KEY_MUHENKAN", [KEY_KPENTER] = "KEY_KPENTER", [KEY_RIGHTCTRL] = "KEY_RIGHTCTRL", [KEY_KPSLASH] = "KEY_KPSLASH", [KEY_SYSRQ] = "KEY_SYSRQ", [KEY_RIGHTALT] = "KEY_RIGHTALT", [KEY_HOME] = "KEY_HOME", [KEY_UP] = "KEY_UP", [KEY_PAGEUP] = "KEY_PAGEUP", [KEY_LEFT] = "KEY_LEFT", [KEY_RIGHT] = "KEY_RIGHT", [KEY_END] = "KEY_END", [KEY_DOWN] = "KEY_DOWN", [KEY_PAGEDOWN] = "KEY_PAGEDOWN", [KEY_INSERT] = "KEY_INSERT", [KEY_DELETE] = "KEY_DELETE", [KEY_KPEQUAL] = "KEY_KPEQUAL", [KEY_PAUSE] = "KEY_PAUSE", [KEY_KPCOMMA] = "KEY_KPCOMMA", [KEY_HANGEUL] = "KEY_HANGEUL", [KEY_HANJA] = "KEY_HANJA", [KEY_YEN] = "KEY_YEN", [KEY_LEFTMETA] = "KEY_LEFTMETA", [KEY_RIGHTMETA] = "KEY_RIGHTMETA", [KEY_COMPOSE] = "KEY_COMPOSE", [KEY_F13] = "KEY_F13", [KEY_F14] = "KEY_F14", [KEY_F15] = "KEY_F15", [KEY_PRINT] = "KEY_PRINT", }; const char * xfree86_to_display[KEY_MAX] = { [KEY_RESERVED] = "Reserved", [KEY_ESC] = "Esc", [KEY_1] = "1", [KEY_2] = "2", [KEY_3] = "3", [KEY_4] = "4", [KEY_5] = "5", [KEY_6] = "6", [KEY_7] = "7", [KEY_8] = "8", [KEY_9] = "9", [KEY_0] = "0", [KEY_MINUS] = "-", [KEY_EQUAL] = "=", [KEY_BACKSPACE] = "Backspace", [KEY_TAB] = "Tab", [KEY_Q] = "Q", [KEY_W] = "W", [KEY_E] = "E", [KEY_R] = "R", [KEY_T] = "T", [KEY_Y] = "Y", [KEY_U] = "U", [KEY_I] = "I", [KEY_O] = "O", [KEY_P] = "P", [KEY_LEFTBRACE] = "{", [KEY_RIGHTBRACE] = "}", [KEY_ENTER] = "Enter", [KEY_LEFTCTRL] = "LCtrl", [KEY_A] = "A", [KEY_S] = "S", [KEY_D] = "D", [KEY_F] = "F", [KEY_G] = "G", [KEY_H] = "H", [KEY_J] = "J", [KEY_K] = "K", [KEY_L] = "L", [KEY_SEMICOLON] = ";", [KEY_APOSTROPHE] = "'", [KEY_GRAVE] = "`", [KEY_LEFTSHIFT] = "LShift", [KEY_BACKSLASH] = "\\", [KEY_Z] = "Z", [KEY_X] = "X", [KEY_C] = "C", [KEY_V] = "V", [KEY_B] = "B", [KEY_N] = "N", [KEY_M] = "M", [KEY_COMMA] = ",", [KEY_DOT] = ".", [KEY_SLASH] = "/", [KEY_RIGHTSHIFT] = "RShift", [KEY_KPASTERISK] = "*", [KEY_LEFTALT] = "LAlt", [KEY_SPACE] = "Space", [KEY_CAPSLOCK] = "CapsLock", [KEY_F1] = "F1", [KEY_F2] = "F2", [KEY_F3] = "F3", [KEY_F4] = "F4", [KEY_F5] = "F5", [KEY_F6] = "F6", [KEY_F7] = "F7", [KEY_F8] = "F8", [KEY_F9] = "F9", [KEY_F10] = "F10", [KEY_NUMLOCK] = "NumLock", [KEY_SCROLLLOCK] = "ScrollLock", [KEY_KP7] = "KP7", [KEY_KP8] = "KP8", [KEY_KP9] = "KP9", [KEY_KPMINUS] = "KPMinus", [KEY_KP4] = "KP4", [KEY_KP5] = "KP5", [KEY_KP6] = "KP6", [KEY_KPPLUS] = "KPPlus", [KEY_KP1] = "KP1", [KEY_KP2] = "KP2", [KEY_KP3] = "KP3", [KEY_KP0] = "KP0", [KEY_KPDOT] = "KPDOT", [KEY_102ND] = "102ND", [KEY_F11] = "F11", [KEY_F12] = "F12", [KEY_RO] = "RO", [KEY_HENKAN] = "Henkan", [KEY_KATAKANAHIRAGANA] = "Kana", [KEY_MUHENKAN] = "Muhenkan", [KEY_KPENTER] = "KPEnter", [KEY_RIGHTCTRL] = "RCtrl", [KEY_KPSLASH] = "KPSlash", [KEY_SYSRQ] = "SysRQ", [KEY_RIGHTALT] = "RAlt", [KEY_HOME] = "Home", [KEY_UP] = "↑", [KEY_PAGEUP] = "PageUp", [KEY_LEFT] = "←", [KEY_RIGHT] = "→", [KEY_END] = "End", [KEY_DOWN] = "↓", [KEY_PAGEDOWN] = "PageDown", [KEY_INSERT] = "Insert", [KEY_DELETE] = "Delete", [KEY_KPEQUAL] = "KPEqual", [KEY_PAUSE] = "Pause", [KEY_KPCOMMA] = "KPComma", [KEY_HANGEUL] = "Hangul", [KEY_HANJA] = "Hanja", [KEY_YEN] = "Yen", [KEY_LEFTMETA] = "LWin", [KEY_RIGHTMETA] = "RWin", [KEY_COMPOSE] = "Compose", [KEY_F13] = "F13", [KEY_F14] = "F14", [KEY_F15] = "F15", [KEY_PRINT] = "Print", }; #endif looking-glass-0+b4+dfsg.1/client/src/kb.h000066400000000000000000000017631411177132200201630ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include extern const uint32_t xfree86_to_ps2[KEY_MAX]; extern const char * xfree86_to_str[KEY_MAX]; extern const char * xfree86_to_display[KEY_MAX]; looking-glass-0+b4+dfsg.1/client/src/keybind.c000066400000000000000000000115201411177132200211770ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "keybind.h" #include "main.h" #include "app.h" #include "core.h" #include "kb.h" #include "spice/spice.h" #include static void bind_fullscreen(int sc, void * opaque) { app_setFullscreen(!app_getFullscreen()); } static void bind_video(int sc, void * opaque) { g_state.stopVideo = !g_state.stopVideo; app_alert( LG_ALERT_INFO, g_state.stopVideo ? "Video Stream Disabled" : "Video Stream Enabled" ); if (g_state.stopVideo) core_stopFrameThread(); else core_startFrameThread(); } static void bind_showFPS(int sc, void * opaque) { g_state.showFPS = !g_state.showFPS; app_showFPS(g_state.showFPS); } static void bind_rotate(int sc, void * opaque) { if (g_params.winRotate == LG_ROTATE_MAX-1) g_params.winRotate = 0; else ++g_params.winRotate; core_updatePositionInfo(); } static void bind_input(int sc, void * opaque) { g_state.ignoreInput = !g_state.ignoreInput; if (g_state.ignoreInput) core_setCursorInView(false); else g_state.ds->realignPointer(); app_alert( LG_ALERT_INFO, g_state.ignoreInput ? "Input Disabled" : "Input Enabled" ); } static void bind_quit(int sc, void * opaque) { g_state.state = APP_STATE_SHUTDOWN; } static void bind_mouseSens(int sc, void * opaque) { bool inc = (bool)opaque; if (inc) { if (g_cursor.sens < 9) ++g_cursor.sens; } else { if (g_cursor.sens > -9) --g_cursor.sens; } char msg[20]; snprintf(msg, sizeof(msg), "Sensitivity: %s%d", g_cursor.sens > 0 ? "+" : "", g_cursor.sens); app_alert( LG_ALERT_INFO, msg ); } static void bind_ctrlAltFn(int sc, void * opaque) { const uint32_t ctrl = xfree86_to_ps2[KEY_LEFTCTRL]; const uint32_t alt = xfree86_to_ps2[KEY_LEFTALT ]; const uint32_t fn = xfree86_to_ps2[sc]; spice_key_down(ctrl); spice_key_down(alt ); spice_key_down(fn ); spice_key_up(ctrl); spice_key_up(alt ); spice_key_up(fn ); } static void bind_passthrough(int sc, void * opaque) { sc = xfree86_to_ps2[sc]; spice_key_down(sc); spice_key_up (sc); } void keybind_register(void) { app_registerKeybind(KEY_F, bind_fullscreen, NULL, "Full screen toggle"); app_registerKeybind(KEY_V, bind_video , NULL, "Video stream toggle"); app_registerKeybind(KEY_D, bind_showFPS , NULL, "FPS display toggle"); app_registerKeybind(KEY_R, bind_rotate , NULL, "Rotate the output clockwise by 90° increments"); app_registerKeybind(KEY_Q, bind_quit , NULL, "Quit"); if (g_params.useSpiceInput) { app_registerKeybind(KEY_I , bind_input , NULL , "Spice keyboard & mouse toggle"); app_registerKeybind(KEY_INSERT, bind_mouseSens, (void*)true , "Increase mouse sensitivity in capture mode"); app_registerKeybind(KEY_DELETE, bind_mouseSens, (void*)false, "Descrease mouse sensitivity in capture mode"); app_registerKeybind(KEY_F1 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F1 to the guest"); app_registerKeybind(KEY_F2 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F2 to the guest"); app_registerKeybind(KEY_F3 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F3 to the guest"); app_registerKeybind(KEY_F4 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F4 to the guest"); app_registerKeybind(KEY_F5 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F5 to the guest"); app_registerKeybind(KEY_F6 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F6 to the guest"); app_registerKeybind(KEY_F7 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F7 to the guest"); app_registerKeybind(KEY_F8 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F8 to the guest"); app_registerKeybind(KEY_F9 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F9 to the guest"); app_registerKeybind(KEY_F10, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F10 to the guest"); app_registerKeybind(KEY_F11, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F11 to the guest"); app_registerKeybind(KEY_F12, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F12 to the guest"); app_registerKeybind(KEY_LEFTMETA , bind_passthrough, NULL, "Send LWin to the guest"); app_registerKeybind(KEY_RIGHTMETA, bind_passthrough, NULL, "Send RWin to the guest"); } } looking-glass-0+b4+dfsg.1/client/src/keybind.h000066400000000000000000000016101411177132200212030ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_KEYBIND_ #define _H_LG_KEYBIND_ void keybind_register(void); #endif looking-glass-0+b4+dfsg.1/client/src/lg-renderer.c000066400000000000000000000031741411177132200217660ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include bool LG_RendererValidatorBool(const char * value) { if (!value) return false; return (strcasecmp(value, "1" ) == 0) || (strcasecmp(value, "0" ) == 0) || (strcasecmp(value, "true" ) == 0) || (strcasecmp(value, "false" ) == 0) || (strcasecmp(value, "yes" ) == 0) || (strcasecmp(value, "no" ) == 0) || (strcasecmp(value, "on" ) == 0) || (strcasecmp(value, "off" ) == 0) || (strcasecmp(value, "enable" ) == 0) || (strcasecmp(value, "disable") == 0); } bool LG_RendererValueToBool(const char * value) { return (strcasecmp(value, "1" ) == 0) || (strcasecmp(value, "true" ) == 0) || (strcasecmp(value, "yes" ) == 0) || (strcasecmp(value, "on" ) == 0) || (strcasecmp(value, "enable" ) == 0); }looking-glass-0+b4+dfsg.1/client/src/ll.c000066400000000000000000000062711411177132200201700ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "ll.h" #include "common/locking.h" #include #include struct ll_item { void * data; struct ll_item * next; }; struct ll { struct ll_item * head; struct ll_item * tail; struct ll_item * pos; unsigned int count; LG_Lock lock; }; struct ll * ll_new(void) { struct ll * list = malloc(sizeof(struct ll)); list->head = NULL; list->tail = NULL; list->pos = NULL; list->count = 0; LG_LOCK_INIT(list->lock); return list; } void ll_free(struct ll * list) { // never free a list with items in it! assert(!list->head); LG_LOCK_FREE(list->lock); free(list); } void ll_push(struct ll * list, void * data) { struct ll_item * item = malloc(sizeof(struct ll_item)); item->data = data; item->next = NULL; LG_LOCK(list->lock); ++list->count; if (!list->head) { list->head = item; list->tail = item; LG_UNLOCK(list->lock); return; } list->tail->next = item; list->tail = item; LG_UNLOCK(list->lock); } bool ll_shift(struct ll * list, void ** data) { LG_LOCK(list->lock); if (!list->head) { LG_UNLOCK(list->lock); return false; } --list->count; struct ll_item * item = list->head; list->head = item->next; list->pos = NULL; if (list->tail == item) list->tail = NULL; LG_UNLOCK(list->lock); if (data) *data = item->data; free(item); return true; } bool ll_peek_head(struct ll * list, void ** data) { LG_LOCK(list->lock); if (!list->head) { LG_UNLOCK(list->lock); return false; } *data = list->head->data; LG_UNLOCK(list->lock); return true; } bool ll_peek_tail(struct ll * list, void ** data) { LG_LOCK(list->lock); if (!list->tail) { LG_UNLOCK(list->lock); return false; } *data = list->tail->data; LG_UNLOCK(list->lock); return true; } unsigned int ll_count(struct ll * list) { return list->count; } void ll_reset (struct ll * list) { LG_LOCK(list->lock); list->pos = NULL; LG_UNLOCK(list->lock); } bool ll_walk(struct ll * list, void ** data) { LG_LOCK(list->lock); if (!list->pos) { if (!list->head) { LG_UNLOCK(list->lock); return false; } list->pos = list->head; } else { if (!list->pos->next) { LG_UNLOCK(list->lock); return false; } list->pos = list->pos->next; } *data = list->pos->data; LG_UNLOCK(list->lock); return true; } looking-glass-0+b4+dfsg.1/client/src/main.c000066400000000000000000000654051411177132200205110ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "main.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/debug.h" #include "common/crash.h" #include "common/KVMFR.h" #include "common/stringutils.h" #include "common/thread.h" #include "common/locking.h" #include "common/event.h" #include "common/ivshmem.h" #include "common/time.h" #include "common/version.h" #include "core.h" #include "app.h" #include "keybind.h" #include "clipboard.h" #include "ll.h" #include "egl_dynprocs.h" // forwards static int cursorThread(void * unused); static int renderThread(void * unused); static LGEvent *e_startup = NULL; static LGEvent *e_frame = NULL; static LGThread *t_spice = NULL; static LGThread *t_render = NULL; static LGThread *t_cursor = NULL; struct AppState g_state; struct CursorState g_cursor; // this structure is initialized in config.c struct AppParams g_params = { 0 }; static void lgInit(void) { g_state.state = APP_STATE_RUNNING; g_state.formatValid = false; g_state.resizeDone = true; if (g_cursor.grab) core_setGrab(false); g_cursor.useScale = false; g_cursor.scale.x = 1.0; g_cursor.scale.y = 1.0; g_cursor.draw = false; g_cursor.inView = false; g_cursor.guest.valid = false; // if spice is not in use, hide the local cursor if (!core_inputEnabled() && g_params.hideMouse) g_state.ds->showPointer(false); else g_state.ds->showPointer(true); } static int renderThread(void * unused) { if (!g_state.lgr->render_startup(g_state.lgrData)) { g_state.state = APP_STATE_SHUTDOWN; /* unblock threads waiting on the condition */ lgSignalEvent(e_startup); return 1; } LG_LOCK_INIT(g_state.lgrLock); g_state.lgr->on_show_fps(g_state.lgrData, g_state.showFPS); /* signal to other threads that the renderer is ready */ lgSignalEvent(e_startup); struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); while(g_state.state != APP_STATE_SHUTDOWN) { if (g_params.fpsMin != 0) { lgWaitEventAbs(e_frame, &time); clock_gettime(CLOCK_MONOTONIC, &time); tsAdd(&time, g_state.frameTime); } int resize = atomic_load(&g_state.lgrResize); if (resize) { if (g_state.lgr) g_state.lgr->on_resize(g_state.lgrData, g_state.windowW, g_state.windowH, g_state.windowScale, g_state.dstRect, g_params.winRotate); atomic_compare_exchange_weak(&g_state.lgrResize, &resize, 0); } LG_LOCK(g_state.lgrLock); if (!g_state.lgr->render(g_state.lgrData, g_params.winRotate)) { LG_UNLOCK(g_state.lgrLock); break; } LG_UNLOCK(g_state.lgrLock); if (g_state.showFPS) { const uint64_t t = nanotime(); g_state.renderTime += t - g_state.lastFrameTime; g_state.lastFrameTime = t; ++g_state.renderCount; if (g_state.renderTime > 1e9) { const float avgUPS = 1000.0f / (((float)g_state.renderTime / atomic_exchange_explicit(&g_state.frameCount, 0, memory_order_acquire)) / 1e6f); const float avgFPS = 1000.0f / (((float)g_state.renderTime / g_state.renderCount) / 1e6f); g_state.lgr->update_fps(g_state.lgrData, avgUPS, avgFPS); g_state.renderTime = 0; g_state.renderCount = 0; } } const uint64_t now = microtime(); if (!g_state.resizeDone && g_state.resizeTimeout < now) { if (g_params.autoResize) { g_state.ds->setWindowSize( g_state.dstRect.w, g_state.dstRect.h ); } g_state.resizeDone = true; } app_handleRenderEvent(now); } g_state.state = APP_STATE_SHUTDOWN; if (t_cursor) lgJoinThread(t_cursor, NULL); core_stopFrameThread(); g_state.lgr->deinitialize(g_state.lgrData); g_state.lgr = NULL; LG_LOCK_FREE(g_state.lgrLock); return 0; } static int cursorThread(void * unused) { LGMP_STATUS status; PLGMPClientQueue queue; LG_RendererCursor cursorType = LG_CURSOR_COLOR; lgWaitEvent(e_startup, TIMEOUT_INFINITE); // subscribe to the pointer queue while(g_state.state == APP_STATE_RUNNING) { status = lgmpClientSubscribe(g_state.lgmp, LGMP_Q_POINTER, &queue); if (status == LGMP_OK) break; if (status == LGMP_ERR_NO_SUCH_QUEUE) { usleep(1000); continue; } DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status)); g_state.state = APP_STATE_SHUTDOWN; break; } while(g_state.state == APP_STATE_RUNNING) { LGMPMessage msg; if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK) { if (status == LGMP_ERR_QUEUE_EMPTY) { if (g_cursor.redraw && g_cursor.guest.valid) { g_cursor.redraw = false; g_state.lgr->on_mouse_event ( g_state.lgrData, g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput), g_cursor.guest.x, g_cursor.guest.y ); lgSignalEvent(e_frame); } const struct timespec req = { .tv_sec = 0, .tv_nsec = g_params.cursorPollInterval * 1000L }; struct timespec rem; while(nanosleep(&req, &rem) < 0) if (errno != -EINTR) { DEBUG_ERROR("nanosleep failed"); break; } continue; } if (status == LGMP_ERR_INVALID_SESSION) g_state.state = APP_STATE_RESTART; else { DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status)); g_state.state = APP_STATE_SHUTDOWN; } break; } KVMFRCursor * cursor = (KVMFRCursor *)msg.mem; g_cursor.guest.visible = msg.udata & CURSOR_FLAG_VISIBLE; if (msg.udata & CURSOR_FLAG_SHAPE) { switch(cursor->type) { case CURSOR_TYPE_COLOR : cursorType = LG_CURSOR_COLOR ; break; case CURSOR_TYPE_MONOCHROME : cursorType = LG_CURSOR_MONOCHROME ; break; case CURSOR_TYPE_MASKED_COLOR: cursorType = LG_CURSOR_MASKED_COLOR; break; default: DEBUG_ERROR("Invalid cursor type"); lgmpClientMessageDone(queue); continue; } g_cursor.guest.hx = cursor->hx; g_cursor.guest.hy = cursor->hy; const uint8_t * data = (const uint8_t *)(cursor + 1); if (!g_state.lgr->on_mouse_shape( g_state.lgrData, cursorType, cursor->width, cursor->height, cursor->pitch, data) ) { DEBUG_ERROR("Failed to update mouse shape"); lgmpClientMessageDone(queue); continue; } } if (msg.udata & CURSOR_FLAG_POSITION) { bool valid = g_cursor.guest.valid; g_cursor.guest.x = cursor->x; g_cursor.guest.y = cursor->y; g_cursor.guest.valid = true; // if the state just became valid if (valid != true && core_inputEnabled()) { core_alignToGuest(); app_resyncMouseBasic(); } // tell the DS there was an update core_handleGuestMouseUpdate(); } lgmpClientMessageDone(queue); g_cursor.redraw = false; g_state.lgr->on_mouse_event ( g_state.lgrData, g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput), g_cursor.guest.x, g_cursor.guest.y ); if (g_params.mouseRedraw && g_cursor.guest.visible) lgSignalEvent(e_frame); } lgmpClientUnsubscribe(&queue); return 0; } int main_frameThread(void * unused) { struct DMAFrameInfo { KVMFRFrame * frame; size_t dataSize; int fd; }; LGMP_STATUS status; PLGMPClientQueue queue; uint32_t formatVer = 0; size_t dataSize = 0; LG_RendererFormat lgrFormat; struct DMAFrameInfo dmaInfo[LGMP_Q_FRAME_LEN] = {0}; const bool useDMA = g_params.allowDMA && ivshmemHasDMA(&g_state.shm) && g_state.lgr->supports && g_state.lgr->supports(g_state.lgrData, LG_SUPPORTS_DMABUF); if (useDMA) DEBUG_INFO("Using DMA buffer support"); lgWaitEvent(e_startup, TIMEOUT_INFINITE); if (g_state.state != APP_STATE_RUNNING) return 0; // subscribe to the frame queue while(g_state.state == APP_STATE_RUNNING) { status = lgmpClientSubscribe(g_state.lgmp, LGMP_Q_FRAME, &queue); if (status == LGMP_OK) break; if (status == LGMP_ERR_NO_SUCH_QUEUE) { usleep(1000); continue; } DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status)); g_state.state = APP_STATE_SHUTDOWN; break; } while(g_state.state == APP_STATE_RUNNING && !g_state.stopVideo) { LGMPMessage msg; if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK) { if (status == LGMP_ERR_QUEUE_EMPTY) { const struct timespec req = { .tv_sec = 0, .tv_nsec = g_params.framePollInterval * 1000L }; struct timespec rem; while(nanosleep(&req, &rem) < 0) if (errno != -EINTR) { DEBUG_ERROR("nanosleep failed"); break; } continue; } if (status == LGMP_ERR_INVALID_SESSION) g_state.state = APP_STATE_RESTART; else { DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status)); g_state.state = APP_STATE_SHUTDOWN; } break; } KVMFRFrame * frame = (KVMFRFrame *)msg.mem; struct DMAFrameInfo *dma = NULL; if (!g_state.formatValid || frame->formatVer != formatVer) { // setup the renderer format with the frame format details lgrFormat.type = frame->type; lgrFormat.width = frame->width; lgrFormat.height = frame->height; lgrFormat.stride = frame->stride; lgrFormat.pitch = frame->pitch; if (frame->height != frame->realHeight) { const float needed = ((frame->realHeight * frame->pitch * 2) / 1048576.0f) + 10.0f; const int size = (int)powf(2.0f, ceilf(logf(needed) / logf(2.0f))); DEBUG_BREAK(); DEBUG_WARN("IVSHMEM too small, screen truncated"); DEBUG_WARN("Recommend increase size to %d MiB", size); DEBUG_BREAK(); app_alert(LG_ALERT_ERROR, "IVSHMEM too small, screen truncated\n" "Recommend increasing size to %d MiB", size); } switch(frame->rotation) { case FRAME_ROT_0 : lgrFormat.rotate = LG_ROTATE_0 ; break; case FRAME_ROT_90 : lgrFormat.rotate = LG_ROTATE_90 ; break; case FRAME_ROT_180: lgrFormat.rotate = LG_ROTATE_180; break; case FRAME_ROT_270: lgrFormat.rotate = LG_ROTATE_270; break; } g_state.rotate = lgrFormat.rotate; bool error = false; switch(frame->type) { case FRAME_TYPE_RGBA: case FRAME_TYPE_BGRA: case FRAME_TYPE_RGBA10: dataSize = lgrFormat.height * lgrFormat.pitch; lgrFormat.bpp = 32; break; case FRAME_TYPE_RGBA16F: dataSize = lgrFormat.height * lgrFormat.pitch; lgrFormat.bpp = 64; break; default: DEBUG_ERROR("Unsupported frameType"); error = true; break; } if (error) { lgmpClientMessageDone(queue); g_state.state = APP_STATE_SHUTDOWN; break; } g_state.formatValid = true; formatVer = frame->formatVer; DEBUG_INFO("Format: %s %ux%u stride:%u pitch:%u rotation:%d", FrameTypeStr[frame->type], frame->width, frame->height, frame->stride, frame->pitch, frame->rotation); LG_LOCK(g_state.lgrLock); if (!g_state.lgr->on_frame_format(g_state.lgrData, lgrFormat, useDMA)) { DEBUG_ERROR("renderer failed to configure format"); g_state.state = APP_STATE_SHUTDOWN; LG_UNLOCK(g_state.lgrLock); break; } LG_UNLOCK(g_state.lgrLock); g_state.srcSize.x = lgrFormat.width; g_state.srcSize.y = lgrFormat.height; g_state.haveSrcSize = true; if (g_params.autoResize) g_state.ds->setWindowSize(lgrFormat.width, lgrFormat.height); g_cursor.guest.dpiScale = frame->mouseScalePercent; core_updatePositionInfo(); } if (useDMA) { /* find the existing dma buffer if it exists */ for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i) { if (dmaInfo[i].frame == frame) { dma = &dmaInfo[i]; /* if it's too small close it */ if (dma->dataSize < dataSize) { close(dma->fd); dma->fd = -1; } break; } } /* otherwise find a free buffer for use */ if (!dma) for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i) { if (!dmaInfo[i].frame) { dma = &dmaInfo[i]; dma->frame = frame; dma->fd = -1; break; } } /* open the buffer */ if (dma->fd == -1) { const uintptr_t pos = (uintptr_t)msg.mem - (uintptr_t)g_state.shm.mem; const uintptr_t offset = (uintptr_t)frame->offset + FrameBufferStructSize; dma->dataSize = dataSize; dma->fd = ivshmemGetDMABuf(&g_state.shm, pos + offset, dataSize); if (dma->fd < 0) { DEBUG_ERROR("Failed to get the DMA buffer for the frame"); g_state.state = APP_STATE_SHUTDOWN; break; } } } FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)frame) + frame->offset); if (!g_state.lgr->on_frame(g_state.lgrData, fb, useDMA ? dma->fd : -1)) { lgmpClientMessageDone(queue); DEBUG_ERROR("renderer on frame returned failure"); g_state.state = APP_STATE_SHUTDOWN; break; } if (g_params.autoScreensaver && g_state.autoIdleInhibitState != frame->blockScreensaver) { if (frame->blockScreensaver) g_state.ds->inhibitIdle(); else g_state.ds->uninhibitIdle(); g_state.autoIdleInhibitState = frame->blockScreensaver; } atomic_fetch_add_explicit(&g_state.frameCount, 1, memory_order_relaxed); lgSignalEvent(e_frame); lgmpClientMessageDone(queue); } lgmpClientUnsubscribe(&queue); g_state.lgr->on_restart(g_state.lgrData); if (useDMA) { for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i) if (dmaInfo[i].fd >= 0) close(dmaInfo[i].fd); } return 0; } int spiceThread(void * arg) { while(g_state.state != APP_STATE_SHUTDOWN) if (!spice_process(1000)) { if (g_state.state != APP_STATE_SHUTDOWN) { g_state.state = APP_STATE_SHUTDOWN; DEBUG_ERROR("failed to process spice messages"); } break; } g_state.state = APP_STATE_SHUTDOWN; return 0; } void intHandler(int sig) { switch(sig) { case SIGINT: case SIGTERM: if (g_state.state != APP_STATE_SHUTDOWN) { DEBUG_INFO("Caught signal, shutting down..."); g_state.state = APP_STATE_SHUTDOWN; } else { DEBUG_INFO("Caught second signal, force quitting..."); signal(sig, SIG_DFL); raise(sig); } break; } } static bool tryRenderer(const int index, const LG_RendererParams lgrParams, bool * needsOpenGL) { const LG_Renderer *r = LG_Renderers[index]; if (!IS_LG_RENDERER_VALID(r)) { DEBUG_ERROR("FIXME: Renderer %d is invalid, skipping", index); return false; } // create the renderer g_state.lgrData = NULL; *needsOpenGL = false; if (!r->create(&g_state.lgrData, lgrParams, needsOpenGL)) return false; // initialize the renderer if (!r->initialize(g_state.lgrData)) { r->deinitialize(g_state.lgrData); return false; } DEBUG_INFO("Using Renderer: %s", r->get_name()); return true; } static int lg_run(void) { memset(&g_state, 0, sizeof(g_state)); g_cursor.sens = g_params.mouseSens; if (g_cursor.sens < -9) g_cursor.sens = -9; else if (g_cursor.sens > 9) g_cursor.sens = 9; g_state.showFPS = g_params.showFPS; // search for the best displayserver ops to use for(int i = 0; i < LG_DISPLAYSERVER_COUNT; ++i) if (LG_DisplayServers[i]->probe()) { g_state.ds = LG_DisplayServers[i]; break; } assert(g_state.ds); ASSERT_LG_DS_VALID(g_state.ds); // init the subsystem if (!g_state.ds->earlyInit()) { DEBUG_ERROR("Subsystem early init failed"); return -1; } // override the SIGINIT handler so that we can tell the difference between // SIGINT and the user sending a close event, such as ALT+F4 signal(SIGINT , intHandler); signal(SIGTERM, intHandler); // try map the shared memory if (!ivshmemOpen(&g_state.shm)) { DEBUG_ERROR("Failed to map memory"); return -1; } // try to connect to the spice server if (g_params.useSpiceInput || g_params.useSpiceClipboard) { if (g_params.useSpiceClipboard) spice_set_clipboard_cb( cb_spiceNotice, cb_spiceData, cb_spiceRelease, cb_spiceRequest); if (!spice_connect(g_params.spiceHost, g_params.spicePort, "")) { DEBUG_ERROR("Failed to connect to spice server"); return -1; } while(g_state.state != APP_STATE_SHUTDOWN && !spice_ready()) if (!spice_process(1000)) { g_state.state = APP_STATE_SHUTDOWN; DEBUG_ERROR("Failed to process spice messages"); return -1; } spice_mouse_mode(true); if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice)) { DEBUG_ERROR("spice create thread failed"); return -1; } } // select and init a renderer bool needsOpenGL; LG_RendererParams lgrParams; lgrParams.quickSplash = g_params.quickSplash; if (g_params.forceRenderer) { DEBUG_INFO("Trying forced renderer"); if (!tryRenderer(g_params.forceRendererIndex, lgrParams, &needsOpenGL)) { DEBUG_ERROR("Forced renderer failed to iniailize"); return -1; } g_state.lgr = LG_Renderers[g_params.forceRendererIndex]; } else { // probe for a a suitable renderer for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) { if (tryRenderer(i, lgrParams, &needsOpenGL)) { g_state.lgr = LG_Renderers[i]; break; } } } if (!g_state.lgr) { DEBUG_INFO("Unable to find a suitable renderer"); return -1; } // initialize the window dimensions at init for renderers g_state.windowW = g_params.w; g_state.windowH = g_params.h; g_state.windowCX = g_params.w / 2; g_state.windowCY = g_params.h / 2; core_updatePositionInfo(); const LG_DSInitParams params = { .title = g_params.windowTitle, .x = g_params.x, .y = g_params.y, .w = g_params.w, .h = g_params.h, .center = g_params.center, .fullscreen = g_params.fullscreen, .resizable = g_params.allowResize, .borderless = g_params.borderless, .maximize = g_params.maximize, .opengl = needsOpenGL }; g_state.dsInitialized = g_state.ds->init(params); if (!g_state.dsInitialized) { DEBUG_ERROR("Failed to initialize the displayserver backend"); return -1; } if (g_params.noScreensaver) g_state.ds->inhibitIdle(); // ensure renderer viewport is aware of the current window size core_updatePositionInfo(); if (g_params.fpsMin <= 0) { // default 30 fps g_state.frameTime = 1000000000ULL / 30ULL; } else { DEBUG_INFO("Using the FPS minimum from args: %d", g_params.fpsMin); g_state.frameTime = 1000000000ULL / (unsigned long long)g_params.fpsMin; } keybind_register(); // setup the startup condition if (!(e_startup = lgCreateEvent(false, 0))) { DEBUG_ERROR("failed to create the startup event"); return -1; } // setup the new frame event if (!(e_frame = lgCreateEvent(true, 0))) { DEBUG_ERROR("failed to create the frame event"); return -1; } lgInit(); // start the renderThread so we don't just display junk if (!lgCreateThread("renderThread", renderThread, NULL, &t_render)) { DEBUG_ERROR("render create thread failed"); return -1; } // wait for startup to complete so that any error messages below are output at // the end of the output lgWaitEvent(e_startup, TIMEOUT_INFINITE); g_state.ds->startup(); g_state.cbAvailable = g_state.ds->cbInit && g_state.ds->cbInit(); if (g_state.cbAvailable) g_state.cbRequestList = ll_new(); LGMP_STATUS status; while(g_state.state == APP_STATE_RUNNING) { if ((status = lgmpClientInit(g_state.shm.mem, g_state.shm.size, &g_state.lgmp)) == LGMP_OK) break; DEBUG_ERROR("lgmpClientInit Failed: %s", lgmpStatusString(status)); return -1; } /* this short timeout is to allow the LGMP host to update the timestamp before * we start checking for a valid session */ g_state.ds->wait(200); if (g_params.captureOnStart) core_setGrab(true); uint32_t udataSize; KVMFR *udata; int waitCount = 0; restart: while(g_state.state == APP_STATE_RUNNING) { if ((status = lgmpClientSessionInit(g_state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK) break; if (status != LGMP_ERR_INVALID_SESSION && status != LGMP_ERR_INVALID_MAGIC) { DEBUG_ERROR("lgmpClientSessionInit Failed: %s", lgmpStatusString(status)); return -1; } if (waitCount++ == 0) { DEBUG_BREAK(); DEBUG_INFO("The host application seems to not be running"); DEBUG_INFO("Waiting for the host application to start..."); } if (waitCount == 30) { DEBUG_BREAK(); DEBUG_INFO("Please check the host application is running and is the correct version"); DEBUG_INFO("Check the host log in your guest at %%ProgramData%%\\Looking Glass (host)\\looking-glass-host.txt"); DEBUG_INFO("Continuing to wait..."); } g_state.ds->wait(1000); } if (g_state.state != APP_STATE_RUNNING) return -1; // dont show warnings again after the first startup waitCount = 100; const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0; if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION) { DEBUG_BREAK(); DEBUG_ERROR("The host application is not compatible with this client"); DEBUG_ERROR("This is not a Looking Glass error, do not report this"); DEBUG_ERROR("Please install the matching host application for this client"); if (magicMatches) { DEBUG_ERROR("Expected KVMFR version %d, got %d", KVMFR_VERSION, udata->version); DEBUG_ERROR("Client version: %s", BUILD_VERSION); if (udata->version >= 2) DEBUG_ERROR(" Host version: %s", udata->hostver); } else DEBUG_ERROR("Invalid KVMFR magic"); DEBUG_BREAK(); if (magicMatches) { DEBUG_INFO("Waiting for you to upgrade the host application"); while (g_state.state == APP_STATE_RUNNING && udata->version != KVMFR_VERSION) g_state.ds->wait(1000); if (g_state.state != APP_STATE_RUNNING) return -1; goto restart; } else return -1; } DEBUG_INFO("Host ready, reported version: %s", udata->hostver); DEBUG_INFO("Starting session"); if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor)) { DEBUG_ERROR("cursor create thread failed"); return 1; } if (!core_startFrameThread()) return -1; while(g_state.state == APP_STATE_RUNNING) { if (!lgmpClientSessionValid(g_state.lgmp)) { g_state.state = APP_STATE_RESTART; break; } g_state.ds->wait(100); } if (g_state.state == APP_STATE_RESTART) { lgSignalEvent(e_startup); lgSignalEvent(e_frame); core_stopFrameThread(); lgJoinThread(t_cursor, NULL); t_cursor = NULL; lgInit(); g_state.lgr->on_restart(g_state.lgrData); DEBUG_INFO("Waiting for the host to restart..."); goto restart; } return 0; } static void lg_shutdown(void) { g_state.state = APP_STATE_SHUTDOWN; if (t_render) { lgSignalEvent(e_startup); lgSignalEvent(e_frame); lgJoinThread(t_render, NULL); } lgmpClientFree(&g_state.lgmp); if (e_frame) { lgFreeEvent(e_frame); e_frame = NULL; } if (e_startup) { lgFreeEvent(e_startup); e_startup = NULL; } // if spice is still connected send key up events for any pressed keys if (g_params.useSpiceInput && spice_ready()) { for(int scancode = 0; scancode < KEY_MAX; ++scancode) if (g_state.keyDown[scancode]) { g_state.keyDown[scancode] = false; spice_key_up(scancode); } spice_disconnect(); if (t_spice) lgJoinThread(t_spice, NULL); } if (g_state.ds) g_state.ds->shutdown(); if (g_state.cbRequestList) { ll_free(g_state.cbRequestList); g_state.cbRequestList = NULL; } app_releaseAllKeybinds(); if (g_state.dsInitialized) g_state.ds->free(); ivshmemClose(&g_state.shm); } int main(int argc, char * argv[]) { // initialize for DEBUG_* macros debug_init(); if (getuid() == 0) { DEBUG_ERROR("Do not run looking glass as root!"); return -1; } DEBUG_INFO("Looking Glass (%s)", BUILD_VERSION); DEBUG_INFO("Locking Method: " LG_LOCK_MODE); if (!installCrashHandler("/proc/self/exe")) DEBUG_WARN("Failed to install the crash handler"); config_init(); ivshmemOptionsInit(); egl_dynProcsInit(); // early renderer setup for option registration for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) LG_Renderers[i]->setup(); for(unsigned int i = 0; i < LG_DISPLAYSERVER_COUNT; ++i) LG_DisplayServers[i]->setup(); if (!config_load(argc, argv)) return -1; const int ret = lg_run(); lg_shutdown(); config_free(); cleanupCrashHandler(); return ret; } looking-glass-0+b4+dfsg.1/client/src/main.h000066400000000000000000000147171411177132200205160ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include "dynamic/displayservers.h" #include "dynamic/renderers.h" #include "common/thread.h" #include "common/types.h" #include "common/ivshmem.h" #include "common/locking.h" #include "spice/spice.h" #include enum RunState { APP_STATE_RUNNING, APP_STATE_RESTART, APP_STATE_SHUTDOWN }; struct AppState { enum RunState state; struct LG_DisplayServerOps * ds; bool dsInitialized; bool stopVideo; bool ignoreInput; bool showFPS; bool escapeActive; uint64_t escapeTime; int escapeAction; bool escapeHelp; KeybindHandle bindings[KEY_MAX]; const char * keyDescription[KEY_MAX]; bool keyDown[KEY_MAX]; bool haveSrcSize; struct Point windowPos; int windowW, windowH; int windowCX, windowCY; double windowScale; LG_RendererRotate rotate; bool focused; struct Border border; struct Point srcSize; LG_RendererRect dstRect; bool posInfoValid; bool alignToGuest; const LG_Renderer * lgr; void * lgrData; atomic_int lgrResize; LG_Lock lgrLock; bool cbAvailable; SpiceDataType cbType; bool cbChunked; size_t cbXfer; struct ll * cbRequestList; struct IVSHMEM shm; PLGMPClient lgmp; PLGMPClientQueue frameQueue; PLGMPClientQueue pointerQueue; LGThread * frameThread; bool formatValid; atomic_uint_least64_t frameTime; uint64_t lastFrameTime; uint64_t renderTime; atomic_uint_least64_t frameCount; uint64_t renderCount; uint64_t resizeTimeout; bool resizeDone; bool autoIdleInhibitState; }; struct AppParams { bool autoResize; bool allowResize; bool keepAspect; bool forceAspect; bool dontUpscale; bool shrinkOnUpscale; bool borderless; bool fullscreen; bool maximize; bool minimizeOnFocusLoss; bool center; int x, y; unsigned int w, h; int fpsMin; bool showFPS; LG_RendererRotate winRotate; bool useSpiceInput; bool useSpiceClipboard; const char * spiceHost; unsigned int spicePort; bool clipboardToVM; bool clipboardToLocal; bool scaleMouseInput; bool hideMouse; bool ignoreQuit; bool noScreensaver; bool autoScreensaver; bool grabKeyboard; bool grabKeyboardOnFocus; int escapeKey; bool ignoreWindowsKeys; bool releaseKeysOnFocusLoss; bool showAlerts; bool captureOnStart; bool quickSplash; bool alwaysShowCursor; uint64_t helpMenuDelayUs; unsigned int cursorPollInterval; unsigned int framePollInterval; bool allowDMA; bool forceRenderer; unsigned int forceRendererIndex; const char * windowTitle; bool mouseRedraw; int mouseSens; bool mouseSmoothing; bool rawMouse; bool autoCapture; bool captureInputOnly; bool showCursorDot; }; struct CBRequest { SpiceDataType type; LG_ClipboardReplyFn replyFn; void * opaque; }; struct KeybindHandle { int sc; KeybindFn callback; void * opaque; }; enum WarpState { WARP_STATE_ON, WARP_STATE_OFF }; struct CursorInfo { /* x & y postiion */ int x , y; /* pointer hotspot offsets */ int hx, hy; /* true if the pointer is visible on the guest */ bool visible; /* true if the details in this struct are valid */ bool valid; /* the DPI scaling of the guest */ uint32_t dpiScale; }; struct CursorState { /* cursor is in grab mode */ bool grab; /* true if we are to draw the cursor on screen */ bool draw; /* true if the cursor is currently in our window */ bool inWindow; /* true if the cursor is currently in the guest view area */ bool inView; /* true if the guest should be realigned to the host when next drawn */ bool realign; /* true if the cursor needs re-drawing/updating */ bool redraw; /* true if the cursor movements should be scaled */ bool useScale; /* the amount to scale the X & Y movements by */ struct DoublePoint scale; /* the dpi scale factor from the guest as a fraction */ double dpiScale; /* the error accumulator */ struct DoublePoint acc; /* the local position */ struct DoublePoint pos; /* true if the position is valid */ bool valid; /* the button state */ unsigned int buttons; /* the delta since last warp when in auto capture mode */ struct DoublePoint delta; /* the scale factor for the mouse sensitiviy */ int sens; /* the mouse warp state */ enum WarpState warpState; /* the guest's cursor position */ struct CursorInfo guest; /* the projected position after move, for app_handleMouseBasic only */ struct Point projected; }; // forwards extern struct AppState g_state; extern struct CursorState g_cursor; extern struct AppParams g_params; int main_frameThread(void * unused); looking-glass-0+b4+dfsg.1/client/src/util.c000066400000000000000000000123611411177132200205330ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "util.h" #include "main.h" #include "common/debug.h" #include "common/stringutils.h" #include #include #include #include #include bool util_fileGetContents(const char * filename, char ** buffer, size_t * length) { FILE * fh = fopen(filename, "r"); if (!fh) { DEBUG_ERROR("Failed to open the file: %s", filename); return false; } if (fseek(fh, 0, SEEK_END) != 0) { DEBUG_ERROR("Failed to seek"); fclose(fh); return false; } long fsize = ftell(fh); if (fseek(fh, 0, SEEK_SET) != 0) { DEBUG_ERROR("Failed to seek"); fclose(fh); return false; } *buffer = malloc(fsize + 1); if (!*buffer) { DEBUG_ERROR("Failed to allocate buffer of %lu bytes", fsize + 1); fclose(fh); return false; } if (fread(*buffer, 1, fsize, fh) != fsize) { DEBUG_ERROR("Failed to read the entire file"); fclose(fh); free(*buffer); return false; } fclose(fh); buffer[fsize] = 0; *length = fsize; return true; } void util_cursorToInt(double ex, double ey, int *x, int *y) { /* only smooth if enabled and not using raw mode */ if (g_params.mouseSmoothing && !(g_cursor.grab && g_params.rawMouse)) { static struct DoublePoint last = { 0 }; /* only apply smoothing to small deltas */ if (fabs(ex - last.x) < 5.0 && fabs(ey - last.y) < 5.0) { ex = last.x = (last.x + ex) / 2.0; ey = last.y = (last.y + ey) / 2.0; } else { last.x = ex; last.y = ey; } } /* convert to int accumulating the fractional error */ ex += g_cursor.acc.x; ey += g_cursor.acc.y; g_cursor.acc.x = modf(ex, &ex); g_cursor.acc.y = modf(ey, &ey); *x = (int)ex; *y = (int)ey; } bool util_guestCurToLocal(struct DoublePoint *local) { if (!g_cursor.guest.valid || !g_state.posInfoValid) return false; const struct DoublePoint point = { .x = g_cursor.guest.x + g_cursor.guest.hx, .y = g_cursor.guest.y + g_cursor.guest.hy }; switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: local->x = (point.x / g_cursor.scale.x) + g_state.dstRect.x; local->y = (point.y / g_cursor.scale.y) + g_state.dstRect.y;; break; case LG_ROTATE_90: local->x = (g_state.dstRect.x + g_state.dstRect.w) - point.y / g_cursor.scale.y; local->y = (point.x / g_cursor.scale.x) + g_state.dstRect.y; break; case LG_ROTATE_180: local->x = (g_state.dstRect.x + g_state.dstRect.w) - point.x / g_cursor.scale.x; local->y = (g_state.dstRect.y + g_state.dstRect.h) - point.y / g_cursor.scale.y; break; case LG_ROTATE_270: local->x = (point.y / g_cursor.scale.y) + g_state.dstRect.x; local->y = (g_state.dstRect.y + g_state.dstRect.h) - point.x / g_cursor.scale.x; break; } return true; } void util_localCurToGuest(struct DoublePoint *guest) { const struct DoublePoint point = g_cursor.pos; switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: guest->x = (point.x - g_state.dstRect.x) * g_cursor.scale.x; guest->y = (point.y - g_state.dstRect.y) * g_cursor.scale.y; break; case LG_ROTATE_90: guest->x = (point.y - g_state.dstRect.y) * g_cursor.scale.y; guest->y = (g_state.dstRect.w - point.x + g_state.dstRect.x) * g_cursor.scale.x; break; case LG_ROTATE_180: guest->x = (g_state.dstRect.w - point.x + g_state.dstRect.x) * g_cursor.scale.x; guest->y = (g_state.dstRect.h - point.y + g_state.dstRect.y) * g_cursor.scale.y; break; case LG_ROTATE_270: guest->x = (g_state.dstRect.h - point.y + g_state.dstRect.y) * g_cursor.scale.y; guest->y = (point.x - g_state.dstRect.x) * g_cursor.scale.x; break; default: assert(!"unreachable"); } } void util_rotatePoint(struct DoublePoint *point) { double temp; switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: break; case LG_ROTATE_90: temp = point->x; point->x = point->y; point->y = -temp; break; case LG_ROTATE_180: point->x = -point->x; point->y = -point->y; break; case LG_ROTATE_270: temp = point->x; point->x = -point->y; point->y = temp; break; } } bool util_hasGLExt(const char * exts, const char * ext) { return str_containsValue(exts, ' ', ext); } looking-glass-0+b4+dfsg.1/common/000077500000000000000000000000001411177132200166325ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/common/CMakeLists.txt000066400000000000000000000013651411177132200213770ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common LANGUAGES C) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") include_directories( ${PROJECT_SOURCE_DIR}/include ) add_definitions(-D_GNU_SOURCE) if(ENABLE_BACKTRACE) add_definitions(-DENABLE_BACKTRACE) endif() if (CMAKE_C_COMPILER_ID STREQUAL "Clang") add_compile_options( "-Wno-unknown-warning-option" ) endif() add_subdirectory(src/platform) set(COMMON_SOURCES src/stringutils.c src/stringlist.c src/option.c src/framebuffer.c src/KVMFR.c src/countedbuffer.c src/runningavg.c ) add_library(lg_common STATIC ${COMMON_SOURCES}) target_link_libraries(lg_common lg_common_platform) target_include_directories(lg_common INTERFACE include PRIVATE src ) looking-glass-0+b4+dfsg.1/common/cmake/000077500000000000000000000000001411177132200177125ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/common/cmake/FindBFD.cmake000066400000000000000000000024751411177132200221200ustar00rootroot00000000000000# Try to find the BFD librairies # BFD_FOUND - system has BFD lib # BFD_INCLUDE_DIR - the BFD include directory # BFD_LIBRARIES - Libraries needed to use BFD if (BFD_INCLUDE_DIR AND BFD_LIBRARIES) # Already in cache, be silent set(BFD_FIND_QUIETLY TRUE) endif (BFD_INCLUDE_DIR AND BFD_LIBRARIES) find_path(BFD_INCLUDE_DIR NAMES bfd.h) find_library(BFD_LIBRARIES NAMES bfd) include(FindPackageHandleStandardArgs) if (";${BFD_LIBRARIES};" MATCHES "bfd.a;") MESSAGE(STATUS "Linking against static bfd") find_library(BFD_LIBIBERTY_LIBRARIES NAMES libiberty.a) find_package_handle_standard_args(BFD_LIBIBERTY DEFAULT_MSG BFD_LIBIBERTY_LIBRARIES) find_library(BFD_LIBZ_LIBRARIES NAMES libz.a) find_package_handle_standard_args(BFD_LIBZ DEFAULT_MSG BFD_LIBZ_LIBRARIES) if (NOT ${BFD_LIBIBERTY_FOUND}) message(FATAL_ERROR "Using static libbfd.a, but libiberty.a not available") elseif (NOT ${BFD_LIBZ_FOUND}) message(FATAL_ERROR "Using static libbfd.a, but libz.a not available") else() list(APPEND BFD_LIBRARIES ${BFD_LIBIBERTY_LIBRARIES} ${BFD_LIBZ_LIBRARIES}) endif() endif() MESSAGE(STATUS "BFD libs: " "${BFD_LIBRARIES}") find_package_handle_standard_args(BFD DEFAULT_MSG BFD_LIBRARIES BFD_INCLUDE_DIR) MESSAGE(STATUS "BFD libs: " "${BFD_LIBRARIES}") mark_as_advanced(BFD_INCLUDE_DIR BFD_LIBRARIES) looking-glass-0+b4+dfsg.1/common/include/000077500000000000000000000000001411177132200202555ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/common/include/common/000077500000000000000000000000001411177132200215455ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/common/include/common/KVMFR.h000066400000000000000000000051311411177132200226030ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_KVMFR_ #define _H_LG_COMMON_KVMFR_ #pragma once #include #include #include "types.h" #define KVMFR_MAGIC "KVMFR---" #define KVMFR_VERSION 10 #define LGMP_Q_POINTER 1 #define LGMP_Q_FRAME 2 #define LGMP_Q_FRAME_LEN 2 #define LGMP_Q_POINTER_LEN 20 enum { CURSOR_FLAG_POSITION = 0x1, CURSOR_FLAG_VISIBLE = 0x2, CURSOR_FLAG_SHAPE = 0x4 }; typedef uint32_t KVMFRCursorFlags; typedef struct KVMFR { char magic[8]; uint32_t version; char hostver[32]; } KVMFR; typedef struct KVMFRCursor { int16_t x, y; // cursor x & y position CursorType type; // shape buffer data type int8_t hx, hy; // shape hotspot x & y uint32_t width; // width of the shape uint32_t height; // height of the shape uint32_t pitch; // row length in bytes of the shape } KVMFRCursor; typedef struct KVMFRFrame { uint32_t formatVer; // the frame format version number FrameType type; // the frame data type uint32_t width; // the frame width uint32_t height; // the frame height uint32_t realHeight; // the real height if the frame was truncated due to low mem FrameRotation rotation; // the frame rotation uint32_t stride; // the row stride (zero if compressed data) uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size) uint32_t offset; // offset from the start of this header to the FrameBuffer header uint32_t mouseScalePercent; // movement scale factor of the mouse (relates to DPI of display, 100 = no scale) bool blockScreensaver; // whether the guest has requested to block screensavers } KVMFRFrame; #endif looking-glass-0+b4+dfsg.1/common/include/common/countedbuffer.h000066400000000000000000000022251411177132200245520ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_COUNTEDBUFFER_ #define _H_LG_COMMON_COUNTEDBUFFER_ #include struct CountedBuffer { _Atomic(size_t) refs; size_t size; char data[]; }; struct CountedBuffer * countedBufferNew(size_t size); void countedBufferAddRef(struct CountedBuffer * buffer); void countedBufferRelease(struct CountedBuffer ** buffer); #endif looking-glass-0+b4+dfsg.1/common/include/common/crash.h000066400000000000000000000017271411177132200230250ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_CRASH_ #define _H_LG_COMMON_CRASH_ #include bool installCrashHandler(const char * exe); void cleanupCrashHandler(void); #endif looking-glass-0+b4+dfsg.1/common/include/common/debug.h000066400000000000000000000104151411177132200230050ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_DEBUG_ #define _H_LG_COMMON_DEBUG_ #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #include #include #include #include "time.h" enum DebugLevel { DEBUG_LEVEL_NONE, DEBUG_LEVEL_INFO, DEBUG_LEVEL_WARN, DEBUG_LEVEL_ERROR, DEBUG_LEVEL_FIXME, DEBUG_LEVEL_FATAL }; extern const char ** debug_lookup; void debug_init(void); #ifdef ENABLE_BACKTRACE void printBacktrace(void); #define DEBUG_PRINT_BACKTRACE() printBacktrace() #else #define DEBUG_PRINT_BACKTRACE() #endif #if defined(_WIN32) && !defined(__GNUC__) #define DIRECTORY_SEPARATOR '\\' #else #define DIRECTORY_SEPARATOR '/' #endif #define STRIPPATH(s) ( \ sizeof(s) > 2 && (s)[sizeof(s)- 3] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 2 : \ sizeof(s) > 3 && (s)[sizeof(s)- 4] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 3 : \ sizeof(s) > 4 && (s)[sizeof(s)- 5] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 4 : \ sizeof(s) > 5 && (s)[sizeof(s)- 6] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 5 : \ sizeof(s) > 6 && (s)[sizeof(s)- 7] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 6 : \ sizeof(s) > 7 && (s)[sizeof(s)- 8] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 7 : \ sizeof(s) > 8 && (s)[sizeof(s)- 9] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 8 : \ sizeof(s) > 9 && (s)[sizeof(s)-10] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 9 : \ sizeof(s) > 10 && (s)[sizeof(s)-11] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 10 : \ sizeof(s) > 11 && (s)[sizeof(s)-12] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 11 : \ sizeof(s) > 12 && (s)[sizeof(s)-13] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 12 : \ sizeof(s) > 13 && (s)[sizeof(s)-14] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 13 : \ sizeof(s) > 14 && (s)[sizeof(s)-15] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 14 : \ sizeof(s) > 15 && (s)[sizeof(s)-16] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 15 : \ sizeof(s) > 16 && (s)[sizeof(s)-17] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 16 : \ sizeof(s) > 17 && (s)[sizeof(s)-18] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 17 : \ sizeof(s) > 18 && (s)[sizeof(s)-19] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 18 : \ sizeof(s) > 19 && (s)[sizeof(s)-20] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 19 : \ sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \ sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s)) #define DEBUG_PRINT(level, fmt, ...) do { \ fprintf(stderr, "%s%12" PRId64 "%20s:%-4u | %-30s | " fmt "%s\n", \ debug_lookup[level], microtime(), STRIPPATH(__FILE__), \ __LINE__, __FUNCTION__, ##__VA_ARGS__, debug_lookup[DEBUG_LEVEL_NONE]); \ } while (0) #define DEBUG_BREAK() DEBUG_PRINT(DEBUG_LEVEL_INFO, "================================================================================") #define DEBUG_INFO(fmt, ...) DEBUG_PRINT(DEBUG_LEVEL_INFO, fmt, ##__VA_ARGS__) #define DEBUG_WARN(fmt, ...) DEBUG_PRINT(DEBUG_LEVEL_WARN, fmt, ##__VA_ARGS__) #define DEBUG_ERROR(fmt, ...) DEBUG_PRINT(DEBUG_LEVEL_ERROR, fmt, ##__VA_ARGS__) #define DEBUG_FIXME(fmt, ...) DEBUG_PRINT(DEBUG_LEVEL_FIXME, fmt, ##__VA_ARGS__) #define DEBUG_FATAL(fmt, ...) do { \ DEBUG_BREAK(); \ DEBUG_PRINT(DEBUG_LEVEL_FATAL, fmt, ##__VA_ARGS__); \ DEBUG_PRINT_BACKTRACE(); \ abort(); \ } while(0) #if defined(DEBUG_SPICE) | defined(DEBUG_IVSHMEM) #define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args) #else #define DEBUG_PROTO(fmt, ...) do {} while(0) #endif #endif looking-glass-0+b4+dfsg.1/common/include/common/dpi.h000066400000000000000000000017641411177132200225020ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_DPI_ #define _H_LG_COMMON_DPI_ #include // At 100% scaling, Windows reports 96 DPI. #define DPI_100_PERCENT 96 UINT monitor_dpi(HMONITOR hMonitor); #endif looking-glass-0+b4+dfsg.1/common/include/common/event.h000066400000000000000000000030621411177132200230400ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_EVENT_ #define _H_LG_COMMON_EVENT_ #include #include #define TIMEOUT_INFINITE ((unsigned int)~0) typedef struct LGEvent LGEvent; LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime); void lgFreeEvent (LGEvent * handle); bool lgWaitEvent (LGEvent * handle, unsigned int timeout); bool lgSignalEvent(LGEvent * handle); bool lgResetEvent (LGEvent * handle); // os specific method to wrap/convert a native event into a LGEvent // for windows this is an event HANDLE LGEvent * lgWrapEvent(void * handle); // Posix specific, not implmented/possible in windows bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts); bool lgWaitEventNS (LGEvent * handle, unsigned int timeout); #endif looking-glass-0+b4+dfsg.1/common/include/common/framebuffer.h000066400000000000000000000036311411177132200242050ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_FRAMEBUFFER_ #define _H_LG_COMMON_FRAMEBUFFER_ #include #include #include typedef struct stFrameBuffer FrameBuffer; typedef bool (*FrameBufferReadFn)(void * opaque, const void * src, size_t size); /** * The size of the FrameBuffer struct */ extern const size_t FrameBufferStructSize; /** * Wait for the framebuffer to fill to the specified size */ bool framebuffer_wait(const FrameBuffer * frame, size_t size); /** * Read data from the KVMFRFrame into the dst buffer */ bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch); /** * Read data from the KVMFRFrame using a callback */ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width, size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque); /** * Prepare the framebuffer for writing */ void framebuffer_prepare(FrameBuffer * frame); /** * Write data from the src buffer into the KVMFRFrame */ bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size); #endif looking-glass-0+b4+dfsg.1/common/include/common/ivshmem.h000066400000000000000000000026601411177132200233720ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_IVSHMEM_ #define _H_LG_COMMON_IVSHMEM_ #include #include struct IVSHMEM { unsigned int size; void * mem; // internal use void * opaque; }; void ivshmemOptionsInit(); bool ivshmemInit(struct IVSHMEM * dev); bool ivshmemOpen(struct IVSHMEM * dev); bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev); void ivshmemClose(struct IVSHMEM * dev); void ivshmemFree(struct IVSHMEM * dev); /* Linux KVMFR support only for now (VM->VM) */ bool ivshmemHasDMA (struct IVSHMEM * dev); int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size); #endif looking-glass-0+b4+dfsg.1/common/include/common/locking.h000066400000000000000000000026411411177132200233470ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_LOCKING_ #define _H_LG_COMMON_LOCKING_ #include "time.h" #include #define LG_LOCK_MODE "Atomic" typedef atomic_flag LG_Lock; #define LG_LOCK_INIT(x) atomic_flag_clear(&(x)) #define LG_LOCK(x) \ while(atomic_flag_test_and_set_explicit(&(x), memory_order_acquire)) { ; } #define LG_UNLOCK(x) \ atomic_flag_clear_explicit(&(x), memory_order_release); #define LG_LOCK_FREE(x) #define INTERLOCKED_INC(x) atomic_fetch_add((x), 1) #define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1) #define INTERLOCKED_SECTION(lock, ...) \ LG_LOCK(lock) \ __VA_ARGS__ \ LG_UNLOCK(lock) #endif looking-glass-0+b4+dfsg.1/common/include/common/option.h000066400000000000000000000050131411177132200232250ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_COMMON_OPTION_ #define _H_COMMON_OPTION_ #include #include "common/stringlist.h" enum OptionType { OPTION_TYPE_NONE = 0, OPTION_TYPE_INT, OPTION_TYPE_STRING, OPTION_TYPE_BOOL, OPTION_TYPE_CUSTOM }; enum doHelpMode { DOHELP_MODE_NO = 0, DOHELP_MODE_YES, DOHELP_MODE_RST }; struct Option; struct Option { char * module; char * name; char * description; const char shortopt; enum OptionType type; union { int x_int; char * x_string; bool x_bool; void * x_custom; } value; bool (*parser )(struct Option * opt, const char * str); bool (*validator)(struct Option * opt, const char ** error); char * (*toString )(struct Option * opt); StringList (*getValues)(struct Option * opt); void (*printHelp)(void); // internal use only bool failed_set; }; // register an NULL terminated array of options bool option_register(struct Option options[]); // lookup the value of an option struct Option * option_get (const char * module, const char * name); int option_get_int (const char * module, const char * name); const char * option_get_string(const char * module, const char * name); bool option_get_bool (const char * module, const char * name); // called by the main application to parse the command line arguments bool option_parse(int argc, char * argv[]); // called by the main application to load configuration from a file bool option_load(const char * filename); // called by the main application to validate the option values bool option_validate(void); // print out the options, help, and their current values void option_print(void); // final cleanup void option_free(void); #endif looking-glass-0+b4+dfsg.1/common/include/common/runningavg.h000066400000000000000000000021011411177132200240660ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 typedef struct RunningAvg * RunningAvg; RunningAvg runningavg_new(int length); void runningavg_free(RunningAvg * ra); void runningavg_push(RunningAvg ra, int64_t value); void runningavg_reset(RunningAvg ra); double runningavg_calc(RunningAvg ra); looking-glass-0+b4+dfsg.1/common/include/common/stringlist.h000066400000000000000000000023121411177132200241160ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_STRINGLIST_ #define _H_LG_COMMON_STRINGLIST_ #include typedef struct StringList * StringList; StringList stringlist_new (bool owns_strings); void stringlist_free (StringList * sl); int stringlist_push (StringList sl, char * str); unsigned int stringlist_count(StringList sl); char * stringlist_at (StringList sl, unsigned int index); #endif looking-glass-0+b4+dfsg.1/common/include/common/stringutils.h000066400000000000000000000024631411177132200243120ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_STRINGUTILS #define _H_LG_COMMON_STRINGUTILS #include // vsprintf but with buffer allocation int valloc_sprintf(char ** str, const char * format, va_list ap) __attribute__ ((format (printf, 2, 0))); // sprintf but with buffer allocation int alloc_sprintf(char ** str, const char * format, ...) __attribute__ ((format (printf, 2, 3))); // Find value in a list separated by delimiter. bool str_containsValue(const char * list, char delimiter, const char * value); #endif looking-glass-0+b4+dfsg.1/common/include/common/sysinfo.h000066400000000000000000000016561411177132200234200ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_SYSUTILS #define _H_LG_COMMON_SYSUTILS // returns the page size long sysinfo_getPageSize(); #endif looking-glass-0+b4+dfsg.1/common/include/common/thread.h000066400000000000000000000022041411177132200231630ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_THREAD_ #define _H_LG_COMMON_THREAD_ #include typedef struct LGThread LGThread; typedef int (*LGThreadFunction)(void * opaque); bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle); bool lgJoinThread (LGThread * handle, int * resultCode); #endif looking-glass-0+b4+dfsg.1/common/include/common/time.h000066400000000000000000000054431411177132200226620ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #pragma once #include #include #if defined(_WIN32) #include #else #include #include #endif typedef struct LGTimer LGTimer; static inline uint64_t microtime(void) { #if defined(_WIN32) static LARGE_INTEGER freq = { 0 }; if (!freq.QuadPart) QueryPerformanceFrequency(&freq); LARGE_INTEGER time; QueryPerformanceCounter(&time); return time.QuadPart / (freq.QuadPart / 1000000LL); #else struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); return (uint64_t)time.tv_sec * 1000000LL + time.tv_nsec / 1000LL; #endif } #if !defined(_WIN32) //FIXME: make win32 versions static inline uint64_t nanotime(void) { struct timespec time; clock_gettime(CLOCK_MONOTONIC_RAW, &time); return ((uint64_t)time.tv_sec * 1000000000LL) + time.tv_nsec; } static inline void nsleep(uint64_t ns) { const struct timespec ts = { .tv_sec = ns / 1e9, .tv_nsec = ns - ((ns / 1e9) * 1e9) }; nanosleep(&ts, NULL); } static inline void tsDiff(struct timespec *diff, const struct timespec *left, const struct timespec *right) { diff->tv_sec = left->tv_sec - right->tv_sec; diff->tv_nsec = left->tv_nsec - right->tv_nsec; if (diff->tv_nsec < 0) { --diff->tv_sec; diff->tv_nsec += 1000000000; } } static inline uint32_t __iter_div_u64_rem(uint64_t dividend, uint32_t divisor, uint64_t *remainder) { uint32_t ret = 0; while (dividend >= divisor) { /* The following asm() prevents the compiler from optimising this loop into a modulo operation. */ asm("" : "+rm"(dividend)); dividend -= divisor; ret++; } *remainder = dividend; return ret; } static inline void tsAdd(struct timespec *a, uint64_t ns) { a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, 1000000000L, &ns); a->tv_nsec = ns; } #endif typedef bool (*LGTimerFn)(void * udata); bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn, void * udata, LGTimer ** result); void lgTimerDestroy(LGTimer * timer); looking-glass-0+b4+dfsg.1/common/include/common/types.h000066400000000000000000000032271411177132200230660ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _LG_TYPES_H_ #define _LG_TYPES_H_ struct Point { int x, y; }; struct DoublePoint { double x, y; }; struct Rect { int x, y, w, h; }; struct Border { int left, top, right, bottom; }; typedef enum FrameType { FRAME_TYPE_INVALID , FRAME_TYPE_BGRA , // BGRA interleaved: B,G,R,A 32bpp FRAME_TYPE_RGBA , // RGBA interleaved: R,G,B,A 32bpp FRAME_TYPE_RGBA10 , // RGBA interleaved: R,G,B,A 10,10,10,2 bpp FRAME_TYPE_RGBA16F , // RGBA interleaved: R,G,B,A 16,16,16,16 bpp float FRAME_TYPE_MAX , // sentinel value } FrameType; typedef enum FrameRotation { FRAME_ROT_0, FRAME_ROT_90, FRAME_ROT_180, FRAME_ROT_270 } FrameRotation; extern const char * FrameTypeStr[FRAME_TYPE_MAX]; typedef enum CursorType { CURSOR_TYPE_COLOR , CURSOR_TYPE_MONOCHROME , CURSOR_TYPE_MASKED_COLOR } CursorType; #endif looking-glass-0+b4+dfsg.1/common/include/common/version.h000066400000000000000000000016261411177132200234100ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_VERSION_ #define _H_LG_COMMON_VERSION_ extern char * BUILD_VERSION; #endif looking-glass-0+b4+dfsg.1/common/include/common/windebug.h000066400000000000000000000023531411177132200235250ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef _H_LG_COMMON_WINDEBUG_ #define _H_LG_COMMON_WINDEBUG_ #include "debug.h" #include #include #ifdef __cplusplus extern "C" { #endif void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status); #define DEBUG_WINERROR(x, y) DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y) bool IsWindows8(); #ifdef __cplusplus } #endif #endif looking-glass-0+b4+dfsg.1/common/src/000077500000000000000000000000001411177132200174215ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/common/src/KVMFR.c000066400000000000000000000017601411177132200204560ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/KVMFR.h" const char * FrameTypeStr[FRAME_TYPE_MAX] = { "FRAME_TYPE_INVALID", "FRAME_TYPE_BGRA", "FRAME_TYPE_RGBA", "FRAME_TYPE_RGBA10", "FRAME_TYPE_RGBA16F" }; looking-glass-0+b4+dfsg.1/common/src/countedbuffer.c000066400000000000000000000025671411177132200224320ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/countedbuffer.h" #include #include struct CountedBuffer * countedBufferNew(size_t size) { struct CountedBuffer * buffer = malloc(sizeof(struct CountedBuffer) + size); if (!buffer) return NULL; atomic_init(&buffer->refs, 1); buffer->size = size; return buffer; } void countedBufferAddRef(struct CountedBuffer * buffer) { atomic_fetch_add(&buffer->refs, 1); } void countedBufferRelease(struct CountedBuffer ** buffer) { if (atomic_fetch_sub(&(*buffer)->refs, 1) == 1) { free(*buffer); *buffer = NULL; } } looking-glass-0+b4+dfsg.1/common/src/framebuffer.c000066400000000000000000000120561411177132200220550ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/framebuffer.h" #include "common/debug.h" //#define FB_PROFILE #ifdef FB_PROFILE #include "common/runningavg.h" #endif #include #include #include #include #include #define FB_CHUNK_SIZE 1048576 // 1MB #define FB_SPIN_LIMIT 10000 // 10ms struct stFrameBuffer { atomic_uint_least32_t wp; uint8_t data[0]; }; const size_t FrameBufferStructSize = sizeof(FrameBuffer); bool framebuffer_wait(const FrameBuffer * frame, size_t size) { while(atomic_load_explicit(&frame->wp, memory_order_acquire) < size) { int spinCount = 0; while(frame->wp < size) { if (++spinCount == FB_SPIN_LIMIT) return false; usleep(1); } } return true; } bool framebuffer_read(const FrameBuffer * frame, void * restrict dst, size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch) { #ifdef FB_PROFILE static RunningAvg ra = NULL; static int raCount = 0; const uint64_t ts = microtime(); if (!ra) ra = runningavg_new(100); #endif uint8_t * restrict d = (uint8_t*)dst; uint_least32_t rp = 0; // copy in large 1MB chunks if the pitches match if (dstpitch == pitch) { size_t remaining = height * pitch; while(remaining) { const size_t copy = remaining < FB_CHUNK_SIZE ? remaining : FB_CHUNK_SIZE; if (!framebuffer_wait(frame, rp + copy)) return false; memcpy(d, frame->data + rp, copy); remaining -= copy; rp += copy; d += copy; } } else { // copy per line to match the pitch of the destination buffer const size_t linewidth = width * bpp; for(size_t y = 0; y < height; ++y) { if (!framebuffer_wait(frame, rp + linewidth)) return false; memcpy(d, frame->data + rp, dstpitch); rp += linewidth; d += dstpitch; } } #ifdef FB_PROFILE runningavg_push(ra, microtime() - ts); if (++raCount % 100 == 0) DEBUG_INFO("Average Copy Time: %.2fμs", runningavg_calc(ra)); #endif return true; } bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width, size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque) { #ifdef FB_PROFILE static RunningAvg ra = NULL; static int raCount = 0; const uint64_t ts = microtime(); if (!ra) ra = runningavg_new(100); #endif uint_least32_t rp = 0; size_t y = 0; const size_t linewidth = width * bpp; while(y < height) { if (!framebuffer_wait(frame, rp + linewidth)) return false; if (!fn(opaque, frame->data + rp, linewidth)) return false; rp += pitch; ++y; } #ifdef FB_PROFILE runningavg_push(ra, microtime() - ts); if (++raCount % 100 == 0) DEBUG_INFO("Average Copy Time: %.2fμs", runningavg_calc(ra)); #endif return true; } /** * Prepare the framebuffer for writing */ void framebuffer_prepare(FrameBuffer * frame) { atomic_store_explicit(&frame->wp, 0, memory_order_release); } bool framebuffer_write(FrameBuffer * frame, const void * restrict src, size_t size) { #ifdef FB_PROFILE static RunningAvg ra = NULL; static int raCount = 0; const uint64_t ts = microtime(); if (!ra) ra = runningavg_new(100); #endif __m128i * restrict s = (__m128i *)src; __m128i * restrict d = (__m128i *)frame->data; size_t wp = 0; _mm_mfence(); /* copy in chunks */ while(size > 63) { __m128i *_d = (__m128i *)d; __m128i *_s = (__m128i *)s; __m128i v1 = _mm_stream_load_si128(_s + 0); __m128i v2 = _mm_stream_load_si128(_s + 1); __m128i v3 = _mm_stream_load_si128(_s + 2); __m128i v4 = _mm_stream_load_si128(_s + 3); _mm_store_si128(_d + 0, v1); _mm_store_si128(_d + 1, v2); _mm_store_si128(_d + 2, v3); _mm_store_si128(_d + 3, v4); s += 4; d += 4; size -= 64; wp += 64; if (wp % FB_CHUNK_SIZE == 0) atomic_store_explicit(&frame->wp, wp, memory_order_release); } if(size) { memcpy(frame->data + wp, s, size); wp += size; } atomic_store_explicit(&frame->wp, wp, memory_order_release); #ifdef FB_PROFILE runningavg_push(ra, microtime() - ts); if (++raCount % 100 == 0) DEBUG_INFO("Average Copy Time: %.2fμs", runningavg_calc(ra)); #endif return true; } looking-glass-0+b4+dfsg.1/common/src/option.c000066400000000000000000000406241411177132200211030ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/option.h" #include "common/debug.h" #include "common/stringutils.h" #include #include #include #include #include struct OptionGroup { const char * module; struct Option ** options; int count; int pad; }; struct State { enum doHelpMode doHelp; struct Option ** options; int oCount; struct OptionGroup * groups; int gCount; }; static struct State state = { .doHelp = DOHELP_MODE_NO, .options = NULL, .oCount = 0, .groups = NULL, .gCount = 0 }; static bool int_parser(struct Option * opt, const char * str) { opt->value.x_int = atol(str); return true; } static bool bool_parser(struct Option * opt, const char * str) { opt->value.x_bool = strcmp(str, "1" ) == 0 || strcmp(str, "on" ) == 0 || strcmp(str, "yes" ) == 0 || strcmp(str, "true") == 0; return true; } static bool string_parser(struct Option * opt, const char * str) { free(opt->value.x_string); opt->value.x_string = strdup(str); return true; } static char * int_toString(struct Option * opt) { int len = snprintf(NULL, 0, "%d", opt->value.x_int); char * ret = malloc(len + 1); sprintf(ret, "%d", opt->value.x_int); return ret; } static char * bool_toString(struct Option * opt) { return strdup(opt->value.x_bool ? "yes" : "no"); } static char * string_toString(struct Option * opt) { if (!opt->value.x_string) return NULL; return strdup(opt->value.x_string); } bool option_register(struct Option options[]) { int new = 0; for(int i = 0; options[i].type != OPTION_TYPE_NONE; ++i) ++new; state.options = realloc( state.options, sizeof(struct Option *) * (state.oCount + new) ); for(int i = 0; options[i].type != OPTION_TYPE_NONE; ++i) { state.options[state.oCount + i] = (struct Option *)malloc(sizeof(struct Option)); struct Option * o = state.options[state.oCount + i]; memcpy(o, &options[i], sizeof(struct Option)); if (!o->parser) { switch(o->type) { case OPTION_TYPE_INT: o->parser = int_parser; break; case OPTION_TYPE_STRING: o->parser = string_parser; break; case OPTION_TYPE_BOOL: o->parser = bool_parser; break; default: DEBUG_ERROR("BUG: Non int/string/bool option types must have a parser"); continue; } } if (!o->toString) { switch(o->type) { case OPTION_TYPE_INT: o->toString = int_toString; break; case OPTION_TYPE_STRING: o->toString = string_toString; break; case OPTION_TYPE_BOOL: o->toString = bool_toString; break; default: DEBUG_ERROR("BUG: Non int/string/bool option types must implement toString"); continue; } } // ensure the string is locally allocated if (o->type == OPTION_TYPE_STRING) { if (o->value.x_string) o->value.x_string = strdup(o->value.x_string); } // add the option to the correct group for help printout bool found = false; for(int g = 0; g < state.gCount; ++g) { struct OptionGroup * group = &state.groups[g]; if (strcmp(group->module, o->module) != 0) continue; found = true; group->options = realloc( group->options, sizeof(struct Option *) * (group->count + 1) ); group->options[group->count] = o; int len = strlen(o->name); if (len > group->pad) group->pad = len; ++group->count; break; } if (!found) { state.groups = realloc( state.groups, sizeof(struct OptionGroup) * (state.gCount + 1) ); struct OptionGroup * group = &state.groups[state.gCount]; ++state.gCount; group->module = o->module; group->options = malloc(sizeof(struct Option *)); group->options[0] = o; group->count = 1; group->pad = strlen(o->name); } } state.oCount += new; return true; }; void option_free(void) { for(int i = 0; i < state.oCount; ++i) { struct Option * o = state.options[i]; if (o->type == OPTION_TYPE_STRING) free(o->value.x_string); free(o); } free(state.options); state.options = NULL; state.oCount = 0; for(int g = 0; g < state.gCount; ++g) { struct OptionGroup * group = &state.groups[g]; if (group->options) free(group->options); } free(state.groups); state.groups = NULL; state.gCount = 0; } static bool option_set(struct Option * opt, const char * value) { if (!opt->parser(opt, value)) { opt->failed_set = true; return false; } opt->failed_set = false; return true; } bool option_parse(int argc, char * argv[]) { for(int a = 1; a < argc; ++a) { struct Option * o = NULL; char * value = NULL; // emulate getopt for backwards compatability if (argv[a][0] == '-') { if (strcmp(argv[a], "-h") == 0 || strcmp(argv[a], "--help") == 0) { state.doHelp = DOHELP_MODE_YES; continue; } if (strcmp(argv[a], "--rst-help") == 0) { state.doHelp = DOHELP_MODE_RST; continue; } if (strlen(argv[a]) != 2) { DEBUG_WARN("Ignored invalid argvument: %s", argv[a]); continue; } for(int i = 0; i < state.oCount; ++i) { if (state.options[i]->shortopt == argv[a][1]) { o = state.options[i]; if (o->type != OPTION_TYPE_BOOL && a < argc - 1) { ++a; value = strdup(argv[a]); } break; } } } else { char * arg = strdup(argv[a]); char * module = strtok(arg , ":"); char * name = strtok(NULL, "="); value = strtok(NULL, "" ); if (!module || !name) { DEBUG_WARN("Ignored invalid argument: %s", argv[a]); free(arg); continue; } o = option_get(module, name); if (value) value = strdup(value); free(arg); } if (!o) { DEBUG_WARN("Ignored unknown argument: %s", argv[a]); free(value); continue; } if (!value) { if (o->type == OPTION_TYPE_BOOL) { o->value.x_bool = !o->value.x_bool; continue; } else if (o->type != OPTION_TYPE_CUSTOM) { DEBUG_WARN("Ignored invalid argument, missing value: %s", argv[a]); continue; } } option_set(o, value); free(value); } return true; } static char * file_parse_module(FILE * fp) { char * module = NULL; int len = 0; for(int c = fgetc(fp); !feof(fp); c = fgetc(fp)) { switch(c) { case ']': if (module) module[len] = '\0'; return module; case '\r': case '\n': free(module); return NULL; default: if (len % 32 == 0) module = realloc(module, len + 32 + 1); module[len++] = c; } } if (module) free(module); return NULL; } static bool process_option_line(const char * module, const char * name, char * value, int valueLen, int lineno) { if (!module) { DEBUG_ERROR("Syntax error on line %d, module not specified for option", lineno); return false; } struct Option * o = option_get(module, name); if (!o) DEBUG_WARN("Ignored unknown option %s:%s", module, name); else { if (value) value[valueLen] = '\0'; if (!option_set(o, value)) DEBUG_ERROR("Failed to set the option value"); } return true; } bool option_load(const char * filename) { FILE * fp = fopen(filename, "r"); if (!fp) return false; bool result = true; int lineno = 1; char * module = NULL; bool line = true; bool comment = false; bool expectLine = false; bool expectValue = false; char * name = NULL; int nameLen = 0; char * value = NULL; int valueLen = 0; char ** p = &name; int * len = &nameLen; for(int c = fgetc(fp); !feof(fp); c = fgetc(fp)) { if (comment && c != '\n') continue; comment = false; switch(c) { case '[': if (expectLine) { DEBUG_ERROR("Syntax error on line %d, expected new line", lineno); result = false; goto exit; } if (line) { free(module); module = file_parse_module(fp); if (!module) { DEBUG_ERROR("Syntax error on line %d, failed to parse the module", lineno); result = false; goto exit; } line = false; expectLine = true; continue; } if (*len % 32 == 0) *p = realloc(*p, *len + 32 + 1); (*p)[(*len)++] = c; break; case '\r': continue; case '\n': if (name && !process_option_line(module, name, value, valueLen, lineno)) { result = false; goto exit; } line = true; expectLine = false; expectValue = false; ++lineno; p = &name; len = &nameLen; free(name); name = NULL; nameLen = 0; free(value); value = NULL; valueLen = 0; break; case '=': if (!expectValue) { if (!name) { DEBUG_ERROR("Syntax error on line %d, expected option name", lineno); result = false; goto exit; } //rtrim while(nameLen > 1 && (name[nameLen-1] == ' ' || name[nameLen-1] == '\t')) --nameLen; name[nameLen] = '\0'; expectValue = true; p = &value; len = &valueLen; continue; } if (*len % 32 == 0) *p = realloc(*p, *len + 32 + 1); (*p)[(*len)++] = c; break; case ';': if (line) { comment = true; break; } // fallthrough default: // ignore non-typeable ascii characters if (c < 32 || c > 126) continue; if (expectLine) { DEBUG_ERROR("Syntax error on line %d, expected new line", lineno); result = false; goto exit; } line = false; //ltrim if (*len == 0 && (c == ' ' || c == '\t')) break; if (*len % 32 == 0) *p = realloc(*p, *len + 32 + 1); (*p)[(*len)++] = c; break; } } if (name && !process_option_line(module, name, value, valueLen, lineno)) result = false; exit: fclose(fp); free(module); free(name ); free(value ); return result; } bool option_validate(void) { if (state.doHelp != DOHELP_MODE_NO) { option_print(); return false; } // validate the option values bool ok = true; for(int i = 0; i < state.oCount; ++i) { struct Option * o = state.options[i]; const char * error = NULL; bool invalid = o->failed_set; if (!invalid && o->validator) invalid = !o->validator(o, &error); if (invalid) { printf("\nInvalid value provided to the option: %s:%s\n", o->module, o->name); if (error) printf("\n Error: %s\n", error); if (o->getValues) { StringList values = o->getValues(o); printf("\nValid values are:\n\n"); for(unsigned int v = 0; v < stringlist_count(values); ++v) printf(" * %s\n", stringlist_at(values, v)); stringlist_free(&values); } if (o->printHelp) { printf("\n"); o->printHelp(); } ok = false; } } if (!ok) printf("\n"); return ok; } void option_print_hrule(char * headerLine, int maxLen, char ruleChar) { printf(" +%c", ruleChar); for (int i = 0; i < maxLen; i++) { if(i < strlen(headerLine)) { if (headerLine[i] == '|') { putc('+', stdout); continue; } } putc(ruleChar, stdout); } printf("%c+\n", ruleChar); } void option_print(void) { printf( "The following is a complete list of options accepted by this application\n\n" ); for(int g = 0; g < state.gCount; ++g) { StringList lines = stringlist_new(true); StringList values = stringlist_new(true); int len; int maxLen; int valueLen = 5; char * line; char * headerLine; // ensure the pad length is atleast as wide as the heading if (state.groups[g].pad < 4) state.groups[g].pad = 4; // get the values and the max value length for(int i = 0; i < state.groups[g].count; ++i) { struct Option * o = state.groups[g].options[i]; char * value = o->toString(o); if (!value) { value = strdup("NULL"); len = 4; } else len = strlen(value); if (len > valueLen) valueLen = len; stringlist_push(values, value); } // add the heading maxLen = alloc_sprintf( &line, "%-*s | Short | %-*s | Description", (int)(strlen(state.groups[g].module) + state.groups[g].pad + 1), "Long", valueLen, "Value" ); assert(maxLen > 0); headerLine = line; stringlist_push(lines, line); for(int i = 0; i < state.groups[g].count; ++i) { struct Option * o = state.groups[g].options[i]; char * value = stringlist_at(values, i); len = alloc_sprintf( &line, "%s:%-*s | %c%c | %-*s | %s", o->module, state.groups[g].pad, o->name, o->shortopt ? '-' : ' ', o->shortopt ? o->shortopt : ' ', valueLen, value, o->description ); assert(len > 0); stringlist_push(lines, line); if (len > maxLen) maxLen = len; } stringlist_free(&values); // print out the lines for(int i = 0; i < stringlist_count(lines); ++i) { if (i == 0) { option_print_hrule(headerLine, maxLen, '-'); } char * line = stringlist_at(lines, i); printf(" | %-*s |\n", maxLen, line); if (i == 0) { option_print_hrule(headerLine, maxLen, state.doHelp == DOHELP_MODE_RST ? '=' : '-'); } else if (state.doHelp == DOHELP_MODE_RST && i < stringlist_count(lines) - 1) { option_print_hrule(headerLine, maxLen, '-'); } } option_print_hrule(headerLine, maxLen, '-'); stringlist_free(&lines); printf("\n"); } } struct Option * option_get(const char * module, const char * name) { for(int i = 0; i < state.oCount; ++i) { struct Option * o = state.options[i]; if ((strcmp(o->module, module) == 0) && (strcmp(o->name, name) == 0)) return o; } return NULL; } int option_get_int(const char * module, const char * name) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to get the value for option %s:%s", module, name); return -1; } assert(o->type == OPTION_TYPE_INT); return o->value.x_int; } const char * option_get_string(const char * module, const char * name) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to get the value for option %s:%s", module, name); return NULL; } assert(o->type == OPTION_TYPE_STRING); return o->value.x_string; } bool option_get_bool(const char * module, const char * name) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to get the value for option %s:%s", module, name); return false; } assert(o->type == OPTION_TYPE_BOOL); return o->value.x_bool; } looking-glass-0+b4+dfsg.1/common/src/platform/000077500000000000000000000000001411177132200212455ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/common/src/platform/CMakeLists.txt000066400000000000000000000004361411177132200240100ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common_platform LANGUAGES C) if (UNIX) add_subdirectory("linux") elseif(WIN32) add_subdirectory("windows") endif() add_library(lg_common_platform INTERFACE) target_link_libraries(lg_common_platform INTERFACE lg_common_platform_code) looking-glass-0+b4+dfsg.1/common/src/platform/linux/000077500000000000000000000000001411177132200224045ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/common/src/platform/linux/CMakeLists.txt000066400000000000000000000007331411177132200251470ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common_platform_code LANGUAGES C) include_directories( ${PROJECT_SOURCE_DIR}/include ${PROJECT_TOP} ) add_library(lg_common_platform_code STATIC debug.c crash.c sysinfo.c thread.c event.c ivshmem.c time.c ) if(ENABLE_BACKTRACE) find_package(BFD) target_link_libraries(lg_common_platform_code ${BFD_LIBRARIES}) endif() target_link_libraries(lg_common_platform_code lg_common pthread rt ) looking-glass-0+b4+dfsg.1/common/src/platform/linux/bfd.inc.h000066400000000000000000000022131411177132200240560ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 */ #ifndef PACKAGE #define PACKAGE #ifndef PACKAGE_VERSION #define PACKAGE_VERSION #include #undef PACKAGE_VERSION #else #include #endif #undef PACKAGE #else #ifndef PACKAGE_VERSION #define PACKAGE_VERSION #include #undef PACKAGE_VERSION #else #include #endif #endiflooking-glass-0+b4+dfsg.1/common/src/platform/linux/crash.c000066400000000000000000000136121411177132200236530ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/crash.h" #include "common/debug.h" #include "common/version.h" #if defined(ENABLE_BACKTRACE) #include #include #include #include #include #include #include #include #include #include #include "bfd.inc.h" struct range { intptr_t start, end; }; struct crash { char * exe; struct range * ranges; int rangeCount; bfd * fd; asection * section; asymbol ** syms; long symCount; bool loaded; }; static struct crash crash = {0}; static bool load_symbols(void) { bfd_init(); crash.fd = bfd_openr(crash.exe, NULL); if (!crash.fd) { DEBUG_ERROR("failed to open '%s'", crash.exe); return false; } crash.fd->flags |= BFD_DECOMPRESS; char **matching; if (!bfd_check_format_matches(crash.fd, bfd_object, &matching)) { DEBUG_ERROR("executable is not a bfd_object"); return false; } crash.section = bfd_get_section_by_name(crash.fd, ".text"); if (!crash.section) { DEBUG_ERROR("failed to find .text section"); return false; } if ((bfd_get_file_flags(crash.fd) & HAS_SYMS) == 0) { DEBUG_ERROR("executable '%s' has no symbols", crash.exe); return false; } long storage = bfd_get_symtab_upper_bound(crash.fd); crash.syms = (asymbol **)malloc(storage); crash.symCount = bfd_canonicalize_symtab(crash.fd, crash.syms); if (crash.symCount < 0) { DEBUG_ERROR("failed to get the symbol count"); return false; } return true; } static bool lookup_address(bfd_vma pc, const char ** filename, const char ** function, unsigned int * line, unsigned int * discriminator) { #ifdef bfd_get_section_flags if ((bfd_get_section_flags(crash.fd, crash.section) & SEC_ALLOC) == 0) #else if ((bfd_section_flags(crash.section) & SEC_ALLOC) == 0) #endif return false; #ifdef bfd_get_section_size bfd_size_type size = bfd_get_section_size(crash.section); #else bfd_size_type size = bfd_section_size(crash.section); #endif if (pc >= size) return false; if (!bfd_find_nearest_line_discriminator( crash.fd, crash.section, crash.syms, pc, filename, function, line, discriminator )) return false; if (!*filename) return false; return true; } void cleanupCrashHandler(void) { if (crash.syms) free(crash.syms); if (crash.fd) bfd_close(crash.fd); if (crash.ranges) free(crash.ranges); if (crash.exe) free(crash.exe); } static int dl_iterate_phdr_callback(struct dl_phdr_info * info, size_t size, void * data) { // we are not a module, and as such we don't have a name if (strlen(info->dlpi_name) != 0) return 0; size_t ttl = 0; for(int i = 0; i < info->dlpi_phnum; ++i) { const ElfW(Phdr) hdr = info->dlpi_phdr[i]; if (hdr.p_type == PT_LOAD && (hdr.p_flags & PF_X) == PF_X) ttl += hdr.p_memsz; } crash.ranges = realloc(crash.ranges, sizeof(struct range) * (crash.rangeCount + 1)); crash.ranges[crash.rangeCount].start = info->dlpi_addr; crash.ranges[crash.rangeCount].end = info->dlpi_addr + ttl; ++crash.rangeCount; return 0; } void printBacktrace(void) { void * array[50]; char ** messages; int size, i; size = backtrace(array, 50); messages = backtrace_symbols(array, size); for (i = 2; i < size && messages != NULL; ++i) { intptr_t base = -1; for(int c = 0; c < crash.rangeCount; ++c) { if ((intptr_t)array[i] >= crash.ranges[c].start && (intptr_t)array[i] < crash.ranges[c].end) { base = crash.ranges[c].start + crash.section->vma; break; } } if (base != -1) { const char * filename, * function; unsigned int line, discriminator; if (lookup_address((intptr_t)array[i] - base, &filename, &function, &line, &discriminator)) { DEBUG_ERROR("[trace]: (%d) %s:%u (%s)", i - 2, filename, line, function); continue; } } DEBUG_ERROR("[trace]: (%d) %s", i - 2, messages[i]); } free(messages); } static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) { DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION); DEBUG_ERROR("signal %d (%s), address is %p", sig_num, strsignal(sig_num), info->si_addr); printBacktrace(); cleanupCrashHandler(); abort(); } bool installCrashHandler(const char * exe) { struct sigaction sigact = { 0 }; crash.exe = realpath(exe, NULL); if (!load_symbols()) { DEBUG_WARN("Unable to load the binary symbols, not installing crash handler"); return true; } dl_iterate_phdr(dl_iterate_phdr_callback, NULL); sigact.sa_sigaction = crit_err_hdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO; if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0) { DEBUG_ERROR("Error setting signal handler for %d (%s)", SIGSEGV, strsignal(SIGSEGV)); return false; } return true; } #else //ENABLE_BACKTRACE bool installCrashHandler(const char * exe) { return true; } void cleanupCrashHandler(void) { } void printBacktrace(void) { } #endif looking-glass-0+b4+dfsg.1/common/src/platform/linux/debug.c000066400000000000000000000033111411177132200236340ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/debug.h" #include #define COLOR_RESET "\033[0m" #define COLOR_YELLOW "\033[0;33m" #define COLOR_RED "\033[0;31m" #define COLOR_CYAN "\033[0;36m" #define COLOR_WHITE "\033[0;37m" const char ** debug_lookup = NULL; void debug_init(void) { static const char * colorLookup[] = { COLOR_RESET , // DEBUG_LEVEL_NONE COLOR_RESET "[I] ", // DEBUG_LEVEL_INFO COLOR_YELLOW "[W] ", // DEBUG_LEVEL_WARN COLOR_RED "[E] ", // DEBUG_LEVEL_ERROR COLOR_CYAN "[F] ", // DEBUG_LEVEL_FIXME COLOR_WHITE "[!] " // DEBUG_LEVEL_FATAL }; static const char * plainLookup[] = { "" , // DEBUG_LEVEL_NONE "[I] ", // DEBUG_LEVEL_INFO "[W] ", // DEBUG_LEVEL_WARN "[E] ", // DEBUG_LEVEL_ERROR "[F] ", // DEBUG_LEVEL_FIXME "[!] " // DEBUG_LEVEL_FATAL }; debug_lookup = (isatty(STDERR_FILENO) == 1) ? colorLookup : plainLookup; } looking-glass-0+b4+dfsg.1/common/src/platform/linux/event.c000066400000000000000000000102261411177132200236720ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/event.h" #include "common/debug.h" #include #include #include #include #include #include struct LGEvent { pthread_mutex_t mutex; pthread_cond_t cond; atomic_uint count; bool autoReset; }; LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime) { LGEvent * handle = (LGEvent *)calloc(sizeof(LGEvent), 1); if (!handle) { DEBUG_ERROR("Failed to allocate memory"); return NULL; } if (pthread_mutex_init(&handle->mutex, NULL) != 0) { DEBUG_ERROR("Failed to create the mutex"); free(handle); return NULL; } pthread_condattr_t cattr; pthread_condattr_init(&cattr); if (pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC) != 0) { DEBUG_ERROR("Failed to set the condition clock to realtime"); pthread_mutex_destroy(&handle->mutex); free(handle); return NULL; } if (pthread_cond_init(&handle->cond, &cattr) != 0) { pthread_mutex_destroy(&handle->mutex); free(handle); return NULL; } handle->autoReset = autoReset; return handle; } void lgFreeEvent(LGEvent * handle) { assert(handle); pthread_cond_destroy (&handle->cond ); pthread_mutex_destroy(&handle->mutex); free(handle); } bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts) { assert(handle); bool ret = true; int count = 0; int res; while(ret && (count = atomic_load(&handle->count)) == 0) { if (pthread_mutex_lock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to lock the mutex"); return false; } if (!ts) { if ((res = pthread_cond_wait(&handle->cond, &handle->mutex)) != 0) { DEBUG_ERROR("Failed to wait on the condition (err: %d)", res); ret = false; } } else { switch((res = pthread_cond_timedwait(&handle->cond, &handle->mutex, ts))) { case 0: break; case ETIMEDOUT: ret = false; break; default: ret = false; DEBUG_ERROR("Timed wait failed (err: %d)", res); break; } } if (pthread_mutex_unlock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to unlock the mutex"); return false; } } if (ret && handle->autoReset) atomic_fetch_sub(&handle->count, count); return ret; } bool lgWaitEventNS(LGEvent * handle, unsigned int timeout) { if (timeout == TIMEOUT_INFINITE) return lgWaitEventAbs(handle, NULL); struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t nsec = ts.tv_nsec + timeout; if(nsec > 1000000000UL) { ts.tv_nsec = nsec - 1000000000UL; ++ts.tv_sec; } else ts.tv_nsec = nsec; return lgWaitEventAbs(handle, &ts); } bool lgWaitEvent(LGEvent * handle, unsigned int timeout) { if (timeout == TIMEOUT_INFINITE) return lgWaitEventAbs(handle, NULL); return lgWaitEventNS(handle, timeout * 1000000U); } bool lgSignalEvent(LGEvent * handle) { assert(handle); const bool signalled = atomic_fetch_add_explicit(&handle->count, 1, memory_order_acquire) > 0; if (signalled) return true; if (pthread_cond_broadcast(&handle->cond) != 0) { DEBUG_ERROR("Failed to signal the condition"); return false; } return true; } bool lgResetEvent(LGEvent * handle) { assert(handle); atomic_store(&handle->count, 0); return true; } looking-glass-0+b4+dfsg.1/common/src/platform/linux/ivshmem.c000066400000000000000000000125741411177132200242310ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/ivshmem.h" #include #include #include #include #include #include #include #include #include #include #include "common/debug.h" #include "common/option.h" #include "common/stringutils.h" #include "module/kvmfr.h" struct IVSHMEMInfo { int devFd; int size; bool hasDMA; }; static bool ivshmemDeviceValidator(struct Option * opt, const char ** error) { // if it's not a kvmfr device, it must be a file on disk if (strlen(opt->value.x_string) > 3 && memcmp(opt->value.x_string, "kvmfr", 5) != 0) { struct stat st; if (stat(opt->value.x_string, &st) != 0) { *error = "Invalid path to the ivshmem file specified"; return false; } return true; } return true; } static StringList ivshmemDeviceGetValues(struct Option * option) { StringList sl = stringlist_new(true); DIR * d = opendir("/sys/class/kvmfr"); if (!d) return sl; struct dirent * dir; while((dir = readdir(d)) != NULL) { if (dir->d_name[0] == '.') continue; char * devName; alloc_sprintf(&devName, "/dev/%s", dir->d_name); stringlist_push(sl, devName); } closedir(d); return sl; } void ivshmemOptionsInit(void) { struct Option options[] = { { .module = "app", .name = "shmFile", .shortopt = 'f', .description = "The path to the shared memory file, or the name of the kvmfr device to use, ie: kvmfr0", .type = OPTION_TYPE_STRING, .value.x_string = "/dev/shm/looking-glass", .validator = ivshmemDeviceValidator, .getValues = ivshmemDeviceGetValues }, {0} }; option_register(options); } bool ivshmemInit(struct IVSHMEM * dev) { // FIXME: split code from ivshmemOpen return true; } bool ivshmemOpen(struct IVSHMEM * dev) { return ivshmemOpenDev(dev, option_get_string("app", "shmFile")); } bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice) { assert(dev); unsigned int devSize; int devFd = -1; bool hasDMA; dev->opaque = NULL; DEBUG_INFO("KVMFR Device : %s", shmDevice); if (strlen(shmDevice) > 8 && memcmp(shmDevice, "/dev/kvmfr", 10) == 0) { devFd = open(shmDevice, O_RDWR, (mode_t)0600); if (devFd < 0) { DEBUG_ERROR("Failed to open: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } // get the device size devSize = ioctl(devFd, KVMFR_DMABUF_GETSIZE, 0); hasDMA = true; } else { struct stat st; if (stat(shmDevice, &st) != 0) { DEBUG_ERROR("Failed to stat: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } devSize = st.st_size; devFd = open(shmDevice, O_RDWR, (mode_t)0600); if (devFd < 0) { DEBUG_ERROR("Failed to open: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } hasDMA = false; } void * map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, devFd, 0); if (map == MAP_FAILED) { DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo)); info->size = devSize; info->devFd = devFd; info->hasDMA = hasDMA; dev->opaque = info; dev->size = devSize; dev->mem = map; return true; } void ivshmemClose(struct IVSHMEM * dev) { assert(dev); if (!dev->opaque) return; struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; munmap(dev->mem, info->size); close(info->devFd); free(info); dev->mem = NULL; dev->size = 0; dev->opaque = NULL; } void ivshmemFree(struct IVSHMEM * dev) { // FIXME: split code from ivshmemClose } bool ivshmemHasDMA(struct IVSHMEM * dev) { assert(dev && dev->opaque); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; return info->hasDMA; } int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size) { assert(ivshmemHasDMA(dev)); assert(dev && dev->opaque); assert(offset + size <= dev->size); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; // align to the page size size = (size & ~(0x1000-1)) + 0x1000; const struct kvmfr_dmabuf_create create = { .flags = KVMFR_DMABUF_FLAG_CLOEXEC, .offset = offset, .size = size }; int fd = ioctl(info->devFd, KVMFR_DMABUF_CREATE, &create); if (fd < 0) DEBUG_ERROR("Failed to create the dma buffer"); return fd; } looking-glass-0+b4+dfsg.1/common/src/platform/linux/sysinfo.c000066400000000000000000000016141411177132200242440ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 long sysinfo_getPageSize(void) { return sysconf(_SC_PAGESIZE); } looking-glass-0+b4+dfsg.1/common/src/platform/linux/thread.c000066400000000000000000000040361411177132200240220ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/thread.h" #include #include #include "common/debug.h" struct LGThread { const char * name; LGThreadFunction function; void * opaque; pthread_t handle; int resultCode; }; static void * threadWrapper(void * opaque) { LGThread * handle = (LGThread *)opaque; handle->resultCode = handle->function(handle->opaque); return NULL; } bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle) { *handle = (LGThread*)malloc(sizeof(LGThread)); (*handle)->name = name; (*handle)->function = function; (*handle)->opaque = opaque; if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0) { DEBUG_ERROR("pthread_create failed for thread: %s", name); free(*handle); *handle = NULL; return false; } pthread_setname_np((*handle)->handle, name); return true; } bool lgJoinThread(LGThread * handle, int * resultCode) { if (pthread_join(handle->handle, NULL) != 0) { DEBUG_ERROR("pthread_join failed for thread: %s", handle->name); free(handle); return false; } if (resultCode) *resultCode = handle->resultCode; free(handle); return true; } looking-glass-0+b4+dfsg.1/common/src/platform/linux/time.c000066400000000000000000000047451411177132200235200ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/time.h" #include "common/debug.h" #include #include #include #include #include struct LGTimer { LGTimerFn fn; void * udata; timer_t id; bool running; }; static void TimerProc(union sigval arg) { LGTimer * timer = (LGTimer *)arg.sival_ptr; if (!timer->fn(timer->udata)) { if (timer_delete(timer->id)) DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno)); timer->running = false; } } bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn, void * udata, LGTimer ** result) { LGTimer * ret = malloc(sizeof(LGTimer)); if (!ret) { DEBUG_ERROR("failed to malloc LGTimer struct"); return false; } ret->fn = fn; ret->udata = udata; ret->running = true; struct sigevent sev = { .sigev_notify = SIGEV_THREAD, .sigev_notify_function = &TimerProc, .sigev_value.sival_ptr = ret, }; if (timer_create(CLOCK_MONOTONIC, &sev, &ret->id)) { DEBUG_ERROR("failed to create timer: %s", strerror(errno)); free(ret); return false; } struct timespec interval = { .tv_sec = 0, .tv_nsec = intervalMS * 1000 * 1000, }; struct itimerspec spec = { .it_interval = interval, .it_value = interval, }; if (timer_settime(ret->id, 0, &spec, NULL)) { DEBUG_ERROR("failed to set timer: %s", strerror(errno)); timer_delete(ret->id); free(ret); return false; } *result = ret; return true; } void lgTimerDestroy(LGTimer * timer) { if (timer->running) { if (timer_delete(timer->id)) DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno)); } free(timer); } looking-glass-0+b4+dfsg.1/common/src/platform/windows/000077500000000000000000000000001411177132200227375ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/common/src/platform/windows/CMakeLists.txt000066400000000000000000000007161411177132200255030ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common_platform_code LANGUAGES C) include_directories( ${PROJECT_TOP}/vendor/ivshmem ) add_library(lg_common_platform_code STATIC debug.c crash.c dpi.c sysinfo.c thread.c event.c windebug.c ivshmem.c time.c ) target_link_libraries(lg_common_platform_code lg_common setupapi ) if (ENABLE_BACKTRACE) target_link_libraries(lg_common_platform_code dbghelp) endif() looking-glass-0+b4+dfsg.1/common/src/platform/windows/crash.c000066400000000000000000000120111411177132200241760ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/crash.h" #include "common/debug.h" #include "common/version.h" #ifdef ENABLE_BACKTRACE #include #include #include #include static const char * exception_name(DWORD code) { switch (code) { case EXCEPTION_ACCESS_VIOLATION: return "ACCESS_VIOLATION"; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "ARRAY_BOUNDS_EXCEEDED"; case EXCEPTION_BREAKPOINT: return "BREAKPOINT"; case EXCEPTION_DATATYPE_MISALIGNMENT: return "DATATYPE_MISALIGNMENT"; case EXCEPTION_FLT_DENORMAL_OPERAND: return "FLT_DENORMAL_OPERAND"; case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "FLT_DIVIDE_BY_ZERO"; case EXCEPTION_FLT_INEXACT_RESULT: return "FLT_INEXACT_RESULT"; case EXCEPTION_FLT_INVALID_OPERATION: return "FLT_INVALID_OPERATION"; case EXCEPTION_FLT_OVERFLOW: return "FLT_OVERFLOW"; case EXCEPTION_FLT_STACK_CHECK: return "FLT_STACK_CHECK"; case EXCEPTION_FLT_UNDERFLOW: return "FLT_UNDERFLOW"; case EXCEPTION_ILLEGAL_INSTRUCTION: return "ILLEGAL_INSTRUCTION"; case EXCEPTION_IN_PAGE_ERROR: return "IN_PAGE_ERROR"; case EXCEPTION_INT_DIVIDE_BY_ZERO: return "INT_DIVIDE_BY_ZERO"; case EXCEPTION_INT_OVERFLOW: return "INT_OVERFLOW"; case EXCEPTION_INVALID_DISPOSITION: return "INVALID_DISPOSITION"; case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "NONCONTINUABLE_EXCEPTION"; case EXCEPTION_PRIV_INSTRUCTION: return "PRIV_INSTRUCTION"; case EXCEPTION_SINGLE_STEP: return "SINGLE_STEP"; case EXCEPTION_STACK_OVERFLOW: return "STACK_OVERFLOW"; default: return "unknown"; } } static LONG CALLBACK exception_filter(EXCEPTION_POINTERS * exc) { PEXCEPTION_RECORD excInfo = exc->ExceptionRecord; CONTEXT context; memcpy(&context, exc->ContextRecord, sizeof context); DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION); DEBUG_ERROR("exception 0x%08lx (%s), address is %p", excInfo->ExceptionCode, exception_name(excInfo->ExceptionCode), excInfo->ExceptionAddress); if (!SymInitialize(GetCurrentProcess(), NULL, TRUE)) { DEBUG_ERROR("Failed to SymInitialize: 0x%08lx, could not generate stack trace", GetLastError()); goto fail; } SymSetOptions(SYMOPT_LOAD_LINES); STACKFRAME64 frame = { 0 }; frame.AddrPC.Offset = context.Rip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = context.Rbp; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Offset = context.Rsp; frame.AddrStack.Mode = AddrModeFlat; HANDLE hProcess = GetCurrentProcess(); HANDLE hThread = GetCurrentThread(); for (int i = 1; StackWalk64(IMAGE_FILE_MACHINE_AMD64, hProcess, hThread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL); ++i) { DWORD64 moduleBase = SymGetModuleBase64(hProcess, frame.AddrPC.Offset); char moduleName[MAX_PATH]; if (moduleBase && GetModuleFileNameA((HMODULE) moduleBase, moduleName, MAX_PATH)) { DWORD64 disp; char symbolBuf[sizeof(SYMBOL_INFO) + 255]; PSYMBOL_INFO symbol = (PSYMBOL_INFO) symbolBuf; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->MaxNameLen = 256; if (SymFromAddr(hProcess, frame.AddrPC.Offset, &disp, symbol)) { IMAGEHLP_LINE line = { sizeof(IMAGEHLP_LINE), 0 }; DWORD lineDisp; if (SymGetLineFromAddr64(hProcess, frame.AddrPC.Offset, &lineDisp, &line)) DEBUG_ERROR("[trace]: %2d: %s:%s+0x%" PRIx64 " (%s:%ld+0x%lx)", i, moduleName, symbol->Name, disp, line.FileName, line.LineNumber, lineDisp); else DEBUG_ERROR("[trace]: %2d: %s:%s+0x%" PRIx64, i, moduleName, symbol->Name, disp); } else DEBUG_ERROR("[trace]: %2d: %s+0x%08" PRIx64, i, moduleName, frame.AddrPC.Offset - moduleBase); } else DEBUG_ERROR("[trace]: %2d: 0x%016" PRIx64, i, frame.AddrPC.Offset); } SymCleanup(hProcess); fail: fflush(stderr); return EXCEPTION_CONTINUE_SEARCH; } bool installCrashHandler(const char * exe) { SetUnhandledExceptionFilter(exception_filter); return true; } #else bool installCrashHandler(const char * exe) { return true; } #endif looking-glass-0+b4+dfsg.1/common/src/platform/windows/debug.c000066400000000000000000000022361411177132200241740ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/debug.h" const char ** debug_lookup = NULL; void debug_init(void) { static const char * plainLookup[] = { "" , // DEBUG_LEVEL_NONE "[I] ", // DEBUG_LEVEL_INFO "[W] ", // DEBUG_LEVEL_WARN "[E] ", // DEBUG_LEVEL_ERROR "[F] ", // DEBUG_LEVEL_FIXME "[!] " // DEBUG_LEVEL_FATAL }; debug_lookup = plainLookup; } looking-glass-0+b4+dfsg.1/common/src/platform/windows/dpi.c000066400000000000000000000033211411177132200236560ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/dpi.h" #include "common/windebug.h" typedef enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI, MDT_ANGULAR_DPI, MDT_RAW_DPI, MDT_DEFAULT } MONITOR_DPI_TYPE; typedef HRESULT (WINAPI *GetDpiForMonitor_t)(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT * dpiX, UINT * dpiY); UINT monitor_dpi(HMONITOR hMonitor) { HMODULE shcore = LoadLibraryA("shcore.dll"); if (!shcore) { DEBUG_ERROR("Could not load shcore.dll"); return DPI_100_PERCENT; } GetDpiForMonitor_t GetDpiForMonitor = (GetDpiForMonitor_t) GetProcAddress(shcore, "GetDpiForMonitor"); if (!GetDpiForMonitor) { DEBUG_ERROR("Could not find GetDpiForMonitor"); return DPI_100_PERCENT; } UINT dpiX, dpiY; HRESULT status = GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); if (FAILED(status)) { DEBUG_WINERROR("GetDpiForMonitor failed", status); return DPI_100_PERCENT; } return dpiX; } looking-glass-0+b4+dfsg.1/common/src/platform/windows/event.c000066400000000000000000000041161411177132200242260ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/event.h" #include "common/windebug.h" #include "common/time.h" #include #include LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime) { HANDLE handle = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL); if (!handle) { DEBUG_WINERROR("Failed to create the event", GetLastError()); return NULL; } return (LGEvent *)handle; } LGEvent * lgWrapEvent(void * handle) { return (LGEvent *)handle; } void lgFreeEvent(LGEvent * event) { CloseHandle((HANDLE)event); } bool lgWaitEvent(LGEvent * event, unsigned int timeout) { const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout; do { switch(WaitForSingleObject((HANDLE)event, to)) { case WAIT_OBJECT_0: return true; case WAIT_ABANDONED: continue; case WAIT_TIMEOUT: if (timeout == TIMEOUT_INFINITE) continue; return false; case WAIT_FAILED: DEBUG_WINERROR("Wait for event failed", GetLastError()); return false; } DEBUG_ERROR("Unknown wait event return code"); } while(0); return false; } bool lgSignalEvent(LGEvent * event) { return SetEvent((HANDLE)event); } bool lgResetEvent(LGEvent * event) { return ResetEvent((HANDLE)event); } looking-glass-0+b4+dfsg.1/common/src/platform/windows/ivshmem.c000066400000000000000000000112201411177132200245470ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/ivshmem.h" #include "common/option.h" #include "common/windebug.h" #include #include "ivshmem.h" #include #include #include struct IVSHMEMInfo { HANDLE handle; }; void ivshmemOptionsInit(void) { static struct Option options[] = { { .module = "os", .name = "shmDevice", .description = "The IVSHMEM device to use", .type = OPTION_TYPE_INT, .value.x_int = 0 }, {0} }; option_register(options); } bool ivshmemInit(struct IVSHMEM * dev) { assert(dev && !dev->opaque); HANDLE handle; HDEVINFO devInfoSet; PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL; SP_DEVICE_INTERFACE_DATA devInterfaceData = {0}; devInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE); devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); const int shmDevice = option_get_int("os", "shmDevice"); if (SetupDiEnumDeviceInterfaces(devInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &devInterfaceData) == FALSE) { DWORD error = GetLastError(); if (error == ERROR_NO_MORE_ITEMS) { DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error); return false; } DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error); return false; } DWORD reqSize = 0; SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, NULL, 0, &reqSize, NULL); if (!reqSize) { DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError()); return false; } infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1); infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, infData, reqSize, NULL, NULL)) { free(infData); DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError()); return false; } handle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0); if (handle == INVALID_HANDLE_VALUE) { SetupDiDestroyDeviceInfoList(devInfoSet); free(infData); DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError()); return false; } free(infData); SetupDiDestroyDeviceInfoList(devInfoSet); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo)); info->handle = handle; dev->opaque = info; dev->size = 0; dev->mem = NULL; return true; } bool ivshmemOpen(struct IVSHMEM * dev) { assert(dev && dev->opaque && !dev->mem); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; IVSHMEM_SIZE size; if (!DeviceIoControl(info->handle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL)) { DEBUG_WINERROR("DeviceIoControl Failed", GetLastError()); return 0; } IVSHMEM_MMAP_CONFIG config = { .cacheMode = IVSHMEM_CACHE_WRITECOMBINED }; IVSHMEM_MMAP map = { 0 }; if (!DeviceIoControl( info->handle, IOCTL_IVSHMEM_REQUEST_MMAP, &config, sizeof(IVSHMEM_MMAP_CONFIG), &map , sizeof(IVSHMEM_MMAP), NULL, NULL)) { DEBUG_WINERROR("DeviceIoControl Failed", GetLastError()); return false; } dev->size = (unsigned int)size; dev->mem = map.ptr; return true; } void ivshmemClose(struct IVSHMEM * dev) { assert(dev && dev->opaque && dev->mem); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; if (!DeviceIoControl(info->handle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL)) DEBUG_WINERROR("DeviceIoControl failed", GetLastError()); dev->size = 0; dev->mem = NULL; } void ivshmemFree(struct IVSHMEM * dev) { assert(dev && dev->opaque && !dev->mem); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; free(info); dev->opaque = NULL; } looking-glass-0+b4+dfsg.1/common/src/platform/windows/sysinfo.c000066400000000000000000000017751411177132200246070ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 int sysinfo_gfx_max_multisample(void) { //FIXME: Implement this return 4; } long sysinfo_getPageSize(void) { SYSTEM_INFO si; GetSystemInfo(&si); return si.dwPageSize; }looking-glass-0+b4+dfsg.1/common/src/platform/windows/thread.c000066400000000000000000000047041411177132200243570ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/thread.h" #include "common/debug.h" #include "common/windebug.h" #include struct LGThread { const char * name; LGThreadFunction function; void * opaque; HANDLE handle; DWORD threadID; int resultCode; }; static DWORD WINAPI threadWrapper(LPVOID lpParameter) { LGThread * handle = (LGThread *)lpParameter; handle->resultCode = handle->function(handle->opaque); return 0; } bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle) { *handle = (LGThread *)malloc(sizeof(LGThread)); (*handle)->name = name; (*handle)->function = function; (*handle)->opaque = opaque; (*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID); if (!(*handle)->handle) { free(*handle); *handle = NULL; DEBUG_WINERROR("CreateThread failed", GetLastError()); return false; } return true; } bool lgJoinThread(LGThread * handle, int * resultCode) { while(true) { switch(WaitForSingleObject(handle->handle, INFINITE)) { case WAIT_OBJECT_0: if (resultCode) *resultCode = handle->resultCode; CloseHandle(handle->handle); free(handle); return true; case WAIT_ABANDONED: case WAIT_TIMEOUT: continue; case WAIT_FAILED: DEBUG_WINERROR("Wait for thread failed", GetLastError()); CloseHandle(handle->handle); free(handle); return false; } break; } DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError()); return false; } looking-glass-0+b4+dfsg.1/common/src/platform/windows/time.c000066400000000000000000000034731411177132200240500ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/time.h" #include "common/debug.h" // decared by the platform extern HWND MessageHWND; struct LGTimer { LGTimerFn fn; void * udata; UINT_PTR handle; bool running; }; static void TimerProc(HWND Arg1, UINT Arg2, UINT_PTR Arg3, DWORD Arg4) { LGTimer * timer = (LGTimer *)Arg3; if (!timer->fn(timer->udata)) { KillTimer(Arg1, timer->handle); timer->running = false; } } bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn, void * udata, LGTimer ** result) { LGTimer * ret = malloc(sizeof(LGTimer)); if (!ret) { DEBUG_ERROR("failed to malloc LGTimer struct"); return false; } ret->fn = fn; ret->udata = udata; ret->running = true; ret->handle = SetTimer(MessageHWND, (UINT_PTR)ret, intervalMS, TimerProc); *result = ret; return true; } void lgTimerDestroy(LGTimer * timer) { if (timer->running) { if (!KillTimer(MessageHWND, timer->handle)) DEBUG_ERROR("failed to destroy the timer"); } free(timer); } looking-glass-0+b4+dfsg.1/common/src/platform/windows/windebug.c000066400000000000000000000043701411177132200247130ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/windebug.h" #include void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status) { char *buffer; FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, status, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&buffer, 1024, NULL ); for(size_t i = strlen(buffer) - 1; i > 0; --i) if (buffer[i] == '\n' || buffer[i] == '\r') buffer[i] = 0; fprintf(stderr, "%12" PRId64 " [E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", microtime(), file, line, function, desc, (int)status, buffer); LocalFree(buffer); } /* credit for this function to: https://stackoverflow.com/questions/17399302/how-can-i-detect-windows-8-1-in-a-desktop-application */ inline static BOOL CompareWindowsVersion(DWORD dwMajorVersion, DWORD dwMinorVersion) { OSVERSIONINFOEX ver; DWORDLONG dwlConditionMask = 0; ZeroMemory(&ver, sizeof(OSVERSIONINFOEX)); ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); ver.dwMajorVersion = dwMajorVersion; ver.dwMinorVersion = dwMinorVersion; VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL); VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL); return VerifyVersionInfo(&ver, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask); } bool IsWindows8(void) { return (CompareWindowsVersion(6, 3) == TRUE) || (CompareWindowsVersion(6, 2) == TRUE); } looking-glass-0+b4+dfsg.1/common/src/runningavg.c000066400000000000000000000031671411177132200217520ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/runningavg.h" #include struct RunningAvg { int length, samples; int pos; int64_t value; int64_t values[0]; }; RunningAvg runningavg_new(int length) { struct RunningAvg * ra = calloc(1, sizeof(*ra) + sizeof(*ra->values) * length); ra->length = length; return ra; } void runningavg_free(RunningAvg * ra) { free(*ra); *ra = NULL; } void runningavg_push(RunningAvg ra, int64_t value) { if (ra->samples == ra->length) ra->value -= ra->values[ra->pos]; else ++ra->samples; ra->value += value; ra->values[ra->pos++] = value; if (ra->pos == ra->length) ra->pos = 0; } void runningavg_reset(RunningAvg ra) { ra->samples = 0; ra->pos = 0; ra->value = 0; } double runningavg_calc(RunningAvg ra) { return (double)ra->value / ra->samples; } looking-glass-0+b4+dfsg.1/common/src/stringlist.c000066400000000000000000000035501411177132200217720ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 "common/stringlist.h" #include struct StringList { bool owns_strings; unsigned int size; unsigned int count; char ** list; }; StringList stringlist_new(bool owns_strings) { StringList sl = malloc(sizeof(struct StringList)); sl->owns_strings = owns_strings; sl->size = 32; sl->count = 0; sl->list = malloc(sizeof(char *) * sl->size); return sl; } void stringlist_free(StringList * sl) { if ((*sl)->owns_strings) for(unsigned int i = 0; i < (*sl)->count; ++i) free((*sl)->list[i]); free((*sl)->list); free((*sl)); *sl = NULL; } int stringlist_push (StringList sl, char * str) { if (sl->count == sl->size) { sl->size += 32; sl->list = realloc(sl->list, sizeof(char *) * sl->size); } unsigned int index = sl->count; sl->list[sl->count++] = str; return index; } unsigned int stringlist_count(StringList sl) { return sl->count; } char * stringlist_at(StringList sl, unsigned int index) { if (index >= sl->count) return NULL; return sl->list[index]; }looking-glass-0+b4+dfsg.1/common/src/stringutils.c000066400000000000000000000042071411177132200221570ustar00rootroot00000000000000/** * Looking Glass * Copyright (C) 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 #include #include #include #include #include "common/stringutils.h" int valloc_sprintf(char ** str, const char * format, va_list ap) { if (!str) return -1; *str = NULL; va_list ap1; va_copy(ap1, ap); // for some reason some versions of GCC warn about format being NULL when any // kind of optimization is enabled, this is a false positive. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-truncation" const int len = vsnprintf(*str, 0, format, ap1); #pragma GCC diagnostic pop va_end(ap1); if (len < 0) return len; *str = malloc(len + 1); int ret = vsnprintf(*str, len + 1, format, ap); if (ret < 0) { free(*str); *str = NULL; return ret; } return ret; } int alloc_sprintf(char ** str, const char * format, ...) { va_list ap; va_start(ap, format); int ret = valloc_sprintf(str, format, ap); va_end(ap); return ret; } bool str_containsValue(const char * list, char delimiter, const char * value) { size_t len = strlen(value); const char span[] = {delimiter, '\0'}; while (*list) { if (*list == delimiter) { ++list; continue; } size_t n = strcspn(list, span); if (n == len && strncmp(value, list, n) == 0) return true; list += n; } return false; } looking-glass-0+b4+dfsg.1/contrib/000077500000000000000000000000001411177132200170025ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/contrib/redhat/000077500000000000000000000000001411177132200202515ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/contrib/redhat/qemu-ivshmem-sock.pp000066400000000000000000000034211411177132200241640ustar00rootroot00000000000000||SE Linux Moduleqemu-ivshmem-sock1.0@dirsearchfilemapgetattrreadopenunix_stream_socket connectto sock_filewriteobject_r@@@@ @user_tmp_t@svirt_t@virtd_t @unconfined_t@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@dirfileunix_stream_socket sock_fileobject_r user_tmp_tsvirt_tvirtd_t unconfined_tlooking-glass-0+b4+dfsg.1/doc/000077500000000000000000000000001411177132200161075ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/doc/.gitignore000066400000000000000000000000101411177132200200660ustar00rootroot00000000000000_build looking-glass-0+b4+dfsg.1/doc/Makefile000066400000000000000000000011721411177132200175500ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) looking-glass-0+b4+dfsg.1/doc/build.rst000066400000000000000000000133301411177132200177400ustar00rootroot00000000000000.. _building: Building ######## The following instructions will help you build Looking Glass from source code. Before attempting this, you should have a basic understanding of how to use the shell. .. _download_source: Downloading ----------- Visit the Looking Glass `Download Page `__, and download the stable version (**recommended**). You can also download a *bleeding-edge version*, or the latest RC version during a Release Candidate cycle. Developers can clone the source code repo with ``git``. .. code:: bash git clone --recursive https://github.com/gnif/LookingGlass.git .. warning:: Please only clone from Git if you're a developer, and know what you're doing. Looking Glass requires git submodules that must be setup and updated when building. Source code downloads from the website come bundled with the necessary submodules. .. note:: When using the latest bleeding-edge client version, you *MUST* download and install the corresponding host application. .. _build_client_section: Client ------ .. _installing_build_dependencies: Installing Build Dependencies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These required libraries and tools should be installed first. .. _client_dependencies: Required Dependencies ^^^^^^^^^^^^^^^^^^^^^ - cmake - gcc \| clang - fonts-freefont-ttf - libegl-dev - libgl-dev - libfontconfig1-dev - libgmp-dev - libspice-protocol-dev - make - nettle-dev - pkg-config .. _client_deps_may_be_disabled: May be disabled <<<<<<<<<<<<<<< These dependencies are required by default, but may be omitted if their feature is disabled when running :ref:`cmake `. - Disable with ``cmake -DENABLE_BACKTRACE=no ..`` - binutils-dev - Disable with ``cmake -DENABLE_X11=no ..`` - libx11-dev - libxfixes-dev - libxi-dev - libxinerama-dev - libxss-dev - Disable with ``cmake -DENABLE_WAYLAND=no ..`` - libwayland-bin - libwayland-dev - wayland-protocols .. _client_deps_deprecated: Deprecated <<<<<<<<<< These dependencies may be used, but are not required, and will be removed in the future. - Enable with ``cmake -DENABLE_SDL=yes ..`` - libsdl2-dev - libsdl2-ttf-dev .. _client_fetching_with_apt: Fetching with APT ^^^^^^^^^^^^^^^^^ You can fetch these dependencies with the following command: ``apt-get install binutils-dev cmake fonts-freefont-ttf libfontconfig1-dev libegl-dev libspice-protocol-dev nettle-dev libx11-dev libxi-dev libxinerama-dev libxss-dev libwayland-dev wayland-protocols`` You may omit some dependencies, if you disable the feature which requires them when running :ref:`cmake `. (See :ref:`client_deps_may_be_disabled`) .. _client_building: Building ~~~~~~~~ If you've downloaded the source code as a zip file, simply unzip and ``cd`` into the new directory. If you've cloned the repo with ``git``, then ``cd`` into the *LookingGlass* directory. .. code:: bash mkdir client/build cd client/build cmake ../ make This will build the **looking-glass-client** binary, which is used to display frames from the guest. .. seealso:: - :ref:`Installing the Client ` - :ref:`Client Usage ` .. note:: The most common compile error is related to backtrace support. This can be disabled by adding the following option to the cmake command: ``-DENABLE_BACKTRACE=0``. However, if you disable this and need support for a crash, use ``gdb`` to obtain a backtrace manually. .. _host_building: Host ---- These instructions help you build the host yourself from the :ref:`downloaded source code `. .. warning:: Building the host from source code is not recommended for most purposes, and should only be attempted by users who are prepared to handle issues on their own. Please download the pre-built binary installers from https://looking-glass.io/downloads for stability, and increased support. .. note:: The pre-built binaries also include NvFBC support built in, which is only available to current Nvidia SDK license holders, and cannot be enabled when building the host without also having a license. .. _host_win_on_win: For Windows on Windows ~~~~~~~~~~~~~~~~~~~~~~ 1. Download and install msys2 x86_64 from `http://www.msys2.org/ `__ following the setup instructions provided 2. Run the MSYS2 shell 3. Download build dependencies with pacman .. code:: bash pacman -Fy pacman -Sy git make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake 4. Change directory to the source tree with ``cd`` 5. Configure the project and build it .. code:: bash mkdir host/build cd host/build cmake -G "MSYS Makefiles" .. make .. _host_linux_on_linux: For Linux on Linux ~~~~~~~~~~~~~~~~~~ Make a ``host/build`` directory, then run ``cmake`` .. code:: bash mkdir host/build cd host/build cmake .. make .. _host_win_cross_on_linux: For Windows cross compiling on Linux ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like :ref:`host_linux_on_linux`, but using the mingw64 toolchain to cross-compile a Windows ``.exe`` file. .. code:: bash mkdir host/build cd host/build cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake .. make .. _host_build_installer: Building the Windows installer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. :ref:`Build ` the host on Linux. 2. Install ``nsis`` .. code:: bash apt-get install nsis 3. Use ``makensis`` to build the installer. .. code:: bash cd host/build/platform/Windows makensis installer.nsi .. _host_questions: This will build ``looking-glass-host-setup.exe`` under ``host/build/platform/Windows/looking-glass-host-setup.exe`` .. seealso:: :ref:`Installing the Host ` looking-glass-0+b4+dfsg.1/doc/conf.py000066400000000000000000000045131411177132200174110ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'Looking Glass' copyright = '2021, Looking Glass team' author = 'Geoffrey McRae and the Looking Glass team' # The full version, including alpha/beta/rc tags release = 'B4' rst_prolog = """ .. |license| replace:: GPLv2 """ # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ ] # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # Explicitly state master_doc instead of relying on default master_doc = 'index' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' html_theme_options = { 'logo': 'icon-128x128.png', 'fixed_sidebar': 'true', } html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', 'searchbox.html', ] } html_favicon = '../resources/icon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['../resources/icon-128x128.png'] looking-glass-0+b4+dfsg.1/doc/credits.rst000066400000000000000000000005111411177132200202730ustar00rootroot00000000000000.. _looking_glass_team: Looking Glass team ################## A list of the wonderful people who make this possible. .. _lg_devs: Developers ------------------ * gnif (Geoffrey McRae) * quantum (Guanzhong Chen) * xyene (Tudor Brindus) .. _lg_documentation_guys: Documentation ------------- * JJRcop (Jonathan Rubenstein) looking-glass-0+b4+dfsg.1/doc/faq.rst000066400000000000000000000203771411177132200174210ustar00rootroot00000000000000Frequently Asked Questions ########################## General ------- .. _how_does_looking_glass_work: How does Looking Glass work? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This YouTube video featured created by the author features a detailed explanation: https://www.youtube.com/watch?v=U44lihtNVVM .. _can_i_feed_the_vm_directly_into_obs: Can I feed the VM directly into OBS? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Looking Glass now features a functional :doc:`OBS plugin `, which acts as another Looking Glass client, but instead feeds the captured frames into OBS. .. _why_is_my_ups_so_low: Why is my UPS (Updates Per Second) so low? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are several reasons why this can happen, the most common are your capture resolution, or refresh rate. The windows capture methods currently struggle to capture high resolutions under certain circumstances. Some titles do some strange things at early initialization that cause capture performance issues. One such title is the Unigine Valley benchmark where the capture rate is limited to 1/2 the actual rate. For an unknown reason to both myself and the Unigine developers a simple task switch (alt+tab) in and out resolves the issue. This is not a Looking Glass bug. .. _is_my_gpu_supported: Is my GPU supported? ~~~~~~~~~~~~~~~~~~~~ Your guest GPU almost certainly supports DXGI. Use DxDiag to confirm that you have support for WDDM 1.2 or greater. The server-side (guest) probing error "Capture is not possible, unsupported device or driver" indicates NVidia duplication has failed, not that DXGI has failed. You can fix the error by specifying ``-c DXGI`` .. _why_do_i_need_spice_if_i_dont_want_a_spice_display_device: Why do I need Spice if I don't want a Spice display device? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You don't need Display Spice enabled. Looking Glass has a Spice client built in to provide some conveniences, but you can disable it with the "-s" argument. .. note:: Without Spice, Looking Glass cannot send mouse/keyboard input to the guest and clipboard synchronization is disabled. .. _where_is_the_host_application_for_linux: Where is the host application for Linux? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The "Windows host application" is actually the display server, which runs in the guest VM. The only thing that needs to run in your Linux host OS is the ``looking-glass-client`` application. You can :ref:`build ` a version of the host for Linux as well. Mouse ----- .. _the_mouse_is_jumpy_slow_laggy_when_using_spice: The mouse is jumpy, slow, laggy when using SPICE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Please be sure to install the SPICE guest tools from https://www.spice-space.org/download.html#windows-binaries. .. _mouse_desync_when_entering: The mouse position is wrong when entering the window ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is due to windows mouse acceleration, it can be disabled by following one of these methods: - Disabling pointer precision (Control Panel > Mouse > Pointer Options > Uncheck Enhance pointer precision) - By changing the acceleration behavior with the following registry magic: http://donewmouseaccel.blogspot.com.au/2010/03/markc-windows-7-mouse-acceleration-fix.html (Contrary to the title this works just fine on Windows 10) .. _the_cursor_position_doesnt_update_until_i_click: The cursor position doesn't update until I click ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Make sure you have removed the Virtual Tablet Device from the Virtual Machine. Due to the design of Windows, absolute pointing devices break applications/games that require cursor capture, and as such Looking Glass does not support them. Audio ----- Looking Glass does not support audio routing. The preferred solution is to pass through QEMU's audio to your host's audio system. Another popular solution is to use `Scream `_, a virtual sound card which pipes audio through the network. A guide for setting up scream is available on the wiki: https://looking-glass.io/wiki/Using_Scream_over_LAN .. _faq_win: Windows ------- .. _nvfbc_nvidia_capture_api_doesnt_work: NvFBC (NVIDIA Capture API) doesn't work ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ NvFBC is only supported on professional-grade GPUs, and will not function on consumer-grade cards like those from the GeForce series. If you have a supported card, you can enable NVFBC by adding the following to the host ini file, found at ``%ProgramFiles%\Looking Glass (host)\looking-glass-host.ini`` (create one if it doesn't exist): .. code:: INI [app] capture=nvfbc .. _the_screen_stops_updating_when_left_idle_for_a_time: The screen stops updating when left idle for a time ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Windows is likely turning off the display to save power, you can prevent this by adjusting the ``Power Options`` in the control panel. .. _faq_host: Host ---- Where is the log? ~~~~~~~~~~~~~~~~~ The log file for the host application is located at:: %ProgramData%\Looking Glass (host)\looking-glass-host.txt You can also open the log file by right clicking on the Looking Glass system tray icon, then clicking *Open Log File*. This opens the log file in Notepad. The log file for the looking glass service is located at:: %ProgramData%\Looking Glass (host)\looking-glass-host-service.txt This is useful for troubleshooting errors related to the host application not starting. High priority capture using DXGI and Secure Desktop (UAC) capture support ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ By default Windows gives priority to the foreground application for any GPU work which causes issues with capture if the foreground application is consuming 100% of the available GPU resources. The looking glass host application is able to increase the kernel GPU thread to realtime priority which fixes this, but in order to do so it must run as the ``SYSTEM`` user account. To do this, Looking Glass needs to run as a service. This can be accomplished by either using the NSIS installer which will do this for you, or you can use the following command to Install the service manually: :: looking-glass-host.exe InstallService To remove the service use the following command: :: looking-glass-host.exe UninstallService This will also enable the host application to capture the secure desktop which includes things like the lock screen and UAC prompts. .. _faq_host_admin_privs: Why does the host require Administrator privileges? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is intentional for several reasons. 1. NvFBC requires a system wide hook to correctly obtain the cursor position as NVIDIA decided to not provide this as part of the cursor updates. 2. NvFBC requires administrator level access to enable the interface in the first place. 3. General capture performance is boosted by taking advantage of high priority scheduling with SYSTEM level privileges. NvFBC (NVIDIA Frame Buffer Capture) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Why can't I compile NvFBC support into the host? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You must download and install the NVidia Capture SDK. Please note that by doing so you will be agreeing to NVIDIA's SDK License agreement. .. _a_note_about_ivshmem_and_scream_audio: Why doesn't Looking Glass work with Scream over IVSHMEM? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. warning:: Using IVSHMEM with Scream may interfere with Looking Glass, as they may try to use the same device. Please do not use the IVSHMEM plugin for Scream. To fix this issue, use the default network transfer method. The IVSHMEM method induces additional latency that is built into its implementation. When using VirtIO for a network device the VM is already using a highly optimized memory copy anyway so there is no need to make another one. If you insist on using IVSHMEM for Scream—despite its inferiority to the default network implementation—the Windows Host Application can be told what device to use. Edit the file ``%ProgramFiles%\Looking Glass (host)\looking-glass-host.ini``, (create one if it doesn't exist) then, you can add the ``os:shmDevice`` option like so: .. code:: INI [os] shmDevice=1 looking-glass-0+b4+dfsg.1/doc/images/000077500000000000000000000000001411177132200173545ustar00rootroot00000000000000looking-glass-0+b4+dfsg.1/doc/images/Looking-Glass-OBS-Source-Add.png000066400000000000000000000410451411177132200251040ustar00rootroot00000000000000PNG  IHDR,Fh9 pHYs+ IDATx^w\aT* KbuT@@Ņ緸֊XԊ`EZu`8P@d  $<&y>w9^.y AV4rȵk_EEɓ'dݺux[nqI&h47mfllWvssڵ޽{'OQ  8PCC#33~555Gب2hл1ѣG]vӧ;88񤤤.]8;;3 addBtWRRpuujdzl&9i$"7m4k׮ܹE=233gff+++Xӧ߹sƏaX^^8p'O]0`i444-[{nj,N81o޼ÇWWW'$$ :]f͚p‚6 UUUfJyyy!!!"r.==}8uܹsx̙7oӧO@PPZuu  V3gg0%%%'Nhy 7o^hhH$JIIxjj+2l0|ChԀ|i9n[b~7j⫀RO>ХKC>}ZhGIIڵkJ J .B!AE($H  "$\BPHp .B!AE($H =񢢢 ̛7oĉ5 Pf͛7sU ÇWUUu Ƹq㼼f͚5f̘^zeddˆ'OTTTO?UVV8pڵkݍSιh3IF}Y *++󫫫-?Ā|X,%y\gg眜WW7nb }},UUU333>One``rϟD#GڵTs1000aBll,%~uC=<<֯_/ erI.F6lXhhŋMLL: JBɭtuuW^}رǏlllH߿ Ülmmkjj,_~9 Ǝkii?WZW^^N?(GGG +--x.\ ^~~j)_Z9 /^uuuŃ{NKK# k׮={lkA׏|{۶mF8p0<<ߟ2`cc3s;vxyy,]Ꚛ\r…4mĉ$886l/(Ç_~bccX,S 444(//qD"ϟ?744o߾}6a]hy?) :4>>ðK.jkkEgϞD"ÇS :VrN ~zuu5Omʉ'***VZfeX,Ư|sssbFL=f"HMM1U,3 OÇן>}뫩YYY~ s-[l͚5ےL3 NOO׷D"!p\| NIA \rOv pWW :P_ݼy:u255}AAA5k,--l6ñ4?w\QQ%J]\\t3.u]MM ,,,\.WSS2f? \]]{Aєj B{Њ_#.FJt:%޽5119sH$p~7 55511188ݻx_~|D'RI`'HӴ{4Zeꅲ_+(i _n1f&255LM}=卂׹u.K]zmP}" P.ޛiZjfm;ks_j4jgڜĂ~ݽN;AQMW@{QZ:te(8,w$iX/~>WDXY?GrP&n}}&3tof LXc3Zi܃_&`왕$ :ۨa][Q Y~ew>ڻHOu WMڊVvEgՙjڌ0z֋g'7i`qT FS3qP7VބF"*:c! 뼈Ģ+4z7uM5M5u(ה2 ̪?S_߫6c (ե"Vצr:>(iv& %wnT^nLY |rïF򍒦>R DFFfxzpf7@I]PkYտyV0̢1d{1)i 42&,ͅ ך,f/m|sJ6ݣэ,샓 O.n*!`k@cU 9U%Ҭ̟Ri;R%ND5C 4_*\&(xagd$?%Q)`R\ BByLuo]o>D'芫!~g&x'`s4iLkAUOuqot2XF`> 3BTQ'a mV,֥h?yQNC0I% ao[.)I6^Mffw e޲кn$Mh(ϭLH?zmJok$bĵe:ݙj:aGg &iOTQd5 F]O,~QPgvMAU6K &cRI}3i72 m&&?A[##Wk6f'sw[ŗO5xðݻw\StWE^T:u:zL.3\^t6PE0_;5AFs#CRQ ]k2%q[=#l-3Rۘ_Do_ҘZ];,#>]g,oXT4j;'2L5ns\V|q3T7~E'T?AfLA6kLj0L*6>;~x"d2׮][M0{dfۘ_$έ %Uh=b~2 6ӧaV_ܔ64KO5OE4 W 6m2S)^3Iğ]A闻='?[,?. >/sj|TfoٲvJi f3Wӈha]8jaRW^F$ܖZnݔY41黛y TVV߿Ub$\y x%K\ðODC|qgaD"`;x<ޖ-[GD^.A~'Zb۶m":8 ᶄӧt:`F=~… D̙3ǎ#W!"y {T* \_zxxEO:pqE2 :lz…lhhHKK_ Cl,i;^RLSS󗝿Xܻ{%r?Bd0 0L*jhKht:3޻`nn~;ߚ~gϞ6C|u:Dz^zY&77:ŋǏ?0^EGGɞBݽK.|YhOC \\\=JD OI'%us8+W߾}f3L:pݻ7f̘Ǐ766vjj*QG;w.//LKK}}}///ccc@pu}͛ץK&J~ҽ{}Ԫ/\PPP@m־hn?"* H  "$\BPHp .B!AE($H  "$\BPHp .B!3 L ɴKڿpnM]]i49{9:;;q8찰0uh9nsv24hP^^_``=nacc3s;vxyy,]9i•i'ӹs߸qC"ϟ_Cbv%SSSmmmfmKO@T*mLEEQG,3 H$RSS# ڔ!\ݻw[#x@ rJtt4UhL;suuѣFSVVo5mIqL? xð{?7wz}qCCT*} >9UUUb8::i-aXXXXMMMCCC chhHN4 ߼yL4dCI&$ѭ[7d #Lc젠 rܶ)O>$W^^iӦFWWW X,Cv‘H$Liaql6y۷'&&5n}}@ W q^xhoo?`+WRJl]h~J++ϴɆLc.]8;;3 addBm)YҒfs8 nllTQQ4=׈#Fp\:>cƌFPؽ{whEEEݻwWSS 沲sQQzyy9sϏ`<Z IDAT&66vUUU.]rG\UUUccc2Z,ɆPMUUUppY<<:!!aСDwnذڵ DO< 7nLMMMLL .--{.і9--ի:uڵkr劣[rrcJO~-j-9h=ߎƚb999-+KK˧Ow;LAٷo߯ԁ-ؽ{w``W666^vŋ[ͧ>JҜ윜TjllJB/ڔ(jA[BfϞo}j2> W;%&&&&&mK0 ۴i5jZ+\WߟBhmЀ1nReTYFݍ '' G999&rrrG(fggcaXvvv 6#F\W^!-\@E* r.B!AE($H  "W{PxLЈPH>bݝ:u3ZD,S srrիWՋ|dTTT܆ f"##?r7o޼yf]Ai]bD#Gmۆ*AƍT&&&WO8q%|s|w}gmm}|NMSVV *++1bʕ+}||իW̙/P%OsnjVF|¦[3]*e0}={37njǏ'Y`7n :0exk׮ J˗/r ˗An444O2666/^p!jkk]TTdmmmjjO82EPPP/߾}{ܹZZZeeeӦMmZ"0ϣ,j9pŁSL9qđ#G|||VZ5a>5 ̮^zmrsѳgϦ+L:588I-[뫣y7o<~XnP;HOOǽo߾ލ;v˖-[lي+dn)ٳ w*ɱ9Qg2]j={b ggTTTg >IIIѣ%%%"SNfff۷>߿?4M0͛...ү_X()))//ð5p\0~ק2R.\رcŋ[tcX" ]1_ WL'OhJllɓ-,,޼yx٩kii5- \.wݺugϞ%թj/T2]jh4ǭ[bcc zeeeAhbBB*S)s7dR]]4x` 8"NuKOOon[dtuu׬Y_#7'|}:h ӥcG^x1j9h^ZZ*[oΙ3bh4 FsܸqcРAwtpp044LIIin[6lpBbb*I3|e>tmR&MڰaD"3f?~… >>>NNNǎ##?nkk[PP@BBB̙4ȑ#eeeMw={u &&&!!!emMƏG.\fMLLo<9ֻWoj󨨨 ߴ"Kk#̙b D[@s!7̋~'2fkk۷S *|>==z4͘0aP()444FN-W.U={?_~cK {mJ,HK7n֭OiO0sHHH2d!\%%`)p8[/ZZZzzz#555?2d7|Pda&&&ׯWVV萴Ν;O>B9ȉ'ܹ)..[{xx:u W!C>|( \\\@__X _ .`dN>>7n4hQlٲr__ߍ7N8wfeeaoo'OY2޻wEIIZYY͙3-222"5ҥKرDCC#00ŋ'O\b? C ӻwxj'6X,{.[޽)#QttX,"|32dȹs*++%M('%%رc5k[`R `VSS3X\XXa}ii)mmm]?Uooo333##_nѣGܹC\n]]]]]J| 3!`V^^?LٳgϞ=x?æMtttiCii;^%%%}&ZImhhHQVVƿ!rKncccbbQ -<}ʕ+ի٭_RZYYfUTTD"Z4g0#hKKKO:uV:"x<K$:߳(%ɟ"􊊊l^$`̙3t:~^ھ};eH?>éKOO߽{7GFFkjj޽RSSKKK)Rkiio۶۷fJyyy!!!IC>u: ⿥ v.B!AE($H  "$\BPHp .B!AE($H  "$\Bq9=A;t_WiI_ fWZZt>Lt]͂ ΝRC]]]( Ƹq㼼f͚5f̘^zedd|LLLWc|)zI&M<88k:99X#t3{NKK9<333((ڵk$J5jƍ}||rrr/_N###sss#"" DGGgԩWC{urV#FFFw!7 /z*L<, `SL!7550 ڂ-PN&MtA5/\QWWoUUU#Gį_^UU% m!=ZRR">ae~޻wv]OOSRR)iΝ#""jkk ''֊Dׯ׏_l ƢE;ւ`OCN6_{9;1~^z1LAK+0HTWWr:6C'C|ԩ4555C 9s#""m\P($/C)l6L[ɓ'd!۲y&رcm۶*;wEY,_L 5d>ť+++qqqg~葪*bX, @ptRgĪL[1c444 0TUUi4%tTw6yAAA5k,--l6ñMM7oTUUhC13f#huuuFh=z8p 9A i =<<\]]áϛb(>EEE3gTUUUVVҥ M6 O8N׮])M g@K,V^z7o&&&_Euw6JSNjhhx֭[/_RSSs5ݻ6lrv .899;vGYpa}}}MMM\\E8UUU4QQQ1 ~?⾩+JƏ@gΝMcbbN> AAAjjj.\((( gee1ݻ@Mmgzeeef̘1c @N6}~έ폙3gr8_Z1tTAiOٳfff&&&Ԃ A(:> $j!PPHp .B!AE($H  "$\BPHp .B!AE($H駟馭)/K?0}tbuÆ 6m"V[|tt'[|&$;v;vСCׯڼ5t֍l\]]voOXppU611144$&Z[[@JJ eׁFX)""wժU111f{ŅpfΜyтk3g|1~+󸩩sgEիW7飸ž}7nX,? w6ϟ??<,Z0SSӵkz{{o߾wĈxN62۷??~NNNѣGMfii) O8Ob+**655'N'7o޼)//{{Ǐ'''wﮤ;v3gϟ믿Λ7M6m+.]ڭ[7UUULcT| ֶbmmN++϶7666İ*JCCCLFGod#[֭[]oۛ|?vǏ{zzFGG/[ iܴ/\HKKa0{NIIIIIbXbR?99˗xZ}}}0`sjjjϜ9CTilfnn`0,--KJJRRRʊrgCCC7o(++xty@111555 O<[RRR^^a<˳gO --mԩfffUUUe~z?D8b`&1. bԔilR[[7?zٳgs' qf3eʔ(hFW K%DB| ?@D>(#ꓜlcccnn.̐b{SYYI(f6_m,-->|ӿ7666M qۛ۷\emo|a+#uFKK=,,ѣÇ766 Us O״!ڜA;`X <|pl6dN47glXXX(J?~}Ms$$$B>5mo444vH~BW^^M-hBH_***ÃZ tTiP$\}ބRC̀RBPHp .B!AE($HD!o5 㠐@tdR!:&(E($H  "$\BPHp .B!AE($H ( sssruuuGm BڵkPPн{o[cffL.m{|?ߺL<==>D|/##!,,ێ&L)++NJJUV=}[yyy#ښr8ssk׮Q NLL3g΁<==#""-Zdd*FĈ w$ :tСC&L#/^zH$C 0,===%%eȐ!DW2iDhkڿp R|F6Ҧεd#ښ/ܪb଩īG}a0x<ܔlb>2mM.DFF3fʔ) FjllLѬgϞMQMd#Ÿ6b b}m`ZZږ-[FbB7o^zEﯩYRR{n.ӧQ#555kjj6@cXlp8DyS37n1kJJJ3g[xN&4{w***sB111gϜmqndnU͛7qp8bfXp8߲j?ȑ#q>}ZhllܿVVL<ř\A0 ;uݻ0p@\nj;~nU,:իצ͛233L]]|>IDATdƌ/^=JQ-NߒKLLLƌ{+ZERidD$??$$$DGG˳j s`C=`Ȑ!׮^Gqig϶6}{Mrɥ Ë1K.ɹj sKƷo] k1\.w!yyy'@|\<5$ PxhJW._F$O?Al܆3+peXL~PܿD"xRRD"0ѣGxPՏPSSh"j6(ƈ@P@E($H  "fs1|ۭ8|,_F( ͉  pesL>A$4/O/j!IR|Nw]UUZͧ[0!_6OߎM4QIENDB`looking-glass-0+b4+dfsg.1/doc/images/Looking-Glass-OBS-config.png000066400000000000000000002254411411177132200244270ustar00rootroot00000000000000PNG  IHDR5 pHYs+ IDATx^}y&EusAT1QD\"Kbn\Ѩ} *q!F%hl7n 2l30v=SU]U];t:uԩS 5`r€ X) !x<`[ C0`{ 0``0`V 0`a 0 jBl5r‚p9$_=Q9iC,Sf 0 jBl5ȣZ%0`=f հ4;" ;x? l4JT !i8{He1hҒ@K(2d0,-MQ l. ES#ÔypJV1LF]<35hr_\bb[m,xB,<)ZԤgn,Dے'%'k.KN cj777h]n @TllᨈVh@H.,Tg{/DP**,1"2 @Kں7m-Yjf`r!%fzH5mlD8| 'F7BO"%01 k6iGFq"c&DO[C :dJdֶΚ$h$hՊpy]4D$f-8 7_EO[TBJ p!n\j3t1DDwvt)pm:1A MhhBKhD IW$i{Dy'v~TP#WTȂb0(ޭՊ u+Ck5Z夥X"RkmmimEcY31PG#`EG Ӧ< wb -~  M((bƴԞ$@ʌD0LYED:Qo,"Z;+{,i6X,+abrhTEA2pJz˜6Sq%! YPJ5-j^*͉CB_JFff9uqNm+)"""21"ں7oe=TzQbbp=/Y l3,/ _ XY m:YYVѐ+so#|,2{S;ZyݡN3G Qe1ƈZYZmc ]&V+ qW]a Z{c=-9q eWx^Ylm%،su=1F KK @-@^תؔEt%AObbKY趉#\ _?zMh łܾ F1TT2Q*`l\!(57ߦ6֎mypJ虴ҡϊhKPWve5Rg u!ӧJnLeЦX/+֨4]kD!B6ČJXkóL#aPVtEAjuU-1(PS+Jgx1ǺㆢER0)b yx NV6UFmhׂhT5Cg-oѤU\vQafXa 1,#&8aKROD}I@̬pDZ$U~!ïDo/ȻBRDRQLY~/PӠ!°|!x:&T"-2ӗRYN >X^LP8B̢ћ$ogXE*3U ,-hxέ'%E5ಢ1HzhjOv(h2 9kRtpUBj\d\RxXr rM61D܎EtţEdE(?zQ^؉* wYWxY@Q^z|6&[\Z> _]υlbĈVX[% 4|&:|UU;l v]z4<[(9GaZ H?MlKlҬ-SΙMi%)@:Ջ+g +3j`<[koܩ3FZvL6Ef*..eGw^u4nD vd.g^)L?Xg6{a7rf ]Hc:Y׳i$6*`Z+rLxK 54Sbm}!8nb bj #٭`148A6R0B9I0,bHUm2$m<5e;0"'Oұ0 +' ]Jˌe P p Fr\DRDhs%O7njvܮpCY )ERW^шB"b*cfvE]עXGl_<@T2I I0b[*GH)i9 IcEqAfǯ uPPTffCSQHN-DkҖ-B[.T,kŠl?Io8v(b-c*!a%*;B t; 5`ρMߣK'P%F'|eAW.'J좯pҷN֘dwp- / icڊ@1jI}R\\|l#lvQR$"gl"q1Im;,& (-yCpOm79dL9%=K"ֹS2TmtP;-Z7h>kyEm#0ژ$zY(Q[*@-]k'j;~651O8K."]9/=H(c@IP2fv%[D1 N.g0,ѴZ+`Y\Li DvV!dĥI=e_#i5oSm+M zE2/(X$Z [[[c̪Ug*"fvA,k[o"RSʓi؁l,,Pݾx,}h"07\\b1Uq8F[ *#HrX'Skl!tsֶ1>Qjd!X'ȐЊ+SD 30? /=C-%F >T/g>uBp1 D G:TL^PYo!f~ Ӡ8 !4/:-3d;ammLUj4[-II+f'@6nR|;+K¸, QB_P5Ͳ37 f˙VH z׋n8nbBQ&F,FY +Z˱5!"ƘY:fm%BD:Q%_`*GBLrv4٢MLkŘc/FGVۤHj, /-*.>5i&W0`&2G6:ҦAxl 34~A -"^Bd ]6O |;}2jF-,da7b9) Bg񯨰3 T$w1_'2b$xXdw8 1\!8 /6 R)Q.Ro u_PuE,5}AI1JQâVT#756װm*S&AvZ\o [׷%żD@\kġ-ZDHh_R$:3qX˓S<}je%3$LV9%>~v cw^q+mtg2.jPk139+㱥>GhmM1ҎcD]UZ9w9Pl5YUNgSiAb4%?ASQs{d+ENliA6<  $mPkb6G'@.-J3 R1¹ QumDS,uQЉUN]TH,,+-3+e#"Bo窛SP0&Ĉ1f4;IKZ ɚt> ~P1&j;"7QY1bNw-4 UPW.@w3;!=s.z%) j$eI:JxV*4`v#L1 Ufw#u+:P.M:$"q1Mv|RELH㦍mvp;.D>acfT3vnl5u!v^&"bֺofI9OΤc.=_g@!^LOITLӦru].d) JID eu tfARPhr~{Mm(+6k| *-CVhZ HvZMdžxJc6 '5B#4Fu͹1hAcXM@:!枆ĖHNB%ҫiwҪ [6' Ѿ..o$0%@Uv@@NEoO 2R$t%"⇵No qIj^E_ArO8&@Xlcy ^V` Za]J5ZkcKcY[1b@ka@4I_Qݑ| B0 ҽH[Pœ ]Q֚W-a#p8OGFM%S eM@DĈHںu ?IIEK^Ըr}&mC[|!ʂVZHNy{ eR26`8n*=FA~30=l<::^!dˆQA8c8A[]r7d{Gڊꪵo#TfTfg@[㺮a-DXba1oZPdɥ~{X#i z(2ȧz"l"/$RP)(%GbaP6e" +PxQ.eY\ 9;Cc"7| RVeLI2ESFKrAEb !{Y۝pziPRbc*32R Icmmi@ nǺZ<*Yp*@WR44z"JL zRImJiSej,*9'84+  (9{t# eP .u2*;FQr?Mefԭe@"Ż ֭,z?U`e . FRUb@kckn ÔPrD`EOaPLVnbi ˒(D&dE1F+W6#QDwb!XZioJPtCCK?Igyᝧ1Hbh4v7} nA"Ibn"Rff[ۺzbОNuSһRћK)bbMz'aEBo#) wPoK5 [[ @N+ B1l_W"-los Hu0)Kv@Q.LZBa1>4)SX==9VVw}Z[Xq0Ѭ뺮iɱ՛b,A݅fD;QⅠ@~FNy2Z˘_gѝ(aFjDl ŅՉsEs4NDi28k%'?l.X75hU,EG|'5q#/Iis~<kckmN%ZDFd-tf颷05<8=k %;ч:*("CF35HCB[n}jRo( Ა<})LCدR"qڣNms r>nme}B̘*wѣ@O\ja2n=GT;);S`rƥe&"I_P-Z֙Cpg.6ʫfd(Y`l"DѶgpM/"Q RiEF"E@\7,SYCwm@H]kB*#ѪQ=+WgۨK KRLdPLɦhE׉u:E!8s҇nes%Rm5wPd25I?! ҳ^4S)1jYun-)sXN~?r"I%>o% ɀBZ?ԫ49BzRcԆ(A@LvbsKmM؊EjTju=c+vc@ ݊AZUgn~ ZmS'"S-(DFڅQť -ًW)$KHjDe 2ǭX. pQ5wO $4df)$5%M̓1Wg\zDTvfxK&142 I g㴦5mS""šҪ1̨Zk뺶%(5048r ln2>Dǥ| SL0W eSsmX:/UGIO\\ J\7ތNmۚ6Dm`*%W ]zF׾I!Ӿ9%şgI('ǹ@Gўe*x e$},XMUȻ(K$VΚ9ִb4ݲTn })(m9-6I4JW-CA݄2PZc*13Sښ֒㚤,k .#{rZJ#'ޕKd,6nwf? n6"yhw~eϡ:#乐z#bt@2fs!PQb4] :Jʠg$$0T [:$DZyh\Dmq s"+(k'И+[y4Y܅,Mexb-}E`RU">Uvb ,0 -rMZ8Ѽ$'#rsrnDD3 6 Rqu_7ܪD m.u OXz.q@R$W+6MȻX4+HC4N&'F%E" (ʭ2EB#q S13;Yk9m]շ7IU̥XLޕ" @ZAҌFMb'҅͂:SEzR3(rm55Ri*J/}z[.Gs;o8 2&#S^BYড়}KZ0ѴA9X@-9q [S6t,V-혀1b cj^cKNn^ne4[#z +{@hXӤ gMaܖÆ'íOrmq @='i6,^?WAݛM@CNOu݇^8OC|.D%cY~@DŽNm3gXr 1Ɲ+`1Q~fy>".>Tƌf*#;׵5Ɲ5JuKW -"$KrD\3DUpB$@R=}D{0+AO1zѰIͲ.rA ֚Ә=s,DZKҊ7xGzU?Z(Wmr&Ps坯 !=T9v\sN@jFf4Z v5fJO IRX;ˣ//dFv| SPS]M8aEk 9fsuMDAD<+EsdYs"ރ<c(tht͂ ^2/YpWQQug޼;d `|sI"FzLcV^mZkQנ"t&8~ V1&\E6i\ŋNWz D[3QBd%r\U>k{eT(t ֌uǍ\BC3ēe=>$ұ)kM6'z[åWAm(wS0Z4UiSP~;.VnrsPL!@0s[gizkNC\CJ&TQi?TR"b*SffEH5-kuBPlN!MQeu r][I dp bXZ&#)JYwtQUDBij$ rШۨtlUS4# ac-@K|Y?("}BRc|9kKkD-N`=<4(\;ԘNbt Fvɘm#5G` _Ea!{@:lǨژZd]׬kOY`4S)_rꥈ,@ +-+Nl4NmDt+%1,q.(X!% +!xԯ!(_5 |FT %VN [\Z$F5_+-K"@K0KOJt$ExP(Knb/^MðeM"2gaĈ@fgF"ME&pYHTIOXd]^gt@k%(UQH"K͜!HMx?-_=uQ$XԂ%^Dx < az@<٪2\%Ƅ3)~N MFIك&ҼZD#l0slϣM;zңe.vVⶵ0FUgiimͱR'7 j5nWڣ[,K",pg/D8P!I>MpmL!>\}vq'c&Y/^J=Wa&;Q &~JfEYKjV083FqP$*-P4E#!%M!;\@93U ŵ) No7c3H]T`IК-(1ՑiOlb@m32kZZA/MRJ>.g)t,6'hU)":ڊlXW4 `mݏgoو ضd!xj#o37<Z r Ʊ4!0y¡gn㚏!E\ lׂnٸU?r 0`҄~-AcO[2툿J$$G1>`430?O⯋>6 !443o"k}s{&D7^]ﯻ<À, V!h th=⯆8;+߰זz>xͯ9̋.kַw}GK_lڴ9<`K%! zOZMO_W=&nn_imvmo}[_}HK9>'xLJ>r-0`%FaPuOC>_Cuv#$`Zu [jճ~̍;cg믿> k_|1ܸg=իV 7RVfLnn @*g>hn>_X@N-&qc۾/XnݡzI'֭{xꩧ'OO~Sy€X,~ D0"F<=_g]tW#"ߔ~{]8_n:Ёu#^-wtQO<''S[ߚ=<ԏ]Uk_ugN?}W;u%G~;!z[6RZKg2U;ii/{ 9)Ѣf  `DV55ӄkgv/=g}o)n{ێF7K.g??_غ7G=裞GD;} 8=fׯs}?1'߇_>_~߰͛rpءkOcL}Oȣ[n-'Ϟ3nz.oܸqz'<1~!{v<wUgοB\\Y]zvi7}k#SE$S`0.&A6]|^qq _}R@4/}w[nw_|5k֜r)/>=/:}!4_;M64U.~neLζ S7nٲ?;Ջtr͞JXoHHN;~Io馛_v33w]]}MS\u3:Cn8FKKS`ݽ@㩣 JcC=Ǡ@/ P"VՖ_N_>я~+^/W;ӟwyp'7+uvPU'?w}G?%ħ>v'cʀ aG~+裾uyz;ϟf͞\{=_p!q7ta}ȦM3g=橇Ъy<Ûlko} |^u}E=Ӟ8#غwm=9|{m޲;s? Go;P=?w?}?};߽_'y}ܺuW" t}3|;mԱPD^1õFZx.B8qaT]%7\=d!⯆f! Ѕ2Le~߇Ӌ.袋.g}^Wy򕯼oꪫcw=?].uwWbvv&l;"Bǝ=A~NFO{Ʊ7o?_go//;W/'p?'O;\yUO9<8yoTkԣzk^w 7o>_{V5!ǿ Oy5kss>os?Wv>>zꪪSܻC[c>}?8c '}i||7|k}o|ͿY|L=V|bۯ[vn8y? .?oz׷x~пWJD֭{կ]}߻ ^>/we[{.Nlje6k y K%rV˽6Ͻ8͆~%\uU'pW\qWp Xk/38cTy_p[^~u'>O~;o_8=c/|s>wgE-y޳q͛7yء_~_K.^taUuW|aϜqM7ms:Z{郿U7mw{>^W]uչ>A W^ry/"E,S[G?əg[ssqE?^_::㳟{Zkԣxۿ-oύL~|Oo޴iM>; >辿oo_湙 wuu_>g|g|ozz_W5_7vM=[w~#7pUWמ6ژYp =  9X]b- R|999=Ȓ.<]2X[*O|O 6mW}B֍{r׬]kY7ؼ뮻${/m 377~soc w=/2̌F߹~|q/ߟ\;THqEk\s͵EҮu]M']vzpu.x4\smvu|3U7޴qwS~.軇Ѓ_V;u]Ke=|~S>tuz#K!@| "2S`hR%x[jU6pþ{饗 @F 7%Sqķxͫ/.W` k׬ {vUn:>{}A_m'Tp6bXru]}3lu}}8з}Q7GG:U) ׮98P^kEy /y+s;~_:Roذ!mns&%D?rn=%/// O?MSoڸ>g3^}K_W 779ЇBIEPj.DR7_7œh_!qk;W~1y1oN1yCο;mi~+_~N| o_`9vZ6K_Gv'?~_וm͞{>cp?aG|[|iO=zmosE37޸qJlƥg?=s[ "{v58nFXTѶ͜՟o{=CŘq}]t7nO8mG= pϸhE\^pw]Anw=!O}f}~ͫw߻ffV^nervuכ5vm7htc6orWZᏜz>3jn^Jԃv{-,dRMYs}.J1A;߹C_z~{ߋ{ǣzfffnn.N:x;gtɬe/~o{y_?‹.=jv@gK.O|n֝(~xYxM>3s~wqO}/)ˋg/}Î8'! ozs}~Tꫯ~{9u~ҥu IDAT;Nrq]xӏyӏ>~i?ys 7x9_|7Pj^~=/lΌO_tzq/W /ηT]~s=vu\s /C}C^w~xE.JO?.wZwd=;׾?1ׯ_S>|啿K*k}ag8!J .G''jdn+Kc7=Nxӛ>OwW>Ol׽׭#xgBg_5`J<?[|;yaSS.$Z*Mx $ %  tQ>شiNs}sO< /1/=o Xn,d?wL}@6H1%R<6m~ٳwuTn<>׭>\4`| qeX*uS`Gae~~ޑaݺ:gF7mxW} `!َVFaf+S"%!2##h!tKǪ<;w>0["nE<fxS0+N΀lXܬ&)Bz.|pOljvJ 8"ݹI%0`6%)c*D=Sn~c{πlOXZp/H0["FQ18(<` vMׅ&'` *40lP<` eӱ Agh r7_x:oiچF$q !m5ћHT`Q5!h)v{ݛ;݉ )1B$Axӵ`zt1dӁ`Yuj? I [i߆r,,)=)ܰ4Z@#/Y?wѸ1ڲěՃAUU-]f8! >oߨ#'2Bp_VF@f.uެ G O92"Ч - X1ƘQ[[Π6kBVeϸBm L^GmedU . F1G\R) ' dѥX$1GV;b1R@BzBLg@iTH ?qb -Q`Ic\%؇HC.غ- [weI;F]B7E AL3E,QJdT!~MD4c|ަ;BbϰIɕH#b3`n!##1emHg0iΨ捛*`\eI e 'Oϣ;Vl-[>k)6Z_!XJ3A_$jMwR[ B_`FUM+ x<8Nc@mb-NX*鲅i@}H՚{HXgbN]у4A[ W $bk#"#e]thІ h2h陧ɵQmV #V$ s&բ;JZkt t+"kX?0VX#Oلy5z9ac{~l@3o \'8PfNq |Hb63gOW ]P̌Ȭv=q@U/=5@y!|eIph:tF~7T\4IgEኞ"htn4g&GDƄDt /YTк٨`PPMA'Z1FGQDTnz]j+U#ԴaT} ;#UځN/[zbFڻTZ+@e`L=1ajnnND!-wDLI z=bz@@E"*Eޏ12Gǔwv݊sX#"2 d>YZdnҥǼZpb1V64 8Am]JctYjl|О_]Sj ÃXO *@܁Z`xd2^>$t8GKK*>vC3skvԙޤ""#,nuP,`bEY'޷;gK_LDgK #8dս m up %jht]!p cIe5G袳[PJ/as^. &@d|]-ڡUJ{bVD*tcmZwCc4BpD#IO1K!P ]wnSpS jiI<`>79# 0+݌Cܥ@DKs]%knȌD?bn39k-DKZ!oSwnĬ3 񁡑r:S$pYb4 h3+7H4gƭ,$z6)ٸeRaz*i. Rkg]<@lHLк+T$r1Ԭ,5jgtZ_t|>y([߫ ^xiB4@zO]ӊ&9%oMwiM]5tjX1Ռ*C0ωiٍ8hRrUwO9U$d>$ٖS1=.% 3MM.,͵H,>C4 - } ^|l=XQMO4Ϳ}1٦tHKiٝCH2 E;xS|>WW_(K\!!{Gho0ayB#E/m:KNO_O)x[* y)7OP?plK@+7)VMr=}a&ҔO245Ng/H ˅fv\~:4`.>MP#-G{1\y;>b=L6%b+Cef 6ȸ9G];@<@甒! @ӠK۵fnKJj`S"д7\(-.y_ri'v{Z) SbS:6HWƊ0ԺܞS/dsXz(a5c:`$#01TĴZېEC%}Ț\9DO_&Z8("r4MǻZmx"5%Uܑqf8C*Īytȍ-[V$Ia6t:ysZ%H,i'; Տ4T|3o疨Flr 4%)( #?*uۻ I[]Hz"Rrw"T,0R~RJy\ЋcqM XNCA}%(ï[oUULj}lͫMMiP3Ԙ-s:46p'A ySIH)]{uNO_H)D-dTv]DsD rF*`F1i[!)8Ӊ<{,ה%W6nGUud. a?r.uW}O3rx;z tB2ԋ`l4q2lе" B&Np^-VKP"(-1ۨf9E~^L)4=5 .Bܶs,S:- c{u{Y ϒ(kyR@@g(w҇apwNj[&;48NNF~hsG+X<,ѽtd +lĺcR//3Pc<1M|siϏr$ɥ:r} ^fΩາj;ث!c5 ZR9rЄU!3Μ)>/Fr 5,)//@B{fR, \I炫zɕz:X5;TN$H (E1`LjkxE!o6p{T M,srSY|/.3q4\f]AsКvW] y JɲQ.'H25 Ֆߑ8GI>J)/#j߀g<&UF)n/,ze[mkA(}Mm±< iYu^h4NDJY<$/:(V()Xe^IkZw3j.rոN\0sxH$nSn]hH$NQm}`(NB :- Jq"}>rx.H` j<.y@24 $P. cEp,lb1JIXx tJ/OJ"@5֮H`ݽ#B5x@cU՘nbd錨W~$ Ҝv} r3F˗$,7攃m+R\35UƦKu4G9=.~-@p})XQм5j4lf@EA\o[e`"˥daSȿCbovdxdyDbXeNEt6=@+ͳg莦m[܍/|wL= `K]]+( KL:B~)__8}L+zp$yO4,4+#P$D,=P9I3$Ay\) !Ž1+F^Eb"5=̴*}gGV3} j`U d{AfBض1FĚ2w&Nt>H)<Õ$ <En`i.3[_Ң2R^$)!Pa5/BMGӓ Bw瀲%)Q@h9p`(dB_tv_vW6 unj ;;lhtkV*D FY\-r:bW$Ot~_^,$Nݎ]EH!$IˆA24S @dE,&jEd^ S(%~rؾ<$)i1+*-0R^ůZj*bFv+^y4eHx|l.'$7k%Bo9'*y*?+ZV;fK!VF>e %xsXMP}$IwW֐o},S q^g D~8D4p3Mђ_M !M!MQ`km1^^A'B93~ Ђ?7jUittNрG6 IDAT4;勤y愔&EcYպ<٥xM>7_ ZJ q pyʞ**Ț 6bWH0<ڈ{/UFa.&pʫ@m ܑQU&nΨ7 \rwY&{z:9uꒉ;#UvjGŅ&$BTYCns?HQ߲۬C#.ZYȆ™FG]h󅏞/N52GI,A3\X*^ ## `^>M5KI#/R]{gVT+ڭ4]nRvWQd@, 7݈ш،L.e@3˗=}WH ؆ŭ:XiyY,э;c\WZsa.kѶʥMl)e/ց.YVB@(+aO^R˕$34)=KsI4Mr!S${ & HR#Uݒn:scP$4OjIpm(<ɓϳD if8Afeq'c@:ʜkpgL#7L['C}ۄJ  s a@(-!}wߙ<Mx,]:+]a´GJA4;;p]ˆu5yuquw Wj]JM^/^+_|7"8м)9 r{+)˗"w3Q. hih(|!-=^ 0Z>Zq˃|2l1k~$ @}U20@܃tYta#״ay918=Qn^&bH.@iAZb3Y}~Jb>Lt p1آV̴nɮޝMTe' 6WU<8cT@ټø"ia,e4 ;i̐5&S1Q2.I'#㾈&bwH$9]_K ݆j?){#ن" $@''8N4M_h)%H3f!y;3J -}@sk{DLIN)yhw7,ȧiJ;q1FY\g$Ba5ƿYXi8Dvj m&"FAvRBV=V,C`Tn֌m( Pe]mN_`ج;[(rߩ2%H'1Ng>}< ҋ8K&1ݎ Uk\ԋ$Ą$l7}B|gH7! E^%p(w]5=K bk-!*_xNZT;iq BkӔ`'`y  [ކb0/ԝ(T渁נ)tx/]95'N_O/I ;/~aJdvWW3s[誅w x1/8c@5wVX*}FLr,\IRJ_|Yr$ox;=P5D7U=Q'<乁P`}|;Ɓ&jK侬;~2Yp隚7kF{0pAs󓄔.iNۀeY6FºQe3t:Nt Vfk+ i [/dPMcP\I:A[O$7(K Ir4&tJqMx?z$I5 TO'2Ya4}.o QE^j:y>m"HEJiMM#=^ukN \(!圳"Ѿ|֞g!)-!D(ѐbDֆ%8վJ~mࢎZN7O^WI򿭎cI6w+,^84X!c=arX|n^ BriC5 )/wV^ ,A7C'DKؒvhv8n,GX|r-7AA!qpɑ,Kp0z#j78b꿊zNTnVp3Anviu$BOD]$AQgc8z_u\ZqloP /aMcIOnP ^yeQ@4(r3@a.]ߺ!1}X56=^c:<=rI0ϨQ͉D Ty3V*Z\s\"= s*ĶM> Z<"F@2jEٟKdePČ8bx$ɐ \29j:I0'nq}^k)+;n$@1p#áEt:y"M !hՙ?96. i?[W+DP;|jBb\!l+J4 #]pጀB|?>$qgi aV.>w]+:3W R5 t<&=e=j@ ⤮Qdx? +ZRAATȶJ du*+s8Hs|GvZR.?A*11-ژqOM8sHH0dBD I%i4L@RP6)C6j8`/&A&!1 2ff]>q$FqX<)ǽ< Y!R?CnGsXxKƏEKT?C 4;t @֫)/KG+8 `2F/y6dry~N?%#,ΆdV쨍^)Q.T$"#bA7eCLѹ3\㱉-XF0W,̌e!.s\[SGE/[7zOc8hQG>[NgI 2'uJk~Isފ.^NwdT8YM4evb5O:큅H;o%MJ=N$c[sb\0f9f}'7/vФ7 ^JJoQcc-e5$F[k$rDR"7DNCzaml+5'.yC%.DG:vק̓ty&$ŝ^% #[Zc- T`HRlaKY$Q2VmE¥82+Y;J%d,(±%/=$(Zc+2@ / 8wM! r`o{JES ŲHf#Ϧ_(4qLzeU$K!2\ d\0\9;KYЄ]a6$3wR~^Ⱥ@X HAxQz\ rM'KrX4Mc<ϧi;&@03izyyy B8~Vn^[{`cy$x;o@ut!r&$;M/@:_D@rʾ3Wjpo o3"<2R<>̝'»c(L;P76 wa Wkd DAC Jۡ 2V6ˣr""fgd6Mq̓__|&WE5k7-?WME2/,$p?t,IR| baW wӕ`+9/:]E:g(.Mdi'k֬{Q`M{QC}8HIo\J\щI1ͿL9,/ F(\rE sg4~oJ>3T>Tbg{,.Uhi$V?mvCI=E\*{Y>`*$=NR"բ89t9IkA=%'%i:ͺ_^/]]K!~nǓ{J A;}}:O/~i_"#6W,M ƢD\s:qry n2J`|]yߊHJyhe>pce0bH}Un+~w8hU91)PEʮ]gQǩ؛/X*P^DHE&'Ɉfft$hN}J|9%yx3L1RTp"%) {ExՅ(P\HjgAi<\Z IA'J"v/QyRrpӗGG`-l׻⢶~*ٷO۹=wE&ѕձo.^m}do_/_I˄8&Wp APib ե+5ІJZ_[ao]،-z?hXFР5hccm;oD6e7ғp8_fOvaiR|H$|\ ץᠦ3L8:`y$̽&sӬ}#{ y 262I-/]V弊߄4ZuXON Kdx_?4%.(Hp"V H /<{%ǒUsH}$$ "I99*i:)Hꦴ k?]4ϊU͚lwBg:ruC6d<~Q f<}7h')U*V*ޢ=Hgj#FլU>hdq컁Hf\0,J#G=Ţ5o~ o`o芢TP ߊ*J%P<)'$/3I>d|MB@$Y>^[G Ruk%LP{30P_*X 5hȪ>E*?ѴȐ%0(=@C{͹`mȚfYɈR;/ms# 2_Qq ?â NDBͱ^ME_J3p,YUg]oQǑVflA MCtQH-BoKv@K6Fq9}$$ P2/4?_/ XP QFژw"vM(žhUd.J +#!'4 FD)3e}hFIsZӇo]j\]{;)W3]'fTָRa+l"8,o "*N*ny'*d,I_^hǟC5e ;H`e3#똚96?6ʼ| "oɦ/_A/߾ DLhJahlF2;.MVY,Q .8"쒧D3H6M@~(̓] "HP͐ÃT/(Y_h6Qz8zBLA^N #5y$M<=HK)߿LN-0:Ⱦ͚/bĐ&$H͌yOiN$N6z.2OjYFIAF]o; (;(}f6m\*ɛ6 ۜlJ6L!h *gwQ ƨk- Xd{kMlf`Dաb$+<iZkAGMHIA@$)%\NO_ߟ/I" q)ˌ.J"xYt$0vHI˾XRj+%Fk*E1կJ$ DHDXe81M4Q0=fPhߟm-koF{^ @=$?Fp@hZH_ip j 4wYFivȋJ4_@#x߿x_r܀C WڋR/vO z pzp;,?I{F@`tI/c"~a@g+n>+!ʳMw Jأp;^]ؔnzl,[lGr> _1~WzyiRXUr{i 8VAP@y-B-T3)Zl:hQS@ʅKO $fG1T.CшW"8|ֶQ4 yu|cL<-O mXE#Z ]rX~`ϧgl~sX6<~dt3KWZyP92 bx}~i/7^1/[*Fd4޲T e3BITiKŸq[k>St:2dǞGug[7oKGK(MvD˕ޏ_Ĕ˿o0G A4ȊuD X IDATx/\"JfA,2jE'i_>l0GS-fvM8P(4Pz= S2}JȁB*$~D0/n+2jB0(Isյ Zn% y,YJT)C8pL8^r/&u۟˷C$=N})1`(B|(.ѫv*6i)Tx &f}D=/| ``+E,kG(? l{2/Z y^:(%PA+DwӾ@|,3rpM瓻{<mݨ-wX?/ [= UJ#Xpdnna; cތ e3HRO vy~-=_ @+ײtkW ]kp)$0!p_#H~?+6t>č|eml75Y}Cjc8fuF[h8f%orUuQ- ,B_Ms|ALI{G[r#l}:\UF j9_:Y%A_;!duUaO_~O˷縟,3 68#ԞFq)+JڝB#U$D2s֢ D#/ VJ";f~~a ,LQM2"wNd?/tbK(CWUxbkX]nIJ$N=y^78A^nތґ*L#*âi"9oֽETjq`CE.h mM-f5m0u#_@>6*ͭ %<m`XLZ"@B9ZWy 阩w/ {Pήc"g "'$i_~]_ONtPF'= - 8LBK宰j<9 W)'=O7 ?.x`v85y 9|j35!AQl&Q֘Xw$RLdN%=+͞8M?]f}̘H Q_tdfp^Rr>l $A~-opݨAbՌnXmAhvC^t$jOE/F%ͲB=KHSPPiwC.$ L%Nej-FS5V@ZYnA0c{]iΛ0(RhV)1 8)8)%$'*˷@qU)A&I9-2Ii 3{瓙\ ד=os?_XTwmoG VS"r&I%r[i _L` #2=/p \ra. etbҚǑP "uׯ!'X#R;f7tBM԰#uHG%Mv*h|)Ú_W WKA isz2wuCU<"T$kH'󒇶 .jA5dkA&M%0 .0!//.ʧ_Oogggh+ɧiR`q= )0xBQ19>'vʈ;bԽ8gDpXپ[x tmDWV|J\PPat:}?寿08`ROh LgloĞ*FG5|i{1oo1ٸʇb|c })E }`]pA}a ;9b>5 z VoE,fT(IhP eZtȖ"5q) e_ߗ{|{N)`5$MrH4(O- HQI=p-煺-͆ZZըVB=g+1rpM]yO,F=Y sf#c0Fn\-Gw&k>- ߯:~Q*ͽ(}|S &jCL3@^ څF^KJ %0bH[1@-5[df,}TɌəT3I. Pnpq9K<<φ.|P4%JNRF%Mp|Oh~EHBv)?|7h4[)σ7+s?;s4oB`á4`$@#xx9~;}?,i& ҅$ >PZ :' IU&/ ncS.rRM !89lYj@9 >k Y bRjb!g֔HSTEEyL`ڵ$7lʢ HW<ʨiTs FTa!׎*$@VF;6XXU5/\vv=F`nd('<-a27ٟ_Χ͗y9qyN`i{"D.olUuw:M7׷#;1trxGIr oju{؅~,T^L@Z;)me-]6CxǟOry10%O F2Ъ.-lUV1c8eW0&B,דPYFǫ}K$%%Y7s-%墯&GkTKrhЖro*<7.2B8(ɮ5j&m@S/ۚ) b&hq=߂~4Hƙ<)cmD B>AAM0qO .ߑLݾ~/ 3DLg\ tJliCʝ4Jp/V 8!όm 8z/7=HXD9j!doC˰u#]-El ٗ募>_|\dD"@M=_}v@4_pc"/rU3/dRٮQ3۔A 2bbG֤A* "켢ɸ@!'Hm!.!5=J[^rd i̊5q{1SjFZ?k#z ?~S8XUȧ{ p[J8 +Wz." \ѽO | ]idM}הx%1BlvH2|xn =sWsȋ5ls|$p@ǽʘ@Y1_nHXK8lXZ$ Lo(l3"ex0{ȗwI Dm$~\ ͒$ױ50N )֌]3Ƒ7IftDjs j:"oڝ14PL6$:@gTĶ)ߋSXaª!> z0rzd>smO|܏zĢ=\!{im E"brglaf"?%_*vEiӥ[J qG.H, VT1OnoG:I@/|/oovɶ!yd7qlwy5ĕZkbKwN%Az2$OM_٢W+m4eG(n&X;t#YAֱWo@[w=GoR[(X{6iv1Ak5oZuEK$T6pY $2fqc,1#=9\N2U)ƞl"jfIE{`mko-c/cۮ쓢2fIgn-X6PHvQEkqim?!F_OъNl *c-iuox`RgUZ rh),مx*cB<4;2̚ #CRX59tA|s}*҆D +A-C$CM*pdP @pIdlZ[BQBTu.-6QeH2FR!S*C]|DCEQ|(bvEEaˀ:M]ڮg91bGnb/^8n@afxq3 Wf:oGx`>]j6n`J;Ԣ Ez{?8Sη9,le4kT.*5*3jfvO &P ECU=^JW&4Iz;0V?Fװ;3ERqm]N !kܒfK:06-zt)lG";܏m\TLs[%M? XT*78_FЀfKZ mD)WWR**h2g4B5fJݺj͡HTZfr %Z$YGP=Z*D,Zo/z>DD{pP *դ6 6y l}F,2u-hŠόOQYVb\kx RJK5_`֦>{ltWLq{ߊP} *':j%=va#x\DqxPXƛ t(F)‡zIHkHɫE~~jDȚ{}0>ZEc8}?Rd7Bt^=c4h1no4 Od\/6ٍ]g a x69N|ĶaPMmh,7=FK :ƒ }ZN*6IngI7k!W8Ɂpy&5>m'W+ H /5QU CTY#vۨlrRf(!?%p^I _TGНeUogO :@O8Y\;~ ~,ӊx;ixe=ko-W{DC]{uq7aG8݌+ Y!efm(7E8F-=$ן"mzfVhR^h-IEJ (2eECԃ~K& $T GIFJdsjej2//Q#^j.Y&=QBy.\ްZZ z@!c[h[N!7GPS {!5VgDhc ,i ӗAR_mU%㵔]A}c? p hP_ @}h±oEnC) oBy万ٕ6~+kb60 ;㪋M)T&[?*Ϲ(lX/Խ/Q,IQzJG4R-}|>@O ^q2n3#m@.ڨoK!~W `(%d-ǪȜ88+l.WWA.ޡsܟhbvD?$]-`ml)IQYrٺӸ6>)SZVdl=imbT(H)|DJ&q.U8P}ISW W]zӢao]{עeG][ib#bQSt)k"#2u.q] G߻JIh.gȝm sQ .1߫2 IIa +#oRƢn(qZc?zGJ|f}hb%3$n$Q.{.蓴bm)cr0"x ɦRA*Bx0S ?ןԅ9 c¢?Ls!d)Oy+j.}e47nDeÙ`RJ7@nǖ:b۶lFyЏ#bT  "Rgup$ii#aF'W;] ((X>9Z ex`o<) m$NG_͈HAJdwa$i]UU'%uفf 9#rqCj4sn@>lVɸȘɎr[UƸpoVmP%nUKPyI@V/Hh+8)b IDATk w@P #Dd :_پT/<1LlFu{47c6' CFjHвE qfK~ H`>OfSjD"hL 'ȿ IKCvPm :u<|j-"`ZA2ew" wQyYtVe$$Ilхv[e5 uLS[jU(N1ӧ vPHdyMw,}=;(e|.qcAB#ViHjL.iQU*2bX yOE|uD )?3?/GUk}!cڝݠ8ACF&%l:W#۱;׺ocm8)[A\Zh+8&U9h!K@ ڃ6 R$ IՏQڄA!yO?O??-YTܚ/+ub'*u \\y1az9Eօ?`a}-DQikMcʡ+W:J,JyAw%vP;A\ sݲ8$8;Q0ngdI@.ZIdZ3e4>F鐫JPK0i\S-&IDϭkD#B𲽼2?x*ė3 (* si2l(?&Y}:-B;T$;S!s}ý s#/߽)~;񧀀({ #D~V&;v0.0tkQ@=GCRBqh 3+zq!$Wk'$ Ըfi0;Q?BiIQ:H8ki'؊ᡐ=Ʊg]$&x7'ǥC#m;.dPhP|{\ǿpC*?Ԙ*Vz_V~j8r2$L%cq 3EPbkTS̈4'M d8{t5j%FLcSHa-X2ŊVx-Uuhc-zjvJ8*,#$,V20'?~%HYs%:I> &mhT-^27FB;(G[-y/‰Ak2@HTj*IZG&̑nF$`G_)Hi)% 0:.`>j/"JܠS5I&n;"[?ېEP4F шhfxihX]n>"[ V':C%Xۅn+X6,muS K3 N*X캘k`;IM-IWʎaD b똜2rKM.F,_'F4}H` }mitjx+3 HU2'"҂X۔`imQ:2X-o8?{=eIRlר Uݽq܅~:>@/.؛5AXe٧iwhTHbdgӒ eId-zO+Id6"| $ ћ4[8&\#wRC~SwDe߰Kg/2b`^yuL0`8/|C[ݏ58: >S7Y2Eܺ4_!}P8"/|:Y[>c3ԡNX9فB۰,r'6_iB$)R\`?)c~ZV7׭}@@H!u371]䙥Z_ ^]P#td7IN[gH`##B.ʠŎ}iX,i:" ț]9=*5.pV逰%THۍE~9!!N[0J&Vϰ+ǫѬzqZxYF!`#~ ϡ =CZBY: X5l@:(6 z`U;][ARiL/=$f򲣺UA[ '5a D\$ԽwS1p #U&XgWD5EwDÜ{;.03^2d~:MC@+IrwD$0MT ۶m z%"؝w2ȸ3n]mX^?Lr|W^vЛ灲q7f:6yƒHwL1D"?(I)(t#Wf ! =uxYM-(  +uh8Q5c.h5 ǞuvN2WSLIi5nRV͑SDA-}O? V$HJo@ץ/Hv-ݷȃ"%;xY{҉a@;XVESL@o3ܢYF2I@M5M"|-}E/]U.Nj"v3m Hv0'm dfNF^V]"x^^oiLh^,ӵ]|  .NOkG!ؾv98|| DW *U_.˲+KZ{au9\ >c9L^>"Y>cp\ C$qH'/ɠ, ') -\6=k"*Ɩ^&sL:v_3R)@2eji !LOeLoڴ R= oF&,B3 E, `&\Up'&?x\ąJe2TĶqN|[ SXi]Ъk{Wd݊^߮W0wR-/k.R֛CiJ,tcw#^s F}#T&VsVLՌLBv ]9'2tzy;(44PLVH*a~0![ow941e  Pd$$<+;N&n+ds48R!C&bG_`2+H= @}`[ W30#ibD7c0]`/fWi,A;r2NOaܚCó1:l!67nYUH?}{tWK9cN7H^ٛ.2%7]һi\NX$8ڀ[4LJ% }钺((HKҫ]8&痶딲 KqBh* ~&@l\E@Έ/ح= ¯"AŇ-Dj$HJ!}h<){@ҐTX?SN WX؛^ݱs<.ص$`w)΄q832)LE 3;эX`ߠN|'>۽v{4.$pይT_)8t9m[ m2M$@{5|§3͉CT.t2F?02dؤC{aRq%/1361?6ŜMj)c6w^0$A`㍥z HYJoel"i^,*Îl~ هYLNTUo52 1XL‚%AblΚZ :OJmD۱Og1*zn@3{Lw\bzݥf@o.$a oCiV3OU ";h<;F |1yT_EHesڷ`׎F%骢,xY)XJU ͽBV~{"'K2K* ,HE @P%6{l1"_qSuָ N/(#0PQNw}l+}.Z˹CG\B1c#^eGY ~l^L mivZx.p(U=nȅκ!WYcusy]v#:3xM&6ҜvezNu>Z/s.ed-at k+X첳u5'@ u- F@D@M35Ri<<Jb6pN熴ߝ j̘$uQ@..d(۩ƤfX !?m,zPQT}~l)4}R/[4! fe+VU k@T"g()-G"V04GVh4[ B2bX%"mZC;3VN#mⷍ{8围)ʪ$m^ >\ն2ƞ!mX}gEw,@;#ڟGj!mX !RԭͬO<{U$%@Eo+@pJ2e(YLקv>oV6DrrD\2Xǐ8`ظ,skV\K)W0o15 ^0 }rU)#Mݪ>c:X>VV߮;LO&Br2 x7((H -C9&\yJo_ﶉstv [HMJ@zY9A͟K,i/^TYp/+'YB HH/B^N< OO'\ ).ߗ|X{tniVA >@KEvaVܘbԈ̲\/]!)X^ZB%1vH*$(uFf-K_ m IVMav|YplNr(,*ZJmzeI;RB{Ԣ*gJM/q)d(Hr i&ث4'D֛1KX4O/`׭ю@wLNj*^~#GT;=1IP7~iZknC6+t t3ﵟ탄;cwmmi\+Y?L.Y:)/xOPrf D RVM 9l^[/T,t1$gt3o;))j>)iq^(\nr&sD` ~V`v+- 9XqWcKu 1QwGBˉ@V$i9e#p7 A;C\jeVXu|H| >NMEhu=%eVx\>:MHI֧0d'u}O&bRm*L@jzi$q%O, 2tUG\RLA{al6n/L2V'IЅkQJv4#dD (m;_&8C1iC`kxUׂv]y >c!kzu{="G%SG06)OW_Mr#h=DfD)ffY]jfj2~zY-UZkTJY%#]3͗|qdUM "-U*v)=WSƶwC.37ޫtlLpQӫ:a]Ԕsh>դ`øρLo߀NΜӋyh:BnO ؊O;Jz ͫ!ZMDwk>%2?A .QajZfcqi) ۵jKƎ+Ҫl\=R$ !N HVD&8 wؿ\BCp4M|VD}$ z=kun%W($A͛C??7. 5H6s.yf|>\"٪R~T;IsV=v UZ{g)E2OHNJ%:l JEث?Fc4+mTSPJP9j${MB=ooZ+)Ydq="uY2/o{ + 2V %+kAR* p:L0!%lݑKٕIՒ>Jbƀۭٵ9Õx'= u0LB:U(՛9];LN'ɖ?2I!Y 2oY w9O1Jw}#pwb^͇he+F#|oc[ʨ6uف0@$6m*` G`5>7BxafQ{uVcǐ&;#xٜk*NIn]\=aJه_~82A IDAT!'CÎI1%ּjJSvU[5w?'wFdܥ(%t!S\[uz`W\e뜁z˲AlZI)`9c֩ou"qD?e` >t%K 2i2˜I2n[܆Z}`hK| &)tf 槀/]6n+CvaZL+:L { $H1K+c}̖1R#)j[p5ڊǽ/X*th |VC A2i0͠q=(][*v M<80sT< Hw>t 5t`h;> zb,3xڥ?sY|:4W`}_JN~F.fFEC-3[f" 'K-o(f< 2+wډ,DZlzAN IrE ʢsdyoo^]x򂮢p#Z?3E[|=>;ғb֣2glʄu>vኤ;ZcwrG"<huxHb찏2>4bRٲB s݁AoGgz [L([1J5:,~ю׸cڽ;[Oh::Jt`;B 2_Tg`$`gHŝfжgSY .E9(N[Z-}{ -QK{L|7HY~LpqU&EuMz1ܜ`hvP=M9 u(#v,]iӣa꾀eX-_ؿ߃ 4ޖ/Q؇ {_c?&g0:d| z%A*F8\Z.,IfdJY}w !P9ȩ:Wv"bG´+u:7wA4d&I2뇠ӘZ3V\,I>fgWgHJv?,nɮ$vfz,Om~]42=y4@A)WKgK\> rё:N`ϐ|Rk%Uqqt—Hph-2l ;j|,x:Y۲/-M\TүEG(xSӷ]G+e 2 ;=.jb@@"cUd7qN(-8|"~ wer2KT.>$@]w!tm?0*R>h͘qv [ͻ5ّQZa^u.CcR *.0C]qtDI@jN.1SPѩY#N @AR,R > V R[ C |iA.obBsDJStѷ υ)U fufxɒOR5_\gZ~ffHM޾P(1Z>H4>q_l'{' +Mg u,I9ת#Vz hMDg0%x3ùt_<|;gaC ;MǞ)ҜhoiV &]e,<ӞU]22Z`&g,-"-PnS$>vNH+* kq5ڶyu@^N1~4T" '&&H&Uwh y~JBm\_B"ܰ n (WO$&8V_ⓨǔ\${~%H$Z_@U GcT:p_X'Ech}H('xau|Xv!pf)@:1@zl<@ Fǀ,qB2!|m142 `+kWNO:c&tuNbd?|u$>.s0wZwzD>7k VamQ ""c @" t): D`'Fb{ 7> |!kXV!ٓ; yAv%>:W\ @$s@FZ`d=9#?2kjɥwL2hl~4njjT A;%!q+ah3eW̗ D|md5?ݍBE&?bfD IHo< $g۫ K! F{O3$+`>`T OKjVa8r=*cӄ" (-]SrK<fm)ҔoҖO7) w?}yzyg^U1FI~{p:oOgQB56V_<,>7:ݵ^@q̸Z[? >RY}A}NG -?f#hy2SQR ͉P@A&W0ǪAK #NBF@@^r*᤟崞m bsߥBsI*Ħ 8 IM+#oa x NDW1? ^Y4`!Y?k wbɑ5zJUd5Kڀ('N:.X(2u'hN%/)ۮb~AY[!% vVxoM8ݒ | }B! ψ1J 30 0XDᇴ!lO)g#tO@ӿ_i_&X'ˈӊ]qƒlOq}T]uqe\Br$(%ܕH+l I,$M|Ʀm7h%2 D@Ĉ(DH@Ƞx ؠH! I'((?ק3}h @ּ {^ّRu`u-2q!dwT0 1Uxvr1~-S]*ˀi`~ e[SZ6k7ơ\6)MmHӖCYzĶc2@Jư@HB2oL/(0FmMǪ "q)WI$m!n3 ggmHHgTy P6Dl.18gA[OgIf}o825$謫rdG+4eW4$;!>:\WE/l:ɚrec [Vd 3=mkܢFB "oN40brӛ0n+WׅdrBJG,/i\] &m8o{Y*r֑k@Ÿt]w SLoKr __cj#*4^,}Nf_ʶB+;;ޘ20 D&cH#l!RM5 wcatj$|M#D60JYJhk7!Hn!ǽek-S(˒S:+Bַ'yMZeF 7kʻa74OGo;]F{3ZQ.~ҫ8{}֞! *C9# ]ch G+ &n-ERAoYz^ rr()0 dq 51!1'2Fg @3" bAsWPLJJMOm#$I-b0*>q$oZ_ iC-h]H\@z!!bBċʊd8Ӷm[#bDQ OhthvvɟXN/w.}Q-'hf3ER !dcVɟ#n舵ʰ_!Hlk=0-|}/ChMWKC"/WCE]|;$nBgEF_PlQ>SwSe$EIWC 0um[͊=R8(UyQ=6E4@,$x}L}UDZ.PȊ9 7+ᘱw3j3:eҶ슰6舤pi;[ /|N {ГQ%0K d)`H)@ c<(EmўY0$TR!Εb^]-(CU6ol$9x\Z^PiS@BιWEG+qN] 2xK}oseVZSm/;S?ҾQݗKVk,724"̰Fj $sT5E'Inj^I"zc4<#&Rnu$RT /)&Ug[2$␏uFخފc? Ja>nV-xV֡+}ulg6b1**Q*KRaHؼ/x Z' I ]IIb&ނGoݔ#{1Ov*R]K ~XXmz0G4\;S\ߑ͛ZCW]Oܐc4HRB[3ᛇ)Q&vl9-P^*S-]F_mF zMRۣ tF"ұJެHtNYl}SZ|hCG|3{}rd#TjQצO>ۿN{&m˼gpZ]t+l0&ѐww_ WA$yk|; W^[ֵ0.кwfZp]jHlY 'wdQLIPq0/>RejpB|^n_7m]9 j\C gSٴ;BF=ܲޗ3@;'"O(&H\BLoDZ*0:Hz0Hrq&7t1_nS]?n5bq,,+Ԅi=_Ҝh;)b QўH欶sZe fohWzUՑ]~] -l;.:–BQ7_lоxSHZq.,m:B1v?HA\ O-V'J}'I>1jGo zuÇJ}Q6$ /$zz^ߴx⭋SLX}2gi2ɾ4q4m.@Ք%g.݇p /l? P[!𔕢L9MԑDT+]0RLN¥j,e].vxN, [Jt-zeGu+%VrzB;*oR/|rt67;5n];m洛j3"=dbR2"o,%:}JSЍ&*q'{yۣ [ñR) Km߃Q(pMɗ@R iG([:Cy2s%>vLDJey n)*L5O}˹a-R'R[P=ɦ@z1OsCYATyR#9=̯ZXa4oߩ9 ڝsu1{mcV K C$@@H$Bهٷ*}oj,?W:knA3W cn&8SMxmC>7Q[|D;g! ;b߱a[qE$(}Y >_ľp=G iu=8l6e[GODj#+;X ,NMXكtØr [VWYղu9Ix $%i{+n nK䟪 S׵ WBUt锑*7GvvNzl¨~+-ڤ{$AeL| lHPp=؉8͟VCңN]Uu"a]z:X\^V{Ѯm"W%["~ Rڲzv_1ƒ~wo+!X* 1 K9V\7wn )!IJU%&]/ZZ Iۋ#ϝt~ʮ3u IUc*Nk!H4-$KL`ӲUBNǗųH;b^ ^B!vGԌ̂t^ip>^1u?NhY8其ܝYdXocdۦ+u&S]́oo#nR*"m1yvmW6eծƘ=l9v{f`mJFP+D%' -5lFA1*IQ:T}s?\tg913#>)&|˽-J ٌ39K~ P:@1@Umn'Ϋ-]SW+T|X5K07& R6N2k]^m,ÒRJW-? SUI|洀Q.<^)I{2 l]E^:3|#tr[LI3֯EF H@QOWW'ѱiB_+] IDAT2Vo76XcU۪DžN2WiG`K6wغ7 (#Ƚ8ZVE7hg sq!ӭ0 $I_zfIm$**9VYx$)Da!*1!{K>4DnUbzÃI_TXa$ԿZs'{83kn*Ǒ!VN2C|~.;J Aр^Ҧ~3-.7Pw{? v:-aO6د-!} rpr3G#{Ub6vQ jz\;x)#H:( kp w6r=\joU +Yty-(\6syVV[,(/5IDڴ=]b +/4ǧ XǺ% 6$$+diw>ICY.#"1mc.PzuKSb $Ӻ9ДőR/-ѡk]êR'/j=55BbvWѐrFؔa9J:y[vŤЎ}n*8n x5z}o*[x{GlXՅ^b?jz*J%yciʹ7M& $! d֣${ژޭF٪L,BI-̵9Y ")+-`+)@"{s Bؗ/O"{uq+wHKCY`a$=ER{ZAEG-1n̾FS`N|FU[+J\4> /z=;UĻ/:+b, ~fLuxB 64j2 fLsw*/ hޒ-#(5.J@nǵJI̳K(P$,|C@`nkԞqڝ%Я՟AJ}٪!!$Pj&h]tDM2.qk(~Q^o`".;‚qcK MȔ*Kdnzh?R0 t2{]ς]뻐dA#/ӮW RH 0 |>+8GݼF:1;>Gx "Fyszg:9S`RjMo<+A@驡!,]F\AA)-&hIw83 ɢNE|~=qkdd%O 4J\n1w߀P3~mg hfWe lnQ۲v`7JYUxd$4YJg$NX_@UhRtQHObш]f(,c1$-a֗HVKxE`lK7Iz,-\GW _̽="g 6E tA1F,5f  dV@\=lS +2CWXŻg2UV"K N` D*?)kqHEfiNqzCΧLV U)PU|4On-Luֶm~WΘ'41M/l|[bz)>B^$T7j]&:o?ISS@_$";CDKjLl];YkIOjpU.NNOHCDt/XʍBHF"9n4x  1n$H Rd$l8?ь9$ПP >]}L[ $C)]qZv>yp<BKgɇـx7vUzc.2ƨ1Ql<\}vPvZe }3<-̏hq ]`n[楅~ltSb%6C 3`H:mb+̸l<;Xu?;~!ؚ]wG>I7+C<&.4X^aUq4St[L!sՓoԘG$iה5+% ?A Isu2&IAR Nk !`/cc-sX>BjUYYL&x jr{JS4ZIl  $$k?L&xʷTEk4g<5%v 1?uA]l \:sl4o=URhE=P I{ XOJ\b DOiJj!PZ{\v.aS*Cڢ $x!iB()R4v* 0F{xT8 BQo駴䑈DrKM3ZiIGDY}kxEO(}>*.{ffϾ\ޜs`Ûg֬왵מ={ocZ!Zd*Vv7Qڕ &dJ9&~cs.(?XlhLKbVSMٵ<ǰI3sek6UAT !J).fZ8KP]}5#:c')gf_fkEtm@d)\a /;wSmnh$tK.-ܪ01bY@?UtA@*;dv2I9n=C7'օh_#Z<^Yշ9rdHYh2|3*Rڬhlclsɺ:f1z3,؊"c ɜ q &Fl_c%e,NoY^f^0U0'Q"aYY0@ N|JMot9N܎ifvzuõz'G2c?ڛ6ExZ, 7NY9q35e%7!e)Q&u2H8bM6GHR1@Ϯ a EFL>ij~oejΫKHe7D2 s~,mJ&RdE£9#R ~3$ɏhMكb''1Ʀb7-hz݁ 0(U%3o `j tL ({Lp@̰;؜ 逘C^@@ݼe˯l،\"Kf%Lg {bV[!="$MKIer<"[ /q8*g=G=nfN8QG4ĺpސ#wC"ZCf,b;S)-dxG 3IF*Ga#PLQۏm4J͛`0n*qBI73A^p#+p0R:iXmeC6 L4J:{% ː>(4MgmL{2ŶC28R؝p\Cf/2%%1GZ8j['l:0 ( m ()!҃3rd\nQeAK_S~Ad;Y:N[G,kihWpʌ.lgpa 1ϗ{$,o& -=.2Q.CDhkJ^9 wʥj0 iFkX @5[ H6{D 0g]+茆u,d!js$fN^8 e*2(R]̀jdv*v2mb;9!tg,^ Թ2;dmɆKPA'Y=ĵaG%:-oX[Q-jԆފ@7%Sv!U-J`fv[ٸ,f%8YZ 5J3A4g-gun@]TeZLv "l.[#u.@f% uDhMF.rMPxNn;0_uΗKų9q6X>6tN+*Ǽ4*+aL`r66XŽm:XiGz;U6=ؤ ˒4qe*K^Ra*kfħ<-"m9GcI[E+뽦tkDƷlbV{ڝiL9yݴ&qrcl r`[QɂUs-4rpFkRAWc8I=WFOVLjD$JDҰH8>]zzDw7ܪ""ʍ*sâTʶˆQfh+ NJBK3&#Ũ@RIXnzЅQ2BX,3oD U@ >-HՔBL8N6Ӱh۲aCd[:XT4 7wa[Sfºic;.6 )r iADDvA+Jq4 鋦 oM7]eˁ%6 P( "} ȇ|3 NDRlDWWHJJD+oEm+ɥVRH7P4[ nN;9 a+eT+f֎- trARl!yfӺ99͐RM z7w[`4 S- aKHD230Έp2^i ic"]pw;u/։e !06D~ED) wo!FA^nc.eW6|eVU$]%Fz#}Av mOZRd F:GzX;tDDAmIK0'`i ^0QӢ*""$fd-. ()6#705(SXS\%Q͉aˆrZ, A"Ȇ$U{{!@z~+#nnZ]lڴ)ɂa,ē֝~d~ԥWfW誻mmiy_:b/}>u7_2%#\2pGNllж5;ׇŭzկ-wV!0 1p.)Ѓ f O1;Xm0 Z&"*["I"VXȰ2vHQhHjJ:==YS2Ŷ766CjIN 1Z-_R40<즋ɧY-OE‚-I[YPZkGMQ~;VՐby;T{)G=aXUU%Pq}"@#>Eoü#A Űb(13bNEmZ" P/DC=ᶎD?L+aA! ؒ0 A)f )՚_KKDA9Oon 6!2bCQK2))x!""b iIN*c-#a)ܓf-HPðBM+,$D ."f0,JRBKJy/X#^{c3 :蠃 \` ~>hĉGzjv…;v쐐ѣGoٲƎ9ReM6Y?4iҥKE=cRjx!@lOpfȜIֻ*ڮ*!P;D{O A~aMmXS;kv=j.DB!DY;Y0C(ǫ 4 sĊM\GF ifiIB)l^d4"A j[#:H\=v[ =[H [Bf66ڡ PT[SZO `DS.dt P;ђF,l[l˸ᒣܪ@AJ3O n+.EH8]j*p6" UoP]с0'wFn֭[oN:cرng}V6.,Hĉ^(:,8* 4iҝw9f̘3g.YO7n 0;餓;puםy>$o ]Ϩ+B67n9Z OIz`/~!{և7>Ҏ=kXI.cD[3f $&D ZQŵXRXcD0MgԪN HV~"jhh+kԔ(ZUtQ eKs(ԕM$9H +@43`kH(Di[iZI"r cÕW4#m~@4/SRm "[F %2;]D 5\3yp1ѣo=z}ݮ~X8>_wuG9s&_'x!CΝ `ѢE"FDSn peW^V!VŰa{CY}'6n滓~[P^7hG2dd](E8p&DL$߇:*OT&VVMbqH[zAJ+qH3 ddCDi}6 <{B Xa"JSeRS+ sUn.`wI(v-@$ 0p.F%5@X5NB8||ܷ`6e0 Xq!bTXUHIUHP˄!8$B1TTo]$eQ۳V66Rll*>wf?l~ߞ={UW]5}dއ th"ukP]\^S]Rd}3ݧ:W+szfZT0x*={͆$rhdpswY$v`ȶ=!dL HqS!K`fgKj(2o%]^aD038Vl$Z F;- \("ql=ga|8dmVJ8 X[S&: G1z$2 S]41`YL3;WOfq8nlS@-nt4""@˛9RĒK"1pAkR&K_vBah6AQM?*^Ւqryd^W@ͱ l]7*w #2+IM+ Ǻ$’6lZƺJ Y#d\N~<2GHܤT"iJ){겒`0l2,#5v88`}{Uذ~l_ Q>̉4./[F㒙(!c k3i(nIvc݌dۍ-ELlFkQfФGV2`:MhT3 x<N%hWn8x<AG2dz1L~h'&q^x<=aPL[lӞ+"J0{2\nmvIX &XlmKzYT*B g4C7zo㤓N4i^zOJ|i?lO }W\9s̵kN47o+6}{g0gΜ;v7X::f̘A] 0gΜj;eʔKUjmoN }/\Fi?9 6^ضWڭ_~P}P1yLD*dWPRl^8 B@R@[QSSsWΞ=ܹs7o>_~eoN8իWǥ4W_}e[^iӦD1hР˗Z ƍw.]9c/>}[[ny͛wX@MM̓>8ze˖pԧL`Сַws{P,ǎ;l0"zgnF?4aΜ9uuu\ro\s592=cpQG?o߾;w|KNC 8kΚ5wSA,iӦ~F7n\޽g̘q=|߬׿5}t9'x 8KGuTzw?O<ȏ>h&L8묳\>}?۷ouu_W\q!BD/~nݺG?:㪪V\yUWmܸ٫DQh㢔:˿1#͟o-}f3GRG6l|dt+ѵk ڵkӦM-J <8_K+w JBp̜9s֬Yw;u(QFڳgk >\w{1bw_P( ~ dȐ_+_ʯ &vi_|9S[[{g _]vnݺp =g1a„aÆg>3a„ӧ1bƌW_}u~܄?tr&fz}G_x_W .н{_Wg1b~I,ӦM[|gqWwyh`ƍw}{}ѧv/X3!k֬y'R . pܹ۷o߾}ڧO9rݙ+'|~+?#F0lذG}b9C)u饗zW_};5dX_____sI bŊzJGZ,|G/6l_mk)notAV\yꩧuQ;w|wZc8p tgmTl"¬s?|+0ͻk~7555߫?>c~NpRcǎ=g͚xoyݺu>#EX'!w>' C;R,tSN袋 vwյΥYW^ݯ_#8bɒ%yc)*ɽgϞk֬qn[.]uV;gb}7<@f>͛෿g9eʔ[z뭋/~'K{ gIDAT? &̙3o[Rgo& f޹k ]33PGLA _wu+VxG/$%_M;w'Nx/Y|I+&k\zq͝;W\D-{!:EeУG1cƈ Ϛ aÆ? 6X#{~?~CZ^زe jl2s?Ǐ2eKִ[/-Yĵ† <@ۻwop뉯_~~/ ٰ҅a>1ۭ[7ٸ+m!ҥK}WNi]vQBuu7x뭷/o[^)5o޼xOGU__K/KGu_jժg"ڿ?ON:u*TUuԩSu h!*m߾n p>iӒR[dG-!jhh8??sX[͛7' 6mnj3f̘ѣG7.)rL^~;_"W)<v؈# G?'|i=#jjj裏JO}SAڵkٲe쳏7#Fd.x{}Apwy"6aÆyDt '| _x$͛Lrwq1u9,\>}q-[o}+.]s9 }ݷK.o3N;n}aKAP(Xr-[={-򂉈lg{ "*0lݺ],*t\%8蠃jkkW\cǎO~GqO=zɒ%6gy_5k֭[WSSӣG+W&JX=nݺ)S;vK.{97n8qq.0 -[6y+V]XhC A3o߾ |AzC۷o>}2[6m+/+bӦMFSgq?GP_}ޟ1';Q_So1>b1rz@y;_!æK{](֬Y3|m+bÆ mF&L?sΜ9uSN2 A_.foKO3pATc823XYޑ4#FO~R]]]WW7o޼pqM`_ * `GitHub `_ * `Discord `_ * `Forum `_ * `Community wiki `_ * IRC - #LookingGlass on `libera.chat `_ Donate: * `GitHub `__ * `Ko-Fi `_ * `Patreon `_ * `PayPal `_ * BTC - 14ZFcYjsKPiVreHqcaekvHGL846u3ZuT13 * ETH - 0x6f8aEe454384122bF9ed28f025FBCe2Bce98db85 looking-glass-0+b4+dfsg.1/doc/install.rst000066400000000000000000000752721411177132200203240ustar00rootroot00000000000000.. _installing: Installation ############ .. _client_install: Client ------ The Looking Glass Client recieves frames from the :ref:`Host ` to display on your screen. It also handles input, and can optionally share the system clipboard with your guest OS through Spice. First you must build the client from source code, see :ref:`building`. .. _client_libvirt_configuration: libvirt Configuration ~~~~~~~~~~~~~~~~~~~~~ This article assumes you already have a fully functional libvirt domain with PCI passthrough working on a dedicated monitor. If you use virt-manager, this guide also applies to you, since virt-manager uses libvirt as its back-end. **If you are using QEMU directly, this does not apply to you.** Add the following to your libvirt machine configuration inside the 'devices' section by running ``virsh edit `` where ```` is the name of your virtual machine. .. code:: xml 32 The memory size (show as 32 in the example above) may need to be adjusted as per the :ref:`Determining Memory ` section. .. _client_spice_server: Spice Server ^^^^^^^^^^^^ If you would like to use Spice to give you keyboard and mouse input along with clipboard sync support, make sure you have a ```` device, then: - Find your ``