pax_global_header00006660000000000000000000000064135153056230014515gustar00rootroot0000000000000052 comment=64194cd43a91520747d112256b075b0560b3116b looking-glass-0+b1/000077500000000000000000000000001351530562300142655ustar00rootroot00000000000000looking-glass-0+b1/.gitattributes000066400000000000000000000001371351530562300171610ustar00rootroot00000000000000* text=auto eol=lf *.sln text eol=crlf *.vcxproj text eol=crlf *.vcxproj.filters text eol=crlf looking-glass-0+b1/.github/000077500000000000000000000000001351530562300156255ustar00rootroot00000000000000looking-glass-0+b1/.github/issue_template.md000066400000000000000000000005531351530562300211750ustar00rootroot00000000000000### Required information Host CPU: Host GPU: Guest GPU: Host Kernel version: Host QEMU version: Please describe what were you doing when the problem occured. If the Windows host application crashed please check for file named `looking-glass-host.dmp` and attach it to this bug report. **Reports that do no include this information will be ignored and closed** looking-glass-0+b1/.gitignore000066400000000000000000000001511351530562300162520ustar00rootroot00000000000000module/*.ko module/*.o module/*.mod.c module/.* module/Module.symvers module/modules.order *.a *.o *.exe looking-glass-0+b1/.gitmodules000066400000000000000000000000001351530562300164300ustar00rootroot00000000000000looking-glass-0+b1/CONTRIBUTORS000066400000000000000000000023731351530562300161520ustar00rootroot00000000000000Level1Techs (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+b1/README.md000066400000000000000000000027551351530562300155550ustar00rootroot00000000000000# Looking Glass An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough. * Project Website: https://looking-glass.hostfission.com ## Donations I (Geoffrey McRae) am the primary developer behind this project and I have invested thousands of hours of development time into it. If you like this project and find it useful and would like to help out you can support me directly using the following platforms. * [Ko-Fi](https://ko-fi.com/lookingglass) * [Patreon](https://www.patreon.com/gnif) * [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ESQ72XUPGKXRY) * BTC - 14ZFcYjsKPiVreHqcaekvHGL846u3ZuT13 ## Documentation Please also be sure to see the following files for more information * [client/README.md](client/README.md) * [c-host/README.md](c-host/README.md) * [module/README.md](module/README.md) ## Obtaining and using Looking Glass Please see https://looking-glass.hostfission.com/quickstart ## Latest Version If you would like to use the latest bleeding edge version of Looking Glass please be aware there will be no support at this time. Latest bleeding edge builds of the Windows host application can be obtained from: https://looking-glass.hostfission.com/downloads # Help and support ## Web https://forum.level1techs.com/t/looking-glass-triage/130952 ## Discord https://discord.gg/4ahCn4c ## IRC Join us in the #LookingGlass channel on the FreeNode network ## Trello * https://trello.com/b/tI1Xbwsg/looking-glass looking-glass-0+b1/VERSION000066400000000000000000000000261351530562300153330ustar00rootroot00000000000000B1-rc6-5-g8ad2d5f949+1looking-glass-0+b1/_config.yml000066400000000000000000000000321351530562300164070ustar00rootroot00000000000000theme: jekyll-theme-caymanlooking-glass-0+b1/c-host/000077500000000000000000000000001351530562300154625ustar00rootroot00000000000000looking-glass-0+b1/c-host/.gitignore000066400000000000000000000000151351530562300174460ustar00rootroot00000000000000build/ *.swp looking-glass-0+b1/c-host/CMakeLists.txt000066400000000000000000000033541351530562300202270ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(looking-glass-host C) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") 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() endif() option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON) add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.") add_compile_options( "-Wall" "-Werror" "-Wfatal-errors" "-ffast-math" "-fdata-sections" "-ffunction-sections" "$<$:-O0;-g3;-ggdb>" ) set(CMAKE_C_STANDARD 11) execute_process( COMMAND cat ../VERSION WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} OUTPUT_VARIABLE BUILD_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"') get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE) include_directories( ${PROJECT_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include ${PROJECT_TOP}/vendor/ivshmem ${PKGCONFIG_INCLUDE_DIRS} ${GMP_INCLUDE_DIR} ) #link_libraries( #) set(SOURCES src/app.c ) add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") add_subdirectory(platform) if(WIN32) add_executable(looking-glass-host WIN32 ${SOURCES}) else() add_executable(looking-glass-host ${SOURCES}) endif() target_link_libraries(looking-glass-host lg_common platform ) set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections") install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-host DESTINATION bin/ COMPONENT binary) feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES) looking-glass-0+b1/c-host/README.md000066400000000000000000000065401351530562300167460ustar00rootroot00000000000000# General Questions ## What is this? This is a rewrite of the host application in pure C using the MinGW toolchain. ## Why make this? Several reasons: 1. The client is written in C and I would like to unify the project's language 2. The host is currently hard to build using MinGW and is very Windows specific 3. The host is a jumbled mess of code from all the experimentation going on 4. I would eventually like to be able to port this to run on Linux guests ## When will it be ready? Soon :) ## Will it replace the C++ host? Yes, but only when it is feature complete. ## Why doesn't this use CMake? It does now... ~~Because win-builds doesn't distribute it, so to make it easy for everyone to compile we do not require it.~~ ## How do I build it? #### For Windows on Windows ``` mkdir build cd build cmake -G "MSYS Makefiles" .. make ``` #### For Linux on Linux ``` mkdir build cd build cmake .. make ``` #### For Windows cross compiling on Linux ``` mkdir build cd build cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake .. make ``` ## Where is the log? It is in your user's temp directory: %TEMP%\looking-glass-host.txt For example: C:\Users\YourUser\AppData\Local\Temp\looking-glass-host.txt You can also open it by simply right clicking the tray icon and selecting "Open Log File" ## Why does this version 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. (WIP) 3. DXGI performance can be improved if we have this. (WIP) ## NvFBC (NVIDIA Frame Buffer Capture) ### Why isn't there a build with NvFBC support available. ~~Because NVIDIA have decided to put restrictions on the NvFBC API that simply make it incompatible with the GPL/2 licence. Providing a pre-built binary with NvFBC support would violate the EULA I have agreed to in order to access the NVidia Capture SDK.~~ Either I miss-read the License Agreement or it has been updated, it is now viable to produce a "derived work" from the capture SDK. > 1.1 License Grant. Subject to the terms of this Agreement, NVIDIA hereby grants you a nonexclusive, non-transferable, worldwide, revocable, limited, royalty-free, fully paid-up license during the term of this Agreement to: > (i) install, use and reproduce the Licensed Software delivered by NVIDIA plus make modifications and create derivative works of the source code and header files delivered by NVIDIA, provided that the software is executed only in hardware products as specified by NVIDIA in the accompanying documentation (such as release notes) as supported, to develop, test and service your products (each, a “Customer Product”) that are interoperable with supported hardware products. If the NVIDIA documentation is silent, the supported hardware consists of certain NVIDIA GPUs; and To be safe we are still not including the NVIDIA headers in the repository, but I am now providing pre-built binaries with NvFBC support included. See: https://looking-glass.hostfission.com/downloads ### 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. _-Geoff_ looking-glass-0+b1/c-host/cmake/000077500000000000000000000000001351530562300165425ustar00rootroot00000000000000looking-glass-0+b1/c-host/cmake/NVFBC.cmake000066400000000000000000000000011351530562300203710ustar00rootroot00000000000000 looking-glass-0+b1/c-host/cmake/PostCapture.cmake000066400000000000000000000007761351530562300220270ustar00rootroot00000000000000list(REMOVE_AT CAPTURE 0) list(REMOVE_AT CAPTURE_LINK 0) list(LENGTH CAPTURE CAPTURE_COUNT) file(APPEND ${CAPTURE_H} "#define LG_CAPTURE_COUNT ${CAPTURE_COUNT}\n") foreach(renderer ${CAPTURE}) file(APPEND ${CAPTURE_C} "extern CaptureInterface Capture_${renderer};\n") endforeach() file(APPEND ${CAPTURE_C} "\nconst CaptureInterface * CaptureInterfaces[] =\n{\n") foreach(renderer ${CAPTURE}) file(APPEND ${CAPTURE_C} " &Capture_${renderer},\n") endforeach() file(APPEND ${CAPTURE_C} " NULL\n};") looking-glass-0+b1/c-host/cmake/PreCapture.cmake000066400000000000000000000011371351530562300216200ustar00rootroot00000000000000set(CAPTURE_H "${CMAKE_BINARY_DIR}/include/dynamic/capture.h") set(CAPTURE_C "${CMAKE_BINARY_DIR}/src/capture.c") file(WRITE ${CAPTURE_H} "#include \"interface/capture.h\"\n\n") file(APPEND ${CAPTURE_H} "extern CaptureInterface * CaptureInterfaces[];\n\n") file(WRITE ${CAPTURE_C} "#include \"interface/capture.h\"\n\n") file(APPEND ${CAPTURE_C} "#include \n\n") set(CAPTURE "_") set(CAPTURE_LINK "_") function(add_capture name) set(CAPTURE "${CAPTURE};${name}" PARENT_SCOPE) set(CAPTURE_LINK "${CAPTURE_LINK};capture_${name}" PARENT_SCOPE) add_subdirectory(${name}) endfunction() looking-glass-0+b1/c-host/include/000077500000000000000000000000001351530562300171055ustar00rootroot00000000000000looking-glass-0+b1/c-host/include/interface/000077500000000000000000000000001351530562300210455ustar00rootroot00000000000000looking-glass-0+b1/c-host/include/interface/capture.h000066400000000000000000000043111351530562300226600ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include typedef enum CaptureResult { CAPTURE_RESULT_OK , CAPTURE_RESULT_REINIT , CAPTURE_RESULT_TIMEOUT, CAPTURE_RESULT_ERROR } CaptureResult; typedef enum CaptureFormat { // frame formats CAPTURE_FMT_BGRA , CAPTURE_FMT_RGBA , CAPTURE_FMT_RGBA10, CAPTURE_FMT_YUV420, // pointer formats CAPTURE_FMT_COLOR , CAPTURE_FMT_MONO , CAPTURE_FMT_MASKED, CAPTURE_FMT_MAX } CaptureFormat; typedef struct CaptureFrame { unsigned int width; unsigned int height; unsigned int pitch; unsigned int stride; CaptureFormat format; void * data; } CaptureFrame; typedef struct CapturePointer { int x, y; bool visible; bool shapeUpdate; CaptureFormat format; unsigned int width, height; unsigned int pitch; } CapturePointer; typedef struct CaptureInterface { const char * (*getName )(); void (*initOptions )(); bool (*create )(); bool (*init )(void * pointerShape, const unsigned int pointerSize); void (*stop )(); bool (*deinit )(); void (*free )(); unsigned int (*getMaxFrameSize)(); CaptureResult (*capture )(); CaptureResult (*getFrame )(CaptureFrame * frame ); CaptureResult (*getPointer)(CapturePointer * pointer); } CaptureInterface;looking-glass-0+b1/c-host/include/interface/platform.h000066400000000000000000000036411351530562300230460ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include int app_main(int argc, char * argv[]); bool app_init(); void app_quit(); // these must be implemented for each OS const char * os_getExecutable(); unsigned int os_shmemSize(); bool os_shmemMmap(void **ptr); void os_shmemUnmap(); // os specific thread functions typedef struct osThreadHandle osThreadHandle; typedef int (*osThreadFunction)(void * opaque); bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle); bool os_joinThread (osThreadHandle * handle, int * resultCode); // os specific event functions #define TIMEOUT_INFINITE ((unsigned int)~0) typedef struct osEventHandle osEventHandle; osEventHandle * os_createEvent(bool autoReset); void os_freeEvent (osEventHandle * handle); bool os_waitEvent (osEventHandle * handle, unsigned int timeout); bool os_waitEvents (osEventHandle * handles[], int count, bool waitAll, unsigned int timeout); bool os_signalEvent(osEventHandle * handle); bool os_resetEvent (osEventHandle * handle);looking-glass-0+b1/c-host/platform/000077500000000000000000000000001351530562300173065ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/CMakeLists.txt000066400000000000000000000004241351530562300220460ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(platform LANGUAGES C) if (UNIX) set(PLATFORM "Linux") elseif(WIN32) set(PLATFORM "Windows") endif() add_subdirectory(${PLATFORM}) add_library(platform INTERFACE) target_link_libraries(platform INTERFACE platform_${PLATFORM}) looking-glass-0+b1/c-host/platform/Linux/000077500000000000000000000000001351530562300204055ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Linux/CMakeLists.txt000066400000000000000000000005071351530562300231470ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(platform_Linux LANGUAGES C) include_directories( ${PROJECT_SOURCE_DIR}/include ) add_library(platform_Linux STATIC src/platform.c ) add_subdirectory("capture") target_link_libraries(platform_Linux capture pthread ) target_include_directories(platform_Linux PRIVATE src ) looking-glass-0+b1/c-host/platform/Linux/capture/000077500000000000000000000000001351530562300220505ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Linux/capture/CMakeLists.txt000066400000000000000000000003351351530562300246110ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(capture LANGUAGES C) include("PreCapture") add_capture("XCB") include("PostCapture") add_library(capture STATIC ${CAPTURE_C}) target_link_libraries(capture ${CAPTURE_LINK}) looking-glass-0+b1/c-host/platform/Linux/capture/XCB/000077500000000000000000000000001351530562300224645ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Linux/capture/XCB/CMakeLists.txt000066400000000000000000000003611351530562300252240ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(capture_XCB LANGUAGES C) add_library(capture_XCB STATIC src/xcb.c ) target_link_libraries(capture_XCB lg_common xcb xcb-shm Xfixes ) target_include_directories(capture_XCB PRIVATE src ) looking-glass-0+b1/c-host/platform/Linux/capture/XCB/src/000077500000000000000000000000001351530562300232535ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Linux/capture/XCB/src/xcb.c000066400000000000000000000125441351530562300242010ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/capture.h" #include "interface/platform.h" #include "common/debug.h" #include #include #include #include #include #include #include #include struct xcb { bool initialized; xcb_connection_t * xcb; xcb_screen_t * xcbScreen; uint32_t seg; int shmID; void * data; osEventHandle * frameEvent; unsigned int width; unsigned int height; bool hasFrame; xcb_shm_get_image_cookie_t imgC; xcb_xfixes_get_cursor_image_cookie_t curC; }; struct xcb * this = NULL; // forwards static bool xcb_deinit(); static unsigned int xcb_getMaxFrameSize(); // implementation static const char * xcb_getName() { return "XCB"; } static bool xcb_create() { assert(!this); this = (struct xcb *)calloc(sizeof(struct xcb), 1); this->shmID = -1; this->data = (void *)-1; this->frameEvent = os_createEvent(true); if (!this->frameEvent) { DEBUG_ERROR("Failed to create the frame event"); free(this); return false; } return true; } static bool xcb_init() { assert(this); assert(!this->initialized); os_resetEvent(this->frameEvent); this->xcb = xcb_connect(NULL, NULL); if (!this->xcb || xcb_connection_has_error(this->xcb)) { DEBUG_ERROR("Unable to open the X display"); goto fail; } if (!xcb_get_extension_data(this->xcb, &xcb_shm_id)->present) { DEBUG_ERROR("Missing the SHM extension"); goto fail; } xcb_screen_iterator_t iter; iter = xcb_setup_roots_iterator(xcb_get_setup(this->xcb)); this->xcbScreen = iter.data; this->width = iter.data->width_in_pixels; this->height = iter.data->height_in_pixels; DEBUG_INFO("Frame Size : %u x %u", this->width, this->height); this->seg = xcb_generate_id(this->xcb); this->shmID = shmget(IPC_PRIVATE, xcb_getMaxFrameSize(), IPC_CREAT | 0777); if (this->shmID == -1) { DEBUG_ERROR("shmget failed"); goto fail; } xcb_shm_attach(this->xcb, this->seg ,this->shmID, false); this->data = shmat(this->shmID, NULL, 0); if ((uintptr_t)this->data == -1) { DEBUG_ERROR("shmat failed"); goto fail; } DEBUG_INFO("Frame Data : 0x%" PRIXPTR, (uintptr_t)this->data); this->initialized = true; return true; fail: xcb_deinit(); return false; } static bool xcb_deinit() { assert(this); if ((uintptr_t)this->data != -1) { shmdt(this->data); this->data = (void *)-1; } if (this->shmID != -1) { shmctl(this->shmID, IPC_RMID, NULL); this->shmID = -1; } if (this->xcb) { xcb_disconnect(this->xcb); this->xcb = NULL; } this->initialized = false; return false; } static void xcb_free() { os_freeEvent(this->frameEvent); free(this); this = NULL; } static unsigned int xcb_getMaxFrameSize() { return this->width * this->height * 4; } static CaptureResult xcb_capture() { assert(this); assert(this->initialized); if (!this->hasFrame) { this->imgC = xcb_shm_get_image_unchecked( this->xcb, this->xcbScreen->root, 0, 0, this->width, this->height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, this->seg, 0); this->hasFrame = true; os_signalEvent(this->frameEvent); } return CAPTURE_RESULT_OK; } static CaptureResult xcb_getFrame(CaptureFrame * frame) { assert(this); assert(this->initialized); assert(frame); assert(frame->data); os_waitEvent(this->frameEvent, TIMEOUT_INFINITE); xcb_shm_get_image_reply_t * img; img = xcb_shm_get_image_reply(this->xcb, this->imgC, NULL); if (!img) { DEBUG_ERROR("Failed to get image reply"); return CAPTURE_RESULT_ERROR; } frame->width = this->width; frame->height = this->height; frame->pitch = this->width * 4; frame->stride = this->width; frame->format = CAPTURE_FMT_BGRA; memcpy(frame->data, this->data, this->width * this->height * 4); free(img); this->hasFrame = false; return CAPTURE_RESULT_OK; } static CaptureResult xcb_getPointer(CapturePointer * pointer) { memset(pointer, 0, sizeof(CapturePointer)); return CAPTURE_RESULT_OK; } struct CaptureInterface Capture_XCB = { .getName = xcb_getName, .create = xcb_create, .init = xcb_init, .deinit = xcb_deinit, .free = xcb_free, .getMaxFrameSize = xcb_getMaxFrameSize, .capture = xcb_capture, .getFrame = xcb_getFrame, .getPointer = xcb_getPointer };looking-glass-0+b1/c-host/platform/Linux/src/000077500000000000000000000000001351530562300211745ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Linux/src/platform.c000066400000000000000000000217521351530562300231730ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/platform.h" #include "common/debug.h" #include "common/option.h" #include #include #include #include #include #include #include #include #include #include #include #include struct app { const char * executable; unsigned int shmSize; int shmFD; void * shmMap; }; static struct app app; struct osThreadHandle { const char * name; osThreadFunction function; void * opaque; pthread_t handle; int resultCode; }; void sigHandler(int signo) { DEBUG_INFO("SIGINT"); app_quit(); } static int uioOpenFile(const char * shmDevice, const char * file) { int len = snprintf(NULL, 0, "/sys/class/uio/%s/%s", shmDevice, file); char * path = malloc(len + 1); sprintf(path, "/sys/class/uio/%s/%s", shmDevice, file); int fd = open(path, O_RDONLY); if (fd < 0) { free(path); return -1; } free(path); return fd; } static char * uioGetName(const char * shmDevice) { int fd = uioOpenFile(shmDevice, "name"); if (fd < 0) return NULL; char * name = malloc(32); int len = read(fd, name, 31); if (len <= 0) { free(name); close(fd); return NULL; } name[len] = '\0'; close(fd); while(len > 0 && name[len-1] == '\n') { --len; name[len] = '\0'; } return name; } static int shmOpenDev(const char * shmDevice) { int len = snprintf(NULL, 0, "/dev/%s", shmDevice); char * path = malloc(len + 1); sprintf(path, "/dev/%s", shmDevice); int fd = open(path, O_RDWR, (mode_t)0600); if (fd < 0) { DEBUG_ERROR("Failed to open: %s", path); DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?"); free(path); return -1; } free(path); return fd; } static bool shmDeviceValidator(struct Option * opt, const char ** error) { char * name = uioGetName(opt->value.x_string); if (!name) { *error = "Failed to get the uio device name"; return false; } if (strcmp(name, "KVMFR") != 0) { free(name); *error = "Device is not a KVMFR device"; return false; } free(name); return true; } static StringList shmDeviceGetValues(struct Option * option) { StringList sl = stringlist_new(true); DIR * d = opendir("/sys/class/uio"); if (!d) return sl; struct dirent * dir; while((dir = readdir(d)) != NULL) { if (dir->d_name[0] == '.') continue; char * name = uioGetName(dir->d_name); if (!name) continue; if (strcmp(name, "KVMFR") == 0) stringlist_push(sl, strdup(dir->d_name)); free(name); } closedir(d); return sl; } int main(int argc, char * argv[]) { app.executable = argv[0]; struct Option options[] = { { .module = "os", .name = "shmDevice", .description = "The IVSHMEM device to use", .type = OPTION_TYPE_STRING, .value.x_string = "uio0", .validator = shmDeviceValidator, .getValues = shmDeviceGetValues }, {0} }; option_register(options); int result = app_main(argc, argv); os_shmemUnmap(); close(app.shmFD); return result; } bool app_init() { const char * shmDevice = option_get_string("os", "shmDevice"); // get the device size int fd = uioOpenFile(shmDevice, "maps/map0/size"); if (fd < 0) { DEBUG_ERROR("Failed to open %s/size", shmDevice); DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?"); return false; } char size[32]; int len = read(fd, size, sizeof(size) - 1); if (len <= 0) { DEBUG_ERROR("Failed to read the device size"); close(fd); return false; } size[len] = '\0'; close(fd); app.shmSize = strtoul(size, NULL, 16); // open the device app.shmFD = shmOpenDev(shmDevice); app.shmMap = MAP_FAILED; if (app.shmFD < 0) return false; DEBUG_INFO("KVMFR Device : %s", shmDevice); signal(SIGINT, sigHandler); return true; } const char * os_getExecutable() { return app.executable; } unsigned int os_shmemSize() { return app.shmSize; } bool os_shmemMmap(void **ptr) { if (app.shmMap == MAP_FAILED) { app.shmMap = mmap(0, app.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, app.shmFD, 0); if (app.shmMap == MAP_FAILED) { const char * shmDevice = option_get_string("os", "shmDevice"); DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice); return false; } } *ptr = app.shmMap; return true; } void os_shmemUnmap() { if (app.shmMap == MAP_FAILED) return; munmap(app.shmMap, app.shmSize); app.shmMap = MAP_FAILED; } static void * threadWrapper(void * opaque) { osThreadHandle * handle = (osThreadHandle *)opaque; handle->resultCode = handle->function(handle->opaque); return NULL; } bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle) { *handle = (osThreadHandle*)malloc(sizeof(osThreadHandle)); (*handle)->name = name; (*handle)->function = function; (*handle)->opaque = opaque; if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0) { DEBUG_ERROR("pthread_create failed for thread: %s", name); free(*handle); *handle = NULL; return false; } return true; } bool os_joinThread(osThreadHandle * handle, int * resultCode) { if (pthread_join(handle->handle, NULL) != 0) { DEBUG_ERROR("pthread_join failed for thread: %s", handle->name); free(handle); return false; } if (resultCode) *resultCode = handle->resultCode; free(handle); return true; } struct osEventHandle { pthread_mutex_t mutex; pthread_cond_t cond; bool flag; bool autoReset; }; osEventHandle * os_createEvent(bool autoReset) { osEventHandle * handle = (osEventHandle *)calloc(sizeof(osEventHandle), 1); if (!handle) { DEBUG_ERROR("Failed to allocate memory"); return NULL; } if (pthread_mutex_init(&handle->mutex, NULL) != 0) { DEBUG_ERROR("Failed to create the mutex"); free(handle); return NULL; } if (pthread_cond_init(&handle->cond, NULL) != 0) { pthread_mutex_destroy(&handle->mutex); free(handle); return NULL; } handle->autoReset = autoReset; return handle; } void os_freeEvent(osEventHandle * handle) { assert(handle); pthread_cond_destroy (&handle->cond ); pthread_mutex_destroy(&handle->mutex); free(handle); } bool os_waitEvent(osEventHandle * handle, unsigned int timeout) { assert(handle); if (pthread_mutex_lock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to lock the mutex"); return false; } while(!handle->flag) { if (timeout == TIMEOUT_INFINITE) { if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0) { DEBUG_ERROR("Wait to wait on the condition"); return false; } } else { struct timespec ts; ts.tv_sec = timeout / 1000; ts.tv_nsec = (timeout % 1000) * 1000000; switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, &ts)) { case ETIMEDOUT: return false; default: DEBUG_ERROR("Timed wait failed"); return false; } } } if (handle->autoReset) handle->flag = false; if (pthread_mutex_unlock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to unlock the mutex"); return false; } return true; } bool os_signalEvent(osEventHandle * handle) { assert(handle); if (pthread_mutex_lock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to lock the mutex"); return false; } handle->flag = true; if (pthread_mutex_unlock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to unlock the mutex"); return false; } if (pthread_cond_signal(&handle->cond) != 0) { DEBUG_ERROR("Failed to signal the condition"); return false; } return true; } bool os_resetEvent(osEventHandle * handle) { assert(handle); if (pthread_mutex_lock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to lock the mutex"); return false; } handle->flag = false; if (pthread_mutex_unlock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to unlock the mutex"); return false; } return true; }looking-glass-0+b1/c-host/platform/Windows/000077500000000000000000000000001351530562300207405ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/CMakeLists.txt000066400000000000000000000013151351530562300235000ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(platform_Windows LANGUAGES C) include_directories( ${PROJECT_SOURCE_DIR}/include ) add_library(platform_Windows STATIC src/platform.c src/windebug.c src/mousehook.c ) add_subdirectory("capture") FIND_PROGRAM(WINDRES_EXECUTABLE NAMES "x86_64-w64-mingw32-windres" "windres.exe" DOC "windres executable") ADD_CUSTOM_COMMAND(TARGET platform_Windows POST_BUILD WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" COMMAND ${WINDRES_EXECUTABLE} -i resource.rc -o "${PROJECT_BINARY_DIR}/resource.o" VERBATIM ) target_link_libraries(platform_Windows "${PROJECT_BINARY_DIR}/resource.o" lg_common capture setupapi ) target_include_directories(platform_Windows PRIVATE src ) looking-glass-0+b1/c-host/platform/Windows/app.manifest000066400000000000000000000010521351530562300232460ustar00rootroot00000000000000 Hello World looking-glass-0+b1/c-host/platform/Windows/capture/000077500000000000000000000000001351530562300224035ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/capture/CMakeLists.txt000066400000000000000000000013031351530562300251400ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(capture LANGUAGES C) include(PreCapture) option(USE_NVFBC "Enable NVFBC Support" OFF) option(USE_DXGI "Enable DXGI Support" ON) if(NOT DEFINED NVFBC_SDK) set(NVFBC_SDK "C:/Program Files (x86)/NVIDIA Corporation/NVIDIA Capture SDK") endif() file(TO_CMAKE_PATH "${NVFBC_SDK}" nvfbc_sdk) if(NOT EXISTS "${nvfbc_sdk}/inc" OR NOT IS_DIRECTORY "${nvfbc_sdk}/inc") message("Disabling NVFBC support, can't find the SDK headers") set(USE_NVFBC OFF) endif() if(USE_NVFBC) add_capture("NVFBC") endif() if(USE_DXGI) add_capture("DXGI") endif() include("PostCapture") add_library(capture STATIC ${CAPTURE_C}) target_link_libraries(capture ${CAPTURE_LINK}) looking-glass-0+b1/c-host/platform/Windows/capture/DXGI/000077500000000000000000000000001351530562300231365ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/capture/DXGI/CMakeLists.txt000066400000000000000000000011751351530562300257020ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(capture_DXGI LANGUAGES C) add_library(capture_DXGI STATIC src/dxgi.c ) add_definitions("-DCOBJMACROS -DINITGUID") FIND_PROGRAM(DLLTOOL_EXECUTABLE NAMES "x86_64-w64-mingw32-dlltool" "dlltool" "dlltool.exe" DOC "dlltool executable") ADD_CUSTOM_COMMAND(TARGET capture_DXGI POST_BUILD WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/dll" COMMAND ${DLLTOOL_EXECUTABLE} --def libd3d11.def --output-lib "${PROJECT_BINARY_DIR}/libd3d11.dll" VERBATIM ) target_link_libraries(capture_DXGI lg_common ${PROJECT_BINARY_DIR}/libd3d11.dll dxgi ) target_include_directories(capture_DXGI PRIVATE src ) looking-glass-0+b1/c-host/platform/Windows/capture/DXGI/dll/000077500000000000000000000000001351530562300237115ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/capture/DXGI/dll/libd3d11.def000066400000000000000000000020201351530562300256660ustar00rootroot00000000000000LIBRARY "d3d11.dll" EXPORTS D3DKMTCloseAdapter D3DKMTDestroyAllocation D3DKMTDestroyContext D3DKMTDestroyDevice D3DKMTDestroySynchronizationObject D3DKMTQueryAdapterInfo D3DKMTSetDisplayPrivateDriverFormat D3DKMTSignalSynchronizationObject D3DKMTUnlock D3DKMTWaitForSynchronizationObject OpenAdapter10 OpenAdapter10_2 D3D11CoreCreateDevice D3D11CoreCreateLayeredDevice D3D11CoreGetLayeredDeviceSize D3D11CoreRegisterLayers D3D11CreateDevice D3D11CreateDeviceAndSwapChain D3DKMTCreateAllocation D3DKMTCreateContext D3DKMTCreateDevice D3DKMTCreateSynchronizationObject D3DKMTEscape D3DKMTGetContextSchedulingPriority D3DKMTGetDeviceState D3DKMTGetDisplayModeList D3DKMTGetMultisampleMethodList D3DKMTGetRuntimeData D3DKMTGetSharedPrimaryHandle D3DKMTLock D3DKMTOpenAdapterFromHdc D3DKMTOpenResource D3DKMTPresent D3DKMTQueryAllocationResidency D3DKMTQueryResourceInfo D3DKMTRender D3DKMTSetAllocationPriority D3DKMTSetContextSchedulingPriority D3DKMTSetDisplayMode D3DKMTSetGammaRamp D3DKMTSetVidPnSourceOwner D3DKMTWaitForVerticalBlankEvent looking-glass-0+b1/c-host/platform/Windows/capture/DXGI/src/000077500000000000000000000000001351530562300237255ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/capture/DXGI/src/dxgi.c000066400000000000000000000553641351530562300250410ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/capture.h" #include "interface/platform.h" #include "common/debug.h" #include "common/option.h" #include "windows/debug.h" #include #include #include #include #include "dxgi_extra.h" enum TextureState { TEXTURE_STATE_UNUSED, TEXTURE_STATE_PENDING_MAP, TEXTURE_STATE_MAPPED }; typedef struct Texture { enum TextureState state; ID3D11Texture2D * tex; D3D11_MAPPED_SUBRESOURCE map; osEventHandle * mapped; osEventHandle * free; } Texture; typedef struct Pointer { unsigned int version; unsigned int x, y; unsigned int w, h; bool visible; unsigned int pitch; CaptureFormat format; } Pointer; // locals struct iface { bool initialized; bool stop; IDXGIFactory1 * factory; IDXGIAdapter1 * adapter; IDXGIOutput * output; ID3D11Device * device; ID3D11DeviceContext * deviceContext; D3D_FEATURE_LEVEL featureLevel; IDXGIOutputDuplication * dup; int maxTextures; Texture * texture; int texRIndex; int texWIndex; bool needsRelease; osEventHandle * pointerEvent; unsigned int width; unsigned int height; unsigned int pitch; unsigned int stride; CaptureFormat format; // pointer state Pointer lastPointer; Pointer pointer; // pointer shape void * pointerShape; unsigned int pointerSize; unsigned int pointerUsed; }; static bool dpiDone = false; static struct iface * this = NULL; // forwards static bool dxgi_deinit(); static CaptureResult dxgi_releaseFrame(); // implementation static const char * dxgi_getName() { return "DXGI"; } static void dxgi_initOptions() { struct Option options[] = { { .module = "dxgi", .name = "adapter", .description = "The name of the adapter to capture", .type = OPTION_TYPE_STRING, .value.x_string = NULL }, { .module = "dxgi", .name = "output", .description = "The name of the adapter's output to capture", .type = OPTION_TYPE_STRING, .value.x_string = NULL }, { .module = "dxgi", .name = "maxTextures", .description = "The maximum number of frames to buffer before skipping", .type = OPTION_TYPE_INT, .value.x_int = 3 }, {0} }; option_register(options); } static bool dxgi_create() { assert(!this); this = calloc(sizeof(struct iface), 1); if (!this) { DEBUG_ERROR("failed to allocate iface struct"); return false; } this->pointerEvent = os_createEvent(true); if (!this->pointerEvent) { DEBUG_ERROR("failed to create the pointer event"); free(this); return false; } this->maxTextures = option_get_int("dxgi", "maxTextures"); if (this->maxTextures <= 0) this->maxTextures = 1; this->texture = calloc(sizeof(struct Texture), this->maxTextures); return true; } static bool dxgi_init(void * pointerShape, const unsigned int pointerSize) { assert(this); // this is required for DXGI 1.5 support to function if (!dpiDone) { DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value); HMODULE user32 = LoadLibraryA("user32.dll"); User32_SetProcessDpiAwarenessContext fn; fn = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext"); if (fn) fn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); FreeLibrary(user32); dpiDone = true; } HRESULT status; DXGI_OUTPUT_DESC outputDesc; this->pointerShape = pointerShape; this->pointerSize = pointerSize; this->pointerUsed = 0; this->stop = false; this->texRIndex = 0; this->texWIndex = 0; status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory); if (FAILED(status)) { DEBUG_WINERROR("Failed to create DXGIFactory1", status); goto fail; } const char * optAdapter = option_get_string("dxgi", "adapter"); const char * optOutput = option_get_string("dxgi", "output" ); for(int i = 0; IDXGIFactory1_EnumAdapters1(this->factory, i, &this->adapter) != DXGI_ERROR_NOT_FOUND; ++i) { if (optAdapter) { DXGI_ADAPTER_DESC1 adapterDesc; IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc); const size_t s = (wcslen(adapterDesc.Description)+1) * 2; char * desc = malloc(s); wcstombs(desc, adapterDesc.Description, s); if (strstr(desc, optAdapter) == NULL) { DEBUG_INFO("Not using adapter: %ls", adapterDesc.Description); free(desc); IDXGIAdapter1_Release(this->adapter); this->adapter = NULL; continue; } free(desc); DEBUG_INFO("Adapter matched, trying: %ls", adapterDesc.Description); } for(int n = 0; IDXGIAdapter1_EnumOutputs(this->adapter, n, &this->output) != DXGI_ERROR_NOT_FOUND; ++n) { IDXGIOutput_GetDesc(this->output, &outputDesc); if (optOutput) { const size_t s = (wcslen(outputDesc.DeviceName)+1) * 2; char * desc = malloc(s); wcstombs(desc, outputDesc.DeviceName, s); if (strstr(desc, optOutput) == NULL) { DEBUG_INFO("Not using adapter output: %ls", outputDesc.DeviceName); free(desc); IDXGIOutput_Release(this->output); this->output = NULL; continue; } free(desc); DEBUG_INFO("Adapter output matched, trying: %ls", outputDesc.DeviceName); } if (outputDesc.AttachedToDesktop) break; IDXGIOutput_Release(this->output); this->output = NULL; } if (this->output) break; IDXGIAdapter1_Release(this->adapter); this->adapter = NULL; } if (!this->output) { DEBUG_ERROR("Failed to locate a valid output device"); goto fail; } static const D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1 }; IDXGIAdapter * tmp; status = IDXGIAdapter1_QueryInterface(this->adapter, &IID_IDXGIAdapter, (void **)&tmp); if (FAILED(status)) { DEBUG_ERROR("Failed to query IDXGIAdapter interface"); goto fail; } status = D3D11CreateDevice( tmp, D3D_DRIVER_TYPE_UNKNOWN, NULL, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), D3D11_SDK_VERSION, &this->device, &this->featureLevel, &this->deviceContext); IDXGIAdapter_Release(tmp); if (FAILED(status)) { DEBUG_WINERROR("Failed to create D3D11 device", status); goto fail; } DXGI_ADAPTER_DESC1 adapterDesc; IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc); this->width = outputDesc.DesktopCoordinates.right - outputDesc.DesktopCoordinates.left; this->height = outputDesc.DesktopCoordinates.bottom - outputDesc.DesktopCoordinates.top; DEBUG_INFO("Device Descripion: %ls" , adapterDesc.Description); DEBUG_INFO("Device Vendor ID : 0x%x" , adapterDesc.VendorId); DEBUG_INFO("Device Device ID : 0x%x" , adapterDesc.DeviceId); DEBUG_INFO("Device Video Mem : %u MiB" , (unsigned)(adapterDesc.DedicatedVideoMemory / 1048576)); DEBUG_INFO("Device Sys Mem : %u MiB" , (unsigned)(adapterDesc.DedicatedSystemMemory / 1048576)); DEBUG_INFO("Shared Sys Mem : %u MiB" , (unsigned)(adapterDesc.SharedSystemMemory / 1048576)); DEBUG_INFO("Feature Level : 0x%x" , this->featureLevel); DEBUG_INFO("Capture Size : %u x %u", this->width, this->height); // bump up our priority { IDXGIDevice * dxgi; status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi); if (FAILED(status)) { DEBUG_WINERROR("failed to query DXGI interface from device", status); goto fail; } IDXGIDevice_SetGPUThreadPriority(dxgi, 7); IDXGIDevice_Release(dxgi); } IDXGIOutput5 * output5 = NULL; status = IDXGIOutput_QueryInterface(this->output, &IID_IDXGIOutput5, (void **)&output5); if (FAILED(status)) { DEBUG_WARN("IDXGIOutput5 is not available, please update windows for improved performance!"); DEBUG_WARN("Falling back to IDXIGOutput1"); IDXGIOutput1 * output1 = NULL; status = IDXGIOutput_QueryInterface(this->output, &IID_IDXGIOutput1, (void **)&output1); if (FAILED(status)) { DEBUG_ERROR("Failed to query IDXGIOutput1 from the output"); goto fail; } // we try this twice in case we still get an error on re-initialization for (int i = 0; i < 2; ++i) { status = IDXGIOutput1_DuplicateOutput(output1, (IUnknown *)this->device, &this->dup); if (SUCCEEDED(status)) break; Sleep(200); } if (FAILED(status)) { DEBUG_WINERROR("DuplicateOutput Failed", status); IDXGIOutput1_Release(output1); goto fail; } IDXGIOutput1_Release(output1); } else { const DXGI_FORMAT supportedFormats[] = { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM }; // we try this twice in case we still get an error on re-initialization for (int i = 0; i < 2; ++i) { status = IDXGIOutput5_DuplicateOutput1( output5, (IUnknown *)this->device, 0, sizeof(supportedFormats) / sizeof(DXGI_FORMAT), supportedFormats, &this->dup); if (SUCCEEDED(status)) break; // if access is denied we just keep trying until it isn't if (status == E_ACCESSDENIED) --i; Sleep(200); } if (FAILED(status)) { DEBUG_WINERROR("DuplicateOutput1 Failed", status); IDXGIOutput5_Release(output5); goto fail; } IDXGIOutput5_Release(output5); } DXGI_OUTDUPL_DESC dupDesc; IDXGIOutputDuplication_GetDesc(this->dup, &dupDesc); DEBUG_INFO("Source Format : %s", GetDXGIFormatStr(dupDesc.ModeDesc.Format)); switch(dupDesc.ModeDesc.Format) { case DXGI_FORMAT_B8G8R8A8_UNORM : this->format = CAPTURE_FMT_BGRA ; break; case DXGI_FORMAT_R8G8B8A8_UNORM : this->format = CAPTURE_FMT_RGBA ; break; case DXGI_FORMAT_R10G10B10A2_UNORM: this->format = CAPTURE_FMT_RGBA10; break; default: DEBUG_ERROR("Unsupported source format"); goto fail; } D3D11_TEXTURE2D_DESC texDesc; memset(&texDesc, 0, sizeof(texDesc)); texDesc.Width = this->width; texDesc.Height = this->height; texDesc.MipLevels = 1; texDesc.ArraySize = 1; texDesc.SampleDesc.Count = 1; texDesc.SampleDesc.Quality = 0; texDesc.Usage = D3D11_USAGE_STAGING; texDesc.Format = dupDesc.ModeDesc.Format; texDesc.BindFlags = 0; texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; texDesc.MiscFlags = 0; for(int i = 0; i < this->maxTextures; ++i) { status = ID3D11Device_CreateTexture2D(this->device, &texDesc, NULL, &this->texture[i].tex); if (FAILED(status)) { DEBUG_WINERROR("Failed to create texture", status); goto fail; } this->texture[i].free = os_createEvent(true); if (!this->texture[i].free) { DEBUG_ERROR("Failed to create the texture free event"); goto fail; } // pre-signal the free events to flag as unused os_signalEvent(this->texture[i].free); this->texture[i].mapped = os_createEvent(false); if (!this->texture[i].mapped) { DEBUG_ERROR("Failed to create the texture mapped event"); goto fail; } } // map the texture simply to get the pitch and stride D3D11_MAPPED_SUBRESOURCE mapping; status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource *)this->texture[0].tex, 0, D3D11_MAP_READ, 0, &mapping); if (FAILED(status)) { DEBUG_WINERROR("Failed to map the texture", status); goto fail; } this->pitch = mapping.RowPitch; this->stride = mapping.RowPitch / 4; ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource *)this->texture[0].tex, 0); this->initialized = true; return true; fail: dxgi_deinit(); return false; } static void dxgi_stop() { this->stop = true; os_signalEvent(this->texture[this->texRIndex].mapped); os_signalEvent(this->pointerEvent); } static bool dxgi_deinit() { assert(this); for(int i = 0; i < this->maxTextures; ++i) { this->texture[i].state = TEXTURE_STATE_UNUSED; if (this->texture[i].map.pData) { ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)this->texture[i].tex, 0); this->texture[i].map.pData = NULL; } if (this->texture[i].tex) { ID3D11Texture2D_Release(this->texture[i].tex); this->texture[i].tex = NULL; } if (this->texture[i].free) { os_signalEvent(this->texture[i].free); os_freeEvent(this->texture[i].free); this->texture[i].free = NULL; } if (this->texture[i].mapped) { os_signalEvent(this->texture[i].mapped); os_freeEvent(this->texture[i].mapped); this->texture[i].mapped = NULL; } } if (this->dup) { dxgi_releaseFrame(); IDXGIOutputDuplication_Release(this->dup); this->dup = NULL; } if (this->deviceContext) { ID3D11DeviceContext_Release(this->deviceContext); this->deviceContext = NULL; } if (this->output) { IDXGIOutput_Release(this->output); this->output = NULL; } if (this->device) { ID3D11Device_Release(this->device); this->device = NULL; } if (this->adapter) { IDXGIAdapter1_Release(this->adapter); this->adapter = NULL; } if (this->factory) { // if this doesn't free we have a memory leak DWORD count = IDXGIFactory1_Release(this->factory); this->factory = NULL; if (count != 0) { DEBUG_ERROR("Factory release is %lu, there is a memory leak!", count); return false; } } this->initialized = false; return true; } static void dxgi_free() { assert(this); if (this->initialized) dxgi_deinit(); os_freeEvent(this->pointerEvent); free(this->texture); free(this); this = NULL; } static unsigned int dxgi_getMaxFrameSize() { assert(this); assert(this->initialized); return this->height * this->pitch; } static CaptureResult dxgi_capture() { assert(this); assert(this->initialized); CaptureResult result; HRESULT status; DXGI_OUTDUPL_FRAME_INFO frameInfo; IDXGIResource * res; // if the read texture is pending a mapping for(int i = 0; i < this->maxTextures; ++i) { if (this->texture[i].state != TEXTURE_STATE_PENDING_MAP) continue; Texture * tex = &this->texture[i]; // try to map the resource, but don't wait for it status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map); if (status != DXGI_ERROR_WAS_STILL_DRAWING) { if (FAILED(status)) { DEBUG_WINERROR("Failed to map the texture", status); IDXGIResource_Release(res); return CAPTURE_RESULT_ERROR; } // successful map, set the state and signal that there is a frame available tex->state = TEXTURE_STATE_MAPPED; os_signalEvent(tex->mapped); } } // release the prior frame result = dxgi_releaseFrame(); if (result != CAPTURE_RESULT_OK) return result; status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1, &frameInfo, &res); switch(status) { case S_OK: this->needsRelease = true; break; case DXGI_ERROR_WAIT_TIMEOUT: return CAPTURE_RESULT_TIMEOUT; case WAIT_ABANDONED: case DXGI_ERROR_ACCESS_LOST: return CAPTURE_RESULT_REINIT; default: DEBUG_WINERROR("AcquireNextFrame failed", status); return CAPTURE_RESULT_ERROR; } if (frameInfo.LastPresentTime.QuadPart != 0) { Texture * tex = &this->texture[this->texWIndex]; // check if the texture is free, if not skip the frame to keep up if (!os_waitEvent(tex->free, 0)) { /* NOTE: This is only informational for when debugging, skipping frames is OK as we are likely getting frames faster then the client can render them (ie, vsync off in a title) */ //DEBUG_WARN("Frame skipped"); } else { ID3D11Texture2D * src; status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src); if (FAILED(status)) { DEBUG_WINERROR("Failed to get the texture from the dxgi resource", status); IDXGIResource_Release(res); return CAPTURE_RESULT_ERROR; } // if the texture was mapped, unmap it if (tex->state == TEXTURE_STATE_MAPPED) { ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)tex->tex, 0); tex->map.pData = NULL; } // issue the copy from GPU to CPU RAM and release the src ID3D11DeviceContext_CopyResource(this->deviceContext, (ID3D11Resource *)tex->tex, (ID3D11Resource *)src); ID3D11Texture2D_Release(src); // pending map tex->state = TEXTURE_STATE_PENDING_MAP; // advance our write pointer if (++this->texWIndex == this->maxTextures) this->texWIndex = 0; } } IDXGIResource_Release(res); // if the pointer has moved or changed state bool signalPointer = false; if (frameInfo.LastMouseUpdateTime.QuadPart) { if ( frameInfo.PointerPosition.Position.x != this->lastPointer.x || frameInfo.PointerPosition.Position.y != this->lastPointer.y || frameInfo.PointerPosition.Visible != this->lastPointer.visible ) { this->pointer.x = frameInfo.PointerPosition.Position.x; this->pointer.y = frameInfo.PointerPosition.Position.y; this->pointer.visible = frameInfo.PointerPosition.Visible; signalPointer = true; } } // if the pointer shape has changed if (frameInfo.PointerShapeBufferSize > 0) { // update the buffer if (frameInfo.PointerShapeBufferSize > this->pointerSize) DEBUG_WARN("The pointer shape is too large to fit in the buffer, ignoring the shape"); else { DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo; status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, this->pointerSize, this->pointerShape, &this->pointerUsed, &shapeInfo); if (FAILED(status)) { DEBUG_WINERROR("Failed to get the new pointer shape", status); return CAPTURE_RESULT_ERROR; } switch(shapeInfo.Type) { case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : this->pointer.format = CAPTURE_FMT_COLOR ; break; case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: this->pointer.format = CAPTURE_FMT_MASKED; break; case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : this->pointer.format = CAPTURE_FMT_MONO ; break; default: DEBUG_ERROR("Unsupported cursor format"); return CAPTURE_RESULT_ERROR; } this->pointer.w = shapeInfo.Width; this->pointer.h = shapeInfo.Height; this->pointer.pitch = shapeInfo.Pitch; ++this->pointer.version; signalPointer = true; } } // signal about the pointer update if (signalPointer) os_signalEvent(this->pointerEvent); return CAPTURE_RESULT_OK; } static CaptureResult dxgi_getFrame(CaptureFrame * frame) { assert(this); assert(this->initialized); Texture * tex = &this->texture[this->texRIndex]; if (!os_waitEvent(tex->mapped, 1000)) return CAPTURE_RESULT_TIMEOUT; if (this->stop) return CAPTURE_RESULT_REINIT; // only reset the event if we used the texture os_resetEvent(tex->mapped); frame->width = this->width; frame->height = this->height; frame->pitch = this->pitch; frame->stride = this->stride; frame->format = this->format; memcpy(frame->data, tex->map.pData, this->pitch * this->height); os_signalEvent(tex->free); if (++this->texRIndex == this->maxTextures) this->texRIndex = 0; return CAPTURE_RESULT_OK; } static CaptureResult dxgi_getPointer(CapturePointer * pointer) { assert(this); assert(this->initialized); if (!os_waitEvent(this->pointerEvent, 1000)) return CAPTURE_RESULT_TIMEOUT; if (this->stop) return CAPTURE_RESULT_REINIT; Pointer p; memcpy(&p, &this->pointer, sizeof(Pointer)); pointer->x = p.x; pointer->y = p.y; pointer->width = p.w; pointer->height = p.h; pointer->pitch = p.pitch; pointer->visible = p.visible; pointer->format = p.format; pointer->shapeUpdate = p.version > this->lastPointer.version; memcpy(&this->lastPointer, &p, sizeof(Pointer)); return CAPTURE_RESULT_OK; } static CaptureResult dxgi_releaseFrame() { assert(this); if (!this->needsRelease) return CAPTURE_RESULT_OK; HRESULT status = IDXGIOutputDuplication_ReleaseFrame(this->dup); switch(status) { case S_OK: break; case DXGI_ERROR_INVALID_CALL: DEBUG_WINERROR("Frame was already released", status); return CAPTURE_RESULT_ERROR; case WAIT_ABANDONED: case DXGI_ERROR_ACCESS_LOST: { this->needsRelease = false; return CAPTURE_RESULT_REINIT; } default: DEBUG_WINERROR("ReleaseFrame failed", status); return CAPTURE_RESULT_ERROR; } this->needsRelease = false; return CAPTURE_RESULT_OK; } struct CaptureInterface Capture_DXGI = { .getName = dxgi_getName, .initOptions = dxgi_initOptions, .create = dxgi_create, .init = dxgi_init, .stop = dxgi_stop, .deinit = dxgi_deinit, .free = dxgi_free, .getMaxFrameSize = dxgi_getMaxFrameSize, .capture = dxgi_capture, .getFrame = dxgi_getFrame, .getPointer = dxgi_getPointer };looking-glass-0+b1/c-host/platform/Windows/capture/DXGI/src/dxgi_extra.h000066400000000000000000000503431351530562300262410ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include // missing declarations in dxgi.h HRESULT __stdcall CreateDXGIFactory1(REFIID riid, void **factory); #define D3D_FEATURE_LEVEL_12_0 0xc000 #define D3D_FEATURE_LEVEL_12_1 0xc100 #ifndef DXGI_ERROR_ACCESS_LOST #define DXGI_ERROR_ACCESS_LOST _HRESULT_TYPEDEF_(0x887A0026L) #endif #ifndef DXGI_ERROR_WAIT_TIMEOUT #define DXGI_ERROR_WAIT_TIMEOUT _HRESULT_TYPEDEF_(0x887A0027L) #endif enum DXGI_OUTDUPL_POINTER_SHAPE_TYPE { DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME = 0x1, DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR = 0x2, DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR = 0x4 }; typedef struct DXGI_OUTDUPL_DESC { DXGI_MODE_DESC ModeDesc; DXGI_MODE_ROTATION Rotation; BOOL DesktopImageInSystemMemory; } DXGI_OUTDUPL_DESC; typedef struct DXGI_OUTDUPL_POINTER_POSITION { POINT Position; BOOL Visible; } DXGI_OUTDUPL_POINTER_POSITION; typedef struct DXGI_OUTDUPL_FRAME_INFO { LARGE_INTEGER LastPresentTime; LARGE_INTEGER LastMouseUpdateTime; UINT AccumulatedFrames; BOOL RectsCoalesced; BOOL ProtectedContentMaskedOut; DXGI_OUTDUPL_POINTER_POSITION PointerPosition; UINT TotalMetadataBufferSize; UINT PointerShapeBufferSize; } DXGI_OUTDUPL_FRAME_INFO; typedef struct DXGI_OUTDUPL_MOVE_RECT { POINT SourcePoint; RECT DestinationRect; } DXGI_OUTDUPL_MOVE_RECT; typedef struct DXGI_OUTDUPL_POINTER_SHAPE_INFO { UINT Type; UINT Width; UINT Height; UINT Pitch; POINT HotSpot; } DXGI_OUTDUPL_POINTER_SHAPE_INFO; DEFINE_GUID(IID_IDXGIOutputDuplication, 0x191cfac3, 0xa341, 0x470d, 0xb2,0x6e,0xa8,0x64,0xf4,0x28,0x31,0x9c); typedef interface IDXGIOutputDuplication IDXGIOutputDuplication; typedef struct IDXGIOutputDuplicationVtbl { BEGIN_INTERFACE /*** IUnknown methods ***/ HRESULT (STDMETHODCALLTYPE *QueryInterface)( IDXGIOutputDuplication* This, REFIID riid, void **ppvObject); ULONG (STDMETHODCALLTYPE *AddRef)( IDXGIOutputDuplication* This); ULONG (STDMETHODCALLTYPE *Release)( IDXGIOutputDuplication* This); /*** IDXGIObject methods ***/ HRESULT (STDMETHODCALLTYPE *SetPrivateData)( IDXGIOutputDuplication* This, REFGUID guid, UINT data_size, const void *data); HRESULT (STDMETHODCALLTYPE *SetPrivateDataInterface)( IDXGIOutputDuplication* This, REFGUID guid, const IUnknown *object); HRESULT (STDMETHODCALLTYPE *GetPrivateData)( IDXGIOutputDuplication* This, REFGUID guid, UINT *data_size, void *data); HRESULT (STDMETHODCALLTYPE *GetParent)( IDXGIOutputDuplication* This, REFIID riid, void **parent); /*** IDXGIOutputDuplication methods ***/ void (STDMETHODCALLTYPE *GetDesc)( IDXGIOutputDuplication* This, DXGI_OUTDUPL_DESC *pDesc); HRESULT (STDMETHODCALLTYPE *AcquireNextFrame)( IDXGIOutputDuplication* This, UINT TimeoutInMilliseconds, DXGI_OUTDUPL_FRAME_INFO *pFrameInfo, IDXGIResource **ppDesktopResource); HRESULT (STDMETHODCALLTYPE *GetFrameDirtyRects)( IDXGIOutputDuplication* This, UINT DirtyRectsBufferSize, RECT *pDirtyRectsBuffer, UINT *pDirtyRectsBufferSizeRequired); HRESULT (STDMETHODCALLTYPE *GetFrameMoveRects)( IDXGIOutputDuplication* This, UINT MoveRectsBufferSize, DXGI_OUTDUPL_MOVE_RECT *pMoveRectBuffer, UINT *pMoveRectsBufferSizeRequired); HRESULT (STDMETHODCALLTYPE *GetFramePointerShape)( IDXGIOutputDuplication* This, UINT PointerShapeBufferSize, void *pPointerShapeBuffer, UINT *pPointerShapeBufferSizeRequired, DXGI_OUTDUPL_POINTER_SHAPE_INFO *pPointerShapeInfo); HRESULT (STDMETHODCALLTYPE *MapDesktopSurface)( IDXGIOutputDuplication* This, DXGI_MAPPED_RECT *pLockedRect); HRESULT (STDMETHODCALLTYPE *UnMapDesktopSurface)( IDXGIOutputDuplication* This); HRESULT (STDMETHODCALLTYPE *ReleaseFrame)( IDXGIOutputDuplication* This); END_INTERFACE } IDXGIOutputDuplicationVtbl; interface IDXGIOutputDuplication { CONST_VTBL IDXGIOutputDuplicationVtbl* lpVtbl; }; #define IDXGIOutputDuplication_Release(This) (This)->lpVtbl->Release(This) #define IDXGIOutputDuplication_GetDesc(This, pDesc) (This)->lpVtbl->GetDesc(This, pDesc) #define IDXGIOutputDuplication_AcquireNextFrame(This, TimeoutInMilliseconds, pFrameInfo, ppDesktopResource) (This)->lpVtbl->AcquireNextFrame(This, TimeoutInMilliseconds, pFrameInfo, ppDesktopResource) #define IDXGIOutputDuplication_GetFrameDirtyRects(This, DirtyRectsBufferSize, pDirectyRectsBuffer, pDirtyRectsBufferSizeRequired) (This)->lpVtbl->GetFrameDirtyRects(This, DirtyRectsBufferSize, pDirectyRectsBuffer, pDirtyRectsBufferSizeRequired) #define IDXGIOutputDuplication_GetFrameMoveRects(This, MoveRectsBufferSize, pDirtyRectsBuffer, pDirtyRectsBufferSizeRequired) (This)->lpVtbl->GetFrameMoveRects(This, MoveRectsBufferSize, pDirtyRectsBuffer, pDirtyRectsBufferSizeRequired) #define IDXGIOutputDuplication_GetFramePointerShape(This, PointerShapeBufferSize, pPointerShapeBuffer, pPointerShapeBufferSizeRequired, pPointerShapeInfo) (This)->lpVtbl->GetFramePointerShape(This, PointerShapeBufferSize, pPointerShapeBuffer, pPointerShapeBufferSizeRequired, pPointerShapeInfo) #define IDXGIOutputDuplication_MapDesktopSurface(This, pLockedRect) (This)->lpVtbl->MapDesktopSurface(This, pLockedRect) #define IDXGIOutputDuplication_UnMapDesktopSurface(This) (This)->lpVtbl->UnMapDesktopSurface(This) #define IDXGIOutputDuplication_ReleaseFrame(This) (This)->lpVtbl->ReleaseFrame(This) typedef struct DXGI_MODE_DESC1 { UINT Width; UINT Height; DXGI_RATIONAL RefreshRate; DXGI_FORMAT Format; DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; DXGI_MODE_SCALING Scaling; BOOL Stereo; } DXGI_MODE_DESC1; #ifndef __dxgicommon_h__ typedef enum DXGI_COLOR_SPACE_TYPE { DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 = 0, DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 = 1, DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709 = 2, DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020 = 3, DXGI_COLOR_SPACE_RESERVED = 4, DXGI_COLOR_SPACE_YCBCR_FULL_G22_NONE_P709_X601 = 5, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601 = 6, DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601 = 7, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709 = 8, DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709 = 9, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020 = 10, DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020 = 11, DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 = 12, DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020 = 13, DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020 = 14, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020 = 15, DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020 = 16, DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020 = 17, DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020 = 18, DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020 = 19, DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P709 = 20, DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P2020 = 21, DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P709 = 22, DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P2020 = 23, DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020 = 24, DXGI_COLOR_SPACE_CUSTOM = 0xFFFFFFFF } DXGI_COLOR_SPACE_TYPE; #endif DEFINE_GUID(IID_IDXGIOutput1, 0x00cddea8, 0x939b, 0x4b83, 0xa3,0x40,0xa6,0x85,0x22,0x66,0x66,0xcc); typedef struct IDXGIOutput1 IDXGIOutput1; typedef struct IDXGIOutput1Vtbl { BEGIN_INTERFACE /*** IUnknown methods ***/ HRESULT (STDMETHODCALLTYPE *QueryInterface)( IDXGIOutput1* This, REFIID riid, void **ppvObject); ULONG (STDMETHODCALLTYPE *AddRef)( IDXGIOutput1* This); ULONG (STDMETHODCALLTYPE *Release)( IDXGIOutput1* This); /*** IDXGIObject methods ***/ HRESULT (STDMETHODCALLTYPE *SetPrivateData)( IDXGIOutput1* This, REFGUID guid, UINT data_size, const void *data); HRESULT (STDMETHODCALLTYPE *SetPrivateDataInterface)( IDXGIOutput1* This, REFGUID guid, const IUnknown *object); HRESULT (STDMETHODCALLTYPE *GetPrivateData)( IDXGIOutput1* This, REFGUID guid, UINT *data_size, void *data); HRESULT (STDMETHODCALLTYPE *GetParent)( IDXGIOutput1* This, REFIID riid, void **parent); /*** IDXGIOutput methods ***/ HRESULT (STDMETHODCALLTYPE *GetDesc)( IDXGIOutput1* This, DXGI_OUTPUT_DESC *desc); HRESULT (STDMETHODCALLTYPE *GetDisplayModeList)( IDXGIOutput1* This, DXGI_FORMAT format, UINT flags, UINT *mode_count, DXGI_MODE_DESC *desc); HRESULT (STDMETHODCALLTYPE *FindClosestMatchingMode)( IDXGIOutput1* This, const DXGI_MODE_DESC *mode, DXGI_MODE_DESC *closest_match, IUnknown *device); HRESULT (STDMETHODCALLTYPE *WaitForVBlank)( IDXGIOutput1* This); HRESULT (STDMETHODCALLTYPE *TakeOwnership)( IDXGIOutput1* This, IUnknown *device, WINBOOL exclusive); void (STDMETHODCALLTYPE *ReleaseOwnership)( IDXGIOutput1* This); HRESULT (STDMETHODCALLTYPE *GetGammaControlCapabilities)( IDXGIOutput1* This, DXGI_GAMMA_CONTROL_CAPABILITIES *gamma_caps); HRESULT (STDMETHODCALLTYPE *SetGammaControl)( IDXGIOutput1* This, const DXGI_GAMMA_CONTROL *gamma_control); HRESULT (STDMETHODCALLTYPE *GetGammaControl)( IDXGIOutput1* This, DXGI_GAMMA_CONTROL *gamma_control); HRESULT (STDMETHODCALLTYPE *SetDisplaySurface)( IDXGIOutput1* This, IDXGISurface *surface); HRESULT (STDMETHODCALLTYPE *GetDisplaySurfaceData)( IDXGIOutput1* This, IDXGISurface *surface); HRESULT (STDMETHODCALLTYPE *GetFrameStatistics)( IDXGIOutput1* This, DXGI_FRAME_STATISTICS *stats); /*** IDXGIOutput1 methods ***/ HRESULT (STDMETHODCALLTYPE *GetDisplayModeList1)( IDXGIOutput1* This, DXGI_FORMAT EnumFormat, UINT Flags, UINT *pNumModes, DXGI_MODE_DESC1 *pDesc); HRESULT (STDMETHODCALLTYPE *FindClosestMatchingMode1)( IDXGIOutput1* This, const DXGI_MODE_DESC1 *pModeToMatch, DXGI_MODE_DESC1 *pClosestMatch, IUnknown *pConcernedDevice); HRESULT (STDMETHODCALLTYPE *GetDisplaySurfaceData1)( IDXGIOutput1* This, IDXGIResource *pDestination); HRESULT (STDMETHODCALLTYPE *DuplicateOutput)( IDXGIOutput1* This, IUnknown *pDevice, IDXGIOutputDuplication **ppOutputDuplication); END_INTERFACE } IDXGIOutput1Vtbl; interface IDXGIOutput1 { CONST_VTBL IDXGIOutput1Vtbl* lpVtbl; }; #define IDXGIOutput1_DuplicateOutput(This,pDevice,ppOutputDuplication) (This)->lpVtbl->DuplicateOutput(This,pDevice,ppOutputDuplication) #define IDXGIOutput1_Release(This) (This)->lpVtbl->Release(This); DEFINE_GUID(IID_IDXGIOutput5, 0x80a07424, 0xab52, 0x42eb, 0x83,0x3c,0x0c,0x42,0xfd,0x28,0x2d,0x98); typedef struct IDXGIOutput5 IDXGIOutput5; typedef struct IDXGIOutput5Vtbl { BEGIN_INTERFACE /*** IUnknown methods ***/ HRESULT (STDMETHODCALLTYPE *QueryInterface)( IDXGIOutput5* This, REFIID riid, void **ppvObject); ULONG (STDMETHODCALLTYPE *AddRef)( IDXGIOutput5* This); ULONG (STDMETHODCALLTYPE *Release)( IDXGIOutput5* This); /*** IDXGIObject methods ***/ HRESULT (STDMETHODCALLTYPE *SetPrivateData)( IDXGIOutput5* This, REFGUID guid, UINT data_size, const void *data); HRESULT (STDMETHODCALLTYPE *SetPrivateDataInterface)( IDXGIOutput5* This, REFGUID guid, const IUnknown *object); HRESULT (STDMETHODCALLTYPE *GetPrivateData)( IDXGIOutput5* This, REFGUID guid, UINT *data_size, void *data); HRESULT (STDMETHODCALLTYPE *GetParent)( IDXGIOutput5* This, REFIID riid, void **parent); /*** IDXGIOutput methods ***/ HRESULT (STDMETHODCALLTYPE *GetDesc)( IDXGIOutput5* This, DXGI_OUTPUT_DESC *desc); HRESULT (STDMETHODCALLTYPE *GetDisplayModeList)( IDXGIOutput5* This, DXGI_FORMAT format, UINT flags, UINT *mode_count, DXGI_MODE_DESC *desc); HRESULT (STDMETHODCALLTYPE *FindClosestMatchingMode)( IDXGIOutput5* This, const DXGI_MODE_DESC *mode, DXGI_MODE_DESC *closest_match, IUnknown *device); HRESULT (STDMETHODCALLTYPE *WaitForVBlank)( IDXGIOutput5* This); HRESULT (STDMETHODCALLTYPE *TakeOwnership)( IDXGIOutput5* This, IUnknown *device, WINBOOL exclusive); void (STDMETHODCALLTYPE *ReleaseOwnership)( IDXGIOutput5* This); HRESULT (STDMETHODCALLTYPE *GetGammaControlCapabilities)( IDXGIOutput5* This, DXGI_GAMMA_CONTROL_CAPABILITIES *gamma_caps); HRESULT (STDMETHODCALLTYPE *SetGammaControl)( IDXGIOutput5* This, const DXGI_GAMMA_CONTROL *gamma_control); HRESULT (STDMETHODCALLTYPE *GetGammaControl)( IDXGIOutput5* This, DXGI_GAMMA_CONTROL *gamma_control); HRESULT (STDMETHODCALLTYPE *SetDisplaySurface)( IDXGIOutput5* This, IDXGISurface *surface); HRESULT (STDMETHODCALLTYPE *GetDisplaySurfaceData)( IDXGIOutput5* This, IDXGISurface *surface); HRESULT (STDMETHODCALLTYPE *GetFrameStatistics)( IDXGIOutput5* This, DXGI_FRAME_STATISTICS *stats); /*** IDXGIOutput1 methods ***/ HRESULT (STDMETHODCALLTYPE *GetDisplayModeList1)( IDXGIOutput5* This, DXGI_FORMAT EnumFormat, UINT Flags, UINT *pNumModes, DXGI_MODE_DESC1 *pDesc); HRESULT (STDMETHODCALLTYPE *FindClosestMatchingMode1)( IDXGIOutput5* This, const DXGI_MODE_DESC1 *pModeToMatch, DXGI_MODE_DESC1 *pClosestMatch, IUnknown *pConcernedDevice); HRESULT (STDMETHODCALLTYPE *GetDisplaySurfaceData1)( IDXGIOutput5* This, IDXGIResource *pDestination); HRESULT (STDMETHODCALLTYPE *DuplicateOutput)( IDXGIOutput5* This, IUnknown *pDevice, IDXGIOutputDuplication **ppOutputDuplication); /*** IDXGIOutput2 methods ***/ BOOL (STDMETHODCALLTYPE *SupportsOverlays)( IDXGIOutput5* This); /*** IDXGIOutput3 methods ***/ HRESULT (STDMETHODCALLTYPE *CheckOverlaySupport)( IDXGIOutput5* This, DXGI_FORMAT EnumFormat, IUnknown *pConcernedDevice, UINT *pFlags); /*** IDXGIOutput4 methods ***/ HRESULT (STDMETHODCALLTYPE *CheckOverlayColorSpaceSupport)( IDXGIOutput5* This, DXGI_FORMAT Format, DXGI_COLOR_SPACE_TYPE ColorSpace, IUnknown *pConcernedDevice, UINT *pFlags); /*** IDXGIOutput5 methods ***/ HRESULT (STDMETHODCALLTYPE *DuplicateOutput1)( IDXGIOutput5* This, IUnknown *pDevice, UINT Flags, UINT SupportedFormatsCount, const DXGI_FORMAT *pSupportedFormats, IDXGIOutputDuplication **ppOutputDuplication); END_INTERFACE } IDXGIOutput5Vtbl; interface IDXGIOutput5 { CONST_VTBL IDXGIOutput5Vtbl* lpVtbl; }; #define IDXGIOutput5_DuplicateOutput1(This,pDevice,Flags,SupportedForamtsCount,pSupportedFormats,ppOutputDuplication) (This)->lpVtbl->DuplicateOutput1(This,pDevice,Flags,SupportedForamtsCount,pSupportedFormats,ppOutputDuplication) #define IDXGIOutput5_Release(This) (This)->lpVtbl->Release(This); static const char * DXGI_FORMAT_STR[] = { "DXGI_FORMAT_UNKNOWN", "DXGI_FORMAT_R32G32B32A32_TYPELESS", "DXGI_FORMAT_R32G32B32A32_FLOAT", "DXGI_FORMAT_R32G32B32A32_UINT", "DXGI_FORMAT_R32G32B32A32_SINT", "DXGI_FORMAT_R32G32B32_TYPELESS", "DXGI_FORMAT_R32G32B32_FLOAT", "DXGI_FORMAT_R32G32B32_UINT", "DXGI_FORMAT_R32G32B32_SINT", "DXGI_FORMAT_R16G16B16A16_TYPELESS", "DXGI_FORMAT_R16G16B16A16_FLOAT", "DXGI_FORMAT_R16G16B16A16_UNORM", "DXGI_FORMAT_R16G16B16A16_UINT", "DXGI_FORMAT_R16G16B16A16_SNORM", "DXGI_FORMAT_R16G16B16A16_SINT", "DXGI_FORMAT_R32G32_TYPELESS", "DXGI_FORMAT_R32G32_FLOAT", "DXGI_FORMAT_R32G32_UINT", "DXGI_FORMAT_R32G32_SINT", "DXGI_FORMAT_R32G8X24_TYPELESS", "DXGI_FORMAT_D32_FLOAT_S8X24_UINT", "DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS", "DXGI_FORMAT_X32_TYPELESS_G8X24_UINT", "DXGI_FORMAT_R10G10B10A2_TYPELESS", "DXGI_FORMAT_R10G10B10A2_UNORM", "DXGI_FORMAT_R10G10B10A2_UINT", "DXGI_FORMAT_R11G11B10_FLOAT", "DXGI_FORMAT_R8G8B8A8_TYPELESS", "DXGI_FORMAT_R8G8B8A8_UNORM", "DXGI_FORMAT_R8G8B8A8_UNORM_SRGB", "DXGI_FORMAT_R8G8B8A8_UINT", "DXGI_FORMAT_R8G8B8A8_SNORM", "DXGI_FORMAT_R8G8B8A8_SINT", "DXGI_FORMAT_R16G16_TYPELESS", "DXGI_FORMAT_R16G16_FLOAT", "DXGI_FORMAT_R16G16_UNORM", "DXGI_FORMAT_R16G16_UINT", "DXGI_FORMAT_R16G16_SNORM", "DXGI_FORMAT_R16G16_SINT", "DXGI_FORMAT_R32_TYPELESS", "DXGI_FORMAT_D32_FLOAT", "DXGI_FORMAT_R32_FLOAT", "DXGI_FORMAT_R32_UINT", "DXGI_FORMAT_R32_SINT", "DXGI_FORMAT_R24G8_TYPELESS", "DXGI_FORMAT_D24_UNORM_S8_UINT", "DXGI_FORMAT_R24_UNORM_X8_TYPELESS", "DXGI_FORMAT_X24_TYPELESS_G8_UINT", "DXGI_FORMAT_R8G8_TYPELESS", "DXGI_FORMAT_R8G8_UNORM", "DXGI_FORMAT_R8G8_UINT", "DXGI_FORMAT_R8G8_SNORM", "DXGI_FORMAT_R8G8_SINT", "DXGI_FORMAT_R16_TYPELESS", "DXGI_FORMAT_R16_FLOAT", "DXGI_FORMAT_D16_UNORM", "DXGI_FORMAT_R16_UNORM", "DXGI_FORMAT_R16_UINT", "DXGI_FORMAT_R16_SNORM", "DXGI_FORMAT_R16_SINT", "DXGI_FORMAT_R8_TYPELESS", "DXGI_FORMAT_R8_UNORM", "DXGI_FORMAT_R8_UINT", "DXGI_FORMAT_R8_SNORM", "DXGI_FORMAT_R8_SINT", "DXGI_FORMAT_A8_UNORM", "DXGI_FORMAT_R1_UNORM", "DXGI_FORMAT_R9G9B9E5_SHAREDEXP", "DXGI_FORMAT_R8G8_B8G8_UNORM", "DXGI_FORMAT_G8R8_G8B8_UNORM", "DXGI_FORMAT_BC1_TYPELESS", "DXGI_FORMAT_BC1_UNORM", "DXGI_FORMAT_BC1_UNORM_SRGB", "DXGI_FORMAT_BC2_TYPELESS", "DXGI_FORMAT_BC2_UNORM", "DXGI_FORMAT_BC2_UNORM_SRGB", "DXGI_FORMAT_BC3_TYPELESS", "DXGI_FORMAT_BC3_UNORM", "DXGI_FORMAT_BC3_UNORM_SRGB", "DXGI_FORMAT_BC4_TYPELESS", "DXGI_FORMAT_BC4_UNORM", "DXGI_FORMAT_BC4_SNORM", "DXGI_FORMAT_BC5_TYPELESS", "DXGI_FORMAT_BC5_UNORM", "DXGI_FORMAT_BC5_SNORM", "DXGI_FORMAT_B5G6R5_UNORM", "DXGI_FORMAT_B5G5R5A1_UNORM", "DXGI_FORMAT_B8G8R8A8_UNORM", "DXGI_FORMAT_B8G8R8X8_UNORM", "DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM", "DXGI_FORMAT_B8G8R8A8_TYPELESS", "DXGI_FORMAT_B8G8R8A8_UNORM_SRGB", "DXGI_FORMAT_B8G8R8X8_TYPELESS", "DXGI_FORMAT_B8G8R8X8_UNORM_SRGB", "DXGI_FORMAT_BC6H_TYPELESS", "DXGI_FORMAT_BC6H_UF16", "DXGI_FORMAT_BC6H_SF16", "DXGI_FORMAT_BC7_TYPELESS", "DXGI_FORMAT_BC7_UNORM", "DXGI_FORMAT_BC7_UNORM_SRGB", "DXGI_FORMAT_AYUV", "DXGI_FORMAT_Y410", "DXGI_FORMAT_Y416", "DXGI_FORMAT_NV12", "DXGI_FORMAT_P010", "DXGI_FORMAT_P016", "DXGI_FORMAT_420_OPAQUE", "DXGI_FORMAT_YUY2", "DXGI_FORMAT_Y210", "DXGI_FORMAT_Y216", "DXGI_FORMAT_NV11", "DXGI_FORMAT_AI44", "DXGI_FORMAT_IA44", "DXGI_FORMAT_P8", "DXGI_FORMAT_A8P8", "DXGI_FORMAT_B4G4R4A4_UNORM", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "DXGI_FORMAT_P208", "DXGI_FORMAT_V208", "DXGI_FORMAT_V408" }; static const char * GetDXGIFormatStr(DXGI_FORMAT format) { if (format > sizeof(DXGI_FORMAT_STR) / sizeof(const char *)) return DXGI_FORMAT_STR[0]; return DXGI_FORMAT_STR[format]; }looking-glass-0+b1/c-host/platform/Windows/capture/NVFBC/000077500000000000000000000000001351530562300232415ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/capture/NVFBC/CMakeLists.txt000066400000000000000000000005351351530562300260040ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(capture_NVFBC LANGUAGES C CXX) add_library(capture_NVFBC STATIC src/nvfbc.c src/wrapper.cpp ) file(TO_CMAKE_PATH "${NVFBC_SDK}" nvfbc_sdk) include_directories(file, "${nvfbc_sdk}/inc") target_include_directories(capture_NVFBC PRIVATE src ) target_link_libraries(capture_NVFBC platform_Windows ) looking-glass-0+b1/c-host/platform/Windows/capture/NVFBC/src/000077500000000000000000000000001351530562300240305ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/capture/NVFBC/src/nvfbc.c000066400000000000000000000170311351530562300252740ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/capture.h" #include "interface/platform.h" #include "windows/platform.h" #include "windows/debug.h" #include "windows/mousehook.h" #include "common/option.h" #include #include #include #include #include "wrapper.h" struct iface { bool stop; NvFBCHandle nvfbc; bool seperateCursor; void * pointerShape; unsigned int pointerSize; unsigned int maxWidth, maxHeight; unsigned int width , height; uint8_t * frameBuffer; NvFBCFrameGrabInfo grabInfo; osEventHandle * frameEvent; osEventHandle * cursorEvents[2]; int mouseX, mouseY, mouseHotX, mouseHotY; bool mouseVisible; }; static struct iface * this = NULL; static void nvfbc_free(); static void getDesktopSize(unsigned int * width, unsigned int * height) { HMONITOR monitor = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTOPRIMARY); MONITORINFO monitorInfo = { .cbSize = sizeof(MONITORINFO) }; GetMonitorInfo(monitor, &monitorInfo); CloseHandle(monitor); *width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; *height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; } static void on_mouseMove(int x, int y) { this->mouseX = x; this->mouseY = y; os_signalEvent(this->cursorEvents[0]); } static const char * nvfbc_getName() { return "NVFBC (NVidia Frame Buffer Capture)"; }; static void nvfbc_initOptions() { struct Option options[] = { { .module = "nvfbc", .name = "decoupleCursor", .description = "Capture the cursor seperately", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, {0} }; option_register(options); } static bool nvfbc_create() { if (!NvFBCInit()) return false; int bufferLen = GetEnvironmentVariable("NVFBC_PRIV_DATA", NULL, 0); uint8_t * privData = NULL; int privDataLen = 0; if(bufferLen) { char * buffer = malloc(bufferLen); GetEnvironmentVariable("NVFBC_PRIV_DATA", buffer, bufferLen); privDataLen = (bufferLen - 1) / 2; privData = (uint8_t *)malloc(privDataLen); char hex[3] = {0}; for(int i = 0; i < privDataLen; ++i) { memcpy(hex, &buffer[i*2], 2); privData[i] = (uint8_t)strtoul(hex, NULL, 16); } free(buffer); } this = (struct iface *)calloc(sizeof(struct iface), 1); if (!NvFBCToSysCreate(privData, privDataLen, &this->nvfbc, &this->maxWidth, &this->maxHeight)) { free(privData); nvfbc_free(); return false; } free(privData); this->frameEvent = os_createEvent(true); if (!this->frameEvent) { DEBUG_ERROR("failed to create the frame event"); nvfbc_free(); return false; } this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor"); return true; } static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize) { this->stop = false; this->pointerShape = pointerShape; this->pointerSize = pointerSize; getDesktopSize(&this->width, &this->height); os_resetEvent(this->frameEvent); HANDLE event; if (!NvFBCToSysSetup( this->nvfbc, BUFFER_FMT_ARGB, !this->seperateCursor, this->seperateCursor, false, 0, (void **)&this->frameBuffer, NULL, &event )) { return false; } this->cursorEvents[0] = os_createEvent(true); mouseHook_install(on_mouseMove); if (this->seperateCursor) this->cursorEvents[1] = os_wrapEvent(event); DEBUG_INFO("Cursor mode : %s", this->seperateCursor ? "decoupled" : "integrated"); Sleep(100); return true; } static void nvfbc_stop() { this->stop = true; os_signalEvent(this->cursorEvents[0]); os_signalEvent(this->frameEvent); } static bool nvfbc_deinit() { mouseHook_remove(); return true; } static void nvfbc_free() { NvFBCToSysRelease(&this->nvfbc); if (this->frameEvent) os_freeEvent(this->frameEvent); free(this); this = NULL; NvFBCFree(); } static unsigned int nvfbc_getMaxFrameSize() { return this->maxWidth * this->maxHeight * 4; } static CaptureResult nvfbc_capture() { getDesktopSize(&this->width, &this->height); NvFBCFrameGrabInfo grabInfo; CaptureResult result = NvFBCToSysCapture( this->nvfbc, 1000, 0, 0, this->width, this->height, &grabInfo ); if (result != CAPTURE_RESULT_OK) return result; memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo)); os_signalEvent(this->frameEvent); return CAPTURE_RESULT_OK; } static CaptureResult nvfbc_getFrame(CaptureFrame * frame) { if (!os_waitEvent(this->frameEvent, 1000)) return CAPTURE_RESULT_TIMEOUT; if (this->stop) return CAPTURE_RESULT_REINIT; frame->width = this->grabInfo.dwWidth; frame->height = this->grabInfo.dwHeight; frame->pitch = this->grabInfo.dwBufferWidth * 4; frame->stride = this->grabInfo.dwBufferWidth; #if 0 //NvFBC never sets bIsHDR so instead we check for any data in the alpha channel //If there is data, it's HDR. This is clearly suboptimal if (!this->grabInfo.bIsHDR) for(int y = 0; y < frame->height; ++y) for(int x = 0; x < frame->width; ++x) { int offset = (y * frame->pitch) + (x * 4); if (this->frameBuffer[offset + 3]) { this->grabInfo.bIsHDR = 1; break; } } #endif frame->format = this->grabInfo.bIsHDR ? CAPTURE_FMT_RGBA10 : CAPTURE_FMT_BGRA; memcpy(frame->data, this->frameBuffer, frame->pitch * frame->height); return CAPTURE_RESULT_OK; } static CaptureResult nvfbc_getPointer(CapturePointer * pointer) { osEventHandle * events[2]; memcpy(&events, &this->cursorEvents, sizeof(osEventHandle *) * 2); if (!os_waitEvents(events, this->seperateCursor ? 2 : 1, false, 1000)) return CAPTURE_RESULT_TIMEOUT; if (this->stop) return CAPTURE_RESULT_REINIT; CaptureResult result; pointer->shapeUpdate = false; if (this->seperateCursor && events[1]) { result = NvFBCToSysGetCursor(this->nvfbc, pointer, this->pointerShape, this->pointerSize); this->mouseVisible = pointer->visible; this->mouseHotX = pointer->x; this->mouseHotY = pointer->y; if (result != CAPTURE_RESULT_OK) return result; } pointer->visible = this->mouseVisible; pointer->x = this->mouseX - this->mouseHotX; pointer->y = this->mouseY - this->mouseHotY; return CAPTURE_RESULT_OK; } struct CaptureInterface Capture_NVFBC = { .getName = nvfbc_getName, .initOptions = nvfbc_initOptions, .create = nvfbc_create, .init = nvfbc_init, .stop = nvfbc_stop, .deinit = nvfbc_deinit, .free = nvfbc_free, .getMaxFrameSize = nvfbc_getMaxFrameSize, .capture = nvfbc_capture, .getFrame = nvfbc_getFrame, .getPointer = nvfbc_getPointer };looking-glass-0+b1/c-host/platform/Windows/capture/NVFBC/src/wrapper.cpp000066400000000000000000000216551351530562300262250ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wrapper.h" #include "windows/debug.h" #include #include #ifdef _WIN64 #define NVFBC_DLL "NvFBC64.dll" #else #define NVFBC_DLL "NvFBC.dll" #endif struct NVAPI { bool initialized; HMODULE dll; NvFBC_CreateFunctionExType createEx; NvFBC_SetGlobalFlagsType setGlobalFlags; NvFBC_GetStatusExFunctionType getStatusEx; NvFBC_EnableFunctionType enable; NvFBC_GetSDKVersionFunctionType getVersion; }; struct stNvFBCHandle { NvFBCToSys * nvfbc; HANDLE cursorEvent; int retry; }; static NVAPI nvapi; bool NvFBCInit() { if (nvapi.initialized) return true; nvapi.dll = LoadLibraryA(NVFBC_DLL); if (!nvapi.dll) { DEBUG_WINERROR("Failed to load " NVFBC_DLL, GetLastError()); return false; } nvapi.createEx = (NvFBC_CreateFunctionExType )GetProcAddress(nvapi.dll, "NvFBC_CreateEx" ); nvapi.setGlobalFlags = (NvFBC_SetGlobalFlagsType )GetProcAddress(nvapi.dll, "NvFBC_SetGlobalFlags"); nvapi.getStatusEx = (NvFBC_GetStatusExFunctionType )GetProcAddress(nvapi.dll, "NvFBC_GetStatusEx" ); nvapi.enable = (NvFBC_EnableFunctionType )GetProcAddress(nvapi.dll, "NvFBC_Enable" ); nvapi.getVersion = (NvFBC_GetSDKVersionFunctionType)GetProcAddress(nvapi.dll, "NvFBC_GetSDKVersion" ); if ( !nvapi.createEx || !nvapi.setGlobalFlags || !nvapi.getStatusEx || !nvapi.enable || !nvapi.getVersion) { DEBUG_ERROR("Failed to get the required proc addresses"); return false; } NvU32 version; if (nvapi.getVersion(&version) != NVFBC_SUCCESS) { DEBUG_ERROR("Failed to get the NvFBC SDK version"); return false; } DEBUG_INFO("NvFBC SDK Version: %lu", version); if (nvapi.enable(NVFBC_STATE_ENABLE) != NVFBC_SUCCESS) { DEBUG_ERROR("Failed to enable the NvFBC interface"); return false; } nvapi.initialized = true; return true; } void NvFBCFree() { if (!nvapi.initialized) return; FreeLibrary(nvapi.dll); nvapi.initialized = false; } bool NvFBCToSysCreate( void * privData, unsigned int privDataSize, NvFBCHandle * handle, unsigned int * maxWidth, unsigned int * maxHeight ) { NvFBCCreateParams params = {0}; params.dwVersion = NVFBC_CREATE_PARAMS_VER; params.dwInterfaceType = NVFBC_TO_SYS; params.pDevice = NULL; params.dwAdapterIdx = 0; params.dwPrivateDataSize = privDataSize; params.pPrivateData = privData; if (nvapi.createEx(¶ms) != NVFBC_SUCCESS) { *handle = NULL; return false; } *handle = (NvFBCHandle)calloc(sizeof(struct stNvFBCHandle), 1); (*handle)->nvfbc = static_cast(params.pNvFBC); if (maxWidth) *maxWidth = params.dwMaxDisplayWidth; if (maxHeight) *maxHeight = params.dwMaxDisplayHeight; return true; } void NvFBCToSysRelease(NvFBCHandle * handle) { if (!*handle) return; (*handle)->nvfbc->NvFBCToSysRelease(); free(*handle); *handle = NULL; } bool NvFBCToSysSetup( NvFBCHandle handle, enum BufferFormat format, bool hwCursor, bool seperateCursorCapture, bool useDiffMap, enum DiffMapBlockSize diffMapBlockSize, void ** frameBuffer, void ** diffMap, HANDLE * cursorEvent ) { NVFBC_TOSYS_SETUP_PARAMS params = {0}; params.dwVersion = NVFBC_TOSYS_SETUP_PARAMS_VER; switch(format) { case BUFFER_FMT_ARGB : params.eMode = NVFBC_TOSYS_ARGB ; break; case BUFFER_FMT_RGB : params.eMode = NVFBC_TOSYS_RGB ; break; case BUFFER_FMT_YYYYUV420p: params.eMode = NVFBC_TOSYS_YYYYUV420p; break; case BUFFER_FMT_RGB_PLANAR: params.eMode = NVFBC_TOSYS_RGB_PLANAR; break; case BUFFER_FMT_XOR : params.eMode = NVFBC_TOSYS_XOR ; break; case BUFFER_FMT_YUV444p : params.eMode = NVFBC_TOSYS_YUV444p ; break; case BUFFER_FMT_ARGB10 : params.eMode = NVFBC_TOSYS_ARGB10; params.bHDRRequest = TRUE; break; default: DEBUG_INFO("Invalid format"); return false; } params.bWithHWCursor = hwCursor ? TRUE : FALSE; params.bEnableSeparateCursorCapture = seperateCursorCapture ? TRUE : FALSE; params.bDiffMap = useDiffMap ? TRUE : FALSE; switch(diffMapBlockSize) { case DIFFMAP_BLOCKSIZE_128X128: params.eDiffMapBlockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_128X128; break; case DIFFMAP_BLOCKSIZE_16X16 : params.eDiffMapBlockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_16X16 ; break; case DIFFMAP_BLOCKSIZE_32X32 : params.eDiffMapBlockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_32X32 ; break; case DIFFMAP_BLOCKSIZE_64X64 : params.eDiffMapBlockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_64X64 ; break; default: DEBUG_ERROR("Invalid diffMapBlockSize"); return false; } params.ppBuffer = frameBuffer; params.ppDiffMap = diffMap; NVFBCRESULT status = handle->nvfbc->NvFBCToSysSetUp(¶ms); if (status != NVFBC_SUCCESS) { DEBUG_ERROR("Failed to setup NVFBCToSys"); return false; } if (cursorEvent) *cursorEvent = params.hCursorCaptureEvent; return true; } CaptureResult NvFBCToSysCapture( NvFBCHandle handle, const unsigned int waitTime, const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height, NvFBCFrameGrabInfo * grabInfo ) { NVFBC_TOSYS_GRAB_FRAME_PARAMS params = {0}; params.dwVersion = NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER; params.dwFlags = NVFBC_TOSYS_WAIT_WITH_TIMEOUT; params.dwWaitTime = waitTime; params.eGMode = NVFBC_TOSYS_SOURCEMODE_CROP; params.dwStartX = x; params.dwStartY = y; params.dwTargetWidth = width; params.dwTargetHeight = height; params.pNvFBCFrameGrabInfo = grabInfo; grabInfo->bMustRecreate = FALSE; NVFBCRESULT status = handle->nvfbc->NvFBCToSysGrabFrame(¶ms); if (grabInfo->bMustRecreate) { DEBUG_INFO("NvFBC reported recreation is required"); return CAPTURE_RESULT_REINIT; } switch(status) { case NVFBC_SUCCESS: handle->retry = 0; break; case NVFBC_ERROR_INVALID_PARAM: if (handle->retry < 2) { Sleep(100); ++handle->retry; return CAPTURE_RESULT_TIMEOUT; } return CAPTURE_RESULT_ERROR; case NVFBC_ERROR_DYNAMIC_DISABLE: DEBUG_ERROR("NvFBC was disabled by someone else"); return CAPTURE_RESULT_ERROR; case NVFBC_ERROR_INVALIDATED_SESSION: DEBUG_WARN("Session was invalidated, attempting to restart"); return CAPTURE_RESULT_REINIT; default: DEBUG_ERROR("Unknown NVFBCRESULT failure 0x%x", status); return CAPTURE_RESULT_ERROR; } return CAPTURE_RESULT_OK; } CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer, void * buffer, unsigned int size) { NVFBC_CURSOR_CAPTURE_PARAMS params; params.dwVersion = NVFBC_CURSOR_CAPTURE_PARAMS_VER; if (handle->nvfbc->NvFBCToSysCursorCapture(¶ms) != NVFBC_SUCCESS) { DEBUG_ERROR("Failed to get the cursor"); return CAPTURE_RESULT_ERROR; } pointer->x = params.dwXHotSpot; pointer->y = params.dwYHotSpot; pointer->width = params.dwWidth; pointer->height = params.dwHeight; pointer->pitch = params.dwPitch; pointer->visible = params.bIsHwCursor; pointer->shapeUpdate = params.bIsHwCursor; if (!params.bIsHwCursor) return CAPTURE_RESULT_OK; switch(params.dwPointerFlags & 0x7) { case 0x1: pointer->format = CAPTURE_FMT_MONO; pointer->height *= 2; break; case 0x2: pointer->format = CAPTURE_FMT_COLOR; break; case 0x4: pointer->format = CAPTURE_FMT_MASKED; break; default: DEBUG_ERROR("Invalid/unknown pointer data format"); return CAPTURE_RESULT_ERROR; } if (params.dwBufferSize > size) { DEBUG_WARN("Cursor data larger then provided buffer"); params.dwBufferSize = size; } memcpy(buffer, params.pBits, params.dwBufferSize); return CAPTURE_RESULT_OK; }looking-glass-0+b1/c-host/platform/Windows/capture/NVFBC/src/wrapper.h000066400000000000000000000044041351530562300256630ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #ifdef __cplusplus extern "C" { #endif #include "interface/capture.h" typedef struct stNvFBCHandle * NvFBCHandle; enum BufferFormat { BUFFER_FMT_ARGB, BUFFER_FMT_RGB, BUFFER_FMT_YYYYUV420p, BUFFER_FMT_RGB_PLANAR, BUFFER_FMT_XOR, BUFFER_FMT_YUV444p, BUFFER_FMT_ARGB10 }; enum DiffMapBlockSize { DIFFMAP_BLOCKSIZE_128X128 = 0, DIFFMAP_BLOCKSIZE_16X16, DIFFMAP_BLOCKSIZE_32X32, DIFFMAP_BLOCKSIZE_64X64 }; bool NvFBCInit(); void NvFBCFree(); bool NvFBCToSysCreate( void * privData, unsigned int privDataSize, NvFBCHandle * handle, unsigned int * maxWidth, unsigned int * maxHeight ); void NvFBCToSysRelease(NvFBCHandle * handle); bool NvFBCToSysSetup( NvFBCHandle handle, enum BufferFormat format, bool hwCursor, bool seperateCursorCapture, bool useDiffMap, enum DiffMapBlockSize diffMapBlockSize, void ** frameBuffer, void ** diffMap, HANDLE * cursorEvent ); CaptureResult NvFBCToSysCapture( NvFBCHandle handle, const unsigned int waitTime, const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height, NvFBCFrameGrabInfo * grabInfo ); CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer, void * buffer, unsigned int size); #ifdef __cplusplus } #endiflooking-glass-0+b1/c-host/platform/Windows/include/000077500000000000000000000000001351530562300223635ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/include/windows/000077500000000000000000000000001351530562300240555ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/include/windows/debug.h000066400000000000000000000022271351530562300253170ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "common/debug.h" #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) #ifdef __cplusplus } #endiflooking-glass-0+b1/c-host/platform/Windows/include/windows/mousehook.h000066400000000000000000000016561351530562300262470ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ typedef void (*MouseHookFn)(int x, int y); void mouseHook_install(MouseHookFn callback); void mouseHook_remove();looking-glass-0+b1/c-host/platform/Windows/include/windows/platform.h000066400000000000000000000016351351530562300260570ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/platform.h" #include osEventHandle * os_wrapEvent(HANDLE event);looking-glass-0+b1/c-host/platform/Windows/resource.rc000066400000000000000000000002071351530562300231140ustar00rootroot00000000000000#include "winuser.h" CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "app.manifest" IDI_APPLICATION ICON "../../../resources/icon.ico" looking-glass-0+b1/c-host/platform/Windows/src/000077500000000000000000000000001351530562300215275ustar00rootroot00000000000000looking-glass-0+b1/c-host/platform/Windows/src/mousehook.c000066400000000000000000000047701351530562300237140ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "windows/mousehook.h" #include "windows/debug.h" #include "platform.h" #include #include struct mouseHook { bool installed; HHOOK hook; MouseHookFn callback; }; static struct mouseHook mouseHook = { 0 }; // forwards static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam); static LRESULT msg_callback(WPARAM wParam, LPARAM lParam); void mouseHook_install(MouseHookFn callback) { struct MSG_CALL_FUNCTION cf; cf.fn = msg_callback; cf.wParam = 1; cf.lParam = (LPARAM)callback; sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf); } void mouseHook_remove() { struct MSG_CALL_FUNCTION cf; cf.fn = msg_callback; cf.wParam = 0; cf.lParam = 0; sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf); } static LRESULT msg_callback(WPARAM wParam, LPARAM lParam) { if (wParam) { if (mouseHook.installed) { DEBUG_WARN("Mouse hook already installed"); return 0; } mouseHook.hook = SetWindowsHookEx(WH_MOUSE_LL, mouseHook_hook, NULL, 0); if (!mouseHook.hook) { DEBUG_WINERROR("Failed to install the mouse hook", GetLastError()); return 0; } mouseHook.installed = true; mouseHook.callback = (MouseHookFn)lParam; } else { if (!mouseHook.installed) return 0; UnhookWindowsHookEx(mouseHook.hook); mouseHook.installed = false; } return 0; } static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION && wParam == WM_MOUSEMOVE) { MSLLHOOKSTRUCT *msg = (MSLLHOOKSTRUCT *)lParam; mouseHook.callback(msg->pt.x, msg->pt.y); } return CallNextHookEx(mouseHook.hook, nCode, wParam, lParam); }looking-glass-0+b1/c-host/platform/Windows/src/platform.c000066400000000000000000000331461351530562300235260ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "platform.h" #include "windows/platform.h" #include "windows/mousehook.h" #include #include #include #include "interface/platform.h" #include "common/debug.h" #include "common/option.h" #include "windows/debug.h" #include "ivshmem.h" #define ID_MENU_OPEN_LOG 3000 #define ID_MENU_EXIT 3001 struct AppState { HINSTANCE hInst; int argc; char ** argv; char executable[MAX_PATH + 1]; HANDLE shmemHandle; bool shmemOwned; IVSHMEM_MMAP shmemMap; HWND messageWnd; HMENU trayMenu; }; static struct AppState app = { .shmemHandle = INVALID_HANDLE_VALUE, .shmemOwned = false, .shmemMap = {0} }; struct osThreadHandle { const char * name; osThreadFunction function; void * opaque; HANDLE handle; DWORD threadID; int resultCode; }; LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_DESTROY: PostQuitMessage(0); break; case WM_CALL_FUNCTION: { struct MSG_CALL_FUNCTION * cf = (struct MSG_CALL_FUNCTION *)lParam; return cf->fn(cf->wParam, cf->lParam); } case WM_TRAYICON: { if (lParam == WM_RBUTTONDOWN) { POINT curPoint; GetCursorPos(&curPoint); SetForegroundWindow(hwnd); UINT clicked = TrackPopupMenu( app.trayMenu, TPM_RETURNCMD | TPM_NONOTIFY, curPoint.x, curPoint.y, 0, hwnd, NULL ); if (clicked == ID_MENU_EXIT ) app_quit(); else if (clicked == ID_MENU_OPEN_LOG) { const char * logFile = option_get_string("os", "logFile"); if (strcmp(logFile, "stderr") == 0) DEBUG_INFO("Ignoring request to open the logFile, logging to stderr"); else ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL); } } break; } default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } static int appThread(void * opaque) { // register our TrayIcon NOTIFYICONDATA iconData = { .cbSize = sizeof(NOTIFYICONDATA), .hWnd = app.messageWnd, .uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP, .uCallbackMessage = WM_TRAYICON, .szTip = "Looking Glass (host)" }; iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION); Shell_NotifyIcon(NIM_ADD, &iconData); int result = app_main(app.argc, app.argv); Shell_NotifyIcon(NIM_DELETE, &iconData); mouseHook_remove(); SendMessage(app.messageWnd, WM_DESTROY, 0, 0); return result; } LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam) { return SendMessage(app.messageWnd, Msg, wParam, lParam); } static BOOL WINAPI CtrlHandler(DWORD dwCtrlType) { if (dwCtrlType == CTRL_C_EVENT) { SendMessage(app.messageWnd, WM_CLOSE, 0, 0); return TRUE; } return FALSE; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { int result = 0; app.hInst = hInstance; char tempPath[MAX_PATH+1]; GetTempPathA(sizeof(tempPath), tempPath); int len = snprintf(NULL, 0, "%slooking-glass-host.txt", tempPath); char * logFilePath = malloc(len + 1); sprintf(logFilePath, "%slooking-glass-host.txt", tempPath); struct Option options[] = { { .module = "os", .name = "shmDevice", .description = "The IVSHMEM device to use", .type = OPTION_TYPE_INT, .value.x_int = 0 }, { .module = "os", .name = "logFile", .description = "The log file to write to", .type = OPTION_TYPE_STRING, .value.x_string = logFilePath }, {0} }; option_register(options); free(logFilePath); // convert the command line to the standard argc and argv LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc); app.argv = malloc(sizeof(char *) * app.argc); for(int i = 0; i < app.argc; ++i) { const size_t s = (wcslen(wargv[i])+1) * 2; app.argv[i] = malloc(s); wcstombs(app.argv[i], wargv[i], s); } LocalFree(wargv); GetModuleFileName(NULL, app.executable, sizeof(app.executable)); // setup a handler for ctrl+c SetConsoleCtrlHandler(CtrlHandler, TRUE); // create a message window so that our message pump works WNDCLASSEX wx = {}; wx.cbSize = sizeof(WNDCLASSEX); wx.lpfnWndProc = DummyWndProc; wx.hInstance = hInstance; wx.lpszClassName = "DUMMY_CLASS"; wx.hIcon = LoadIcon(NULL, IDI_APPLICATION); wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION); wx.hCursor = LoadCursor(NULL, IDC_ARROW); wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE; if (!RegisterClassEx(&wx)) { DEBUG_ERROR("Failed to register message window class"); result = -1; goto finish; } app.messageWnd = CreateWindowEx(0, "DUMMY_CLASS", "DUMMY_NAME", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); app.trayMenu = CreatePopupMenu(); AppendMenu(app.trayMenu, MF_STRING , ID_MENU_OPEN_LOG, "Open Log File"); AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL ); AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" ); // create the application thread osThreadHandle * thread; if (!os_createThread("appThread", appThread, NULL, &thread)) { DEBUG_ERROR("Failed to create the main application thread"); result = -1; goto finish; } while(true) { MSG msg; BOOL bRet = GetMessage(&msg, NULL, 0, 0); if (bRet > 0) { TranslateMessage(&msg); DispatchMessage(&msg); continue; } else if (bRet < 0) { DEBUG_ERROR("Unknown error from GetMessage"); result = -1; goto shutdown; } break; } shutdown: DestroyMenu(app.trayMenu); app_quit(); if (!os_joinThread(thread, &result)) { DEBUG_ERROR("Failed to join the main application thread"); result = -1; } finish: os_shmemUnmap(); if (app.shmemHandle != INVALID_HANDLE_VALUE) CloseHandle(app.shmemHandle); for(int i = 0; i < app.argc; ++i) free(app.argv[i]); free(app.argv); return result; } bool app_init() { const int shmDevice = option_get_int ("os", "shmDevice"); const char * logFile = option_get_string("os", "logFile" ); // redirect stderr to a file if (logFile && strcmp(logFile, "stderr") != 0) freopen(logFile, "a", stderr); // always flush stderr setbuf(stderr, NULL); HDEVINFO deviceInfoSet; PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; deviceInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE); memset(&deviceInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA)); deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); if (SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &deviceInterfaceData) == FALSE) { DWORD error = GetLastError(); if (error == ERROR_NO_MORE_ITEMS) { DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error); return false; } DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error); return false; } DWORD reqSize = 0; SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &reqSize, NULL); if (!reqSize) { DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError()); return false; } infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1); infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, infData, reqSize, NULL, NULL)) { free(infData); DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError()); return false; } app.shmemHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0); if (app.shmemHandle == INVALID_HANDLE_VALUE) { SetupDiDestroyDeviceInfoList(deviceInfoSet); free(infData); DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError()); return false; } free(infData); SetupDiDestroyDeviceInfoList(deviceInfoSet); return true; } const char * os_getExecutable() { return app.executable; } unsigned int os_shmemSize() { IVSHMEM_SIZE size; if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL)) { DEBUG_WINERROR("DeviceIoControl Failed", GetLastError()); return 0; } return (unsigned int)size; } bool os_shmemMmap(void **ptr) { if (app.shmemOwned) { *ptr = app.shmemMap.ptr; return true; } memset(&app.shmemMap, 0, sizeof(IVSHMEM_MMAP)); if (!DeviceIoControl( app.shmemHandle, IOCTL_IVSHMEM_REQUEST_MMAP, NULL, 0, &app.shmemMap, sizeof(IVSHMEM_MMAP), NULL, NULL)) { DEBUG_WINERROR("DeviceIoControl Failed", GetLastError()); return false; } *ptr = app.shmemMap.ptr; app.shmemOwned = true; return true; } void os_shmemUnmap() { if (!app.shmemOwned) return; if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL)) DEBUG_WINERROR("DeviceIoControl failed", GetLastError()); else app.shmemOwned = false; } static DWORD WINAPI threadWrapper(LPVOID lpParameter) { osThreadHandle * handle = (osThreadHandle *)lpParameter; handle->resultCode = handle->function(handle->opaque); return 0; } bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle) { *handle = (osThreadHandle *)malloc(sizeof(osThreadHandle)); (*handle)->name = name; (*handle)->function = function; (*handle)->opaque = opaque; (*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID); if (!(*handle)->handle) { free(*handle); *handle = NULL; DEBUG_WINERROR("CreateThread failed", GetLastError()); return false; } return true; } bool os_joinThread(osThreadHandle * handle, int * resultCode) { while(true) { switch(WaitForSingleObject(handle->handle, INFINITE)) { case WAIT_OBJECT_0: if (resultCode) *resultCode = handle->resultCode; CloseHandle(handle->handle); free(handle); return true; case WAIT_ABANDONED: case WAIT_TIMEOUT: continue; case WAIT_FAILED: DEBUG_WINERROR("Wait for thread failed", GetLastError()); CloseHandle(handle->handle); free(handle); return false; } break; } DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError()); return false; } osEventHandle * os_createEvent(bool autoReset) { HANDLE event = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL); if (!event) { DEBUG_WINERROR("Failed to create the event", GetLastError()); return NULL; } return (osEventHandle*)event; } osEventHandle * os_wrapEvent(HANDLE event) { return (osEventHandle*)event; } void os_freeEvent(osEventHandle * handle) { CloseHandle((HANDLE)handle); } bool os_waitEvent(osEventHandle * handle, unsigned int timeout) { const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout; while(true) { switch(WaitForSingleObject((HANDLE)handle, to)) { case WAIT_OBJECT_0: return true; case WAIT_ABANDONED: continue; case WAIT_TIMEOUT: if (timeout == TIMEOUT_INFINITE) continue; return false; case WAIT_FAILED: DEBUG_WINERROR("Wait for event failed", GetLastError()); return false; } DEBUG_ERROR("Unknown wait event return code"); return false; } } bool os_waitEvents(osEventHandle * handles[], int count, bool waitAll, unsigned int timeout) { const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout; while(true) { DWORD result = WaitForMultipleObjects(count, (HANDLE*)handles, waitAll, to); if (result >= WAIT_OBJECT_0 && result < count) { // null non signalled events from the handle list for(int i = 0; i < count; ++i) if (i != result && !os_waitEvent(handles[i], 0)) handles[i] = NULL; return true; } if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count) continue; switch(result) { case WAIT_TIMEOUT: if (timeout == TIMEOUT_INFINITE) continue; return false; case WAIT_FAILED: DEBUG_WINERROR("Wait for event failed", GetLastError()); return false; } DEBUG_ERROR("Unknown wait event return code"); return false; } } bool os_signalEvent(osEventHandle * handle) { return SetEvent((HANDLE)handle); } bool os_resetEvent(osEventHandle * handle) { return ResetEvent((HANDLE)handle); }looking-glass-0+b1/c-host/platform/Windows/src/platform.h000066400000000000000000000021731351530562300235270ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #define WM_CALL_FUNCTION (WM_USER+1) #define WM_TRAYICON (WM_USER+2) typedef LRESULT (*CallFunction)(WPARAM wParam, LPARAM lParam); struct MSG_CALL_FUNCTION { CallFunction fn; WPARAM wParam; LPARAM lParam; }; LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam);looking-glass-0+b1/c-host/platform/Windows/src/windebug.c000066400000000000000000000026711351530562300235050ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "windows/debug.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, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer); LocalFree(buffer); }looking-glass-0+b1/c-host/src/000077500000000000000000000000001351530562300162515ustar00rootroot00000000000000looking-glass-0+b1/c-host/src/app.c000066400000000000000000000270501351530562300172010ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/platform.h" #include "interface/capture.h" #include "dynamic/capture.h" #include "common/debug.h" #include "common/option.h" #include "common/locking.h" #include "common/KVMFR.h" #include "common/crash.h" #include #include #include #include #include #define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F) #define ALIGN_UP(x) ALIGN_DN(x + 0x7F) #define MAX_FRAMES 2 struct app { unsigned int clientInstance; KVMFRHeader * shmHeader; uint8_t * pointerData; unsigned int pointerDataSize; unsigned int pointerOffset; CaptureInterface * iface; uint8_t * frames; unsigned int frameSize; uint8_t * frame[MAX_FRAMES]; unsigned int frameOffset[MAX_FRAMES]; bool running; bool reinit; osThreadHandle * pointerThread; osThreadHandle * frameThread; }; static struct app app; static int pointerThread(void * opaque) { DEBUG_INFO("Pointer thread started"); volatile KVMFRCursor * ci = &(app.shmHeader->cursor); uint8_t flags; bool pointerValid = false; bool shapeValid = false; unsigned int clientInstance = 0; CapturePointer pointer = { 0 }; while(app.running) { bool resend = false; pointer.shapeUpdate = false; switch(app.iface->getPointer(&pointer)) { case CAPTURE_RESULT_OK: { pointerValid = true; break; } case CAPTURE_RESULT_REINIT: { app.reinit = true; DEBUG_INFO("Pointer thread reinit"); return 0; } case CAPTURE_RESULT_ERROR: { DEBUG_ERROR("Failed to get the pointer"); return 0; } case CAPTURE_RESULT_TIMEOUT: { // if the pointer is valid and the client has restarted, send it if (pointerValid && clientInstance != app.clientInstance) { resend = true; break; } continue; } } clientInstance = app.clientInstance; // wait for the client to finish with the previous update while((ci->flags & ~KVMFR_CURSOR_FLAG_UPDATE) != 0 && app.running) usleep(1000); flags = KVMFR_CURSOR_FLAG_UPDATE; ci->x = pointer.x; ci->y = pointer.y; flags |= KVMFR_CURSOR_FLAG_POS; if (pointer.visible) flags |= KVMFR_CURSOR_FLAG_VISIBLE; // if we have shape data if (pointer.shapeUpdate || (shapeValid && resend)) { switch(pointer.format) { case CAPTURE_FMT_COLOR : ci->type = CURSOR_TYPE_COLOR ; break; case CAPTURE_FMT_MONO : ci->type = CURSOR_TYPE_MONOCHROME ; break; case CAPTURE_FMT_MASKED: ci->type = CURSOR_TYPE_MASKED_COLOR; break; default: DEBUG_ERROR("Invalid pointer format: %d", pointer.format); continue; } ci->width = pointer.width; ci->height = pointer.height; ci->pitch = pointer.pitch; ci->dataPos = app.pointerOffset; ++ci->version; shapeValid = true; flags |= KVMFR_CURSOR_FLAG_SHAPE; } // update the flags for the client ci->flags = flags; } DEBUG_INFO("Pointer thread stopped"); return 0; } static int frameThread(void * opaque) { DEBUG_INFO("Frame thread started"); volatile KVMFRFrame * fi = &(app.shmHeader->frame); bool frameValid = false; int frameIndex = 0; unsigned int clientInstance = 0; CaptureFrame frame = { 0 }; while(app.running) { frame.data = app.frame[frameIndex]; switch(app.iface->getFrame(&frame)) { case CAPTURE_RESULT_OK: break; case CAPTURE_RESULT_REINIT: { app.reinit = true; DEBUG_INFO("Frame thread reinit"); return 0; } case CAPTURE_RESULT_ERROR: { DEBUG_ERROR("Failed to get the frame"); return 0; } case CAPTURE_RESULT_TIMEOUT: { if (frameValid && clientInstance != app.clientInstance) { // resend the last frame if (--frameIndex < 0) frameIndex = MAX_FRAMES - 1; break; } continue; } } clientInstance = app.clientInstance; // wait for the client to finish with the previous frame while(fi->flags & KVMFR_FRAME_FLAG_UPDATE && app.running) usleep(1000); switch(frame.format) { case CAPTURE_FMT_BGRA : fi->type = FRAME_TYPE_BGRA ; break; case CAPTURE_FMT_RGBA : fi->type = FRAME_TYPE_RGBA ; break; case CAPTURE_FMT_RGBA10: fi->type = FRAME_TYPE_RGBA10; break; case CAPTURE_FMT_YUV420: fi->type = FRAME_TYPE_YUV420; break; default: DEBUG_ERROR("Unsupported frame format %d, skipping frame", frame.format); continue; } fi->width = frame.width; fi->height = frame.height; fi->stride = frame.stride; fi->pitch = frame.pitch; fi->dataPos = app.frameOffset[frameIndex]; frameValid = true; INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE); if (++frameIndex == MAX_FRAMES) frameIndex = 0; } DEBUG_INFO("Frame thread stopped"); return 0; } bool startThreads() { app.running = true; if (!os_createThread("CursorThread", pointerThread, NULL, &app.pointerThread)) { DEBUG_ERROR("Failed to create the pointer thread"); return false; } if (!os_createThread("FrameThread", frameThread, NULL, &app.frameThread)) { DEBUG_ERROR("Failed to create the frame thread"); return false; } return true; } bool stopThreads() { bool ok = true; app.running = false; app.iface->stop(); if (app.frameThread && !os_joinThread(app.frameThread, NULL)) { DEBUG_WARN("Failed to join the frame thread"); ok = false; } app.frameThread = NULL; if (app.pointerThread && !os_joinThread(app.pointerThread, NULL)) { DEBUG_WARN("Failed to join the pointer thread"); ok = false; } app.pointerThread = NULL; return ok; } static bool captureStart() { DEBUG_INFO("Using : %s", app.iface->getName()); const unsigned int maxFrameSize = app.iface->getMaxFrameSize(); if (maxFrameSize > app.frameSize) { DEBUG_ERROR("Maximum frame size of %d bytes excceds maximum space available", maxFrameSize); return false; } DEBUG_INFO("Capture Size : %u MiB (%u)", maxFrameSize / 1048576, maxFrameSize); DEBUG_INFO("==== [ Capture Start ] ===="); return startThreads(); } static bool captureRestart() { DEBUG_INFO("==== [ Capture Restart ] ===="); if (!stopThreads()) return false; if (!app.iface->deinit() || !app.iface->init(app.pointerData, app.pointerDataSize)) { DEBUG_ERROR("Failed to reinitialize the capture device"); return false; } if (!captureStart()) return false; return true; } // this is called from the platform specific startup routine int app_main(int argc, char * argv[]) { if (!installCrashHandler(os_getExecutable())) DEBUG_WARN("Failed to install the crash handler"); // register capture interface options for(int i = 0; CaptureInterfaces[i]; ++i) if (CaptureInterfaces[i]->initOptions) CaptureInterfaces[i]->initOptions(); // try load values from a config file option_load("looking-glass-host.ini"); // parse the command line arguments if (!option_parse(argc, argv)) { option_free(); DEBUG_ERROR("Failure to parse the command line"); return -1; } if (!option_validate()) { option_free(); return -1; } // perform platform specific initialization if (!app_init()) return -1; unsigned int shmemSize = os_shmemSize(); uint8_t * shmemMap = NULL; int exitcode = 0; DEBUG_INFO("Looking Glass Host (" BUILD_VERSION ")"); DEBUG_INFO("IVSHMEM Size : %u MiB", shmemSize / 1048576); if (!os_shmemMmap((void **)&shmemMap) || !shmemMap) { DEBUG_ERROR("Failed to map the shared memory"); return -1; } DEBUG_INFO("IVSHMEM Address : 0x%" PRIXPTR, (uintptr_t)shmemMap); app.shmHeader = (KVMFRHeader *)shmemMap; app.pointerData = (uint8_t *)ALIGN_UP(shmemMap + sizeof(KVMFRHeader)); app.pointerDataSize = 1048576; // 1MB fixed for pointer size, should be more then enough app.pointerOffset = app.pointerData - shmemMap; app.frames = (uint8_t *)ALIGN_UP(app.pointerData + app.pointerDataSize); app.frameSize = ALIGN_DN((shmemSize - (app.frames - shmemMap)) / MAX_FRAMES); DEBUG_INFO("Max Cursor Size : %u MiB" , app.pointerDataSize / 1048576); DEBUG_INFO("Max Frame Size : %u MiB" , app.frameSize / 1048576); DEBUG_INFO("Cursor : 0x%" PRIXPTR " (0x%08x)", (uintptr_t)app.pointerData, app.pointerOffset); for (int i = 0; i < MAX_FRAMES; ++i) { app.frame [i] = app.frames + i * app.frameSize; app.frameOffset[i] = app.frame[i] - shmemMap; DEBUG_INFO("Frame %d : 0x%" PRIXPTR " (0x%08x)", i, (uintptr_t)app.frame[i], app.frameOffset[i]); } CaptureInterface * iface = NULL; for(int i = 0; CaptureInterfaces[i]; ++i) { iface = CaptureInterfaces[i]; DEBUG_INFO("Trying : %s", iface->getName()); if (!iface->create()) { iface = NULL; continue; } if (iface->init(app.pointerData, app.pointerDataSize)) break; iface->free(); iface = NULL; } if (!iface) { DEBUG_ERROR("Failed to find a supported capture interface"); exitcode = -1; goto fail; } app.iface = iface; // initialize the shared memory headers memcpy(app.shmHeader->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)); app.shmHeader->version = KVMFR_HEADER_VERSION; // zero and notify the client we are starting memset(&(app.shmHeader->frame ), 0, sizeof(KVMFRFrame )); memset(&(app.shmHeader->cursor), 0, sizeof(KVMFRCursor)); app.shmHeader->flags &= ~KVMFR_HEADER_FLAG_RESTART; if (!captureStart()) { exitcode = -1; goto exit; } volatile char * flags = (volatile char *)&(app.shmHeader->flags); while(app.running) { if (INTERLOCKED_AND8(flags, ~(KVMFR_HEADER_FLAG_RESTART)) & KVMFR_HEADER_FLAG_RESTART) { DEBUG_INFO("Client restarted"); ++app.clientInstance; } if (app.reinit && !captureRestart()) { exitcode = -1; goto exit; } app.reinit = false; switch(iface->capture()) { case CAPTURE_RESULT_OK: break; case CAPTURE_RESULT_TIMEOUT: continue; case CAPTURE_RESULT_REINIT: if (!captureRestart()) { exitcode = -1; goto exit; } app.reinit = false; continue; case CAPTURE_RESULT_ERROR: DEBUG_ERROR("Capture interface reported a fatal error"); exitcode = -1; goto finish; } } finish: stopThreads(); exit: iface->deinit(); iface->free(); fail: os_shmemUnmap(); return exitcode; } void app_quit() { app.running = false; }looking-glass-0+b1/c-host/toolchain-mingw64.cmake000066400000000000000000000012001351530562300217260ustar00rootroot00000000000000# the name of the target operating system SET(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) # here is the target environment located SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) looking-glass-0+b1/client/000077500000000000000000000000001351530562300155435ustar00rootroot00000000000000looking-glass-0+b1/client/.gitignore000066400000000000000000000000221351530562300175250ustar00rootroot00000000000000bin/ build/ *.swp looking-glass-0+b1/client/.vimrc000066400000000000000000000002531351530562300166640ustar00rootroot00000000000000packadd termdebug function Debug() !cd build && make if v:shell_error == 0 TermdebugCommand build/looking-glass-client endif endfunction command Debug call Debug() looking-glass-0+b1/client/CMakeLists.txt000066400000000000000000000047431351530562300203130ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(looking-glass-client C) set(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() 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_CB_X11 "Enable X11 clipboard integration" ON) add_feature_info(ENABLE_CB_X11 ENABLE_CB_X11 "X11 Clipboard Integration.") option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON) add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.") add_compile_options( "-Wall" "-Werror" "-Wfatal-errors" "-ffast-math" "-fdata-sections" "-ffunction-sections" "$<$:-O0;-g3;-ggdb>" ) set(EXE_FLAGS "-Wl,--gc-sections") set(CMAKE_C_STANDARD 11) find_package(PkgConfig) pkg_check_modules(PKGCONFIG REQUIRED sdl2 x11 ) execute_process( COMMAND cat ../VERSION WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} OUTPUT_VARIABLE BUILD_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) find_package(GMP) add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"') add_definitions(-D ATOMIC_LOCKING) add_definitions(-D GL_GLEXT_PROTOTYPES) get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE) include_directories( ${PROJECT_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include ${PKGCONFIG_INCLUDE_DIRS} ${GMP_INCLUDE_DIR} ) link_libraries( ${PKGCONFIG_LIBRARIES} ${GMP_LIBRARIES} rt m ) set(SOURCES src/main.c src/app.c src/config.c src/lg-renderer.c src/ll.c src/utils.c ) add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") add_subdirectory(spice) add_subdirectory(renderers) add_subdirectory(clipboards) add_subdirectory(fonts) add_subdirectory(decoders) add_executable(looking-glass-client ${SOURCES}) target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER}) target_link_libraries(looking-glass-client ${EXE_FLAGS} lg_common spice renderers clipboards fonts ) install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-client DESTINATION bin/ COMPONENT binary) feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES) looking-glass-0+b1/client/DEBUGGING.md000066400000000000000000000020471351530562300173630ustar00rootroot00000000000000# 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+b1/client/README.md000066400000000000000000000245141351530562300170300ustar00rootroot00000000000000# Looking Glass Client This is the Looking Glass client application that is designed to work in tandem with the Looking Glass Host application --- ## Building the Application ### Build Dependencies * binutils-dev * cmake * fonts-freefont-ttf * libsdl2-dev * libsdl2-ttf-dev * libspice-protocol-dev * libfontconfig1-dev * libx11-dev * nettle-dev #### Debian (and maybe Ubuntu) apt-get install binutils-dev cmake fonts-freefont-ttf libsdl2-dev libsdl2-ttf-dev libspice-protocol-dev libfontconfig1-dev libx11-dev nettle-dev ### Building mkdir build cd build cmake ../ make Should this all go well you should be left with the file `looking-glass-client` --- ## Usage Tips ### Key Bindings By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key. Below are a list of current key bindings: | Command | Description | |-|-| | ScrLk | Toggle cursor screen capture | | ScrLk+F | Full Screen toggle | | ScrLk+I | Spice keyboard & mouse enable toggle | | ScrLk+N | Toggle night vision mode (EGL renderer only!) | | ScrLk+Insert | Increase mouse sensitivity (in capture mode only) | | ScrLk+Del | Decrease mouse sensitivity (in capture mode only) | | ScrLk+F1 | Send Ctrl+Alt+F1 to the guest | | ScrLk+F2 | Send Ctrl+Alt+F2 to the guest | | ScrLk+F3 | Send Ctrl+Alt+F3 to the guest | | ScrLk+F4 | Send Ctrl+Alt+F4 to the guest | | ScrLk+F5 | Send Ctrl+Alt+F5 to the guest | | ScrLk+F6 | Send Ctrl+Alt+F6 to the guest | | ScrLk+F7 | Send Ctrl+Alt+F7 to the guest | | ScrLk+F8 | Send Ctrl+Alt+F8 to the guest | | ScrLk+F9 | Send Ctrl+Alt+F9 to the guest | | ScrLk+F10 | Send Ctrl+Alt+F10 to the guest | | ScrLk+F11 | Send Ctrl+Alt+F11 to the guest | | ScrLk+F12 | Send Ctrl+Alt+F12 to the guest | ### Setting options via command line arguments The syntax is simple: `module:name=value`, for example: ./looking-glass-client win:fullScreen=yes egl:nvGain=1 ### Setting options via configuration files By default the application will look for and load the config files in the following locations * /etc/looking-glass-client.ini * ~/.looking-glass-client.ini The format of this file is the commonly known INI format, for example: [win] fullScreen=yes [egl] nvGain=1 Command line arguments will override any options loaded from the config files. ### Supported options ``` |-------------------------------------------------------------------------------------------------------------------------| | Long | Short | Value | Description | |-------------------------------------------------------------------------------------------------------------------------| | app:configFile | -C | NULL | A file to read additional configuration from | | app:shmFile | -f | /dev/shm/looking-glass | The path to the shared memory file | | app:shmSize | -L | 0 | Specify the size in MB of the shared memory file (0 = detect) | | app:renderer | -g | auto | Specify the renderer to use | | app:license | -l | no | Show the license for this application and then terminate | | app:cursorPollInterval | | 1000 | How often to check for a cursor update in microseconds | | app:framePollInterval | | 1000 | How often to check for a frame update in microseconds | |-------------------------------------------------------------------------------------------------------------------------| |-------------------------------------------------------------------------------------------------------------| | Long | Short | Value | Description | |-------------------------------------------------------------------------------------------------------------| | win:title | | Looking Glass (client) | The window title | | win:position | | center | Initial window position at startup | | win:size | | 1024x768 | Initial window size at startup | | win:autoResize | -a | no | Auto resize the window to the guest | | win:allowResize | -n | yes | Aallow the window to be manually resized | | win:keepAspect | -r | yes | Maintain the correct aspect ratio | | win:borderless | -d | no | Borderless mode | | win:fullScreen | -F | no | Launch in fullscreen borderless mode | | win:maximize | -T | no | Launch window maximized | | win:minimizeOnFocusLoss | | yes | Minimize window on focus loss | | win:fpsLimit | -K | 200 | Frame rate limit (0 = disable - not recommended) | | win:showFPS | -k | no | Enable the FPS & UPS display | | win:ignoreQuit | -Q | no | Ignore requests to quit (ie: Alt+F4) | | win:noScreensaver | -S | no | Prevent the screensaver from starting | | win:alerts | -q | yes | Show on screen alert messages | |-------------------------------------------------------------------------------------------------------------| |---------------------------------------------------------------------------------------------------------------------------------------| | Long | Short | Value | Description | |---------------------------------------------------------------------------------------------------------------------------------------| | input:grabKeyboard | -G | yes | Grab the keyboard in capture mode | | input:escapeKey | -m | 71 = ScrollLock | Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values | | input:hideCursor | -M | yes | Hide the local mouse cursor | | input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) | |---------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------------------------------------------------------------| | Long | Short | Value | Description | |------------------------------------------------------------------------------------------------------------------| | spice:enable | -s | yes | Enable the built in SPICE client for input and/or clipboard support | | spice:host | -c | 127.0.0.1 | The SPICE server host or UNIX socket | | spice:port | -p | 5900 | The SPICE server port (0 = unix socket) | | spice:input | | yes | Use SPICE to send keyboard and mouse input events to the guest | | spice:clipboard | | yes | Use SPICE to syncronize the clipboard contents with the guest | | spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM | | spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM | | spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled | |------------------------------------------------------------------------------------------------------------------| |--------------------------------------------------------------------------| | Long | Short | Value | Description | |--------------------------------------------------------------------------| | egl:vsync | | no | Enable vsync | | egl:nvGainMax | | 1 | The maximum night vision gain | | egl:nvGain | | 0 | The initial night vision gain at startup | |--------------------------------------------------------------------------| |------------------------------------------------------------------------------------| | Long | Short | Value | Description | |------------------------------------------------------------------------------------| | opengl:mipmap | | yes | Enable mipmapping | | opengl:vsync | | yes | Enable vsync | | opengl:preventBuffer | | yes | Prevent the driver from buffering frames | | opengl:amdPinnedMem | | yes | Use GL_AMD_pinned_memory if it is available | |------------------------------------------------------------------------------------| ``` looking-glass-0+b1/client/clipboards/000077500000000000000000000000001351530562300176655ustar00rootroot00000000000000looking-glass-0+b1/client/clipboards/CMakeLists.txt000066400000000000000000000026151351530562300224310ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(clipboards LANGUAGES C) set(CLIPBOARD_H "${CMAKE_BINARY_DIR}/include/dynamic/clipboards.h") set(CLIPBOARD_C "${CMAKE_BINARY_DIR}/src/clipboards.c") file(WRITE ${CLIPBOARD_H} "#include \"interface/clipboard.h\"\n\n") file(APPEND ${CLIPBOARD_H} "extern LG_Clipboard * LG_Clipboards[];\n\n") file(WRITE ${CLIPBOARD_C} "#include \"interface/clipboard.h\"\n\n") file(APPEND ${CLIPBOARD_C} "#include \n\n") set(CLIPBOARDS "_") set(CLIPBOARDS_LINK "_") function(add_clipboard name) set(CLIPBOARDS "${CLIPBOARDS};${name}" PARENT_SCOPE) set(CLIPBOARDS_LINK "${CLIPBOARDS_LINK};clipboard_${name}" PARENT_SCOPE) add_subdirectory(${name}) endfunction() # Add/remove clipboards here! if (ENABLE_CB_X11) add_clipboard(X11) endif() list(REMOVE_AT CLIPBOARDS 0) list(REMOVE_AT CLIPBOARDS_LINK 0) list(LENGTH CLIPBOARDS CLIPBOARD_COUNT) file(APPEND ${CLIPBOARD_H} "#define LG_CLIPBOARD_COUNT ${CLIPBOARD_COUNT}\n") foreach(clipboard ${CLIPBOARDS}) file(APPEND ${CLIPBOARD_C} "extern LG_Clipboard LGC_${clipboard};\n") endforeach() file(APPEND ${CLIPBOARD_C} "\nconst LG_Clipboard * LG_Clipboards[] =\n{\n") foreach(clipboard ${CLIPBOARDS}) file(APPEND ${CLIPBOARD_C} " &LGC_${clipboard},\n") endforeach() file(APPEND ${CLIPBOARD_C} " NULL\n};\n\n") add_library(clipboards STATIC ${CLIPBOARD_C}) target_link_libraries(clipboards ${CLIPBOARDS_LINK}) looking-glass-0+b1/client/clipboards/X11/000077500000000000000000000000001351530562300202365ustar00rootroot00000000000000looking-glass-0+b1/client/clipboards/X11/CMakeLists.txt000066400000000000000000000007421351530562300230010ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(clipboard_X11 LANGUAGES C) find_package(PkgConfig) pkg_check_modules(CLIPBOARD_PKGCONFIG REQUIRED x11 xfixes ) add_library(clipboard_X11 STATIC src/x11.c ) target_link_libraries(clipboard_X11 ${CLIPBOARD_PKGCONFIG_LIBRARIES} lg_common ) target_include_directories(clipboard_X11 PUBLIC $ $ PRIVATE src ${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b1/client/clipboards/X11/src/000077500000000000000000000000001351530562300210255ustar00rootroot00000000000000looking-glass-0+b1/client/clipboards/X11/src/x11.c000066400000000000000000000225141351530562300216060ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/clipboard.h" #include "common/debug.h" #include struct state { Display * display; Window window; Atom aSelection; Atom aCurSelection; Atom aTargets; Atom aSelData; Atom aIncr; Atom aTypes[LG_CLIPBOARD_DATA_NONE]; LG_ClipboardReleaseFn releaseFn; LG_ClipboardRequestFn requestFn; LG_ClipboardNotifyFn notifyFn; LG_ClipboardDataFn dataFn; LG_ClipboardData type; // XFixes vars int eventBase; int errorBase; }; static struct state * this = NULL; static const char * atomTypes[] = { "UTF8_STRING", "image/png", "image/bmp", "image/tiff", "image/jpeg" }; static const char * x11_cb_getName() { return "X11"; } static bool x11_cb_init( SDL_SysWMinfo * wminfo, LG_ClipboardReleaseFn releaseFn, LG_ClipboardNotifyFn notifyFn, LG_ClipboardDataFn dataFn) { // final sanity check if (wminfo->subsystem != SDL_SYSWM_X11) { DEBUG_ERROR("wrong subsystem"); return false; } this = (struct state *)malloc(sizeof(struct state)); memset(this, 0, sizeof(struct state)); this->display = wminfo->info.x11.display; this->window = wminfo->info.x11.window; this->aSelection = XInternAtom(this->display, "CLIPBOARD", False); this->aTargets = XInternAtom(this->display, "TARGETS" , False); this->aSelData = XInternAtom(this->display, "SEL_DATA" , False); this->aIncr = XInternAtom(this->display, "INCR" , False); this->aCurSelection = BadValue; this->releaseFn = releaseFn; this->notifyFn = notifyFn; this->dataFn = dataFn; for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) { this->aTypes[i] = XInternAtom(this->display, atomTypes[i], False); if (this->aTypes[i] == BadAlloc || this->aTypes[i] == BadValue) { DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]); free(this); this = NULL; return false; } } // we need the raw X events SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); // use xfixes to get clipboard change notifications if (!XFixesQueryExtension(this->display, &this->eventBase, &this->errorBase)) { DEBUG_ERROR("failed to initialize xfixes"); free(this); this = NULL; return false; } XFixesSelectSelectionInput(this->display, this->window, XA_PRIMARY , XFixesSetSelectionOwnerNotifyMask); XFixesSelectSelectionInput(this->display, this->window, this->aSelection, XFixesSetSelectionOwnerNotifyMask); return true; } static void x11_cb_free() { free(this); this = NULL; } static void x11_cb_reply_fn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size) { XEvent *s = (XEvent *)opaque; XChangeProperty( this->display , s->xselection.requestor, s->xselection.property , s->xselection.target , 8, PropModeReplace, data, size); XSendEvent(this->display, s->xselection.requestor, 0, 0, s); XFlush(this->display); free(s); } static void x11_cb_wmevent(SDL_SysWMmsg * msg) { XEvent e = msg->msg.x11.event; if (e.type == SelectionRequest) { XEvent * s = (XEvent *)malloc(sizeof(XEvent)); s->xselection.type = SelectionNotify; s->xselection.requestor = e.xselectionrequest.requestor; s->xselection.selection = e.xselectionrequest.selection; s->xselection.target = e.xselectionrequest.target; s->xselection.property = e.xselectionrequest.property; s->xselection.time = e.xselectionrequest.time; if (!this->requestFn) { s->xselection.property = None; XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s); XFlush(this->display); free(s); return; } // target list requested if (e.xselectionrequest.target == this->aTargets) { Atom targets[2]; targets[0] = this->aTargets; targets[1] = this->aTypes[this->type]; XChangeProperty( e.xselectionrequest.display, e.xselectionrequest.requestor, e.xselectionrequest.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, sizeof(targets) / sizeof(Atom)); XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s); XFlush(this->display); free(s); return; } // look to see if we can satisfy the data type for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (this->aTypes[i] == e.xselectionrequest.target && this->type == i) { // request the data this->requestFn(x11_cb_reply_fn, s); return; } // report no data s->xselection.property = None; XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s); XFlush(this->display); } if (e.type == SelectionClear && ( e.xselectionclear.selection == XA_PRIMARY || e.xselectionclear.selection == this->aSelection) ) { this->aCurSelection = BadValue; this->releaseFn(); return; } // if someone selected data if (e.type == this->eventBase + XFixesSelectionNotify) { XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&e; // check if the selection is valid and it isn't ourself if ( (sne->selection != XA_PRIMARY && sne->selection != this->aSelection) || sne->owner == this->window || sne->owner == 0 ) { return; } // remember which selection we are working with this->aCurSelection = sne->selection; XConvertSelection( this->display, sne->selection, this->aTargets, this->aTargets, this->window, CurrentTime); return; } if (e.type == SelectionNotify) { if (e.xselection.property == None) return; Atom type; int format; unsigned long itemCount, after; unsigned char *data; XGetWindowProperty( this->display, this->window, e.xselection.property, 0, ~0L, // start and length True , // delete the property AnyPropertyType, &type, &format, &itemCount, &after, &data); // the target list if (e.xselection.property == this->aTargets) { // 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) { if (data) XFree(data); return; } // 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 (this->aTypes[n] == targets[i]) { // we have a match, so send the notification this->notifyFn(n); XFree(data); return; } } // no matches this->notifyFn(LG_CLIPBOARD_DATA_NONE); XFree(data); return; } if (format == this->aIncr) { DEBUG_WARN("fixme: large paste buffers are not yet supported"); XFree(data); return; } for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (this->aTypes[i] == type) { this->dataFn(i, data, itemCount); XFree(data); return; } DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(this->display, type)); XFree(data); return; } } static void x11_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type) { this->requestFn = requestFn; this->type = type; XSetSelectionOwner(this->display, XA_PRIMARY , this->window, CurrentTime); XSetSelectionOwner(this->display, this->aSelection, this->window, CurrentTime); XFlush(this->display); } static void x11_cb_release() { this->requestFn = NULL; XSetSelectionOwner(this->display, XA_PRIMARY , None, CurrentTime); XSetSelectionOwner(this->display, this->aSelection, None, CurrentTime); XFlush(this->display); } static void x11_cb_request(LG_ClipboardData type) { if (this->aCurSelection == BadValue) return; XConvertSelection( this->display, this->aCurSelection, this->aTypes[type], this->aSelData, this->window, CurrentTime); } const LG_Clipboard LGC_X11 = { .getName = x11_cb_getName, .init = x11_cb_init, .free = x11_cb_free, .wmevent = x11_cb_wmevent, .notice = x11_cb_notice, .release = x11_cb_release, .request = x11_cb_request };looking-glass-0+b1/client/cmake/000077500000000000000000000000001351530562300166235ustar00rootroot00000000000000looking-glass-0+b1/client/cmake/FindGMP.cmake000066400000000000000000000012501351530562300210470ustar00rootroot00000000000000# 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+b1/client/cmake/MakeObject.cmake000066400000000000000000000027301351530562300216330ustar00rootroot00000000000000function(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() looking-glass-0+b1/client/decoders/000077500000000000000000000000001351530562300173335ustar00rootroot00000000000000looking-glass-0+b1/client/decoders/CMakeLists.txt000066400000000000000000000007201351530562300220720ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(decoders LANGUAGES C) #find_package(PkgConfig) #pkg_check_modules(DECODERS_PKGCONFIG REQUIRED #) add_library(decoders STATIC src/null.c src/yuv420.c ) target_link_libraries(decoders lg_common ${DECODERS_PKGCONFIG_LIBRARIES} ) target_include_directories(decoders PUBLIC $ $ PRIVATE src ${DECODERS_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b1/client/decoders/src/000077500000000000000000000000001351530562300201225ustar00rootroot00000000000000looking-glass-0+b1/client/decoders/src/h264.c000066400000000000000000000675341351530562300207700ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "lg-decoder.h" #include "debug.h" #include "memcpySSE.h" #include "parsers/nal.h" #include #include #include #include #include #define SURFACE_NUM 3 struct Inst { LG_RendererFormat format; SDL_Window * window; VADisplay vaDisplay; int vaMajorVer, vaMinorVer; VASurfaceID vaSurfaceID[SURFACE_NUM]; VAConfigID vaConfigID; VAContextID vaContextID; int lastSID; int currentSID; VAPictureH264 curPic; VAPictureH264 oldPic; int frameNum; int fieldCount; VABufferID picBufferID[SURFACE_NUM]; VABufferID matBufferID[SURFACE_NUM]; VABufferID sliBufferID[SURFACE_NUM]; VABufferID datBufferID[SURFACE_NUM]; bool t2First; int sliceType; NAL nal; }; static const unsigned char MatrixBufferH264[] = { //ScalingList4x4[6][16] 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, //ScalingList8x8[2][64] 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; static bool lgd_h264_create (void ** opaque); static void lgd_h264_destroy (void * opaque); static bool lgd_h264_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window); static void lgd_h264_deinitialize (void * opaque); static LG_OutFormat lgd_h264_get_out_format (void * opaque); static unsigned int lgd_h264_get_frame_pitch (void * opaque); static unsigned int lgd_h264_get_frame_stride(void * opaque); static bool lgd_h264_decode (void * opaque, const uint8_t * src, size_t srcSize); static bool lgd_h264_get_buffer (void * opaque, uint8_t * dst, size_t dstSize); static bool lgd_h264_init_gl_texture (void * opaque, GLenum target, GLuint texture, void ** ref); static void lgd_h264_free_gl_texture (void * opaque, void * ref); static bool lgd_h264_update_gl_texture(void * opaque, void * ref); #define check_surface(x, y, z) _check_surface(__LINE__, x, y, z) static bool _check_surface(const unsigned int line, struct Inst * this, unsigned int sid, VASurfaceStatus *out) { VASurfaceStatus surfStatus; VAStatus status = vaQuerySurfaceStatus( this->vaDisplay, this->vaSurfaceID[sid], &surfStatus ); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaQuerySurfaceStatus: %s", vaErrorStr(status)); return false; } #if 0 DEBUG_INFO("L%d: surface %u status: %d", line, sid, surfStatus); #endif if (out) *out = surfStatus; return true; } static bool lgd_h264_create(void ** opaque) { // 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; this->vaSurfaceID[0] = VA_INVALID_ID; this->vaConfigID = VA_INVALID_ID; this->vaContextID = VA_INVALID_ID; for(int i = 0; i < SURFACE_NUM; ++i) this->picBufferID[i] = this->matBufferID[i] = this->sliBufferID[i] = this->datBufferID[i] = VA_INVALID_ID; if (!nal_initialize(&this->nal)) { DEBUG_INFO("Failed to initialize NAL parser"); free(this); return false; } lgd_h264_deinitialize(this); return true; } static void lgd_h264_destroy(void * opaque) { struct Inst * this = (struct Inst *)opaque; nal_deinitialize(this->nal); lgd_h264_deinitialize(this); free(this); } static bool lgd_h264_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window) { struct Inst * this = (struct Inst *)opaque; lgd_h264_deinitialize(this); memcpy(&this->format, &format, sizeof(LG_RendererFormat)); this->window = window; SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); if (!SDL_GetWindowWMInfo(window, &wminfo)) { DEBUG_ERROR("Failed to get SDL window WM Info"); return false; } switch(wminfo.subsystem) { case SDL_SYSWM_X11: this->vaDisplay = vaGetDisplayGLX(wminfo.info.x11.display); break; default: DEBUG_ERROR("Unsupported window subsystem"); return false; } VAStatus status; status = vaInitialize(this->vaDisplay, &this->vaMajorVer, &this->vaMinorVer); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaInitialize Failed"); return false; } DEBUG_INFO("Vendor: %s", vaQueryVendorString(this->vaDisplay)); VAEntrypoint entryPoints[5]; int entryPointCount; status = vaQueryConfigEntrypoints( this->vaDisplay, VAProfileH264High, entryPoints, &entryPointCount ); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaQueryConfigEntrypoints Failed"); return false; } int ep; for(ep = 0; ep < entryPointCount; ++ep) if (entryPoints[ep] == VAEntrypointVLD) break; if (ep == entryPointCount) { DEBUG_ERROR("Failed to find VAEntrypointVLD index"); return false; } VAConfigAttrib attrib; attrib.type = VAConfigAttribRTFormat; vaGetConfigAttributes( this->vaDisplay, VAProfileH264High, VAEntrypointVLD, &attrib, 1); if (!(attrib.value & VA_RT_FORMAT_YUV420)) { DEBUG_ERROR("Failed to find desired YUV420 RT format"); return false; } status = vaCreateConfig( this->vaDisplay, VAProfileH264High, VAEntrypointVLD, &attrib, 1, &this->vaConfigID); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaCreateConfig"); return false; } status = vaCreateSurfaces( this->vaDisplay, VA_RT_FORMAT_YUV420, this->format.width, this->format.height, this->vaSurfaceID, SURFACE_NUM, NULL, 0 ); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaCreateSurfaces"); return false; } for(int i = 0; i < SURFACE_NUM; ++i) if (!check_surface(this, i, NULL)) return false; status = vaCreateContext( this->vaDisplay, this->vaConfigID, this->format.width, this->format.height, VA_PROGRESSIVE, this->vaSurfaceID, SURFACE_NUM, &this->vaContextID ); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaCreateContext"); return false; } this->currentSID = 0; this->sliceType = 2; this->t2First = true; status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[0]); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaBeginPicture"); return false; } return true; } static void lgd_h264_deinitialize(void * opaque) { struct Inst * this = (struct Inst *)opaque; for(int i = 0; i < SURFACE_NUM; ++i) { if (this->picBufferID[i] != VA_INVALID_ID) vaDestroyBuffer(this->vaDisplay, this->picBufferID[i]); if (this->matBufferID[i] != VA_INVALID_ID) vaDestroyBuffer(this->vaDisplay, this->matBufferID[i]); if (this->sliBufferID[i] != VA_INVALID_ID) vaDestroyBuffer(this->vaDisplay, this->sliBufferID[i]); if (this->datBufferID[i] != VA_INVALID_ID) vaDestroyBuffer(this->vaDisplay, this->datBufferID[i]); this->picBufferID[i] = this->matBufferID[i] = this->sliBufferID[i] = this->datBufferID[i] = VA_INVALID_ID; } if (this->vaSurfaceID[0] != VA_INVALID_ID) vaDestroySurfaces(this->vaDisplay, this->vaSurfaceID, SURFACE_NUM); this->vaSurfaceID[0] = VA_INVALID_ID; if (this->vaContextID != VA_INVALID_ID) vaDestroyContext(this->vaDisplay, this->vaContextID); this->vaContextID = VA_INVALID_ID; if (this->vaConfigID != VA_INVALID_ID) vaDestroyConfig(this->vaDisplay, this->vaConfigID); this->vaConfigID = VA_INVALID_ID; if (this->vaDisplay) vaTerminate(this->vaDisplay); this->vaDisplay = NULL; } static LG_OutFormat lgd_h264_get_out_format(void * opaque) { return LG_OUTPUT_YUV420; } static unsigned int lgd_h264_get_frame_pitch(void * opaque) { struct Inst * this = (struct Inst *)opaque; return this->format.width * 4; } static unsigned int lgd_h264_get_frame_stride(void * opaque) { struct Inst * this = (struct Inst *)opaque; return this->format.width; } static bool get_buffer(struct Inst * this, const VABufferType type, const unsigned int size, VABufferID * buf_id) { if (*buf_id != VA_INVALID_ID) return true; VAStatus status = vaCreateBuffer(this->vaDisplay, this->vaContextID, type, size, 1, NULL, buf_id); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("Failed to create buffer: %s", vaErrorStr(status)); return false; } if (!check_surface(this, this->currentSID, NULL)) return false; return true; } static bool setup_pic_buffer(struct Inst * this, const NAL_SLICE * slice) { VAStatus status; VABufferID * picBufferID = &this->picBufferID[this->currentSID]; if (!get_buffer(this, VAPictureParameterBufferType, sizeof(VAPictureParameterBufferH264), picBufferID)) { DEBUG_ERROR("get picBuffer failed"); return false; } VAPictureParameterBufferH264 *p; status = vaMapBuffer(this->vaDisplay, *picBufferID, (void **)&p); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status)); return false; } const NAL_SPS * sps; if (!nal_get_sps(this->nal, &sps)) { DEBUG_ERROR("nal_get_sps"); return false; } const NAL_PPS * pps; if (!nal_get_pps(this->nal, &pps)) { DEBUG_ERROR("nal_get_pps"); return false; } memset(p, 0, sizeof(VAPictureParameterBufferH264)); p->picture_width_in_mbs_minus1 = sps->pic_width_in_mbs_minus1; p->picture_height_in_mbs_minus1 = sps->pic_height_in_map_units_minus1; p->bit_depth_luma_minus8 = sps->bit_depth_luma_minus8; p->bit_depth_chroma_minus8 = sps->bit_depth_chroma_minus8; p->num_ref_frames = sps->num_ref_frames; p->seq_fields.value = 0; p->seq_fields.bits.chroma_format_idc = sps->chroma_format_idc; p->seq_fields.bits.residual_colour_transform_flag = sps->gaps_in_frame_num_value_allowed_flag; p->seq_fields.bits.frame_mbs_only_flag = sps->frame_mbs_only_flag; p->seq_fields.bits.mb_adaptive_frame_field_flag = sps->mb_adaptive_frame_field_flag; p->seq_fields.bits.direct_8x8_inference_flag = sps->direct_8x8_inference_flag; p->seq_fields.bits.MinLumaBiPredSize8x8 = sps->level_idc >= 31; p->seq_fields.bits.log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4; p->seq_fields.bits.pic_order_cnt_type = sps->pic_order_cnt_type; p->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = sps->log2_max_pic_order_cnt_lsb_minus4; p->seq_fields.bits.delta_pic_order_always_zero_flag = sps->delta_pic_order_always_zero_flag; #if 0 // these are deprecated, FMO is not supported p->num_slice_groups_minus1 = pps->num_slice_groups_minus1; p->slice_group_map_type = pps->slice_group_map_type; p->slice_group_change_rate_minus1 = pps->slice_group_change_rate_minus1; #endif p->pic_init_qp_minus26 = pps->pic_init_qp_minus26; p->pic_init_qs_minus26 = pps->pic_init_qs_minus26; p->chroma_qp_index_offset = pps->chroma_qp_index_offset; p->second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset; p->pic_fields.value = 0; p->pic_fields.bits.entropy_coding_mode_flag = pps->entropy_coding_mode_flag; p->pic_fields.bits.weighted_pred_flag = pps->weighted_pred_flag; p->pic_fields.bits.weighted_bipred_idc = pps->weighted_bipred_idc; p->pic_fields.bits.transform_8x8_mode_flag = pps->transform_8x8_mode_flag; p->pic_fields.bits.field_pic_flag = slice->field_pic_flag; p->pic_fields.bits.constrained_intra_pred_flag = pps->constrained_intra_pred_flag; p->pic_fields.bits.pic_order_present_flag = pps->pic_order_present_flag; p->pic_fields.bits.deblocking_filter_control_present_flag = pps->deblocking_filter_control_present_flag; p->pic_fields.bits.redundant_pic_cnt_present_flag = pps->redundant_pic_cnt_present_flag; p->pic_fields.bits.reference_pic_flag = slice->nal_ref_idc != 0; p->frame_num = slice->frame_num; for(int i = 0; i < 16; ++i) { p->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID; p->ReferenceFrames[i].picture_id = 0xFFFFFFFF; } this->curPic.picture_id = this->vaSurfaceID[this->currentSID]; this->curPic.frame_idx = p->frame_num; this->curPic.flags = 0; this->curPic.BottomFieldOrderCnt = this->fieldCount; this->curPic.TopFieldOrderCnt = this->fieldCount; memcpy(&p->CurrPic, &this->curPic, sizeof(VAPictureH264)); if (this->sliceType != 2) { memcpy(&p->ReferenceFrames[0], &this->oldPic, sizeof(VAPictureH264)); p->ReferenceFrames[0].flags = 0; } status = vaUnmapBuffer(this->vaDisplay, *picBufferID); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status)); return false; } return true; } static bool setup_mat_buffer(struct Inst * this) { VAStatus status; VABufferID * matBufferID = &this->matBufferID[this->currentSID]; if (!get_buffer(this, VAIQMatrixBufferType, sizeof(VAIQMatrixBufferH264), matBufferID)) { DEBUG_ERROR("get matBuffer failed"); return false; } VAIQMatrixBufferH264 * m; status = vaMapBuffer(this->vaDisplay, *matBufferID, (void **)&m); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status)); return false; } memcpy(m, MatrixBufferH264, sizeof(MatrixBufferH264)); status = vaUnmapBuffer(this->vaDisplay, *matBufferID); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status)); return false; } return true; } static void fill_pred_weight_table( NAL_PW_TABLE_L * list, uint32_t active, uint32_t luma_log2_weight_denom, uint8_t luma_weight_flag, short luma_weight[32], short luma_offset[32], uint32_t chroma_log2_weight_denom, uint8_t chroma_weight_flag, short chroma_weight[32][2], short chroma_offset[32][2] ) { assert(active < 32); for(uint32_t i = 0; i <= active; ++i) { NAL_PW_TABLE_L * l = &list[i]; if (luma_weight_flag) { luma_weight[i] = l->luma_weight; luma_offset[i] = l->luma_offset; } else { luma_weight[i] = 1 << luma_log2_weight_denom; luma_weight[i] = 0; } if (chroma_weight_flag) { chroma_weight[i][0] = l->chroma_weight[0]; chroma_offset[i][0] = l->chroma_offset[0]; chroma_weight[i][1] = l->chroma_weight[1]; chroma_offset[i][1] = l->chroma_offset[1]; } else { chroma_weight[i][0] = 1 << chroma_log2_weight_denom; chroma_weight[i][0] = 0; chroma_weight[i][1] = 1 << chroma_log2_weight_denom; chroma_weight[i][1] = 0; } } } static bool setup_sli_buffer(struct Inst * this, size_t srcSize, const NAL_SLICE * slice, const size_t seek) { VAStatus status; VABufferID * sliBufferID = &this->sliBufferID[this->currentSID]; if (!get_buffer(this, VASliceParameterBufferType, sizeof(VASliceParameterBufferH264), sliBufferID)) { DEBUG_ERROR("get sliBuffer failed"); return false; } VASliceParameterBufferH264 * s; status = vaMapBuffer(this->vaDisplay, *sliBufferID, (void **)&s); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status)); return false; } memset(s, 0, sizeof(VASliceParameterBufferH264)); s->slice_data_size = srcSize; s->slice_data_bit_offset = seek << 3; s->slice_data_flag = VA_SLICE_DATA_FLAG_ALL; s->first_mb_in_slice = slice->first_mb_in_slice; s->slice_type = slice->slice_type; s->direct_spatial_mv_pred_flag = slice->direct_spatial_mv_pred_flag; s->num_ref_idx_l0_active_minus1 = slice->num_ref_idx_l0_active_minus1; s->num_ref_idx_l1_active_minus1 = slice->num_ref_idx_l1_active_minus1; s->cabac_init_idc = slice->cabac_init_idc; s->slice_qp_delta = slice->slice_qp_delta; s->disable_deblocking_filter_idc = slice->disable_deblocking_filter_idc; s->slice_alpha_c0_offset_div2 = slice->slice_alpha_c0_offset_div2; s->slice_beta_offset_div2 = slice->slice_beta_offset_div2; s->luma_log2_weight_denom = slice->pred_weight_table.luma_log2_weight_denom; s->chroma_log2_weight_denom = slice->pred_weight_table.chroma_log2_weight_denom; s->luma_weight_l0_flag = slice->pred_weight_table.luma_weight_flag [0]; s->chroma_weight_l0_flag = slice->pred_weight_table.chroma_weight_flag[0]; s->luma_weight_l1_flag = slice->pred_weight_table.luma_weight_flag [1]; s->chroma_weight_l1_flag = slice->pred_weight_table.chroma_weight_flag[1]; //RefPicList0/1 fill_pred_weight_table( slice->pred_weight_table.l0, s->num_ref_idx_l0_active_minus1, s->luma_log2_weight_denom, s->luma_weight_l0_flag, s->luma_weight_l0, s->luma_offset_l0, s->chroma_log2_weight_denom, s->chroma_weight_l0_flag, s->chroma_weight_l0, s->chroma_weight_l0 ); fill_pred_weight_table( slice->pred_weight_table.l1, s->num_ref_idx_l1_active_minus1, s->luma_log2_weight_denom, s->luma_weight_l1_flag, s->luma_weight_l1, s->luma_offset_l1, s->chroma_log2_weight_denom, s->chroma_weight_l1_flag, s->chroma_weight_l1, s->chroma_weight_l1 ); #if 0 if (this->sliceType == 2) { set_slice_parameter_buffer_t2(s, this->t2First); this->t2First = false; } else { set_slice_parameter_buffer(s); memcpy(&s->RefPicList0[0], &this->oldPic, sizeof(VAPictureH264)); s->RefPicList0[0].flags = 0; } #endif status = vaUnmapBuffer(this->vaDisplay, *sliBufferID); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status)); return false; } return true; } static bool setup_dat_buffer(struct Inst * this, const uint8_t * src, size_t srcSize) { VAStatus status; VABufferID * datBufferID = &this->datBufferID[this->currentSID]; if (!get_buffer(this, VASliceDataBufferType, srcSize, datBufferID)) { DEBUG_ERROR("get datBuffer failed"); return false; } uint8_t * d; status = vaMapBuffer(this->vaDisplay, *datBufferID, (void **)&d); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status)); return false; } memcpySSE(d, src, srcSize); status = vaUnmapBuffer(this->vaDisplay, *datBufferID); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status)); return false; } return true; } static bool lgd_h264_decode(void * opaque, const uint8_t * src, size_t srcSize) { VAStatus status; struct Inst * this = (struct Inst *)opaque; size_t seek; if (!nal_parse(this->nal, src, srcSize, &seek)) { DEBUG_WARN("nal_parse, perhaps mid stream"); return true; } const NAL_SLICE * slice; if (!nal_get_slice(this->nal, &slice)) { DEBUG_WARN("nal_get_slice failed"); return true; } assert(seek < srcSize); this->sliceType = slice->slice_type; // don't start until we have an I-FRAME if (this->frameNum == 0 && this->sliceType != NAL_SLICE_TYPE_I) return true; { if (!setup_pic_buffer(this, slice)) return false; if (!setup_mat_buffer(this)) return false; VABufferID bufferIDs[] = { this->picBufferID[this->currentSID], this->matBufferID[this->currentSID] }; status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status)); return false; } // intel broke the ABI here, see: // https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9 // the buffers are released by vaRenderPicture in old versions if (this->vaMajorVer == 0 && this->vaMinorVer < 40) { this->picBufferID[this->currentSID] = this->matBufferID[this->currentSID] = VA_INVALID_ID; } } { if (!setup_sli_buffer(this, srcSize, slice, seek)) return false; if (!setup_dat_buffer(this, src, srcSize )) return false; VABufferID bufferIDs[] = { this->sliBufferID[this->currentSID], this->datBufferID[this->currentSID] }; status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status)); return false; } // intel broke the ABI here, see: // https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9 // the buffers are released by vaRenderPicture in old versions if (this->vaMajorVer == 0 && this->vaMinorVer < 40) { this->sliBufferID[this->currentSID] = this->datBufferID[this->currentSID] = VA_INVALID_ID; } } status = vaEndPicture(this->vaDisplay, this->vaContextID); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaEndPicture: %s", vaErrorStr(status)); return false; } // advance to the next surface and save the old picture info this->lastSID = this->currentSID; if (++this->currentSID == SURFACE_NUM) this->currentSID = 0; this->frameNum += 1; this->fieldCount += 2; memcpy(&this->oldPic, &this->curPic, sizeof(VAPictureH264)); // prepare the next surface status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[this->currentSID]); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaBeginPicture: %s", vaErrorStr(status)); return false; } return true; } static bool lgd_h264_get_buffer(void * opaque, uint8_t * dst, size_t dstSize) { struct Inst * this = (struct Inst *)opaque; VAStatus status; // don't return anything until we have some data if (this->frameNum == 0) return true; // ensure the surface is ready status = vaSyncSurface(this->vaDisplay, this->vaSurfaceID[this->lastSID]); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaSyncSurface: %s", vaErrorStr(status)); return false; } #if 0 // this doesn't work on my system, seems the vdpau va driver is bugged VASurfaceStatus surfStatus; if (!check_surface(this, this->lastSID, &surfStatus)) return false; if (surfStatus != VASurfaceReady) { DEBUG_ERROR("vaSyncSurface didn't block, the surface is not ready!"); return false; } #endif // get the decoded data VAImage decoded = { .image_id = VA_INVALID_ID, .buf = VA_INVALID_ID }; status = vaDeriveImage(this->vaDisplay, this->vaSurfaceID[this->lastSID], &decoded); if (status == VA_STATUS_ERROR_OPERATION_FAILED) { VAImageFormat format = { .fourcc = VA_FOURCC_NV12, .byte_order = VA_LSB_FIRST, .bits_per_pixel = 12 }; status = vaCreateImage( this->vaDisplay, &format, this->format.width, this->format.height, &decoded ); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaCreateImage: %s", vaErrorStr(status)); return false; } status = vaPutImage( this->vaDisplay, this->vaSurfaceID[this->lastSID], decoded.image_id, 0 , 0 , this->format.width, this->format.height, 0 , 0 , this->format.width, this->format.height ); if (status != VA_STATUS_SUCCESS) { vaDestroyImage(this->vaDisplay, decoded.image_id); DEBUG_ERROR("vaPutImage: %s", vaErrorStr(status)); return false; } } else { if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaDeriveImage: %s", vaErrorStr(status)); return false; } } uint8_t * d; status = vaMapBuffer(this->vaDisplay, decoded.buf, (void **)&d); if (status != VA_STATUS_SUCCESS) { vaDestroyImage(this->vaDisplay, decoded.image_id); DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status)); return false; } memcpySSE(dst, d, decoded.data_size); status = vaUnmapBuffer(this->vaDisplay, decoded.buf); if (status != VA_STATUS_SUCCESS) { vaDestroyImage(this->vaDisplay, decoded.image_id); DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status)); return false; } status = vaDestroyImage(this->vaDisplay, decoded.image_id); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaDestroyImage: %s", vaErrorStr(status)); return false; } return true; } static bool lgd_h264_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref) { struct Inst * this = (struct Inst *)opaque; VAStatus status; status = vaCreateSurfaceGLX(this->vaDisplay, target, texture, ref); if (status != VA_STATUS_SUCCESS) { *ref = NULL; DEBUG_ERROR("vaCreateSurfaceGLX: %s", vaErrorStr(status)); return false; } return true; } static void lgd_h264_free_gl_texture(void * opaque, void * ref) { struct Inst * this = (struct Inst *)opaque; VAStatus status; status = vaDestroySurfaceGLX(this->vaDisplay, ref); if (status != VA_STATUS_SUCCESS) DEBUG_ERROR("vaDestroySurfaceGLX: %s", vaErrorStr(status)); } static bool lgd_h264_update_gl_texture(void * opaque, void * ref) { struct Inst * this = (struct Inst *)opaque; VAStatus status; // don't return anything until we have some data if (this->frameNum == 0) return true; status = vaCopySurfaceGLX( this->vaDisplay, ref, this->vaSurfaceID[this->lastSID], 0 ); if (status != VA_STATUS_SUCCESS) { DEBUG_ERROR("vaCopySurfaceGLX: %s", vaErrorStr(status)); return false; } return true; } const LG_Decoder LGD_H264 = { .name = "H.264", .create = lgd_h264_create, .destroy = lgd_h264_destroy, .initialize = lgd_h264_initialize, .deinitialize = lgd_h264_deinitialize, .get_out_format = lgd_h264_get_out_format, .get_frame_pitch = lgd_h264_get_frame_pitch, .get_frame_stride = lgd_h264_get_frame_stride, .decode = lgd_h264_decode, .get_buffer = lgd_h264_get_buffer, .has_gl = true, .init_gl_texture = lgd_h264_init_gl_texture, .free_gl_texture = lgd_h264_free_gl_texture, .update_gl_texture = lgd_h264_update_gl_texture };looking-glass-0+b1/client/decoders/src/null.c000066400000000000000000000074631351530562300212520ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/decoder.h" #include "common/debug.h" #include "common/memcpySSE.h" #include #include struct Inst { LG_RendererFormat format; const uint8_t * src; }; static bool lgd_null_create (void ** opaque); static void lgd_null_destroy (void * opaque); static bool lgd_null_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window); static void lgd_null_deinitialize (void * opaque); static LG_OutFormat lgd_null_get_out_format (void * opaque); static unsigned int lgd_null_get_frame_pitch (void * opaque); static unsigned int lgd_null_get_frame_stride(void * opaque); static bool lgd_null_decode (void * opaque, const uint8_t * src, size_t srcSize); static const uint8_t * lgd_null_get_buffer (void * opaque); static bool lgd_null_create(void ** opaque) { // 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)); return true; } static void lgd_null_destroy(void * opaque) { free(opaque); } static bool lgd_null_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window) { struct Inst * this = (struct Inst *)opaque; memcpy(&this->format, &format, sizeof(LG_RendererFormat)); return true; } static void lgd_null_deinitialize(void * opaque) { struct Inst * this = (struct Inst *)opaque; memset(this, 0, sizeof(struct Inst)); } static LG_OutFormat lgd_null_get_out_format(void * opaque) { struct Inst * this = (struct Inst *)opaque; switch(this->format.type) { case FRAME_TYPE_BGRA : return LG_OUTPUT_BGRA; case FRAME_TYPE_RGBA : return LG_OUTPUT_RGBA; case FRAME_TYPE_RGBA10: return LG_OUTPUT_RGBA10; default: DEBUG_ERROR("Unknown frame type"); return LG_OUTPUT_INVALID; } } static unsigned int lgd_null_get_frame_pitch(void * opaque) { struct Inst * this = (struct Inst *)opaque; return this->format.pitch; } static unsigned int lgd_null_get_frame_stride(void * opaque) { struct Inst * this = (struct Inst *)opaque; return this->format.stride; } static bool lgd_null_decode(void * opaque, const uint8_t * src, size_t srcSize) { struct Inst * this = (struct Inst *)opaque; this->src = src; return true; } static const uint8_t * lgd_null_get_buffer(void * opaque) { struct Inst * this = (struct Inst *)opaque; if (!this->src) return NULL; return this->src; } const LG_Decoder LGD_NULL = { .name = "NULL", .create = lgd_null_create, .destroy = lgd_null_destroy, .initialize = lgd_null_initialize, .deinitialize = lgd_null_deinitialize, .get_out_format = lgd_null_get_out_format, .get_frame_pitch = lgd_null_get_frame_pitch, .get_frame_stride = lgd_null_get_frame_stride, .decode = lgd_null_decode, .get_buffer = lgd_null_get_buffer };looking-glass-0+b1/client/decoders/src/yuv420.c000066400000000000000000000121631351530562300213420ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/decoder.h" #include "common/debug.h" #include "common/memcpySSE.h" #include #include #include #include struct Pixel { uint8_t b, g, r, a; }; struct Inst { LG_RendererFormat format; struct Pixel * pixels; unsigned int yBytes; }; static bool lgd_yuv420_create (void ** opaque); static void lgd_yuv420_destroy (void * opaque); static bool lgd_yuv420_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window); static void lgd_yuv420_deinitialize (void * opaque); static LG_OutFormat lgd_yuv420_get_out_format (void * opaque); static unsigned int lgd_yuv420_get_frame_pitch (void * opaque); static unsigned int lgd_yuv420_get_frame_stride(void * opaque); static bool lgd_yuv420_decode (void * opaque, const uint8_t * src, size_t srcSize); static const uint8_t * lgd_yuv420_get_buffer (void * opaque); static bool lgd_yuv420_create(void ** opaque) { // 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)); return true; } static void lgd_yuv420_destroy(void * opaque) { free(opaque); } static bool lgd_yuv420_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window) { struct Inst * this = (struct Inst *)opaque; memcpy(&this->format, &format, sizeof(LG_RendererFormat)); this->yBytes = format.width * format.height; this->pixels = malloc(sizeof(struct Pixel) * (format.width * format.height)); return true; } static void lgd_yuv420_deinitialize(void * opaque) { struct Inst * this = (struct Inst *)opaque; free(this->pixels); } static LG_OutFormat lgd_yuv420_get_out_format(void * opaque) { return LG_OUTPUT_BGRA; } static unsigned int lgd_yuv420_get_frame_pitch(void * opaque) { struct Inst * this = (struct Inst *)opaque; return this->format.width * 4; } static unsigned int lgd_yuv420_get_frame_stride(void * opaque) { struct Inst * this = (struct Inst *)opaque; return this->format.width; } static bool lgd_yuv420_decode(void * opaque, const uint8_t * src, size_t srcSize) { //FIXME: implement this properly using GLSL struct Inst * this = (struct Inst *)opaque; const unsigned int hw = this->format.width / 2; const unsigned int hp = this->yBytes / 4; for(size_t y = 0; y < this->format.height; ++y) for(size_t x = 0; x < this->format.width; ++x) { const unsigned int yoff = y * this->format.width + x; const unsigned int uoff = this->yBytes + ((y / 2) * hw + x / 2); const unsigned int voff = uoff + hp; float b = 1.164f * ((float)src[yoff] - 16.0f) + 2.018f * ((float)src[uoff] - 128.0f); float g = 1.164f * ((float)src[yoff] - 16.0f) - 0.813f * ((float)src[voff] - 128.0f) - 0.391f * ((float)src[uoff] - 128.0f); float r = 1.164f * ((float)src[yoff] - 16.0f) + 1.596f * ((float)src[voff] - 128.0f); #define CLAMP(x) (x < 0 ? 0 : (x > 255 ? 255 : x)) this->pixels[yoff].b = CLAMP(b); this->pixels[yoff].g = CLAMP(g); this->pixels[yoff].r = CLAMP(r); } return true; } static const uint8_t * lgd_yuv420_get_buffer(void * opaque) { struct Inst * this = (struct Inst *)opaque; return (uint8_t *)this->pixels; } bool lgd_yuv420_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref) { return false; } void lgd_yuv420_free_gl_texture(void * opaque, void * ref) { } bool lgd_yuv420_update_gl_texture(void * opaque, void * ref) { return false; } const LG_Decoder LGD_YUV420 = { .name = "YUV420", .create = lgd_yuv420_create, .destroy = lgd_yuv420_destroy, .initialize = lgd_yuv420_initialize, .deinitialize = lgd_yuv420_deinitialize, .get_out_format = lgd_yuv420_get_out_format, .get_frame_pitch = lgd_yuv420_get_frame_pitch, .get_frame_stride = lgd_yuv420_get_frame_stride, .decode = lgd_yuv420_decode, .get_buffer = lgd_yuv420_get_buffer, .has_gl = false, //FIXME: Implement this .init_gl_texture = lgd_yuv420_init_gl_texture, .free_gl_texture = lgd_yuv420_free_gl_texture, .update_gl_texture = lgd_yuv420_update_gl_texture };looking-glass-0+b1/client/fonts/000077500000000000000000000000001351530562300166745ustar00rootroot00000000000000looking-glass-0+b1/client/fonts/CMakeLists.txt000066400000000000000000000022051351530562300214330ustar00rootroot00000000000000cmake_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(SDL) 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+b1/client/fonts/SDL/000077500000000000000000000000001351530562300173165ustar00rootroot00000000000000looking-glass-0+b1/client/fonts/SDL/CMakeLists.txt000066400000000000000000000007241351530562300220610ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(font_SDL LANGUAGES C) find_package(PkgConfig) pkg_check_modules(FONT_SDL_PKGCONFIG REQUIRED SDL2_ttf fontconfig ) add_library(font_SDL STATIC src/sdl.c ) target_link_libraries(font_SDL ${FONT_SDL_PKGCONFIG_LIBRARIES} lg_common ) target_include_directories(font_SDL PUBLIC $ $ PRIVATE src ${FONT_SDL_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b1/client/fonts/SDL/src/000077500000000000000000000000001351530562300201055ustar00rootroot00000000000000looking-glass-0+b1/client/fonts/SDL/src/sdl.c000066400000000000000000000074421351530562300210420ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include "interface/font.h" #include "common/debug.h" #include #include static int g_initCount = 0; static FcConfig * g_fontConfig = NULL; struct Inst { TTF_Font * font; }; static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned int size) { if (g_initCount++ == 0) { if (TTF_Init() < 0) { DEBUG_ERROR("TTF_Init Failed"); return false; } g_fontConfig = FcInitLoadConfigAndFonts(); if (!g_fontConfig) { DEBUG_ERROR("FcInitLoadConfigAndFonts Failed"); return false; } } *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; if (!font_name) font_name = "FreeMono"; FcPattern * pat = FcNameParse((const FcChar8*)font_name); FcConfigSubstitute (g_fontConfig, pat, FcMatchPattern); FcDefaultSubstitute(pat); FcResult result; FcChar8 * file = NULL; FcPattern * font = FcFontMatch(g_fontConfig, pat, &result); if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch)) { this->font = TTF_OpenFont((char *)file, size); if (!this->font) { DEBUG_ERROR("TTL_OpenFont Failed"); return false; } } else { DEBUG_ERROR("Failed to locate the requested font: %s", font_name); return false; } FcPatternDestroy(pat); return true; } static void lgf_sdl_destroy(LG_FontObj opaque) { struct Inst * this = (struct Inst *)opaque; if (this->font) TTF_CloseFont(this->font); free(this); if (--g_initCount == 0) TTF_Quit(); } static LG_FontBitmap * lgf_sdl_render(LG_FontObj opaque, unsigned int fg_color, const char * text) { struct Inst * this = (struct Inst *)opaque; SDL_Surface * surface; SDL_Color color; color.r = (fg_color & 0xff000000) >> 24; color.g = (fg_color & 0x00ff0000) >> 16; color.b = (fg_color & 0x0000ff00) >> 8; color.a = (fg_color & 0x000000ff) >> 0; if (!(surface = TTF_RenderText_Blended(this->font, text, color))) { DEBUG_ERROR("Failed to render text: %s", TTF_GetError()); return NULL; } LG_FontBitmap * out = malloc(sizeof(LG_FontBitmap)); if (!out) { SDL_FreeSurface(surface); DEBUG_ERROR("Failed to allocate memory for font bitmap"); return NULL; } out->reserved = surface; out->width = surface->w; out->height = surface->h; out->bpp = surface->format->BytesPerPixel; out->pixels = surface->pixels; return out; } static void lgf_sdl_release(LG_FontObj opaque, LG_FontBitmap * font) { LG_FontBitmap * bitmap = (LG_FontBitmap *)font; SDL_FreeSurface(bitmap->reserved); free(bitmap); } struct LG_Font LGF_SDL = { .name = "SDL", .create = lgf_sdl_create, .destroy = lgf_sdl_destroy, .render = lgf_sdl_render, .release = lgf_sdl_release };looking-glass-0+b1/client/include/000077500000000000000000000000001351530562300171665ustar00rootroot00000000000000looking-glass-0+b1/client/include/interface/000077500000000000000000000000001351530562300211265ustar00rootroot00000000000000looking-glass-0+b1/client/include/interface/app.h000066400000000000000000000036741351530562300220710ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include typedef enum LG_MsgAlert { LG_ALERT_INFO , LG_ALERT_SUCCESS, LG_ALERT_WARNING, LG_ALERT_ERROR } LG_MsgAlert; typedef struct KeybindHandle * KeybindHandle; typedef void (*SuperEventFn)(SDL_Scancode key, 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, ...); /** * Register a handler for the + combination * @param key 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_release_keybind` when it is no longer required */ KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque); /** * Release an existing key binding * @param handle A pointer to the keybind handle to release, may be NULL */ void app_release_keybind(KeybindHandle * handle);looking-glass-0+b1/client/include/interface/clipboard.h000066400000000000000000000044521351530562300232430ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include #include 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 void (* LG_ClipboardReplyFn )(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size); typedef void (* LG_ClipboardRequestFn)(LG_ClipboardReplyFn replyFn, void * opaque); typedef void (* LG_ClipboardReleaseFn)(); typedef void (* LG_ClipboardNotifyFn)(LG_ClipboardData type); typedef void (* LG_ClipboardDataFn )(const LG_ClipboardData type, uint8_t * data, size_t size); typedef const char * (* LG_ClipboardGetName)(); typedef bool (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo, LG_ClipboardReleaseFn releaseFn, LG_ClipboardNotifyFn notifyFn, LG_ClipboardDataFn dataFn); typedef void (* LG_ClipboardFree)(); typedef void (* LG_ClipboardWMEvent)(SDL_SysWMmsg * msg); typedef void (* LG_ClipboardNotice)(LG_ClipboardRequestFn requestFn, LG_ClipboardData type); typedef void (* LG_ClipboardRelease)(); typedef void (* LG_ClipboardRequest)(LG_ClipboardData type); typedef struct LG_Clipboard { LG_ClipboardGetName getName; LG_ClipboardInit init; LG_ClipboardFree free; LG_ClipboardWMEvent wmevent; LG_ClipboardNotice notice; LG_ClipboardRelease release; LG_ClipboardRequest request; } LG_Clipboard;looking-glass-0+b1/client/include/interface/decoder.h000066400000000000000000000052241351530562300227070ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "renderer.h" #include #include #include #include typedef enum LG_OutFormat { LG_OUTPUT_INVALID, LG_OUTPUT_BGRA, LG_OUTPUT_RGBA, LG_OUTPUT_RGBA10, LG_OUTPUT_YUV420 } LG_OutFormat; typedef bool (* LG_DecoderCreate )(void ** opaque); typedef void (* LG_DecoderDestroy )(void * opaque); typedef bool (* LG_DecoderInitialize )(void * opaque, const LG_RendererFormat format, SDL_Window * window); typedef void (* LG_DecoderDeInitialize )(void * opaque); typedef LG_OutFormat (* LG_DecoderGetOutFormat )(void * opaque); typedef unsigned int (* LG_DecoderGetFramePitch )(void * opaque); typedef unsigned int (* LG_DecoderGetFrameStride)(void * opaque); typedef bool (* LG_DecoderDecode )(void * opaque, const uint8_t * src, size_t srcSize); typedef const uint8_t * (* LG_DecoderGetBuffer )(void * opaque); typedef bool (* LG_DecoderInitGLTexture )(void * opaque, GLenum target, GLuint texture, void ** ref); typedef void (* LG_DecoderFreeGLTexture )(void * opaque, void * ref); typedef bool (* LG_DecoderUpdateGLTexture)(void * opaque, void * ref); typedef struct LG_Decoder { // mandatory support const char * name; LG_DecoderCreate create; LG_DecoderDestroy destroy; LG_DecoderInitialize initialize; LG_DecoderDeInitialize deinitialize; LG_DecoderGetOutFormat get_out_format; LG_DecoderGetFramePitch get_frame_pitch; LG_DecoderGetFrameStride get_frame_stride; LG_DecoderDecode decode; LG_DecoderGetBuffer get_buffer; // optional support const bool has_gl; LG_DecoderInitGLTexture init_gl_texture; LG_DecoderFreeGLTexture free_gl_texture; LG_DecoderUpdateGLTexture update_gl_texture; } LG_Decoder;looking-glass-0+b1/client/include/interface/font.h000066400000000000000000000032021351530562300222420ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #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+b1/client/include/interface/renderer.h000066400000000000000000000075451351530562300231200ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include #include #include #include "app.h" #include "common/KVMFR.h" #define IS_LG_RENDERER_VALID(x) \ ((x)->get_name && \ (x)->create && \ (x)->initialize && \ (x)->deinitialize && \ (x)->on_resize && \ (x)->on_mouse_shape && \ (x)->on_mouse_event && \ (x)->on_alert && \ (x)->render_startup && \ (x)->render && \ (x)->update_fps) typedef struct LG_RendererParams { // TTF_Font * font; // TTF_Font * alertFont; bool showFPS; } LG_RendererParams; 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_RendererFormat; typedef struct LG_RendererRect { bool valid; int x; int y; unsigned int w; unsigned 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); typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags); typedef void (* LG_RendererDeInitialize)(void * opaque); typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect); typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data); typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y); typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const uint8_t * data); typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag); typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window); typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS); typedef struct LG_Renderer { LG_RendererGetName get_name; LG_RendererSetup setup; LG_RendererCreate create; LG_RendererInitialize initialize; LG_RendererDeInitialize deinitialize; LG_RendererOnResize on_resize; LG_RendererOnMouseShape on_mouse_shape; LG_RendererOnMouseEvent on_mouse_event; LG_RendererOnFrameEvent on_frame_event; LG_RendererOnAlert on_alert; LG_RendererRender render_startup; LG_RendererRender render; LG_RendererUpdateFPS update_fps; } LG_Renderer;looking-glass-0+b1/client/include/lg-decoders.h000066400000000000000000000021401351530562300215240ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "interface/decoder.h" extern const LG_Decoder LGD_NULL; extern const LG_Decoder LGD_YUV420; const LG_Decoder * LG_Decoders[] = { &LGD_NULL, &LGD_YUV420, NULL // end of array sentinal }; #define LG_DECODER_COUNT ((sizeof(LG_Decoders) / sizeof(LG_Decoder *)) - 1)looking-glass-0+b1/client/include/ll.h000066400000000000000000000023511351530562300177470ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include 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); unsigned int ll_count (struct ll * list); void ll_reset (struct ll * list); bool ll_walk (struct ll * list, void ** data);looking-glass-0+b1/client/include/utils.h000066400000000000000000000056461351530562300205120ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include #include static inline uint64_t microtime() { struct timespec time; clock_gettime(CLOCK_MONOTONIC_RAW, &time); return ((uint64_t)time.tv_sec * 1000000) + (time.tv_nsec / 1000); } static inline uint64_t nanotime() { struct timespec time; clock_gettime(CLOCK_MONOTONIC_RAW, &time); return ((uint64_t)time.tv_sec * 1e9) + time.tv_nsec; } static inline void nsleep(uint64_t ns) { const struct timespec ts = { .tv_sec = ns / 1e9, .tv_nsec = ns - ((ns / 1e9) * 1e9) }; nanosleep(&ts, NULL); } #ifdef ATOMIC_LOCKING #define LG_LOCK_MODE "Atomic" typedef volatile int LG_Lock; #define LG_LOCK_INIT(x) (x) = 0 #define LG_LOCK(x) while(__sync_lock_test_and_set(&(x), 1)) {nsleep(100);} #define LG_UNLOCK(x) __sync_lock_release(&x) #define LG_LOCK_FREE(x) #else #include #define LG_LOCK_MODE "Mutex" typedef SDL_mutex * LG_Lock; #define LG_LOCK_INIT(x) (x = SDL_CreateMutex()) #define LG_LOCK(x) SDL_LockMutex(x) #define LG_UNLOCK(x) SDL_UnlockMutex(x) #define LG_LOCK_FREE(x) SDL_DestroyMutex(x) #endif static inline uint32_t get_bit(const uint8_t * const base, size_t * const offset) { uint32_t out = ((*(base + (*offset >> 0x3))) >> (0x7 - (*offset & 0x7))) & 0x1; ++*offset; return out; } static inline uint32_t get_bits(const uint8_t * const base, size_t * const offset, const uint8_t bits) { uint32_t value = 0; for (int i = 0; i < bits; ++i) value |= (get_bit(base, offset) ? 1 : 0) << (bits - i - 1); return value; } static inline uint32_t decode_u_golomb(const uint8_t * const base, size_t * const offset) { uint32_t i = 0; while(get_bit(base, offset) == 0) ++i; return ((1 << i) - 1 + get_bits(base, offset, i)); } static inline int32_t decode_s_golomb(const uint8_t * const base, size_t * const offset) { const uint32_t g = decode_u_golomb(base, offset); return (g & 0x1) ? (g + 1) / 2 : -(g / 2); } // reads the specified file into a new buffer // the callee must free the buffer bool file_get_contents(const char * filename, char ** buffer, size_t * length);looking-glass-0+b1/client/parsers/000077500000000000000000000000001351530562300172225ustar00rootroot00000000000000looking-glass-0+b1/client/parsers/nal.c000066400000000000000000001027541351530562300201510ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "nal.h" #include "debug.h" #include "utils.h" #include #include #define DEBUG_NAL struct NAL { uint8_t primary_pic_type; bool primary_pic_type_valid; bool sps_valid; NAL_SPS sps; int32_t * sps_offset_for_ref_frame; uint32_t sps_num_ref_frames_in_pic_order_cnt_cycle; bool vui_valid; NAL_VUI vui; NAL_CPB * vui_nal_hrd_parameters_cpb; uint32_t vui_nal_hrd_parameters_cpb_size; NAL_CPB * vui_vcl_hrd_parameters_cpb; uint32_t vui_vcl_hrd_parameters_cpb_size; bool pps_valid; NAL_PPS pps; NAL_SLICE_GROUP * pps_slice_groups; uint32_t pps_slice_groups_size; uint32_t * pps_slice_group_id; uint32_t pps_slice_group_id_size; bool slice_valid; NAL_SLICE slice; NAL_PW_TABLE_L * slice_pred_weight_table_l0; uint32_t slice_pred_weight_table_l0_size; NAL_PW_TABLE_L * slice_pred_weight_table_l1; uint32_t slice_pred_weight_table_l1_size; }; bool nal_initialize(NAL * ptr) { *ptr = (NAL)malloc(sizeof(struct NAL)); memset(*ptr, 0, sizeof(struct NAL)); return true; } void nal_deinitialize(NAL this) { free(this->slice_pred_weight_table_l1); free(this->slice_pred_weight_table_l0); free(this->pps_slice_group_id); free(this->pps_slice_groups); free(this->sps_offset_for_ref_frame); free(this->vui_nal_hrd_parameters_cpb); free(this->vui_vcl_hrd_parameters_cpb); free(this); } static bool parse_nal_hrd( NAL_HRD * const hrd, NAL_CPB ** cpb, uint32_t * cpb_size, const uint8_t * src, size_t size, size_t * const offset) { hrd->cpb_cnt_minus1 = decode_u_golomb(src, offset); hrd->bit_rate_scale = get_bits(src, offset, 4); hrd->cpb_size_scale = get_bits(src, offset, 4); if (*cpb_size < hrd->cpb_size_scale) { *cpb = realloc(*cpb, hrd->cpb_size_scale * sizeof(NAL_CPB)); *cpb_size = hrd->cpb_size_scale; } hrd->cpb = *cpb; for(uint32_t i = 0; i < hrd->cpb_size_scale; ++i) { hrd->cpb[i].bit_rate_value_minus1 = decode_u_golomb(src, offset); hrd->cpb[i].cpb_size_value_minus1 = decode_u_golomb(src, offset); hrd->cpb[i].cbr_flag = get_bit(src, offset); } hrd->initial_cpb_removal_delay_length_minus1 = get_bits(src, offset, 5); hrd->cpb_removal_delay_length_minus1 = get_bits(src, offset, 5); hrd->dpb_output_delay_length_minus1 = get_bits(src, offset, 5); hrd->time_offset_length = get_bits(src, offset, 5); return true; } static bool parse_nal_vui(NAL this, const uint8_t * src, size_t size, size_t * const offset) { NAL_VUI * vui = &this->vui; memset(vui, 0, sizeof(NAL_VUI)); vui->aspect_ratio_info_present_flag = get_bit(src, offset); if (vui->aspect_ratio_info_present_flag) { vui->aspect_ratio_idc = get_bits(src, offset, 8); if (vui->aspect_ratio_idc == IDC_VUI_ASPECT_RATIO_EXTENDED_SAR) { vui->sar_width = get_bits(src, offset, 16); vui->sar_height = get_bits(src, offset, 16); } } vui->overscan_info_present_flag = get_bit(src, offset); if (vui->overscan_info_present_flag) vui->overscan_appropriate_flag = get_bit(src, offset); vui->video_signal_type_present_flag = get_bit(src, offset); if (vui->video_signal_type_present_flag) { vui->video_format = get_bits(src, offset, 3); vui->video_full_range_flag = get_bit(src, offset); vui->colour_description_present_flag = get_bit(src, offset); if (vui->colour_description_present_flag) { vui->colour_primaries = get_bits(src, offset, 8); vui->transfer_characteristics = get_bits(src, offset, 8); vui->matrix_coefficients = get_bits(src, offset, 8); } } vui->chroma_loc_info_present_flag = get_bit(src, offset); if (vui->chroma_loc_info_present_flag) { vui->chroma_sample_loc_type_top_field = decode_u_golomb(src, offset); vui->chroma_sample_loc_type_bottom_field = decode_u_golomb(src, offset); } vui->timing_info_present_flag = get_bit(src, offset); if (vui->timing_info_present_flag) { vui->num_units_in_tick = get_bits(src, offset, 32); vui->time_scale = get_bits(src, offset, 32); vui->fixed_frame_rate_flag = get_bit(src, offset); } vui->nal_hrd_parameters_present_flag = get_bit(src, offset); if (vui->nal_hrd_parameters_present_flag) if (!parse_nal_hrd( &vui->nal_hrd_parameters, &this->vui_nal_hrd_parameters_cpb, &this->vui_nal_hrd_parameters_cpb_size, src, size, offset)) return false; vui->vcl_hrd_parameters_present_flag = get_bit(src, offset); if (vui->vcl_hrd_parameters_present_flag) if (!parse_nal_hrd( &vui->vcl_hrd_parameters, &this->vui_vcl_hrd_parameters_cpb, &this->vui_vcl_hrd_parameters_cpb_size, src, size, offset)) return false; if (vui->nal_hrd_parameters_present_flag || vui->vcl_hrd_parameters_present_flag) vui->low_delay_hrd_flag = get_bit(src, offset); vui->pic_struct_present_flag = get_bit(src, offset); vui->bitstream_restriction_flag = get_bit(src, offset); if (vui->bitstream_restriction_flag) { vui->motion_vectors_over_pic_boundaries_flag = get_bit(src, offset); vui->max_bytes_per_pic_denom = decode_u_golomb(src, offset); vui->max_bits_per_mb_denom = decode_u_golomb(src, offset); vui->log2_max_mv_length_horizontal = decode_u_golomb(src, offset); vui->log2_max_mv_length_vertical = decode_u_golomb(src, offset); vui->num_reorder_frames = decode_u_golomb(src, offset); vui->max_dec_frame_buffering = decode_u_golomb(src, offset); } return true; } static bool parse_nal_trailing_bits(NAL this, const uint8_t * src, size_t size, size_t * const offset) { if (!get_bit(src, offset)) { DEBUG_ERROR("Missing stop bit"); return false; } // byte align *offset = (*offset + 0x7) & ~0x7; return true; } static bool parse_nal_sps(NAL this, const uint8_t * src, size_t size, size_t * const offset) { this->sps_valid = false; memset(&this->sps, 0, sizeof(this->sps)); this->sps.profile_idc = get_bits(src, offset, 8); if ((this->sps.profile_idc != IDC_PROFILE_BASELINE) && (this->sps.profile_idc != IDC_PROFILE_MAIN ) && (this->sps.profile_idc != IDC_PROFILE_EXTENDED) && (this->sps.profile_idc != IDC_PROFILE_HP ) && (this->sps.profile_idc != IDC_PROFILE_Hi10P ) && (this->sps.profile_idc != IDC_PROFILE_Hi422 ) && (this->sps.profile_idc != IDC_PROFILE_Hi444 ) && (this->sps.profile_idc != IDC_PROFILE_CAVLC444)) { DEBUG_ERROR("Invalid profile IDC (%d) encountered", this->sps.profile_idc); return false; } this->sps.constraint_set_flags[0] = get_bit(src, offset); this->sps.constraint_set_flags[1] = get_bit(src, offset); this->sps.constraint_set_flags[2] = get_bit(src, offset); *offset += 5; this->sps.level_idc = get_bits(src, offset, 8); this->sps.seq_parameter_set_id = decode_u_golomb(src, offset); if ((this->sps.profile_idc == IDC_PROFILE_HP ) || (this->sps.profile_idc == IDC_PROFILE_Hi10P ) || (this->sps.profile_idc == IDC_PROFILE_Hi422 ) || (this->sps.profile_idc == IDC_PROFILE_Hi444 ) || (this->sps.profile_idc == IDC_PROFILE_CAVLC444)) { this->sps.chroma_format_idc = decode_u_golomb(src, offset); if (this->sps.chroma_format_idc == IDC_CHROMA_FORMAT_YUV444) this->sps.seperate_colour_plane_flag = get_bit(src, offset); this->sps.bit_depth_luma_minus8 = decode_u_golomb(src, offset); this->sps.bit_depth_chroma_minus8 = decode_u_golomb(src, offset); this->sps.lossless_qpprime_y_zero_flag = get_bit(src, offset); this->sps.seq_scaling_matrix_present_flag = get_bit(src, offset); if (this->sps.seq_scaling_matrix_present_flag) { const int cnt = this->sps.chroma_format_idc == IDC_CHROMA_FORMAT_YUV444 ? 12 : 8; for(int i = 0; i < cnt; ++i) this->sps.seq_scaling_list_present_flag[i] = get_bit(src, offset); } } else this->sps.chroma_format_idc = IDC_CHROMA_FORMAT_YUV420; this->sps.log2_max_frame_num_minus4 = decode_u_golomb(src, offset); this->sps.pic_order_cnt_type = decode_u_golomb(src, offset); if (this->sps.pic_order_cnt_type == 0) this->sps.log2_max_pic_order_cnt_lsb_minus4 = decode_u_golomb(src, offset); else { if (this->sps.pic_order_cnt_type == 1) { this->sps.delta_pic_order_always_zero_flag = get_bit(src, offset); this->sps.offset_for_non_ref_pic = decode_s_golomb(src, offset); this->sps.offset_for_top_to_bottom_field = decode_s_golomb(src, offset); this->sps.num_ref_frames_in_pic_order_cnt_cycle = decode_u_golomb(src, offset); if (this->sps.num_ref_frames_in_pic_order_cnt_cycle > this->sps_num_ref_frames_in_pic_order_cnt_cycle) { this->sps_offset_for_ref_frame = realloc( this->sps_offset_for_ref_frame, this->sps.num_ref_frames_in_pic_order_cnt_cycle * sizeof(int32_t) ); this->sps_num_ref_frames_in_pic_order_cnt_cycle = this->sps.num_ref_frames_in_pic_order_cnt_cycle; } this->sps.offset_for_ref_frame = this->sps_offset_for_ref_frame; for(uint32_t i = 0; i < this->sps.num_ref_frames_in_pic_order_cnt_cycle; ++i) this->sps.offset_for_ref_frame[i] = decode_s_golomb(src, offset); } } this->sps.num_ref_frames = decode_u_golomb(src, offset); this->sps.gaps_in_frame_num_value_allowed_flag = get_bit(src, offset); this->sps.pic_width_in_mbs_minus1 = decode_u_golomb(src, offset); this->sps.pic_height_in_map_units_minus1 = decode_u_golomb(src, offset); this->sps.frame_mbs_only_flag = get_bit(src, offset); if (!this->sps.frame_mbs_only_flag) this->sps.mb_adaptive_frame_field_flag = get_bit(src, offset); this->sps.direct_8x8_inference_flag = get_bit(src, offset); this->sps.frame_cropping_flag = get_bit(src, offset); if (this->sps.frame_cropping_flag) { this->sps.frame_crop_left_offset = decode_u_golomb(src, offset); this->sps.frame_crop_right_offset = decode_u_golomb(src, offset); this->sps.frame_crop_top_offset = decode_u_golomb(src, offset); this->sps.frame_crop_bottom_offset = decode_u_golomb(src, offset); } this->sps.vui_parameters_present_flag = get_bit(src, offset); #ifdef DEBUG_NAL DEBUG_INFO("SPS\n" "profile_idc : %u\n" "constraint_set_flags : %u %u %u\n" "level_idc : %u\n" "sec_parameter_set_id : %u\n" "chroma_format_idc : %u\n" "seperate_colour_plane_flag : %u\n" "bit_depth_luma_minus8 : %u\n" "bit_depth_chroma_minus8 : %u\n" "lossless_qpprime_y_zero_flag : %u\n" "seq_scaling_matrix_present_flag : %u\n" "log2_max_frame_num_minus4 : %u\n" "pic_order_cnt_type : %u\n" "log2_max_pic_order_cnt_lsb_minus4 : %u\n" "delta_pic_order_always_zero_flag : %u\n" "offset_for_non_ref_pic : %d\n" "offset_for_top_to_bottom_field : %d\n" "num_ref_frames_in_pic_order_cnt_cycle: %u\n" "num_ref_frames : %u\n" "gaps_in_frame_num_value_allowed_flag : %u\n" "pic_width_in_mbs_minus1 : %3u (%u)\n" "pic_height_in_map_units_minus1 : %3u (%u)\n" "frame_mbs_only_flag : %u\n" "mb_adaptive_frame_field_flag : %u\n" "direct_8x8_inference_flag : %u\n" "frame_cropping_flag : %u\n" "frame_crop_left_offset : %u\n" "frame_crop_right_offset : %u\n" "frame_crop_top_offset : %u\n" "frame_crop_bottom_offset : %u\n" "vui_parameters_present_flag : %u", this->sps.profile_idc, this->sps.constraint_set_flags[0], this->sps.constraint_set_flags[1], this->sps.constraint_set_flags[2], this->sps.level_idc, this->sps.seq_parameter_set_id, this->sps.chroma_format_idc, this->sps.seperate_colour_plane_flag, this->sps.bit_depth_luma_minus8, this->sps.bit_depth_chroma_minus8, this->sps.lossless_qpprime_y_zero_flag, this->sps.seq_scaling_matrix_present_flag, this->sps.log2_max_frame_num_minus4, this->sps.pic_order_cnt_type, this->sps.log2_max_pic_order_cnt_lsb_minus4, this->sps.delta_pic_order_always_zero_flag, this->sps.offset_for_non_ref_pic, this->sps.offset_for_top_to_bottom_field, this->sps.num_ref_frames_in_pic_order_cnt_cycle, this->sps.num_ref_frames, this->sps.gaps_in_frame_num_value_allowed_flag, this->sps.pic_width_in_mbs_minus1 , (this->sps.pic_width_in_mbs_minus1 + 1) * 16, this->sps.pic_height_in_map_units_minus1, (this->sps.pic_height_in_map_units_minus1 + 1) * 16, this->sps.frame_mbs_only_flag, this->sps.mb_adaptive_frame_field_flag, this->sps.direct_8x8_inference_flag, this->sps.frame_cropping_flag, this->sps.frame_crop_left_offset, this->sps.frame_crop_right_offset, this->sps.frame_crop_top_offset, this->sps.frame_crop_bottom_offset, this->sps.vui_parameters_present_flag ); #endif if (this->sps.vui_parameters_present_flag) { if (!parse_nal_vui(this, src, size, offset)) return false; this->vui_valid = true; } if (!parse_nal_trailing_bits(this, src, size, offset)) return false; this->sps_valid = true; return true; } static bool parse_nal_pps(NAL this, const uint8_t * src, size_t size, size_t * const offset) { NAL_PPS * pps = &this->pps; this->pps_valid = false; memset(pps, 0, sizeof(NAL_PPS)); pps->pic_parameter_set_id = decode_u_golomb(src, offset); pps->seq_parameter_set_id = decode_u_golomb(src, offset); pps->entropy_coding_mode_flag = get_bit(src, offset); pps->pic_order_present_flag = get_bit(src, offset); pps->num_slice_groups_minus1 = decode_u_golomb(src, offset); if (pps->num_slice_groups_minus1 > 0) { pps->slice_group_map_type = decode_u_golomb(src, offset); if (pps->slice_group_map_type == 0 || pps->slice_group_map_type == 2) { if (this->pps_slice_groups_size < pps->num_slice_groups_minus1 + 1) { this->pps_slice_groups_size = pps->num_slice_groups_minus1 + 1; this->pps_slice_groups = (NAL_SLICE_GROUP *)realloc( this->pps_slice_groups, this->pps_slice_groups_size * sizeof(NAL_SLICE_GROUP)); } pps->slice_groups = this->pps_slice_groups; memset(pps->slice_groups, 0, (pps->num_slice_groups_minus1 + 1) * sizeof(NAL_SLICE_GROUP)); if (pps->slice_group_map_type == 0) { for(uint32_t group = 0; group <= pps->num_slice_groups_minus1; ++group) pps->slice_groups[group].t0.run_length_minus1 = decode_u_golomb(src, offset); } else { for(uint32_t group = 0; group < pps->num_slice_groups_minus1; ++group) { pps->slice_groups[group].t2.top_left = decode_u_golomb(src, offset); pps->slice_groups[group].t2.bottom_right = decode_u_golomb(src, offset); } } } else { if (pps->slice_group_map_type == 3 || pps->slice_group_map_type == 4 || pps->slice_group_map_type == 5) { pps->slice_group_change_direction_flag = get_bit(src, offset); pps->slice_group_change_rate_minus1 = decode_u_golomb(src, offset); } else { if (pps->slice_group_map_type == 6) { pps->pic_size_in_map_units_minus1 = decode_u_golomb(src, offset); uint32_t slice_groups = pps->pic_size_in_map_units_minus1 + 1; uint32_t bits = 0; if ((slice_groups & (slice_groups - 1)) != 0) ++slice_groups; while(slice_groups > 0) { slice_groups >>= 1; ++bits; } if (this->pps_slice_group_id_size < pps->pic_size_in_map_units_minus1 + 1) { this->pps_slice_group_id_size = pps->pic_size_in_map_units_minus1 + 1; this->pps_slice_group_id = realloc(this->pps_slice_group_id, this->pps_slice_group_id_size * sizeof(uint32_t)); } pps->slice_group_id = this->pps_slice_group_id; for(uint32_t group = 0; group <= pps->pic_size_in_map_units_minus1; ++group) pps->slice_group_id[group] = get_bits(src, offset, bits); } else { DEBUG_ERROR("Invalid slice_group_map_type: %d", pps->slice_group_map_type); return false; } } } } pps->num_ref_idx_l0_active_minus1 = decode_u_golomb(src, offset); pps->num_ref_idx_l1_active_minus1 = decode_u_golomb(src, offset); pps->weighted_pred_flag = get_bit(src, offset); pps->weighted_bipred_idc = get_bits(src, offset, 2); pps->pic_init_qp_minus26 = decode_s_golomb(src, offset); pps->pic_init_qs_minus26 = decode_s_golomb(src, offset); pps->chroma_qp_index_offset = decode_s_golomb(src, offset); pps->deblocking_filter_control_present_flag = get_bit(src, offset); pps->constrained_intra_pred_flag = get_bit(src, offset); pps->redundant_pic_cnt_present_flag = get_bit(src, offset); if (pps->num_ref_idx_l0_active_minus1 + 1 > this->slice_pred_weight_table_l0_size) { this->slice_pred_weight_table_l0_size = pps->num_ref_idx_l0_active_minus1 + 1; this->slice_pred_weight_table_l0 = realloc(this->slice_pred_weight_table_l0, this->slice_pred_weight_table_l0_size * sizeof(NAL_PW_TABLE_L)); } if (pps->num_ref_idx_l1_active_minus1 + 1 > this->slice_pred_weight_table_l1_size) { this->slice_pred_weight_table_l1_size = pps->num_ref_idx_l1_active_minus1 + 1; this->slice_pred_weight_table_l1 = realloc(this->slice_pred_weight_table_l1, this->slice_pred_weight_table_l1_size * sizeof(NAL_PW_TABLE_L)); } const bool extraData = get_bit(src, offset) == 0; --*offset; if (extraData) { pps->transform_8x8_mode_flag = get_bit(src, offset); pps->pic_scaling_matrix_present_flag = get_bit(src, offset); if (pps->pic_scaling_matrix_present_flag) { //TODO } pps->second_chroma_qp_index_offset = decode_s_golomb(src, offset); } #ifdef DEBUG_NAL DEBUG_INFO("PPS:\n" "pic_parameter_set_id : %u\n" "seq_parameter_set_id : %u\n" "entropy_coding_mode_flag : %u\n" "pic_order_present_flag : %u\n" "num_slice_groups_minus1 : %u\n" "slice_group_map_type : %u\n" "slice_group_change_direction_flag : %u\n" "slice_group_change_rate_minus1 : %u\n" "pic_size_in_map_units_minus1 : %u\n" "num_ref_idx_l0_active_minus1 : %u\n" "num_ref_idx_l1_active_minus1 : %u\n" "weighted_pred_flag : %u\n" "weighted_bipred_idc : %u\n" "pic_init_qp_minus26 : %d\n" "pic_init_qs_minus26 : %d\n" "chroma_qp_index_offset : %d\n" "deblocking_filter_control_present_flag: %u\n" "constrained_intra_pred_flag : %u\n" "redundant_pic_cnt_present_flag : %u\n" "transform_8x8_mode_flag : %u\n" "pic_scaling_matrix_present_flag : %u\n" "second_chroma_qp_index_offset : %u", pps->pic_parameter_set_id, pps->seq_parameter_set_id, pps->entropy_coding_mode_flag, pps->pic_order_present_flag, pps->num_slice_groups_minus1, pps->slice_group_map_type, pps->slice_group_change_direction_flag, pps->slice_group_change_rate_minus1, pps->pic_size_in_map_units_minus1, pps->num_ref_idx_l0_active_minus1, pps->num_ref_idx_l1_active_minus1, pps->weighted_pred_flag, pps->weighted_bipred_idc, pps->pic_init_qp_minus26, pps->pic_init_qs_minus26, pps->chroma_qp_index_offset, pps->deblocking_filter_control_present_flag, pps->constrained_intra_pred_flag, pps->redundant_pic_cnt_present_flag, pps->transform_8x8_mode_flag, pps->pic_scaling_matrix_present_flag, pps->second_chroma_qp_index_offset ); #endif if (!parse_nal_trailing_bits(this, src, size, offset)) return false; this->pps_valid = true; return true; } static bool parse_nal_ref_pic_list_reordering(NAL this, const uint8_t * src, size_t size, size_t * const offset) { NAL_SLICE * slice = &this->slice; NAL_RPL_REORDER * rpl = &this->slice.ref_pic_list_reordering; if (slice->slice_type != NAL_SLICE_TYPE_I && slice->slice_type != NAL_SLICE_TYPE_SI) { rpl->ref_pic_list_reordering_flag_l0 = get_bit(src, offset); if(rpl->ref_pic_list_reordering_flag_l0) { int index = 0; NAL_RPL_REORDER_L * l; do { if (index > 2) { DEBUG_ERROR("too many reorder records"); return false; } l = &rpl->l0[index++]; l->valid = true; l->reordering_of_pic_nums_idc = decode_u_golomb(src, offset); if (l->reordering_of_pic_nums_idc == 0 || l->reordering_of_pic_nums_idc == 1) l->abs_diff_pic_num_minus1 = decode_u_golomb(src, offset); else if (l->reordering_of_pic_nums_idc == 2) l->long_term_pic_num = decode_u_golomb(src, offset); } while(l->reordering_of_pic_nums_idc != 3); } } if (slice->slice_type == NAL_SLICE_TYPE_B) { rpl->ref_pic_list_reordering_flag_l1 = get_bit(src, offset); if (rpl->ref_pic_list_reordering_flag_l1) { int index = 0; NAL_RPL_REORDER_L * l; do { if (index > 2) { DEBUG_ERROR("too many reorder records"); return false; } l = &rpl->l1[index++]; l->valid = true; l->reordering_of_pic_nums_idc = decode_u_golomb(src, offset); if (l->reordering_of_pic_nums_idc == 0 || l->reordering_of_pic_nums_idc == 1) l->abs_diff_pic_num_minus1 = decode_u_golomb(src, offset); else if (l->reordering_of_pic_nums_idc == 2) l->long_term_pic_num = decode_u_golomb(src, offset); } while(l->reordering_of_pic_nums_idc != 3); } } return true; } static bool parse_pred_weight_table(NAL this, const uint8_t * src, size_t size, size_t * const offset) { NAL_SLICE * slice = &this->slice; NAL_PW_TABLE * tbl = &this->slice.pred_weight_table; tbl->luma_log2_weight_denom = decode_u_golomb(src, offset); if (this->sps.chroma_format_idc != 0) tbl->chroma_log2_weight_denom = decode_u_golomb(src, offset); for(uint32_t i = 0; i <= this->pps.num_ref_idx_l0_active_minus1; ++i) { NAL_PW_TABLE_L * l = &tbl->l0[i]; tbl->luma_weight_flag[0] = get_bit(src, offset); if (tbl->luma_weight_flag[0]) { l->luma_weight = decode_s_golomb(src, offset); l->luma_offset = decode_s_golomb(src, offset); } if (this->sps.chroma_format_idc != 0) { tbl->chroma_weight_flag[0] = get_bit(src, offset); if (tbl->chroma_weight_flag[0]) for(int j = 0; j < 2; ++j) { l->chroma_weight[j] = decode_s_golomb(src, offset); l->chroma_offset[j] = decode_s_golomb(src, offset); } } } if (slice->slice_type == NAL_SLICE_TYPE_B) { for(uint32_t i = 0; i <= this->pps.num_ref_idx_l1_active_minus1; ++i) { NAL_PW_TABLE_L * l = &tbl->l1[i]; tbl->luma_weight_flag[1] = get_bit(src, offset); if (tbl->luma_weight_flag[1]) { l->luma_weight = decode_s_golomb(src, offset); l->luma_offset = decode_s_golomb(src, offset); } if (this->sps.chroma_format_idc != 0) { tbl->chroma_weight_flag[1] = get_bit(src, offset); if (tbl->chroma_weight_flag[1]) for(int j = 0; j < 2; ++j) { l->chroma_weight[j] = decode_s_golomb(src, offset); l->chroma_offset[j] = decode_s_golomb(src, offset); } } } } return true; } static bool parse_dec_ref_pic_marking( NAL this, const uint8_t ref_unit_type, const uint8_t * src, size_t size, size_t * const offset ) { NAL_RP_MARKING * m = &this->slice.dec_ref_pic_marking; if (ref_unit_type == 5) { m->no_output_of_prior_pics_flag = get_bit(src, offset); m->long_term_reference_flag = get_bit(src, offset); } else { m->adaptive_ref_pic_marking_mode_flag = get_bit(src, offset); if (m->adaptive_ref_pic_marking_mode_flag) { uint32_t op; do { op = decode_u_golomb(src, offset); if (op == 1 || op == 3) m->difference_of_pic_nums_minus1 = decode_u_golomb(src, offset); if (op == 2) m->long_term_pic_num = decode_u_golomb(src, offset); if (op == 3 || op == 6) m->long_term_frame_idx = decode_u_golomb(src, offset); if (op == 4) m->max_long_term_frame_idx_plus1 = decode_u_golomb(src, offset); } while (op != 0); } } return true; } static bool parse_nal_coded_slice( NAL this, const uint8_t ref_idc, const uint8_t ref_unit_type, const uint8_t * src, size_t size, size_t * const offset ) { if (!this->sps_valid || !this->pps_valid) return false; NAL_SLICE * slice = &this->slice; memset(slice, 0, sizeof(NAL_SLICE)); slice->nal_ref_idc = ref_idc; slice->first_mb_in_slice = decode_u_golomb(src, offset); slice->slice_type = decode_u_golomb(src, offset); slice->pic_parameter_set_id = decode_u_golomb(src, offset); slice->frame_num = get_bits(src, offset, this->sps.log2_max_frame_num_minus4 + 4); slice->pred_weight_table.l0 = this->slice_pred_weight_table_l0; slice->pred_weight_table.l1 = this->slice_pred_weight_table_l1; if (!this->sps.frame_mbs_only_flag) { slice->field_pic_flag = get_bit(src, offset); if (slice->field_pic_flag) slice->bottom_field_flag = get_bit(src, offset); } if (ref_unit_type == 5) slice->idr_pic_id = decode_u_golomb(src, offset); if (this->sps.pic_order_cnt_type == 0) { slice->pic_order_cnt_lsb = get_bits(src, offset, this->sps.log2_max_pic_order_cnt_lsb_minus4 + 4); if (this->pps.pic_order_present_flag && !slice->field_pic_flag) slice->delta_pic_order_cnt_bottom = decode_s_golomb(src, offset); } else if (this->sps.pic_order_cnt_type == 1 && !this->sps.delta_pic_order_always_zero_flag) { slice->delta_pic_order_cnt[0] = decode_s_golomb(src, offset); if (this->pps.pic_order_present_flag && !slice->field_pic_flag) slice->delta_pic_order_cnt[1] = decode_s_golomb(src, offset); } if (this->pps.redundant_pic_cnt_present_flag) slice->redundant_pic_cnt = decode_u_golomb(src, offset); if (slice->slice_type == NAL_SLICE_TYPE_B) slice->direct_spatial_mv_pred_flag = get_bit(src, offset); if (slice->slice_type == NAL_SLICE_TYPE_P || slice->slice_type == NAL_SLICE_TYPE_SP || slice->slice_type == NAL_SLICE_TYPE_B) { slice->num_ref_idx_active_override_flag = get_bit(src, offset); if (slice->num_ref_idx_active_override_flag) { slice->num_ref_idx_l0_active_minus1 = decode_u_golomb(src, offset); if (slice->slice_type == NAL_SLICE_TYPE_B) slice->num_ref_idx_l1_active_minus1 = decode_u_golomb(src, offset); } } if (!parse_nal_ref_pic_list_reordering(this, src, size, offset)) return false; if ((this->pps.weighted_pred_flag && (slice->slice_type == NAL_SLICE_TYPE_P || slice->slice_type == NAL_SLICE_TYPE_SP)) || (this->pps.weighted_bipred_idc == 1 && slice->slice_type == NAL_SLICE_TYPE_B)) { if (!parse_pred_weight_table(this, src, size, offset)) return false; } if (ref_idc != 0) if (!parse_dec_ref_pic_marking(this, ref_unit_type, src, size, offset)) return false; if (this->pps.entropy_coding_mode_flag && slice->slice_type != NAL_SLICE_TYPE_I && slice->slice_type != NAL_SLICE_TYPE_SI) slice->cabac_init_idc = decode_u_golomb(src, offset); slice->slice_qp_delta = decode_s_golomb(src, offset); if (slice->slice_type == NAL_SLICE_TYPE_SP || slice->slice_type == NAL_SLICE_TYPE_SI) { if (slice->slice_type == NAL_SLICE_TYPE_SP) slice->sp_for_switch_flag = get_bit(src, offset); slice->slice_qs_delta = decode_s_golomb(src, offset); } if (this->pps.deblocking_filter_control_present_flag) { slice->disable_deblocking_filter_idc = decode_u_golomb(src, offset); if (slice->disable_deblocking_filter_idc != 1) { slice->slice_alpha_c0_offset_div2 = decode_s_golomb(src, offset); slice->slice_beta_offset_div2 = decode_s_golomb(src, offset); } } if (this->pps.num_slice_groups_minus1 > 0 && this->pps.slice_group_map_type >= 3 && this->pps.slice_group_map_type <= 5) slice->slice_group_change_cycle = decode_u_golomb(src, offset); #ifdef DEBUG_NAL DEBUG_INFO("SLICE:\n" "first_mb_in_slice : %u\n" "slice_type : %u\n" "pic_parameter_set_id : %u\n" "frame_num : %u\n" "field_pic_flag : %u\n" "bottom_field_flag : %u\n" "idr_pic_id : %u\n" "pic_order_cnt_lsb : %u\n" "delta_pic_order_cnt_bottom : %d\n" "delta_pic_order_cnt[0] : %d\n" "delta_pic_order_cnt[1] : %d\n" "redundant_pic_cnt : %u\n" "direct_spatial_mv_pred_flag : %u\n" "num_ref_idx_active_override_flag: %u\n" "num_ref_idx_l0_active_minus1 : %u\n" "num_ref_idx_l1_active_minus1 : %u", slice->first_mb_in_slice, slice->slice_type, slice->pic_parameter_set_id, slice->frame_num, slice->field_pic_flag, slice->bottom_field_flag, slice->idr_pic_id, slice->pic_order_cnt_lsb, slice->delta_pic_order_cnt_bottom, slice->delta_pic_order_cnt[0], slice->delta_pic_order_cnt[1], slice->redundant_pic_cnt, slice->direct_spatial_mv_pred_flag, slice->num_ref_idx_active_override_flag, slice->num_ref_idx_l0_active_minus1, slice->num_ref_idx_l1_active_minus1 ); #endif if (!parse_nal_trailing_bits(this, src, size, offset)) return false; this->slice_valid = true; return true; } bool nal_parse(NAL this, const uint8_t * src, size_t size, size_t * seek) { #ifdef DEBUG_NAL static FILE * fd = NULL; if (!fd) fd = fopen("/tmp/stream.h264", "w"); fwrite(src, size, 1, fd); fflush(fd); #endif *seek = 0; for(size_t i = 0; i < size - 4; ++i) { if (src[i++] != 0 || src[i++] != 0) break; if (src[i] == 0) ++i; if (src[i++] != 1) break; size_t offset = i << 3; #ifdef DEBUG_NAL DEBUG_INFO("nal @ %lu (%lu)", *seek, offset); #endif // ensure the forbidden zero bit is not set if (get_bit(src, &offset) != 0) { DEBUG_ERROR("forbidden_zero_bit is set"); return false; } uint8_t ref_idc = get_bits(src, &offset, 2); uint8_t ref_unit_type = get_bits(src, &offset, 5); DEBUG_INFO("ref idc: %d, ref unit type: %d", ref_idc, ref_unit_type); switch(ref_unit_type) { case NAL_TYPE_CODED_SLICE_IDR: case NAL_TYPE_CODED_SLICE_NON_IDR: case NAL_TYPE_CODED_SLICE_AUX: if (!parse_nal_coded_slice(this, ref_idc, ref_unit_type, src, size, &offset)) return false; break; case NAL_TYPE_AUD: { this->primary_pic_type = get_bits(src, &offset, 3); this->primary_pic_type_valid = true; if (!parse_nal_trailing_bits(this, src, size, &offset)) return false; break; } case NAL_TYPE_SPS: if (!parse_nal_sps(this, src, size, &offset)) return false; break; case NAL_TYPE_PPS: if (!parse_nal_pps(this, src, size, &offset)) return false; break; default: DEBUG_ERROR("Unknown NAL ref unit type: %d", ref_unit_type); return false; } i = offset >> 3; *seek = i; } return true; } bool nal_get_sps(NAL this, const NAL_SPS ** sps) { if (!this->sps_valid) return false; *sps = &this->sps; return true; } bool nal_get_primary_picture_type(NAL this, uint8_t * pic_type) { if (!this->primary_pic_type_valid) return false; *pic_type = this->primary_pic_type; return true; } bool nal_get_pps(NAL this, const NAL_PPS ** pps) { if (!this->pps_valid) return false; *pps = &this->pps; return true; } bool nal_get_slice(NAL this, const NAL_SLICE ** slice) { if (!this->slice_valid) return false; *slice = &this->slice; return true; }looking-glass-0+b1/client/parsers/nal.h000066400000000000000000000224121351530562300201460ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #define NAL_TYPE_CODED_SLICE_NON_IDR 1 #define NAL_TYPE_CODED_SLICE_DATA_PARTITION_A 2 #define NAL_TYPE_CODED_SLICE_DATA_PARTITION_B 3 #define NAL_TYPE_CODED_SLICE_DATA_PARTITION_C 4 #define NAL_TYPE_CODED_SLICE_IDR 5 #define NAL_TYPE_SPS 7 #define NAL_TYPE_PPS 8 #define NAL_TYPE_AUD 9 #define NAL_TYPE_END_OF_SEQUENCE 10 #define NAL_TYPE_END_OF_STREAM 11 #define NAL_TYPE_CODED_SLICE_AUX 19 #define IDC_PROFILE_BASELINE 66 #define IDC_PROFILE_MAIN 77 #define IDC_PROFILE_EXTENDED 88 #define IDC_PROFILE_HP 100 #define IDC_PROFILE_Hi10P 110 #define IDC_PROFILE_Hi422 122 #define IDC_PROFILE_Hi444 244 #define IDC_PROFILE_CAVLC444 44 #define IDC_CHROMA_FORMAT_YUV400 0 #define IDC_CHROMA_FORMAT_YUV420 1 #define IDC_CHROMA_FORMAT_YVU422 2 #define IDC_CHROMA_FORMAT_YUV444 3 #define IDC_VUI_ASPECT_RATIO_EXTENDED_SAR 0xFF #define NAL_PICTURE_TYPE_I 0 #define NAL_PICTURE_TYPE_P 1 #define NAL_PICTURE_TYPE_B 2 #define NAL_SLICE_TYPE_P 0 #define NAL_SLICE_TYPE_B 1 #define NAL_SLICE_TYPE_I 2 #define NAL_SLICE_TYPE_SP 3 #define NAL_SLICE_TYPE_SI 4 typedef struct NAL_SPS { uint8_t profile_idc; uint8_t constraint_set_flags[3]; uint8_t level_idc; uint32_t seq_parameter_set_id; uint32_t chroma_format_idc; uint8_t seperate_colour_plane_flag; uint32_t bit_depth_luma_minus8; uint32_t bit_depth_chroma_minus8; uint8_t lossless_qpprime_y_zero_flag; uint8_t seq_scaling_matrix_present_flag; uint8_t seq_scaling_list_present_flag[12]; uint32_t log2_max_frame_num_minus4; uint32_t pic_order_cnt_type; uint32_t log2_max_pic_order_cnt_lsb_minus4; uint8_t delta_pic_order_always_zero_flag; int32_t offset_for_non_ref_pic; int32_t offset_for_top_to_bottom_field; uint32_t num_ref_frames_in_pic_order_cnt_cycle; int32_t * offset_for_ref_frame; uint32_t num_ref_frames; uint8_t gaps_in_frame_num_value_allowed_flag; uint32_t pic_width_in_mbs_minus1; uint32_t pic_height_in_map_units_minus1; uint8_t frame_mbs_only_flag; uint8_t mb_adaptive_frame_field_flag; uint8_t direct_8x8_inference_flag; uint8_t frame_cropping_flag; uint32_t frame_crop_left_offset; uint32_t frame_crop_right_offset; uint32_t frame_crop_top_offset; uint32_t frame_crop_bottom_offset; uint8_t vui_parameters_present_flag; } NAL_SPS; typedef struct NAL_CPB { uint32_t bit_rate_value_minus1; uint32_t cpb_size_value_minus1; uint8_t cbr_flag; } NAL_CPB; typedef struct NAL_HRD { uint32_t cpb_cnt_minus1; uint8_t bit_rate_scale; uint8_t cpb_size_scale; uint8_t cpb_size_count; NAL_CPB * cpb; uint8_t initial_cpb_removal_delay_length_minus1; uint8_t cpb_removal_delay_length_minus1; uint8_t dpb_output_delay_length_minus1; uint8_t time_offset_length; } NAL_HRD; typedef struct NAL_VUI { uint8_t aspect_ratio_info_present_flag; uint8_t aspect_ratio_idc; uint16_t sar_width; uint16_t sar_height; uint8_t overscan_info_present_flag; uint8_t overscan_appropriate_flag; uint8_t video_signal_type_present_flag; uint8_t video_format; uint8_t video_full_range_flag; uint8_t colour_description_present_flag; uint8_t colour_primaries; uint8_t transfer_characteristics; uint8_t matrix_coefficients; uint8_t chroma_loc_info_present_flag; uint32_t chroma_sample_loc_type_top_field; uint32_t chroma_sample_loc_type_bottom_field; uint8_t timing_info_present_flag; uint32_t num_units_in_tick; uint32_t time_scale; uint8_t fixed_frame_rate_flag; uint8_t nal_hrd_parameters_present_flag; NAL_HRD nal_hrd_parameters; uint8_t vcl_hrd_parameters_present_flag; NAL_HRD vcl_hrd_parameters; uint8_t low_delay_hrd_flag; uint8_t pic_struct_present_flag; uint8_t bitstream_restriction_flag; uint8_t motion_vectors_over_pic_boundaries_flag; uint32_t max_bytes_per_pic_denom; uint32_t max_bits_per_mb_denom; uint32_t log2_max_mv_length_horizontal; uint32_t log2_max_mv_length_vertical; uint32_t num_reorder_frames; uint32_t max_dec_frame_buffering; } NAL_VUI; typedef struct NAL_SLICE_GROUP_T0 { uint32_t run_length_minus1; } NAL_SLICE_GROUP_T0; typedef struct NAL_SLICE_GROUP_T2 { uint32_t top_left; uint32_t bottom_right; } NAL_SLICE_GROUP_T2; typedef union NAL_SLICE_GROUP { NAL_SLICE_GROUP_T0 t0; NAL_SLICE_GROUP_T2 t2; } NAL_SLICE_GROUP; typedef struct NAL_PPS { uint32_t pic_parameter_set_id; uint32_t seq_parameter_set_id; uint8_t entropy_coding_mode_flag; uint8_t pic_order_present_flag; uint32_t num_slice_groups_minus1; NAL_SLICE_GROUP * slice_groups; uint32_t slice_group_map_type; uint8_t slice_group_change_direction_flag; uint32_t slice_group_change_rate_minus1; uint32_t pic_size_in_map_units_minus1; uint32_t * slice_group_id; uint32_t num_ref_idx_l0_active_minus1; uint32_t num_ref_idx_l1_active_minus1; uint8_t weighted_pred_flag; uint8_t weighted_bipred_idc; int32_t pic_init_qp_minus26; int32_t pic_init_qs_minus26; int32_t chroma_qp_index_offset; uint8_t deblocking_filter_control_present_flag; uint8_t constrained_intra_pred_flag; uint8_t redundant_pic_cnt_present_flag; uint8_t transform_8x8_mode_flag; uint8_t pic_scaling_matrix_present_flag; uint8_t pic_scaling_list_present_flag[6]; int32_t scaling_list_4x4[6]; int32_t scaling_list_8x8[2]; int32_t second_chroma_qp_index_offset; } NAL_PPS; typedef struct NAL_RPL_REORDER_L { bool valid; uint32_t reordering_of_pic_nums_idc; uint32_t abs_diff_pic_num_minus1; uint32_t long_term_pic_num; } NAL_RPL_REORDER_L; typedef struct NAL_RPL_REORDER { uint8_t ref_pic_list_reordering_flag_l0; NAL_RPL_REORDER_L l0[3]; uint8_t ref_pic_list_reordering_flag_l1; NAL_RPL_REORDER_L l1[3]; } NAL_RPL_REORDER; typedef struct NAL_PW_TABLE_L { int32_t luma_weight; int32_t luma_offset; int32_t chroma_weight[2]; int32_t chroma_offset[2]; } NAL_PW_TABLE_L; typedef struct NAL_PW_TABLE { uint32_t luma_log2_weight_denom; uint32_t chroma_log2_weight_denom; uint8_t luma_weight_flag[2]; uint8_t chroma_weight_flag[2]; NAL_PW_TABLE_L * l0; NAL_PW_TABLE_L * l1; } NAL_PW_TABLE; typedef struct NAL_RP_MARKING { uint8_t no_output_of_prior_pics_flag; uint8_t long_term_reference_flag; uint8_t adaptive_ref_pic_marking_mode_flag; uint32_t memory_management_control_operation; uint32_t difference_of_pic_nums_minus1; uint32_t long_term_pic_num; uint32_t long_term_frame_idx; uint32_t max_long_term_frame_idx_plus1; } NAL_RP_MARKING; typedef struct NAL_SLICE { uint8_t nal_ref_idc; uint32_t first_mb_in_slice; uint32_t slice_type; uint32_t pic_parameter_set_id; uint32_t frame_num; uint8_t field_pic_flag; uint8_t bottom_field_flag; uint32_t idr_pic_id; uint32_t pic_order_cnt_lsb; int32_t delta_pic_order_cnt_bottom; int32_t delta_pic_order_cnt[2]; uint32_t redundant_pic_cnt; uint8_t direct_spatial_mv_pred_flag; uint8_t num_ref_idx_active_override_flag; uint32_t num_ref_idx_l0_active_minus1; uint32_t num_ref_idx_l1_active_minus1; NAL_RPL_REORDER ref_pic_list_reordering; NAL_PW_TABLE pred_weight_table; NAL_RP_MARKING dec_ref_pic_marking; uint32_t cabac_init_idc; int32_t slice_qp_delta; uint8_t sp_for_switch_flag; int32_t slice_qs_delta; uint32_t disable_deblocking_filter_idc; int32_t slice_alpha_c0_offset_div2; int32_t slice_beta_offset_div2; uint32_t slice_group_change_cycle; } NAL_SLICE; typedef struct NAL * NAL; bool nal_initialize (NAL * ptr); void nal_deinitialize(NAL this ); bool nal_parse (NAL this, const uint8_t * src, size_t size, size_t * seek); bool nal_get_primary_picture_type(NAL this, uint8_t * pic_type); bool nal_get_sps (NAL this, const NAL_SPS ** sps ); bool nal_get_pps (NAL this, const NAL_PPS ** pps ); bool nal_get_slice(NAL this, const NAL_SLICE ** slice);looking-glass-0+b1/client/renderers/000077500000000000000000000000001351530562300175345ustar00rootroot00000000000000looking-glass-0+b1/client/renderers/CMakeLists.txt000066400000000000000000000026101351530562300222730ustar00rootroot00000000000000cmake_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+b1/client/renderers/EGL/000077500000000000000000000000001351530562300201435ustar00rootroot00000000000000looking-glass-0+b1/client/renderers/EGL/CMakeLists.txt000066400000000000000000000016371351530562300227120ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(renderer_EGL LANGUAGES C) find_package(PkgConfig) pkg_check_modules(RENDERER_EGL_PKGCONFIG REQUIRED egl wayland-egl gl ) include(MakeObject) make_object( EGL_SHADER shader/desktop.vert shader/desktop_rgb.frag shader/desktop_yuv.frag shader/cursor.vert shader/cursor_rgb.frag shader/cursor_mono.frag shader/fps.vert shader/fps.frag shader/fps_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 ) add_library(renderer_EGL STATIC egl.c shader.c texture.c model.c desktop.c cursor.c fps.c draw.c splash.c alert.c ${EGL_SHADER_OBJS} ) target_link_libraries(renderer_EGL ${RENDERER_EGL_PKGCONFIG_LIBRARIES} lg_common fonts ) target_include_directories(renderer_EGL PRIVATE src ${EGL_SHADER_INCS} ${RENDERER_EGL_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b1/client/renderers/EGL/alert.c000066400000000000000000000132331351530562300214200ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under cahe 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 "utils.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)) { 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_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 ); 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+b1/client/renderers/EGL/alert.h000066400000000000000000000023651351530562300214310ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #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_render (EGL_Alert * alert, const float scaleX, const float scaleY);looking-glass-0+b1/client/renderers/EGL/cursor.c000066400000000000000000000176401351530562300216340ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under cahe 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 "utils.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 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; // textures struct EGL_Texture * texture; struct EGL_Texture * textureMono; // shaders struct EGL_Shader * shader; struct EGL_Shader * shaderMono; // uniforms GLuint uMousePos; GLuint uMousePosMono; // model struct EGL_Model * model; }; 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_texture_init(&(*cursor)->texture)) { DEBUG_ERROR("Failed to initialize the cursor texture"); return false; } if (!egl_texture_init(&(*cursor)->textureMono)) { DEBUG_ERROR("Failed to initialize the cursor mono texture"); return false; } if (!egl_shader_init(&(*cursor)->shader)) { DEBUG_ERROR("Failed to initialize the cursor shader"); return false; } if (!egl_shader_init(&(*cursor)->shaderMono)) { DEBUG_ERROR("Failed to initialize the cursor mono shader"); return false; } if (!egl_shader_compile( (*cursor)->shader, b_shader_cursor_vert , b_shader_cursor_vert_size, b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size)) { DEBUG_ERROR("Failed to compile the cursor shader"); return false; } if (!egl_shader_compile( (*cursor)->shaderMono, b_shader_cursor_vert , b_shader_cursor_vert_size, b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size)) { DEBUG_ERROR("Failed to compile the cursor mono shader"); return false; } (*cursor)->uMousePos = egl_shader_get_uniform_location((*cursor)->shader , "mouse"); (*cursor)->uMousePosMono = egl_shader_get_uniform_location((*cursor)->shaderMono, "mouse"); if (!egl_model_init(&(*cursor)->model)) { DEBUG_ERROR("Failed to initialize the cursor model"); return false; } egl_model_set_default((*cursor)->model); return true; } void egl_cursor_free(EGL_Cursor ** cursor) { if (!*cursor) return; LG_LOCK_FREE((*cursor)->lock); if ((*cursor)->data) free((*cursor)->data); egl_texture_free(&(*cursor)->texture ); egl_texture_free(&(*cursor)->textureMono); egl_shader_free (&(*cursor)->shader ); egl_shader_free (&(*cursor)->shaderMono ); 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; } void egl_cursor_render(EGL_Cursor * cursor) { if (!cursor->visible) return; if (cursor->update) { LG_LOCK(cursor->lock); cursor->update = false; uint8_t * data = cursor->data; // tmp buffer for masked colour uint32_t tmp[cursor->width * cursor->height]; switch(cursor->type) { case LG_CURSOR_MASKED_COLOR: { for(int i = 0; i < cursor->width * cursor->height; ++i) { const uint32_t c = ((uint32_t *)data)[i]; tmp[i] = (c & ~0xFF000000) | (c & 0xFF000000 ? 0x0 : 0xFF000000); } data = (uint8_t *)tmp; // fall through to LG_CURSOR_COLOR // // technically we should also create an XOR texture from the data but this // usage seems very rare in modern software. } case LG_CURSOR_COLOR: { egl_texture_setup(cursor->texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false); egl_texture_update(cursor->texture, data); egl_model_set_texture(cursor->model, cursor->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->texture , EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false); egl_texture_setup (cursor->textureMono, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false); egl_texture_update(cursor->texture , (uint8_t *)and); egl_texture_update(cursor->textureMono, (uint8_t *)xor); break; } } LG_UNLOCK(cursor->lock); } if (cursor->type == LG_CURSOR_MONOCHROME) { glEnable(GL_BLEND); egl_shader_use(cursor->shader); glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2); glBlendFunc(GL_ZERO, GL_SRC_COLOR); egl_model_set_texture(cursor->model, cursor->texture); egl_model_render(cursor->model); egl_shader_use(cursor->shaderMono); glUniform4f(cursor->uMousePosMono, cursor->x, cursor->y, cursor->w, cursor->h / 2); glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); egl_model_set_texture(cursor->model, cursor->textureMono); egl_model_render(cursor->model); glDisable(GL_BLEND); } else { glEnable(GL_BLEND); egl_shader_use(cursor->shader); glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h); glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA); egl_model_render(cursor->model); glDisable(GL_BLEND); } }looking-glass-0+b1/client/renderers/EGL/cursor.h000066400000000000000000000025711351530562300216360ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "interface/renderer.h" typedef struct EGL_Cursor EGL_Cursor; 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); void egl_cursor_render (EGL_Cursor * cursor);looking-glass-0+b1/client/renderers/EGL/desktop.c000066400000000000000000000170161351530562300217650ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under cahe 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 "utils.h" #include "texture.h" #include "shader.h" #include "model.h" #include #include #include "interface/app.h" // these headers are auto generated by cmake #include "desktop.vert.h" #include "desktop_rgb.frag.h" #include "desktop_yuv.frag.h" struct DesktopShader { EGL_Shader * shader; GLint uDesktopPos; GLint uDesktopSize; GLint uNearest; GLint uNV, uNVGain; }; struct EGL_Desktop { EGL_Texture * texture; struct DesktopShader * shader; // the active shader EGL_Model * model; // shader instances struct DesktopShader shader_generic; struct DesktopShader shader_yuv; // internals LG_Lock updateLock; enum EGL_PixelFormat pixFmt; unsigned int width, height; unsigned int pitch; const uint8_t * data; bool update; // night vision KeybindHandle kbNV; int nvMax; int nvGain; }; // forwards void egl_desktop_toggle_nv(SDL_Scancode 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->uNearest = egl_shader_get_uniform_location(shader->shader, "nearest" ); shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" ); shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" ); return true; } bool egl_desktop_init(EGL_Desktop ** desktop) { *desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop)); if (!*desktop) { DEBUG_ERROR("Failed to malloc EGL_Desktop"); return false; } memset(*desktop, 0, sizeof(EGL_Desktop)); if (!egl_texture_init(&(*desktop)->texture)) { 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_init_desktop_shader( &(*desktop)->shader_yuv, b_shader_desktop_vert , b_shader_desktop_vert_size, b_shader_desktop_yuv_frag, b_shader_desktop_yuv_frag_size)) { DEBUG_ERROR("Failed to initialize the yuv 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); LG_LOCK_INIT((*desktop)->updateLock); (*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop); (*desktop)->nvMax = option_get_int("egl", "nvGainMax"); (*desktop)->nvGain = option_get_int("egl", "nvGain" ); return true; } void egl_desktop_toggle_nv(SDL_Scancode 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); } void egl_desktop_free(EGL_Desktop ** desktop) { if (!*desktop) return; LG_LOCK_FREE((*desktop)->updateLock); egl_texture_free(&(*desktop)->texture ); egl_shader_free (&(*desktop)->shader_generic.shader); egl_shader_free (&(*desktop)->shader_yuv.shader ); egl_model_free (&(*desktop)->model ); app_release_keybind(&(*desktop)->kbNV); free(*desktop); *desktop = NULL; } bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const uint8_t * data) { if (sourceChanged) { LG_LOCK(desktop->updateLock); switch(format.type) { case FRAME_TYPE_BGRA: desktop->pixFmt = EGL_PF_BGRA; desktop->shader = &desktop->shader_generic; break; case FRAME_TYPE_RGBA: desktop->pixFmt = EGL_PF_RGBA; desktop->shader = &desktop->shader_generic; break; case FRAME_TYPE_RGBA10: desktop->pixFmt = EGL_PF_RGBA10; desktop->shader = &desktop->shader_generic; break; case FRAME_TYPE_YUV420: desktop->pixFmt = EGL_PF_YUV420; desktop->shader = &desktop->shader_yuv; break; default: DEBUG_ERROR("Unsupported frame format"); LG_UNLOCK(desktop->updateLock); return false; } desktop->width = format.width; desktop->height = format.height; desktop->pitch = format.pitch; desktop->data = data; desktop->update = true; /* defer the actual update as the format has changed and we need to issue GL commands first */ LG_UNLOCK(desktop->updateLock); return true; } /* update the texture now */ return egl_texture_update(desktop->texture, data); } void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged) { if (sourceChanged) { LG_LOCK(desktop->updateLock); if (!egl_texture_setup( desktop->texture, desktop->pixFmt, desktop->width, desktop->height, desktop->pitch, true // streaming texture )) { DEBUG_ERROR("Failed to setup the desktop texture"); LG_UNLOCK(desktop->updateLock); return; } LG_UNLOCK(desktop->updateLock); } if (desktop->update) { desktop->update = false; egl_texture_update(desktop->texture, desktop->data); } } bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest) { if (!desktop->shader) return false; if (egl_texture_process(desktop->texture) != EGL_TEX_STATUS_OK) return false; const struct DesktopShader * shader = desktop->shader; egl_shader_use(shader->shader); glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY); glUniform1i(shader->uNearest , nearest ? 1 : 0); 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); egl_model_render(desktop->model); return true; }looking-glass-0+b1/client/renderers/EGL/desktop.h000066400000000000000000000025541351530562300217730ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "interface/renderer.h" typedef struct EGL_Desktop EGL_Desktop; bool egl_desktop_init(EGL_Desktop ** desktop); void egl_desktop_free(EGL_Desktop ** desktop); bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const uint8_t * data); void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged); bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);looking-glass-0+b1/client/renderers/EGL/draw.c000066400000000000000000000041471351530562300212520ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "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+b1/client/renderers/EGL/draw.h000066400000000000000000000021031351530562300212450ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "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+b1/client/renderers/EGL/egl.c000066400000000000000000000346241351530562300210670ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/renderer.h" #include "common/debug.h" #include "common/option.h" #include "common/sysinfo.h" #include "utils.h" #include "dynamic/fonts.h" #include #include #if defined(SDL_VIDEO_DRIVER_WAYLAND) #include #endif #include "model.h" #include "shader.h" #include "desktop.h" #include "cursor.h" #include "fps.h" #include "splash.h" #include "alert.h" #define SPLASH_FADE_TIME 1000000 #define ALERT_TIMEOUT 2000000 struct Options { bool vsync; }; struct Inst { LG_RendererParams params; struct Options opt; EGLNativeDisplayType nativeDisp; EGLNativeWindowType nativeWind; EGLDisplay display; EGLConfig configs; EGLSurface surface; EGLContext context; 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 LG_RendererFormat format; bool sourceChanged; uint64_t waitFadeTime; bool waitDone; bool showAlert; uint64_t alertTimeout; bool useCloseFlag; bool closeFlag; int width, height; LG_RendererRect destRect; float translateX , translateY; float scaleX , scaleY; float splashRatio; float screenScaleX, screenScaleY; bool useNearest; bool cursorVisible; int cursorX , cursorY; float mouseWidth , mouseHeight; float mouseScaleX, mouseScaleY; const LG_Font * font; LG_FontObj fontObj; }; 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 = true }, { .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 }, {0} }; void update_mouse_shape(struct Inst * this); const char * egl_get_name() { return "EGL"; } void egl_setup() { option_register(egl_options); } bool egl_create(void ** opaque, const LG_RendererParams params) { // 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)); // 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->translateX = 0; this->translateY = 0; this->scaleX = 1.0f; this->scaleY = 1.0f; this->screenScaleX = 1.0f; this->screenScaleY = 1.0f; this->font = LG_Fonts[0]; if (!this->font->create(&this->fontObj, NULL, 16)) { DEBUG_ERROR("Failed to create a font instance"); return false; } return true; } bool egl_initialize(void * opaque, Uint32 * sdlFlags) { const bool doubleBuffer = option_get_bool("egl", "doubleBuffer"); DEBUG_INFO("Double buffering is %s", doubleBuffer ? "on" : "off"); *sdlFlags = SDL_WINDOW_OPENGL; SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , doubleBuffer ? 1 : 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); if (option_get_bool("egl", "multisample")) { int maxSamples = sysinfo_gfx_max_multisample(); if (maxSamples > 1) { if (maxSamples > 4) maxSamples = 4; DEBUG_INFO("Multsampling enabled, max samples: %d", maxSamples); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples); } } return true; } void egl_deinitialize(void * opaque) { struct Inst * this = (struct Inst *)opaque; if (this->font && this->fontObj) this->font->destroy(this->fontObj); 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 ); free(this); } void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect) { struct Inst * this = (struct Inst *)opaque; this->width = width; this->height = height; memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect)); glViewport(0, 0, width, height); if (destRect.valid) { this->translateX = 1.0f - (((destRect.w / 2) + destRect.x) * 2) / (float)width; this->translateY = 1.0f - (((destRect.h / 2) + destRect.y) * 2) / (float)height; this->scaleX = (float)destRect.w / (float)width; this->scaleY = (float)destRect.h / (float)height; } this->mouseScaleX = 2.0f / this->format.width ; this->mouseScaleY = 2.0f / this->format.height; egl_cursor_set_size(this->cursor, (this->mouseWidth * (1.0f / this->format.width )) * this->scaleX, (this->mouseHeight * (1.0f / this->format.height)) * this->scaleY ); this->splashRatio = (float)width / (float)height; this->screenScaleX = 1.0f / width; this->screenScaleY = 1.0f / height; 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 ); } 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_cursor_set_size(this->cursor, (this->mouseWidth * (1.0f / this->format.width )) * this->scaleX, (this->mouseHeight * (1.0f / this->format.height)) * this->scaleY ); 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_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 ); return true; } bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data) { struct Inst * this = (struct Inst *)opaque; this->sourceChanged = ( this->sourceChanged || this->format.type != format.type || this->format.width != format.width || this->format.height != format.height || this->format.pitch != format.pitch ); if (this->sourceChanged) memcpy(&this->format, &format, sizeof(LG_RendererFormat)); this->useNearest = this->width < format.width || this->height < format.height; if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, data)) { DEBUG_INFO("Failed to prepare to update the desktop"); return 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; } bool egl_render_startup(void * opaque, SDL_Window * window) { struct Inst * this = (struct Inst *)opaque; SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); if (!SDL_GetWindowWMInfo(window, &wminfo)) { DEBUG_ERROR("SDL_GetWindowWMInfo failed"); return false; } switch(wminfo.subsystem) { case SDL_SYSWM_X11: { this->nativeDisp = (EGLNativeDisplayType)wminfo.info.x11.display; this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window; break; } #if defined(SDL_VIDEO_DRIVER_WAYLAND) case SDL_SYSWM_WAYLAND: { int width, height; SDL_GetWindowSize(window, &width, &height); this->nativeDisp = (EGLNativeDisplayType)wminfo.info.wl.display; this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height); break; } #endif default: DEBUG_ERROR("Unsupported subsystem"); return false; } this->display = eglGetDisplay(this->nativeDisp); if (this->display == EGL_NO_DISPLAY) { DEBUG_ERROR("eglGetDisplay failed"); return false; } if (!eglInitialize(this->display, NULL, NULL)) { DEBUG_ERROR("Unable to initialize EGL"); return false; } EGLint attr[] = { EGL_BUFFER_SIZE , 32, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLE_BUFFERS , 1, EGL_SAMPLES , 4, 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; } this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, NULL); 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; } eglMakeCurrent(this->display, this->surface, this->surface, this->context); DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR )); DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER)); DEBUG_INFO("Version : %s", glGetString(GL_VERSION )); eglSwapInterval(this->display, this->opt.vsync ? 1 : 0); if (!egl_desktop_init(&this->desktop)) { 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; } return true; } bool egl_render(void * opaque, SDL_Window * window) { struct Inst * this = (struct Inst *)opaque; glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); if (egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX, this->scaleY, this->useNearest)) { if (!this->waitFadeTime) this->waitFadeTime = microtime() + SPLASH_FADE_TIME; egl_cursor_render(this->cursor); } if (!this->waitDone) { 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); } if (this->showAlert) { bool close = false; if (this->useCloseFlag) close = this->closeFlag; else if (this->alertTimeout < microtime()) close = true; if (close) this->showAlert = false; else egl_alert_render(this->alert, this->screenScaleX, this->screenScaleY); } egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY); eglSwapBuffers(this->display, this->surface); // defer texture uploads until after the flip to avoid stalling egl_desktop_perform_update(this->desktop, this->sourceChanged); this->sourceChanged = false; return true; } void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS) { struct Inst * this = (struct Inst *)opaque; if (!this->params.showFPS) return; egl_fps_update(this->fps, avgUPS, avgFPS); } struct LG_Renderer LGR_EGL = { .get_name = egl_get_name, .setup = egl_setup, .create = egl_create, .initialize = egl_initialize, .deinitialize = egl_deinitialize, .on_resize = egl_on_resize, .on_mouse_shape = egl_on_mouse_shape, .on_mouse_event = egl_on_mouse_event, .on_frame_event = egl_on_frame_event, .on_alert = egl_on_alert, .render_startup = egl_render_startup, .render = egl_render, .update_fps = egl_update_fps };looking-glass-0+b1/client/renderers/EGL/fps.c000066400000000000000000000110351351530562300210770ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under cahe 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 "utils.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 ready; 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)) { 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_update(EGL_FPS * fps, const float avgFPS, const float renderFPS) { 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; } egl_texture_setup( fps->texture, EGL_PF_BGRA, bmp->width , bmp->height, bmp->width * bmp->bpp, false ); egl_texture_update ( fps->texture, bmp->pixels ); fps->width = bmp->width; fps->height = bmp->height; fps->ready = true; fps->font->release(fps->fontObj, bmp); } void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY) { if (!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+b1/client/renderers/EGL/fps.h000066400000000000000000000022461351530562300211100ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #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_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+b1/client/renderers/EGL/model.c000066400000000000000000000123341351530562300214120ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "model.h" #include "shader.h" #include "texture.h" #include "common/debug.h" #include "utils.h" #include "ll.h" #include #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+b1/client/renderers/EGL/model.h000066400000000000000000000025641351530562300214230ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #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+b1/client/renderers/EGL/shader.c000066400000000000000000000132041351530562300215550ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "shader.h" #include "common/debug.h" #include "utils.h" #include #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 (!file_get_contents(vertex_file, &vertex_code, &vertex_size)) { DEBUG_ERROR("Failed to read vertex shader"); return false; } DEBUG_INFO("Loaded vertex shader: %s", vertex_file); if (!file_get_contents(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+b1/client/renderers/EGL/shader.h000066400000000000000000000026771351530562300215760ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #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+b1/client/renderers/EGL/shader/000077500000000000000000000000001351530562300214115ustar00rootroot00000000000000looking-glass-0+b1/client/renderers/EGL/shader/alert.frag000066400000000000000000000002561351530562300233640ustar00rootroot00000000000000#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+b1/client/renderers/EGL/shader/alert.vert000066400000000000000000000006401351530562300234220ustar00rootroot00000000000000#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+b1/client/renderers/EGL/shader/alert_bg.frag000066400000000000000000000001271351530562300240310ustar00rootroot00000000000000#version 300 es in highp vec4 c; out highp vec4 color; void main() { color = c; } looking-glass-0+b1/client/renderers/EGL/shader/cursor.vert000066400000000000000000000006511351530562300236320ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; uniform vec4 mouse; out highp vec2 uv; void main() { gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; gl_Position.x += 1.0f; gl_Position.y -= 1.0f; gl_Position.x *= mouse.z; gl_Position.y *= mouse.w; gl_Position.x += mouse.x; gl_Position.y -= mouse.y; uv = vertexUV; } looking-glass-0+b1/client/renderers/EGL/shader/cursor_mono.frag000066400000000000000000000003241351530562300246160ustar00rootroot00000000000000#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+b1/client/renderers/EGL/shader/cursor_rgb.frag000066400000000000000000000002111351530562300244130ustar00rootroot00000000000000#version 300 es in highp vec2 uv; out highp vec4 color; uniform sampler2D sampler1; void main() { color = texture(sampler1, uv); } looking-glass-0+b1/client/renderers/EGL/shader/desktop.vert000066400000000000000000000006101351530562300237610ustar00rootroot00000000000000#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+b1/client/renderers/EGL/shader/desktop_rgb.frag000066400000000000000000000007461351530562300245640ustar00rootroot00000000000000#version 300 es in highp vec2 uv; out highp vec4 color; uniform sampler2D sampler1; uniform int nearest; uniform highp vec2 size; uniform int nv; uniform highp float nvGain; void main() { if(nearest == 1) color = texture(sampler1, uv); else color = texelFetch(sampler1, ivec2(uv * size), 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; } } looking-glass-0+b1/client/renderers/EGL/shader/desktop_yuv.frag000066400000000000000000000017421351530562300246320ustar00rootroot00000000000000#version 300 es in highp vec2 uv; out highp vec4 color; uniform int nearest; uniform highp vec2 size; uniform int nv; uniform highp float nvGain; uniform sampler2D sampler1; uniform sampler2D sampler2; uniform sampler2D sampler3; void main() { highp vec4 yuv; if(nearest == 1) { yuv = vec4( texture(sampler1, uv).r, texture(sampler2, uv).r, texture(sampler3, uv).r, 1.0 ); } else { highp ivec2 px = ivec2(uv * size); yuv = vec4( texelFetch(sampler1, px, 0).r, texelFetch(sampler2, px, 0).r, texelFetch(sampler3, px, 0).r, 1.0 ); } highp mat4 yuv_to_rgb = mat4( 1.0, 0.0 , 1.402, -0.701, 1.0, -0.344, -0.714, 0.529, 1.0, 1.772, 0.0 , -0.886, 1.0, 1.0 , 1.0 , 1.0 ); color = yuv * yuv_to_rgb; 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; } } looking-glass-0+b1/client/renderers/EGL/shader/fps.frag000066400000000000000000000002111351530562300230340ustar00rootroot00000000000000#version 300 es in highp vec2 uv; out highp vec4 color; uniform sampler2D sampler1; void main() { color = texture(sampler1, uv); } looking-glass-0+b1/client/renderers/EGL/shader/fps.vert000066400000000000000000000007511351530562300231060ustar00rootroot00000000000000#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+b1/client/renderers/EGL/shader/fps_bg.frag000066400000000000000000000001341351530562300235100ustar00rootroot00000000000000#version 300 es out highp vec4 color; void main() { color = vec4(0.0, 0.0, 1.0, 0.5); } looking-glass-0+b1/client/renderers/EGL/shader/splash_bg.frag000066400000000000000000000004001351530562300242060ustar00rootroot00000000000000#version 300 es in highp vec3 pos; in highp float a; 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, a); } looking-glass-0+b1/client/renderers/EGL/shader/splash_bg.vert000066400000000000000000000004251351530562300242560ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; uniform float alpha; out highp vec3 pos; out highp float a; void main() { gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; pos = vertexPosition_modelspace; a = alpha; } looking-glass-0+b1/client/renderers/EGL/shader/splash_logo.frag000066400000000000000000000002121351530562300245570ustar00rootroot00000000000000#version 300 es out highp vec4 color; in highp float a; uniform sampler2D sampler1; void main() { color = vec4(1.0, 1.0, 1.0, a); } looking-glass-0+b1/client/renderers/EGL/shader/splash_logo.vert000066400000000000000000000003701351530562300246250ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec3 vertexPosition_modelspace; uniform vec2 scale; out highp float a; void main() { gl_Position.xyz = vertexPosition_modelspace; gl_Position.y *= scale.y; gl_Position.w = 1.0; a = scale.x; } looking-glass-0+b1/client/renderers/EGL/splash.c000066400000000000000000000111721351530562300216030ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under cahe 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 "utils.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 uBGAlpha; 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; } (*splash)->uBGAlpha = egl_shader_get_uniform_location((*splash)->bgShader, "alpha"); 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)->logo); free(*splash); *splash = NULL; } void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); egl_shader_use(splash->bgShader); glUniform1f(splash->uBGAlpha, alpha); egl_model_render(splash->bg); egl_shader_use(splash->logoShader); glUniform2f(splash->uScale, alpha, scaleY); egl_model_render(splash->logo); glDisable(GL_BLEND); }looking-glass-0+b1/client/renderers/EGL/splash.h000066400000000000000000000020471351530562300216110ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include 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+b1/client/renderers/EGL/texture.c000066400000000000000000000244331351530562300220150ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "texture.h" #include "common/debug.h" #include "utils.h" #include #include #include #include struct EGL_Texture { enum EGL_PixelFormat pixFmt; size_t width, height; bool streaming; bool ready; int textureCount; GLuint textures[3]; GLuint samplers[3]; size_t planes[3][3]; GLintptr offsets[3]; GLenum intFormat; GLenum format; GLenum dataType; bool hasPBO; GLuint pbo[2]; int pboRIndex; int pboWIndex; int pboCount; size_t pboBufferSize; void * pboMap[2]; GLsync pboSync[2]; }; bool egl_texture_init(EGL_Texture ** texture) { *texture = (EGL_Texture *)malloc(sizeof(EGL_Texture)); if (!*texture) { DEBUG_ERROR("Failed to malloc EGL_Texture"); return false; } memset(*texture, 0, sizeof(EGL_Texture)); return true; } void egl_texture_free(EGL_Texture ** texture) { if (!*texture) return; if ((*texture)->textureCount > 0) { glDeleteTextures((*texture)->textureCount, (*texture)->textures); glDeleteSamplers((*texture)->textureCount, (*texture)->samplers); } if ((*texture)->hasPBO) { for(int i = 0; i < 2; ++i) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, (*texture)->pbo[i]); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); if ((*texture)->pboSync[i]) glDeleteSync((*texture)->pboSync[i]); } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glDeleteBuffers(2, (*texture)->pbo); } free(*texture); *texture = NULL; } bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming) { int textureCount; texture->pixFmt = pixFmt; texture->width = width; texture->height = height; texture->streaming = streaming; texture->ready = false; switch(pixFmt) { case EGL_PF_BGRA: textureCount = 1; texture->format = GL_BGRA; texture->planes[0][0] = width; texture->planes[0][1] = height; texture->planes[0][2] = stride / 4; texture->offsets[0] = 0; texture->intFormat = GL_BGRA; texture->dataType = GL_UNSIGNED_BYTE; texture->pboBufferSize = height * stride; break; case EGL_PF_RGBA: textureCount = 1; texture->format = GL_RGBA; texture->planes[0][0] = width; texture->planes[0][1] = height; texture->planes[0][2] = stride / 4; texture->offsets[0] = 0; texture->intFormat = GL_BGRA; texture->dataType = GL_UNSIGNED_BYTE; texture->pboBufferSize = height * stride; break; case EGL_PF_RGBA10: textureCount = 1; texture->format = GL_RGBA; texture->planes[0][0] = width; texture->planes[0][1] = height; texture->planes[0][2] = stride / 4; texture->offsets[0] = 0; texture->intFormat = GL_RGB10_A2; texture->dataType = GL_UNSIGNED_INT_2_10_10_10_REV; texture->pboBufferSize = height * stride; break; case EGL_PF_YUV420: textureCount = 3; texture->format = GL_RED; texture->planes[0][0] = width; texture->planes[0][1] = height; texture->planes[0][2] = stride; texture->planes[1][0] = width / 2; texture->planes[1][1] = height / 2; texture->planes[1][2] = stride / 2; texture->planes[2][0] = width / 2; texture->planes[2][1] = height / 2; texture->planes[2][2] = stride / 2; texture->offsets[0] = 0; texture->offsets[1] = stride * height; texture->offsets[2] = texture->offsets[1] + (texture->offsets[1] / 4); texture->dataType = GL_UNSIGNED_BYTE; texture->pboBufferSize = texture->offsets[2] + (texture->offsets[1] / 4); break; default: DEBUG_ERROR("Unsupported pixel format"); return false; } if (textureCount > texture->textureCount) { if (texture->textureCount > 0) { glDeleteTextures(texture->textureCount, texture->textures); glDeleteSamplers(texture->textureCount, texture->samplers); } texture->textureCount = textureCount; glGenTextures(texture->textureCount, texture->textures); glGenSamplers(texture->textureCount, texture->samplers); } for(int i = 0; i < textureCount; ++i) { glSamplerParameteri(texture->samplers[i], GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(texture->samplers[i], GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(texture->samplers[i], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glSamplerParameteri(texture->samplers[i], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, texture->textures[i]); glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->planes[i][0], texture->planes[i][1], 0, texture->format, texture->dataType, NULL); } glBindTexture(GL_TEXTURE_2D, 0); if (streaming) { if (texture->hasPBO) { // release old PBOs and delete the buffers for(int i = 0; i < 2; ++i) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); } glDeleteBuffers(2, texture->pbo); } glGenBuffers(2, texture->pbo); texture->hasPBO = true; for(int i = 0; i < 2; ++i) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]); glBufferStorage( GL_PIXEL_UNPACK_BUFFER, texture->pboBufferSize, NULL, GL_MAP_PERSISTENT_BIT | GL_MAP_WRITE_BIT ); texture->pboMap[i] = glMapBufferRange( GL_PIXEL_UNPACK_BUFFER, 0, texture->pboBufferSize, GL_MAP_PERSISTENT_BIT | GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_FLUSH_EXPLICIT_BIT ); if (!texture->pboMap[i]) { DEBUG_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize); return false; } } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } return true; } bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer) { if (texture->streaming) { /* NOTE: DO NOT use any gl commands here as streaming must be thread safe */ if (texture->pboCount == 2) return true; /* update the GPU buffer */ memcpy(texture->pboMap[texture->pboWIndex], buffer, texture->pboBufferSize); texture->pboSync[texture->pboWIndex] = 0; if (++texture->pboWIndex == 2) texture->pboWIndex = 0; ++texture->pboCount; } else { /* Non streaming, this is NOT thread safe */ for(int i = 0; i < texture->textureCount; ++i) { glBindTexture(GL_TEXTURE_2D, texture->textures[i]); glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[i][0]); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[i][0], texture->planes[i][1], texture->format, texture->dataType, buffer + texture->offsets[i]); } glBindTexture(GL_TEXTURE_2D, 0); } return true; } enum EGL_TexStatus egl_texture_process(EGL_Texture * texture) { if (!texture->streaming) return EGL_TEX_STATUS_OK; if (texture->pboCount == 0) return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY; /* process any buffers that have not yet been flushed */ int pos = texture->pboRIndex; for(int i = 0; i < texture->pboCount; ++i) { if (texture->pboSync[pos] == 0) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[pos]); glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texture->pboBufferSize); texture->pboSync[pos] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } if (++pos == 2) pos = 0; } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); /* wait for the buffer to be ready */ pos = texture->pboRIndex; switch(glClientWaitSync(texture->pboSync[pos], GL_SYNC_FLUSH_COMMANDS_BIT, 0)) { case GL_ALREADY_SIGNALED: case GL_CONDITION_SATISFIED: break; case GL_TIMEOUT_EXPIRED: return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY; case GL_WAIT_FAILED: glDeleteSync(texture->pboSync[pos]); DEBUG_ERROR("glClientWaitSync failed"); return EGL_TEX_STATUS_ERROR; } /* delete the sync and bind the buffer */ glDeleteSync(texture->pboSync[pos]); texture->pboSync[pos] = 0; glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[pos]); /* update the textures */ for(int i = 0; i < texture->textureCount; ++i) { glBindTexture(GL_TEXTURE_2D, texture->textures[i]); glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[i][2]); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[i][0], texture->planes[i][1], texture->format, texture->dataType, (const void *)texture->offsets[i]); } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); /* advance the read index */ if (++texture->pboRIndex == 2) texture->pboRIndex = 0; --texture->pboCount; texture->ready = true; return EGL_TEX_STATUS_OK; } enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture) { /* if there are no new buffers ready, then just bind the textures */ if (texture->streaming && !texture->ready) return EGL_TEX_STATUS_NOTREADY; for(int i = 0; i < texture->textureCount; ++i) { glActiveTexture(GL_TEXTURE0 + i); glBindTexture(GL_TEXTURE_2D, texture->textures[i]); glBindSampler(i, texture->samplers[i]); } return EGL_TEX_STATUS_OK; } int egl_texture_count(EGL_Texture * texture) { return texture->textureCount; }looking-glass-0+b1/client/renderers/EGL/texture.h000066400000000000000000000031731351530562300220200ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "shader.h" #include typedef struct EGL_Texture EGL_Texture; enum EGL_PixelFormat { EGL_PF_RGBA, EGL_PF_BGRA, EGL_PF_RGBA10, EGL_PF_YUV420 }; enum EGL_TexStatus { EGL_TEX_STATUS_NOTREADY, EGL_TEX_STATUS_OK, EGL_TEX_STATUS_ERROR }; bool egl_texture_init(EGL_Texture ** tex); void egl_texture_free(EGL_Texture ** tex); bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming); bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer); 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+b1/client/renderers/OpenGL/000077500000000000000000000000001351530562300206605ustar00rootroot00000000000000looking-glass-0+b1/client/renderers/OpenGL/CMakeLists.txt000066400000000000000000000006501351530562300234210ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(renderer_Opengl LANGUAGES C) find_package(PkgConfig) pkg_check_modules(RENDERER_OPENGL_PKGCONFIG REQUIRED gl glu ) add_library(renderer_OpenGL STATIC opengl.c ) target_link_libraries(renderer_OpenGL ${RENDERER_OPENGL_PKGCONFIG_LIBRARIES} lg_common decoders fonts ) target_include_directories(renderer_OpenGL PRIVATE src ${RENDERER_OPENGL_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b1/client/renderers/OpenGL/opengl.c000066400000000000000000001105461351530562300223170ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/renderer.h" #include #include #include #include #include #include #include #include #include #include "common/debug.h" #include "common/option.h" #include "utils.h" #include "lg-decoders.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 = true }, { .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 } }; 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; SDL_GLContext glContext; SDL_Point window; 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; const LG_Decoder* decoder; void * decoderData; uint64_t drawStart; bool hasBuffers; GLuint vboID[BUFFER_COUNT]; uint8_t * texPixels[BUFFER_COUNT]; LG_Lock syncLock; bool texReady; int texIndex; int texList; int fpsList; int mouseList; LG_RendererRect destRect; bool hasTextures, hasFrames; GLuint frames[BUFFER_COUNT]; GLsync fences[BUFFER_COUNT]; void * decoderFrames[BUFFER_COUNT]; GLuint textures[TEXTURE_COUNT]; struct ll * alerts; int alertList; bool waiting; uint64_t waitFadeTime; bool waitDone; bool fpsTexture; SDL_Rect 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; SDL_Rect mousePos; }; static bool _check_gl_error(unsigned int line, const char * name); #define check_gl_error(name) _check_gl_error(__LINE__, name) static void deconfigure(struct Inst * this); static bool configure(struct Inst * this, SDL_Window *window); static void update_mouse_shape(struct Inst * this, bool * newShape); static bool draw_frame(struct Inst * this); static void draw_mouse(struct Inst * this); static void render_wait(struct Inst * this); const char * opengl_get_name() { return "OpenGL"; } static void opengl_setup() { option_register(opengl_options); } bool opengl_create(void ** opaque, const LG_RendererParams params) { // 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->syncLock ); 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(); return true; } bool opengl_initialize(void * opaque, Uint32 * sdlFlags) { struct Inst * this = (struct Inst *)opaque; if (!this) return false; this->waiting = true; this->waitDone = false; *sdlFlags = SDL_WINDOW_OPENGL; SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); 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->mouseData) free(this->mouseData); if (this->glContext) { SDL_GL_DeleteContext(this->glContext); this->glContext = NULL; } LG_LOCK_FREE(this->formatLock); LG_LOCK_FREE(this->syncLock ); 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_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect) { struct Inst * this = (struct Inst *)opaque; this->window.x = width; this->window.y = height; if (destRect.valid) memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect)); // setup the projection matrix glViewport(0, 0, this->window.x, this->window.y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, this->window.x, this->window.y, 0); 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_event(void * opaque, const LG_RendererFormat format, const uint8_t * data) { struct Inst * this = (struct Inst *)opaque; if (!this) { DEBUG_ERROR("Invalid opaque pointer"); return false; } LG_LOCK(this->formatLock); if (this->reconfigure) { LG_UNLOCK(this->formatLock); return true; } if (!this->configured || this->format.type != format.type || this->format.width != format.width || this->format.height != format.height || this->format.stride != format.stride || this->format.bpp != format.bpp ) { memcpy(&this->format, &format, sizeof(LG_RendererFormat)); this->reconfigure = true; LG_UNLOCK(this->formatLock); return true; } LG_UNLOCK(this->formatLock); LG_LOCK(this->syncLock); if (!this->decoder->decode(this->decoderData, data, format.pitch)) { DEBUG_ERROR("decode returned failure"); LG_UNLOCK(this->syncLock); return false; } this->frameUpdate = true; LG_UNLOCK(this->syncLock); if (this->waiting) { this->waiting = false; this->waitFadeTime = microtime() + FADE_TIME; } 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: %s", TTF_GetError()); free(a); return; } if (closeFlag) { a->useCloseFlag = true; *closeFlag = &a->closeFlag; } ll_push(this->alerts, a); } 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, SDL_Window * window) { struct Inst * this = (struct Inst *)opaque; this->glContext = SDL_GL_CreateContext(window); if (!this->glContext) { DEBUG_ERROR("Failed to create the OpenGL context"); return false; } 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; SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0); this->renderStarted = true; return true; } bool opengl_render(void * opaque, SDL_Window * window) { struct Inst * this = (struct Inst *)opaque; if (!this) return false; if (configure(this, window)) 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->texIndex); draw_mouse(this); if (!this->waitDone) render_wait(this); } if (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); glCallList(this->alertList); glPopMatrix(); break; } if (this->opt.preventBuffer) { SDL_GL_SwapWindow(window); glFinish(); } else SDL_GL_SwapWindow(window); 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->params.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); glPushMatrix(); glLoadIdentity(); 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); glPopMatrix(); 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_resize = opengl_on_resize, .on_mouse_shape = opengl_on_mouse_shape, .on_mouse_event = opengl_on_mouse_event, .on_frame_event = opengl_on_frame_event, .on_alert = opengl_on_alert, .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 GLubyte * errStr = gluErrorString(error); DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr); return true; } static bool configure(struct Inst * this, SDL_Window *window) { LG_LOCK(this->formatLock); if (!this->reconfigure) { LG_UNLOCK(this->formatLock); return this->configured; } if (this->configured) deconfigure(this); switch(this->format.type) { case FRAME_TYPE_BGRA: case FRAME_TYPE_RGBA: case FRAME_TYPE_RGBA10: this->decoder = &LGD_NULL; break; case FRAME_TYPE_YUV420: this->decoder = &LGD_YUV420; break; default: DEBUG_ERROR("Unknown/unsupported compression type"); return false; } DEBUG_INFO("Using decoder: %s", this->decoder->name); if (!this->decoder->create(&this->decoderData)) { DEBUG_ERROR("Failed to create the decoder"); return false; } if (!this->decoder->initialize( this->decoderData, this->format, window)) { DEBUG_ERROR("Failed to initialize decoder"); return false; } switch(this->decoder->get_out_format(this->decoderData)) { case LG_OUTPUT_BGRA: this->intFormat = GL_RGBA8; this->vboFormat = GL_BGRA; this->dataFormat = GL_UNSIGNED_BYTE; break; case LG_OUTPUT_RGBA: this->intFormat = GL_RGBA8; this->vboFormat = GL_RGBA; this->dataFormat = GL_UNSIGNED_BYTE; break; case LG_OUTPUT_RGBA10: this->intFormat = GL_RGB10_A2; this->vboFormat = GL_RGBA; this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV; break; case LG_OUTPUT_YUV420: // fixme this->intFormat = GL_RGBA8; this->vboFormat = GL_BGRA; this->dataFormat = GL_UNSIGNED_BYTE; break; default: DEBUG_ERROR("Format not supported"); LG_UNLOCK(this->formatLock); return false; } // calculate the texture size in bytes this->texSize = this->format.height * this->decoder->get_frame_pitch(this->decoderData); // generate the pixel unpack buffers if the decoder isn't going to do it for us if (!this->decoder->has_gl) { glGenBuffers(BUFFER_COUNT, this->vboID); if (check_gl_error("glGenBuffers")) { LG_UNLOCK(this->formatLock); return false; } this->hasBuffers = true; if (this->amdPinnedMemSupport) { const int pagesize = getpagesize(); this->texPixels[0] = memalign(pagesize, this->texSize * BUFFER_COUNT); memset(this->texPixels[0], 0, this->texSize * BUFFER_COUNT); for(int i = 1; i < BUFFER_COUNT; ++i) this->texPixels[i] = this->texPixels[0] + this->texSize; for(int i = 0; i < BUFFER_COUNT; ++i) { glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]); if (check_gl_error("glBindBuffer")) { LG_UNLOCK(this->formatLock); return false; } 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 false; } } 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 false; } glBufferData( GL_PIXEL_UNPACK_BUFFER, this->texSize, NULL, GL_STREAM_DRAW ); if (check_gl_error("glBufferData")) { LG_UNLOCK(this->formatLock); return false; } } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } } // create the frame textures glGenTextures(BUFFER_COUNT, this->frames); if (check_gl_error("glGenTextures")) { LG_UNLOCK(this->formatLock); return false; } 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 false; } 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 false; } if (this->decoder->has_gl) { if (!this->decoder->init_gl_texture( this->decoderData, GL_TEXTURE_2D, this->frames[i], &this->decoderFrames[i])) { LG_UNLOCK(this->formatLock); return false; } } else { // configure the texture glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } // create the display lists glNewList(this->texList + i, GL_COMPILE); 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 true; } static void deconfigure(struct Inst * this) { if (!this->configured) return; if (this->hasTextures) { glDeleteTextures(TEXTURE_COUNT, this->textures); this->hasTextures = false; } if (this->hasFrames) { if (this->decoder->has_gl) { for(int i = 0; i < BUFFER_COUNT; ++i) { if (this->decoderFrames[i]) this->decoder->free_gl_texture( this->decoderData, this->decoderFrames[i] ); this->decoderFrames[i] = NULL; } } glDeleteTextures(BUFFER_COUNT, this->frames); this->hasFrames = false; } if (this->hasBuffers) { glDeleteBuffers(BUFFER_COUNT, this->vboID); this->hasBuffers = false; } if (this->amdPinnedMemSupport) { if (this->texPixels[0]) free(this->texPixels[0]); for(int i = 0; i < BUFFER_COUNT; ++i) { if (this->fences[i]) { glDeleteSync(this->fences[i]); this->fences[i] = NULL; } this->texPixels[i] = NULL; } } if (this->decoderData) { this->decoder->destroy(this->decoderData); this->decoderData = 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 draw_frame(struct Inst * this) { LG_LOCK(this->syncLock); if (!this->frameUpdate) { LG_UNLOCK(this->syncLock); return true; } if (++this->texIndex == BUFFER_COUNT) this->texIndex = 0; this->frameUpdate = false; LG_UNLOCK(this->syncLock); LG_LOCK(this->formatLock); if (this->decoder->has_gl) { if (!this->decoder->update_gl_texture( this->decoderData, this->decoderFrames[this->texIndex] )) { LG_UNLOCK(this->formatLock); DEBUG_ERROR("Failed to update the texture from the decoder"); return false; } } else { if (glIsSync(this->fences[this->texIndex])) { switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED)) { 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 %s", gluErrorString(glGetError())); break; } glDeleteSync(this->fences[this->texIndex]); this->fences[this->texIndex] = NULL; } const uint8_t * data = this->decoder->get_buffer(this->decoderData); if (!data) { LG_UNLOCK(this->formatLock); DEBUG_ERROR("Failed to get the buffer from the decoder"); return false; } glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]); glPixelStorei(GL_UNPACK_ALIGNMENT , 4); glPixelStorei(GL_UNPACK_ROW_LENGTH , this->decoder->get_frame_stride(this->decoderData) ); // update the buffer, this performs a DMA transfer if possible glBufferSubData( GL_PIXEL_UNPACK_BUFFER, 0, this->texSize, data ); check_gl_error("glBufferSubData"); // 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("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu", this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize ); } // set a fence so we don't overwrite a buffer in use this->fences[this->texIndex] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); // 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)); glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]); 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); 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+b1/client/spice/000077500000000000000000000000001351530562300166465ustar00rootroot00000000000000looking-glass-0+b1/client/spice/CMakeLists.txt000066400000000000000000000007671351530562300214200ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(spice LANGUAGES C) find_package(PkgConfig) pkg_check_modules(SPICE_PKGCONFIG REQUIRED spice-protocol nettle hogweed ) add_definitions(-D USE_NETTLE) add_library(spice STATIC src/spice.c src/rsa.c ) target_link_libraries(spice lg_common ${SPICE_PKGCONFIG_LIBRARIES} ) target_include_directories(spice PUBLIC $ $ PRIVATE src ${SPICE_PKGCONFIG_INCLUDE_DIRS} ) looking-glass-0+b1/client/spice/include/000077500000000000000000000000001351530562300202715ustar00rootroot00000000000000looking-glass-0+b1/client/spice/include/messages.h000066400000000000000000000060211351530562300222500ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #pragma pack(push,1) typedef struct SpicePoint16 { int16_t x, y; } SpicePoint16; typedef struct SpiceMsgMainInit { uint32_t session_id; uint32_t display_channels_hint; uint32_t supported_mouse_modes; uint32_t current_mouse_mode; uint32_t agent_connected; uint32_t agent_tokens; uint32_t multi_media_time; uint32_t ram_hint; } SpiceMsgMainInit; typedef struct SpiceChannelID { uint8_t type; uint8_t channel_id; } SpiceChannelID; typedef struct SpiceMsgMainChannelsList { uint32_t num_of_channels; //SpiceChannelID channels[num_of_channels] } SpiceMainChannelsList; typedef struct SpiceMsgcMainMouseModeRequest { uint16_t mouse_mode; } SpiceMsgcMainMouseModeRequest; typedef struct SpiceMsgPing { uint32_t id; uint64_t timestamp; } SpiceMsgPing, SpiceMsgcPong; typedef struct SpiceMsgSetAck { uint32_t generation; uint32_t window; } SpiceMsgSetAck; typedef struct SpiceMsgcAckSync { uint32_t generation; } SpiceMsgcAckSync; typedef struct SpiceMsgNotify { uint64_t time_stamp; uint32_t severity; uint32_t visibility; uint32_t what; uint32_t message_len; //char message[message_len+1] } SpiceMsgNotify; typedef struct SpiceMsgInputsInit { uint16_t modifiers; } SpiceMsgInputsInit, SpiceMsgInputsKeyModifiers, SpiceMsgcInputsKeyModifiers; typedef struct SpiceMsgcKeyDown { uint32_t code; } SpiceMsgcKeyDown, SpiceMsgcKeyUp; typedef struct SpiceMsgcMousePosition { uint32_t x; uint32_t y; uint16_t button_state; uint8_t display_id; } SpiceMsgcMousePosition; typedef struct SpiceMsgcMouseMotion { int32_t x; int32_t y; uint16_t button_state; } SpiceMsgcMouseMotion; typedef struct SpiceMsgcMousePress { uint8_t button; uint16_t button_state; } SpiceMsgcMousePress, SpiceMsgcMouseRelease; // spice is missing these defines, the offical reference library incorrectly uses the VD defines #define COMMON_CAPS_BYTES (((SPICE_COMMON_CAP_MINI_HEADER + 32) / 8) & ~3) #define COMMON_SET_CAPABILITY(caps, index) \ { (caps)[(index) / 32] |= (1 << ((index) % 32)); } #define MAIN_CAPS_BYTES (((SPICE_MAIN_CAP_SEAMLESS_MIGRATE + 32) / 8) & ~3) #define MAIN_SET_CAPABILITY(caps, index) \ { (caps)[(index) / 32] |= (1 << ((index) % 32)); } #pragma pack(pop)looking-glass-0+b1/client/spice/include/rsa.h000066400000000000000000000021201351530562300212220ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include struct spice_password { char * data; unsigned int size; }; bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result); void spice_rsa_free_password(struct spice_password * pass);looking-glass-0+b1/client/spice/include/spice000077700000000000000000000000001351530562300213722.ustar00rootroot00000000000000looking-glass-0+b1/client/spice/include/spice.h000066400000000000000000000042341351530562300215500ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include typedef enum SpiceDataType { SPICE_DATA_TEXT, SPICE_DATA_PNG, SPICE_DATA_BMP, SPICE_DATA_TIFF, SPICE_DATA_JPEG, SPICE_DATA_NONE } SpiceDataType; typedef void (*SpiceClipboardNotice )(const SpiceDataType type); typedef void (*SpiceClipboardData )(const SpiceDataType type, uint8_t * buffer, uint32_t size); typedef void (*SpiceClipboardRelease)(); typedef void (*SpiceClipboardRequest)(const SpiceDataType type); bool spice_connect(const char * host, const unsigned short port, const char * password); void spice_disconnect(); bool spice_process(); bool spice_ready(); bool spice_key_down (uint32_t code); bool spice_key_up (uint32_t code); bool spice_mouse_mode (bool server); bool spice_mouse_position(uint32_t x, uint32_t y); bool spice_mouse_motion ( int32_t x, int32_t y); bool spice_mouse_press (uint32_t button); bool spice_mouse_release (uint32_t button); bool spice_clipboard_request(SpiceDataType type); bool spice_clipboard_grab (SpiceDataType type); bool spice_clipboard_release(); bool spice_clipboard_data (SpiceDataType type, uint8_t * data, size_t size); /* events */ bool spice_set_clipboard_cb( SpiceClipboardNotice cbNoticeFn, SpiceClipboardData cbDataFn, SpiceClipboardRelease cbReleaseFn, SpiceClipboardRequest cbRequestFn);looking-glass-0+b1/client/spice/src/000077500000000000000000000000001351530562300174355ustar00rootroot00000000000000looking-glass-0+b1/client/spice/src/rsa.c000066400000000000000000000136741351530562300204010ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "rsa.h" #include "common/debug.h" #include #include #include #if defined(USE_OPENSSL) && defined(USE_NETTLE) #error "USE_OPENSSL and USE_NETTLE are both defined" #elif !defined(USE_OPENSSL) && !defined(USE_NETTLE) #error "One of USE_OPENSSL or USE_NETTLE must be defined" #endif #if defined(USE_OPENSSL) #include #include #include #endif #if defined(USE_NETTLE) #include #include #include #include #include #include #define SHA1_HASH_LEN 20 #endif #if defined(USE_NETTLE) /* the below OAEP implementation is derived from the FreeTDS project */ static void memxor(uint8_t * a, const uint8_t * b, const unsigned int len) { for(unsigned int i = 0; i < len; ++i) a[i] = a[i] ^ b[i]; } static void sha1(uint8_t * hash, const uint8_t *data, unsigned int len) { struct sha1_ctx ctx; sha1_init(&ctx); sha1_update(&ctx, len, data); sha1_digest(&ctx, SHA1_HASH_LEN, hash); } static void oaep_mask(uint8_t * dest, size_t dest_len, const uint8_t * mask, size_t mask_len) { uint8_t hash[SHA1_HASH_LEN]; uint8_t seed[mask_len + 4 ]; memcpy(seed, mask, mask_len); for(unsigned int n = 0;; ++n) { (seed+mask_len)[0] = n >> 24; (seed+mask_len)[1] = n >> 16; (seed+mask_len)[2] = n >> 8; (seed+mask_len)[3] = n >> 0; sha1(hash, seed, sizeof(seed)); if (dest_len <= SHA1_HASH_LEN) { memxor(dest, hash, dest_len); break; } memxor(dest, hash, SHA1_HASH_LEN); dest += SHA1_HASH_LEN; dest_len -= SHA1_HASH_LEN; } } static bool oaep_pad(mpz_t m, unsigned int key_size, const uint8_t * message, unsigned int len) { if (len + SHA1_HASH_LEN * 2 + 2 > key_size) { DEBUG_ERROR("Message too long"); return false; } struct { uint8_t all[1]; uint8_t ros[SHA1_HASH_LEN]; uint8_t db [key_size - SHA1_HASH_LEN - 1]; } em; memset(&em, 0, sizeof(em)); sha1(em.db, (uint8_t *)"", 0); em.all[key_size - len - 1] = 0x1; memcpy(em.all + (key_size - len), message, len); /* we are not too worried about randomness since we are just making a local * connection, should anyone use this code outside of LookingGlass please be * sure to use something better such as `gnutls_rnd` */ for(int i = 0; i < SHA1_HASH_LEN; ++i) em.ros[i] = rand() % 255; const int db_len = key_size - SHA1_HASH_LEN - 1; oaep_mask(em.db , db_len , em.ros, SHA1_HASH_LEN); oaep_mask(em.ros, SHA1_HASH_LEN, em.db , db_len ); nettle_mpz_set_str_256_u(m, key_size, em.all); return true; } #endif bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result) { result->size = 0; result->data = NULL; #if defined(USE_OPENSSL) BIO *bioKey = BIO_new(BIO_s_mem()); if (!bioKey) { DEBUG_ERROR("failed to allocate bioKey"); return false; } BIO_write(bioKey, pub_key, SPICE_TICKET_PUBKEY_BYTES); EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL); RSA *rsa = EVP_PKEY_get1_RSA(rsaKey); result->size = RSA_size(rsa); result->data = (char *)malloc(result->size); if (RSA_public_encrypt( strlen(password) + 1, (uint8_t*)password, (uint8_t*)result->data, rsa, RSA_PKCS1_OAEP_PADDING ) <= 0) { free(result->data); result->size = 0; result->data = NULL; DEBUG_ERROR("rsa public encrypt failed"); EVP_PKEY_free(rsaKey); BIO_free(bioKey); return false; } EVP_PKEY_free(rsaKey); BIO_free(bioKey); return true; #endif #if defined(USE_NETTLE) struct asn1_der_iterator der; struct asn1_der_iterator j; struct rsa_public_key pub; if (asn1_der_iterator_first(&der, SPICE_TICKET_PUBKEY_BYTES, pub_key) == ASN1_ITERATOR_CONSTRUCTED && der.type == ASN1_SEQUENCE && asn1_der_decode_constructed_last(&der) == ASN1_ITERATOR_CONSTRUCTED && der.type == ASN1_SEQUENCE && asn1_der_decode_constructed(&der, &j) == ASN1_ITERATOR_PRIMITIVE && j.type == ASN1_IDENTIFIER && asn1_der_iterator_next(&der) == ASN1_ITERATOR_PRIMITIVE && der.type == ASN1_BITSTRING && asn1_der_decode_bitstring_last(&der)) { if (j.length != 9) { DEBUG_ERROR("Invalid key, not RSA"); return false; } if (asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE && j.type == ASN1_NULL && j.length == 0 && asn1_der_iterator_next(&j) == ASN1_ITERATOR_END) { rsa_public_key_init(&pub); if (!rsa_public_key_from_der_iterator(&pub, 0, &der)) { DEBUG_ERROR("Unable to load public key from DER iterator"); rsa_public_key_clear(&pub); return false; } } } mpz_t p; mpz_init(p); oaep_pad(p, pub.size, (uint8_t *)password, strlen(password)+1); mpz_powm(p, p, pub.e, pub.n); result->size = pub.size; result->data = malloc(pub.size); nettle_mpz_get_str_256(pub.size, (uint8_t *)result->data, p); rsa_public_key_clear(&pub); mpz_clear(p); return true; #endif } void spice_rsa_free_password(struct spice_password * pass) { free(pass->data); pass->size = 0; pass->data = NULL; }looking-glass-0+b1/client/spice/src/spice.c000066400000000000000000001234001351530562300207040ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "spice.h" #include "utils.h" #include "common/debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "messages.h" #include "rsa.h" #ifdef DEBUG_SPICE_MOUSE #define DEBUG_MOUSE(fmt, args...) DEBUG_PRINT("[M]", fmt, ##args) #else #define DEBUG_MOUSE(fmt, args...) do {} while(0) #endif #ifdef DEBUG_SPICE_KEYBOARD #define DEBUG_KEYBOARD(fmt, args...) DEBUG_PRINT("[K]", fmt, ##args) #else #define DEBUG_KEYBOARD(fmt, args...) do {} while(0) #endif #ifdef DEBUG_SPICE_CLIPBOARD #define DEBUG_CLIPBOARD(fmt, args...) DEBUG_PRINT("[C]", fmt, ##args) #else #define DEBUG_CLIPBOARD(fmt, args...) do {} while(0) #endif // we don't really need flow control because we are all local // instead do what the spice-gtk library does and provide the largest // possible number #define SPICE_AGENT_TOKENS_MAX ~0 // ============================================================================ // internal structures struct SpiceChannel { bool connected; bool ready; bool initDone; uint8_t channelType; int socket; uint32_t ackFrequency; uint32_t ackCount; uint32_t serial; LG_Lock lock; }; struct SpiceKeyboard { uint32_t modifiers; }; struct SpiceMouse { uint32_t buttonState; int sentCount; int rpos, wpos; }; union SpiceAddr { struct sockaddr addr; struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr_un un; }; struct Spice { char password[32]; short family; union SpiceAddr addr; bool hasAgent; uint32_t serverTokens; uint32_t clientTokens; uint32_t sessionID; uint32_t channelID; struct SpiceChannel scMain; struct SpiceChannel scInputs; struct SpiceKeyboard kb; struct SpiceMouse mouse; bool cbSupported; bool cbSelection; // clipboard variables bool cbAgentGrabbed; bool cbClientGrabbed; SpiceDataType cbType; uint8_t * cbBuffer; uint32_t cbRemain; uint32_t cbSize; SpiceClipboardNotice cbNoticeFn; SpiceClipboardData cbDataFn; SpiceClipboardRelease cbReleaseFn; SpiceClipboardRequest cbRequestFn; }; // globals struct Spice spice = { .sessionID = 0, .scMain .connected = false, .scMain .channelType = SPICE_CHANNEL_MAIN, .scInputs.connected = false, .scInputs.channelType = SPICE_CHANNEL_INPUTS, }; // internal forward decls bool spice_connect_channel (struct SpiceChannel * channel); void spice_disconnect_channel(struct SpiceChannel * channel); bool spice_process_ack(struct SpiceChannel * channel); bool spice_on_common_read (struct SpiceChannel * channel, SpiceMiniDataHeader * header, bool * handled); bool spice_on_main_channel_read (); bool spice_on_inputs_channel_read(); bool spice_agent_process (uint32_t dataSize); bool spice_agent_connect (); bool spice_agent_send_caps(bool request); void spice_agent_on_clipboard(); // utility functions static uint32_t spice_type_to_agent_type(SpiceDataType type); static SpiceDataType agent_type_to_spice_type(uint32_t type); // thread safe read/write methods bool spice_write_msg (struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size); bool spice_agent_write_msg (uint32_t type, const void * buffer, ssize_t size); // non thread safe read/write methods (nl = non-locking) bool spice_read_nl (const struct SpiceChannel * channel, void * buffer, const ssize_t size); ssize_t spice_write_nl (const struct SpiceChannel * channel, const void * buffer, const ssize_t size); bool spice_discard_nl (const struct SpiceChannel * channel, ssize_t size); bool spice_write_msg_nl( struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size, const ssize_t extra); // ============================================================================ bool spice_connect(const char * host, const unsigned short port, const char * password) { strncpy(spice.password, password, sizeof(spice.password) - 1); memset(&spice.addr, 0, sizeof(spice.addr)); if (port == 0) { spice.family = AF_UNIX; spice.addr.un.sun_family = spice.family; strncpy(spice.addr.un.sun_path, host, sizeof(spice.addr.un.sun_path) - 1); DEBUG_INFO("Remote: %s", host); } else { spice.family = AF_INET; inet_pton(spice.family, host, &spice.addr.in.sin_addr); spice.addr.in.sin_family = spice.family; spice.addr.in.sin_port = htons(port); DEBUG_INFO("Remote: %s:%u", host, port); } spice.channelID = 0; if (!spice_connect_channel(&spice.scMain)) { DEBUG_ERROR("connect main channel failed"); return false; } return true; } // ============================================================================ void spice_disconnect() { spice_disconnect_channel(&spice.scMain ); spice_disconnect_channel(&spice.scInputs); spice.sessionID = 0; if (spice.cbBuffer) free(spice.cbBuffer); spice.cbBuffer = NULL; spice.cbRemain = 0; spice.cbSize = 0; spice.cbAgentGrabbed = false; spice.cbClientGrabbed = false; } // ============================================================================ bool spice_ready() { return spice.scMain.connected && spice.scInputs.connected; } // ============================================================================ bool spice_process() { fd_set readSet; FD_ZERO(&readSet); FD_SET(spice.scMain.socket , &readSet); FD_SET(spice.scInputs.socket, &readSet); struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; int rc = select(FD_SETSIZE, &readSet, NULL, NULL, &timeout); if (rc < 0) { DEBUG_ERROR("select failure"); return false; } for(int i = 0; i < FD_SETSIZE; ++i) if (FD_ISSET(i, &readSet)) { if (i == spice.scMain.socket) { if (spice_on_main_channel_read()) { if (spice.scMain.connected && !spice_process_ack(&spice.scMain)) { DEBUG_ERROR("failed to process ack on main channel"); return false; } continue; } else { DEBUG_ERROR("failed to perform read on main channel"); return false; } } if (spice.scInputs.connected && i == spice.scInputs.socket) { if (!spice_process_ack(&spice.scInputs)) { DEBUG_ERROR("failed to process ack on inputs channel"); return false; } if (spice_on_inputs_channel_read()) continue; else { DEBUG_ERROR("failed to perform read on inputs channel"); return false; } } } return true; } // ============================================================================ bool spice_process_ack(struct SpiceChannel * channel) { if (channel->ackFrequency == 0) return true; if (channel->ackCount++ != channel->ackFrequency) return true; channel->ackCount = 0; return spice_write_msg(channel, SPICE_MSGC_ACK, "\0", 1); } // ============================================================================ bool spice_on_common_read(struct SpiceChannel * channel, SpiceMiniDataHeader * header, bool * handled) { *handled = false; if (!spice_read_nl(channel, header, sizeof(SpiceMiniDataHeader))) { DEBUG_ERROR("read failure"); return false; } //#if 0 DEBUG_PROTO("socket: %d, type: %2u, size %6u", channel->socket, header->type, header->size); //#endif if (!channel->initDone) return true; switch(header->type) { case SPICE_MSG_MIGRATE: case SPICE_MSG_MIGRATE_DATA: { DEBUG_PROTO("SPICE_MSG_MIGRATE_DATA"); *handled = true; DEBUG_WARN("migration is not supported"); return false; } case SPICE_MSG_SET_ACK: { DEBUG_INFO("SPICE_MSG_SET_ACK"); *handled = true; SpiceMsgSetAck in; if (!spice_read_nl(channel, &in, sizeof(in))) return false; channel->ackFrequency = in.window; SpiceMsgcAckSync out; out.generation = in.generation; if (!spice_write_msg(channel, SPICE_MSGC_ACK_SYNC, &out, sizeof(out))) return false; return true; } case SPICE_MSG_PING: { DEBUG_PROTO("SPICE_MSG_PING"); *handled = true; SpiceMsgPing in; if (!spice_read_nl(channel, &in, sizeof(in))) return false; const int discard = header->size - sizeof(in); if (!spice_discard_nl(channel, discard)) { DEBUG_ERROR("failed discarding enough bytes (%d) from the ping packet", discard); return false; } else DEBUG_PROTO("discard %d", discard); SpiceMsgcPong out; out.id = in.id; out.timestamp = in.timestamp; if (!spice_write_msg(channel, SPICE_MSGC_PONG, &out, sizeof(out))) return false; return true; } case SPICE_MSG_WAIT_FOR_CHANNELS: case SPICE_MSG_DISCONNECTING : { *handled = true; DEBUG_FIXME("ignored wait-for-channels or disconnect message"); return false; } case SPICE_MSG_NOTIFY: { DEBUG_PROTO("SPICE_MSG_NOTIFY"); SpiceMsgNotify in; if (!spice_read_nl(channel, &in, sizeof(in))) return false; char msg[in.message_len+1]; if (!spice_read_nl(channel, msg, in.message_len+1)) return false; DEBUG_INFO("notify message: %s", msg); *handled = true; return true; } } return true; } // ============================================================================ bool spice_on_main_channel_read() { struct SpiceChannel *channel = &spice.scMain; SpiceMiniDataHeader header; bool handled; if (!spice_on_common_read(channel, &header, &handled)) { DEBUG_ERROR("read failure"); return false; } if (handled) return true; if (!channel->initDone) { if (header.type != SPICE_MSG_MAIN_INIT) { spice_disconnect(); DEBUG_ERROR("expected main init message but got type %u", header.type); return false; } DEBUG_PROTO("SPICE_MSG_MAIN_INIT"); channel->initDone = true; SpiceMsgMainInit msg; if (!spice_read_nl(channel, &msg, sizeof(msg))) { spice_disconnect(); return false; } spice.sessionID = msg.session_id; spice.serverTokens = msg.agent_tokens; spice.hasAgent = msg.agent_connected; if (spice.hasAgent && !spice_agent_connect()) { spice_disconnect(); DEBUG_ERROR("failed to connect to spice agent"); return false; } if (msg.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && !spice_mouse_mode(false)) { DEBUG_ERROR("failed to set mouse mode"); return false; } if (!spice_write_msg(channel, SPICE_MSGC_MAIN_ATTACH_CHANNELS, NULL, 0)) { spice_disconnect(); DEBUG_ERROR("failed to ask for channel list"); return false; } return true; } if (header.type == SPICE_MSG_MAIN_CHANNELS_LIST) { DEBUG_PROTO("SPICE_MSG_MAIN_CHANNELS_LIST"); SpiceMainChannelsList msg; if (!spice_read_nl(channel, &msg, sizeof(msg))) { DEBUG_ERROR("Failed to read channel list msg"); spice_disconnect(); return false; } // documentation doesn't state that the array is null terminated but it seems that it is SpiceChannelID channels[msg.num_of_channels]; if (!spice_read_nl(channel, &channels, msg.num_of_channels * sizeof(SpiceChannelID))) { DEBUG_ERROR("Failed to read channel list vector"); spice_disconnect(); return false; } for(int i = 0; i < msg.num_of_channels; ++i) { DEBUG_PROTO("channel %d = %u", i, channels[i].type); if (channels[i].type == SPICE_CHANNEL_INPUTS) { if (spice.scInputs.connected) { DEBUG_ERROR("inputs channel already connected"); spice_disconnect(); return false; } if (!spice_connect_channel(&spice.scInputs)) { DEBUG_ERROR("failed to connect inputs channel"); spice_disconnect(); return false; } } } return true; } if (header.type == SPICE_MSG_MAIN_AGENT_CONNECTED) { DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_CONNECTED"); spice.hasAgent = true; if (!spice_agent_connect()) { DEBUG_ERROR("failed to connect to spice agent"); spice_disconnect(); return false; } return true; } if (header.type == SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS) { DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS"); uint32_t num_tokens; if (!spice_read_nl(channel, &num_tokens, sizeof(num_tokens))) { DEBUG_ERROR("failed to read agent tokens"); spice_disconnect(); return false; } spice.hasAgent = true; spice.serverTokens = num_tokens; if (!spice_agent_connect()) { DEBUG_ERROR("failed to connect to spice agent"); spice_disconnect(); return false; } return true; } if (header.type == SPICE_MSG_MAIN_AGENT_DISCONNECTED) { DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_DISCONNECTED"); uint32_t error; if (!spice_read_nl(channel, &error, sizeof(error))) { DEBUG_ERROR("failed to read agent disconnect error code"); spice_disconnect(); return false; } DEBUG_INFO("Spice agent disconnected, error: %u", error); spice.hasAgent = false; if (spice.cbBuffer) { free(spice.cbBuffer); spice.cbBuffer = NULL; spice.cbSize = 0; spice.cbRemain = 0; } return true; } if (header.type == SPICE_MSG_MAIN_AGENT_DATA) { DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_DATA"); if (!spice.hasAgent) { DEBUG_WARN("recieved agent data when the agent is yet to be started"); spice_discard_nl(channel, header.size); return true; } if (!spice_agent_process(header.size)) { DEBUG_ERROR("failed to process spice agent message"); spice_disconnect(); return false; } return true; } if (header.type == SPICE_MSG_MAIN_AGENT_TOKEN) { DEBUG_PROTO("SPICE_MSG_MAIN_AGENT_TOKEN"); uint32_t num_tokens; if (!spice_read_nl(channel, &num_tokens, sizeof(num_tokens))) { DEBUG_ERROR("failed to read agent tokens"); spice_disconnect(); return false; } spice.serverTokens = num_tokens; return true; } DEBUG_WARN("main channel unhandled message type %u", header.type); spice_discard_nl(channel, header.size); return true; } // ============================================================================ bool spice_on_inputs_channel_read() { struct SpiceChannel *channel = &spice.scInputs; SpiceMiniDataHeader header; bool handled; if (!spice_on_common_read(channel, &header, &handled)) { DEBUG_ERROR("read failure"); return false; } if (handled) return true; switch(header.type) { case SPICE_MSG_INPUTS_INIT: { DEBUG_PROTO("SPICE_MSG_INPUTS_INIT"); if (channel->initDone) { DEBUG_ERROR("input init message already done"); return false; } channel->initDone = true; SpiceMsgInputsInit in; if (!spice_read_nl(channel, &in, sizeof(in))) return false; return true; } case SPICE_MSG_INPUTS_KEY_MODIFIERS: { DEBUG_PROTO("SPICE_MSG_INPUTS_KEY_MODIFIERS"); SpiceMsgInputsInit in; if (!spice_read_nl(channel, &in, sizeof(in))) return false; spice.kb.modifiers = in.modifiers; return true; } case SPICE_MSG_INPUTS_MOUSE_MOTION_ACK: { DEBUG_PROTO("SPICE_MSG_INPUTS_MOUSE_MOTION_ACK"); const int count = __sync_add_and_fetch(&spice.mouse.sentCount, SPICE_INPUT_MOTION_ACK_BUNCH); if (count < 0) { DEBUG_ERROR("comms failure, too many mouse motion ACKs recieved"); return false; } return true; } } DEBUG_WARN("inputs channel unhandled message type %u", header.type); spice_discard_nl(channel, header.size); return true; } // ============================================================================ bool spice_connect_channel(struct SpiceChannel * channel) { channel->initDone = false; channel->ackFrequency = 0; channel->ackCount = 0; channel->serial = 0; LG_LOCK_INIT(channel->lock); size_t addrSize; switch(spice.family) { case AF_UNIX: addrSize = sizeof(spice.addr.un); break; case AF_INET: addrSize = sizeof(spice.addr.in); break; case AF_INET6: addrSize = sizeof(spice.addr.in6); break; default: DEBUG_ERROR("Unsupported socket family"); return false; } channel->socket = socket(spice.family, SOCK_STREAM, 0); if (channel->socket == -1) return false; if (spice.family != AF_UNIX) { int flag = 1; setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)); } if (connect(channel->socket, &spice.addr.addr, addrSize) == -1) { DEBUG_ERROR("socket connect failure"); close(channel->socket); return false; } channel->connected = true; uint32_t supportCaps[COMMON_CAPS_BYTES / sizeof(uint32_t)]; uint32_t channelCaps[MAIN_CAPS_BYTES / sizeof(uint32_t)]; memset(supportCaps, 0, sizeof(supportCaps)); memset(channelCaps, 0, sizeof(channelCaps)); COMMON_SET_CAPABILITY(supportCaps, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION); COMMON_SET_CAPABILITY(supportCaps, SPICE_COMMON_CAP_AUTH_SPICE ); COMMON_SET_CAPABILITY(supportCaps, SPICE_COMMON_CAP_MINI_HEADER ); if (channel == &spice.scMain) MAIN_SET_CAPABILITY(channelCaps, SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS); SpiceLinkHeader header = { .magic = SPICE_MAGIC , .major_version = SPICE_VERSION_MAJOR, .minor_version = SPICE_VERSION_MINOR, .size = sizeof(SpiceLinkMess) + sizeof(supportCaps ) + sizeof(channelCaps ) }; SpiceLinkMess message = { .connection_id = spice.sessionID, .channel_type = channel->channelType, .channel_id = spice.channelID, .num_common_caps = sizeof(supportCaps) / sizeof(uint32_t), .num_channel_caps = sizeof(channelCaps) / sizeof(uint32_t), .caps_offset = sizeof(SpiceLinkMess) }; if ( !spice_write_nl(channel, &header , sizeof(header )) || !spice_write_nl(channel, &message , sizeof(message )) || !spice_write_nl(channel, &supportCaps, sizeof(supportCaps)) || !spice_write_nl(channel, &channelCaps, sizeof(channelCaps)) ) { DEBUG_ERROR("failed to write the initial payload"); spice_disconnect_channel(channel); return false; } if (!spice_read_nl(channel, &header, sizeof(header))) { DEBUG_ERROR("failed to read SpiceLinkHeader"); spice_disconnect_channel(channel); return false; } if (header.magic != SPICE_MAGIC || header.major_version != SPICE_VERSION_MAJOR) { DEBUG_ERROR("invalid or unsupported protocol version"); spice_disconnect_channel(channel); return false; } if (header.size < sizeof(SpiceLinkReply)) { DEBUG_ERROR("reported data size too small"); spice_disconnect_channel(channel); return false; } SpiceLinkReply reply; if (!spice_read_nl(channel, &reply, sizeof(reply))) { DEBUG_ERROR("failed to read SpiceLinkReply"); spice_disconnect_channel(channel); return false; } if (reply.error != SPICE_LINK_ERR_OK) { DEBUG_ERROR("server replied with error %u", reply.error); spice_disconnect_channel(channel); return false; } uint32_t capsCommon [reply.num_common_caps ]; uint32_t capsChannel[reply.num_channel_caps]; if ( !spice_read_nl(channel, &capsCommon , sizeof(capsCommon)) || !spice_read_nl(channel, &capsChannel, sizeof(capsChannel)) ) { DEBUG_ERROR("failed to read the capabilities"); spice_disconnect_channel(channel); return false; } SpiceLinkAuthMechanism auth; auth.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE; if (!spice_write_nl(channel, &auth, sizeof(auth))) { DEBUG_ERROR("failed to write the auth mechanism"); spice_disconnect_channel(channel); return false; } struct spice_password pass; if (!spice_rsa_encrypt_password(reply.pub_key, spice.password, &pass)) { DEBUG_ERROR("failed to encrypt the password"); spice_disconnect_channel(channel); return false; } if (!spice_write_nl(channel, pass.data, pass.size)) { spice_rsa_free_password(&pass); DEBUG_ERROR("failed to write encrypted data"); spice_disconnect_channel(channel); return false; } spice_rsa_free_password(&pass); uint32_t linkResult; if (!spice_read_nl(channel, &linkResult, sizeof(linkResult))) { DEBUG_ERROR("failed to read SpiceLinkResult"); spice_disconnect_channel(channel); return false; } if (linkResult != SPICE_LINK_ERR_OK) { DEBUG_ERROR("connect code error %u", linkResult); spice_disconnect_channel(channel); return false; } channel->ready = true; return true; } // ============================================================================ void spice_disconnect_channel(struct SpiceChannel * channel) { if (channel->connected) { shutdown(channel->socket, SHUT_WR); char buffer[1024]; ssize_t len = 0; do len = read(channel->socket, buffer, sizeof(buffer)); while(len > 0); close(channel->socket); } channel->connected = false; LG_LOCK_FREE(channel->lock); } // ============================================================================ bool spice_agent_connect() { DEBUG_INFO("Spice agent available, sending start"); spice.clientTokens = SPICE_AGENT_TOKENS_MAX; if (!spice_write_msg(&spice.scMain, SPICE_MSGC_MAIN_AGENT_START, &spice.clientTokens, sizeof(spice.clientTokens))) { DEBUG_ERROR("failed to send agent start message"); return false; } if (!spice_agent_send_caps(true)) return false; return true; } // ============================================================================ bool spice_agent_process(uint32_t dataSize) { if (spice.cbRemain) { const uint32_t r = spice.cbRemain > dataSize ? dataSize : spice.cbRemain; if (!spice_read_nl(&spice.scMain, spice.cbBuffer + spice.cbSize, r)) { DEBUG_ERROR("failed to read the clipboard data"); free(spice.cbBuffer); spice.cbBuffer = NULL; spice.cbRemain = 0; spice.cbSize = 0; return false; } spice.cbRemain -= r; spice.cbSize += r; if (spice.cbRemain == 0) spice_agent_on_clipboard(); return true; } VDAgentMessage msg; #pragma pack(push,1) struct Selection { uint8_t selection; uint8_t reserved[3]; }; #pragma pack(pop) if (!spice_read_nl(&spice.scMain, &msg, sizeof(msg))) { DEBUG_ERROR("failed to read spice agent message"); return false; } dataSize -= sizeof(msg); if (msg.protocol != VD_AGENT_PROTOCOL) { DEBUG_ERROR("invalid or unknown spice agent protocol"); return false; } switch(msg.type) { case VD_AGENT_ANNOUNCE_CAPABILITIES: { VDAgentAnnounceCapabilities *caps = (VDAgentAnnounceCapabilities *)malloc(msg.size); memset(caps, 0, msg.size); if (!spice_read_nl(&spice.scMain, caps, msg.size)) { DEBUG_ERROR("failed to read agent message payload"); free(caps); return false; } const int capsSize = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg.size); spice.cbSupported = VD_AGENT_HAS_CAPABILITY(caps->caps, capsSize, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND) || VD_AGENT_HAS_CAPABILITY(caps->caps, capsSize, VD_AGENT_CAP_CLIPBOARD_SELECTION); spice.cbSelection = VD_AGENT_HAS_CAPABILITY(caps->caps, capsSize, VD_AGENT_CAP_CLIPBOARD_SELECTION); if (spice.cbSupported) DEBUG_INFO("clipboard capability detected"); if (caps->request && !spice_agent_send_caps(false)) { free(caps); return false; } free(caps); return true; } case VD_AGENT_CLIPBOARD: case VD_AGENT_CLIPBOARD_REQUEST: case VD_AGENT_CLIPBOARD_GRAB: case VD_AGENT_CLIPBOARD_RELEASE: { uint32_t remaining = msg.size; if (spice.cbSelection) { struct Selection selection; if (!spice_read_nl(&spice.scMain, &selection, sizeof(selection))) { DEBUG_ERROR("failed to read the clipboard selection"); return false; } remaining -= sizeof(selection); dataSize -= sizeof(selection); } if (msg.type == VD_AGENT_CLIPBOARD_RELEASE) { DEBUG_CLIPBOARD("VD_AGENT_CLIPBOARD_RELEASE"); spice.cbAgentGrabbed = false; if (spice.cbReleaseFn) spice.cbReleaseFn(); return true; } if (msg.type == VD_AGENT_CLIPBOARD || msg.type == VD_AGENT_CLIPBOARD_REQUEST) { uint32_t type; if (!spice_read_nl(&spice.scMain, &type, sizeof(type))) { DEBUG_ERROR("failed to read the clipboard data type"); return false; } remaining -= sizeof(type); dataSize -= sizeof(type); if (msg.type == VD_AGENT_CLIPBOARD) { DEBUG_CLIPBOARD("VD_AGENT_CLIPBOARD"); if (spice.cbBuffer) { DEBUG_ERROR("cbBuffer was never freed"); return false; } spice.cbSize = 0; spice.cbRemain = remaining; spice.cbBuffer = (uint8_t *)malloc(remaining); const uint32_t r = remaining > dataSize ? dataSize : remaining; if (!spice_read_nl(&spice.scMain, spice.cbBuffer, r)) { DEBUG_ERROR("failed to read the clipboard data"); free(spice.cbBuffer); spice.cbBuffer = NULL; spice.cbRemain = 0; spice.cbSize = 0; return false; } spice.cbRemain -= r; spice.cbSize += r; if (spice.cbRemain == 0) spice_agent_on_clipboard(); return true; } else { DEBUG_CLIPBOARD("VD_AGENT_CLIPBOARD_REQUEST"); if (spice.cbRequestFn) spice.cbRequestFn(agent_type_to_spice_type(type)); return true; } } else { DEBUG_CLIPBOARD("VD_AGENT_CLIPBOARD_GRAB"); if (remaining == 0) return true; uint32_t *types = malloc(remaining); if (!spice_read_nl(&spice.scMain, types, remaining)) { DEBUG_ERROR("failed to read the clipboard grab types"); return false; } // there is zero documentation on the types field, it might be a bitfield // but for now we are going to assume it's not. spice.cbType = agent_type_to_spice_type(types[0]); spice.cbAgentGrabbed = true; spice.cbClientGrabbed = false; if (spice.cbSelection) { // Windows doesnt support this, so until it's needed there is no point messing with it DEBUG_ERROR("Fixme!"); return false; } if (spice.cbNoticeFn) spice.cbNoticeFn(spice.cbType); free(types); return true; } } } DEBUG_WARN("unknown agent message type %d", msg.type); spice_discard_nl(&spice.scMain, msg.size); return true; } // ============================================================================ void spice_agent_on_clipboard() { if (spice.cbDataFn) spice.cbDataFn(spice.cbType, spice.cbBuffer, spice.cbSize); free(spice.cbBuffer); spice.cbBuffer = NULL; spice.cbSize = 0; spice.cbRemain = 0; } // ============================================================================ bool spice_agent_send_caps(bool request) { const ssize_t capsSize = sizeof(VDAgentAnnounceCapabilities) + VD_AGENT_CAPS_BYTES; VDAgentAnnounceCapabilities *caps = (VDAgentAnnounceCapabilities *)malloc(capsSize); memset(caps, 0, capsSize); caps->request = request ? 1 : 0; VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION); if (!spice_agent_write_msg(VD_AGENT_ANNOUNCE_CAPABILITIES, caps, capsSize)) { DEBUG_ERROR("failed to send agent capabilities"); free(caps); return false; } free(caps); return true; } // ============================================================================ bool spice_agent_write_msg(uint32_t type, const void * buffer, ssize_t size) { VDAgentMessage msg; msg.protocol = VD_AGENT_PROTOCOL; msg.type = type; msg.opaque = 0; msg.size = size; LG_LOCK(spice.scMain.lock); uint8_t * buf = (uint8_t *)buffer; ssize_t toWrite = size > VD_AGENT_MAX_DATA_SIZE - sizeof(msg) ? VD_AGENT_MAX_DATA_SIZE - sizeof(msg) : size; if (!spice_write_msg_nl(&spice.scMain, SPICE_MSGC_MAIN_AGENT_DATA, &msg, sizeof(msg), toWrite)) { LG_UNLOCK(spice.scMain.lock); DEBUG_ERROR("failed to write agent data header"); return false; } bool first = true; while(toWrite) { bool ok = false; if (first) { ok = spice_write_nl(&spice.scMain, buf, toWrite) == toWrite; first = false; } else { ok = spice_write_msg_nl(&spice.scMain, SPICE_MSGC_MAIN_AGENT_DATA, buf, toWrite, 0); } if (!ok) { LG_UNLOCK(spice.scMain.lock); DEBUG_ERROR("failed to write agent data payload"); return false; } size -= toWrite; buf += toWrite; toWrite = size > VD_AGENT_MAX_DATA_SIZE ? VD_AGENT_MAX_DATA_SIZE : size; } LG_UNLOCK(spice.scMain.lock); return true; } // ============================================================================ ssize_t spice_write_nl(const struct SpiceChannel * channel, const void * buffer, const ssize_t size) { if (!channel->connected) { DEBUG_ERROR("not connected"); return -1; } if (!buffer) { DEBUG_ERROR("invalid buffer argument supplied"); return -1; } ssize_t len = send(channel->socket, buffer, size, 0); if (len != size) DEBUG_WARN("incomplete write"); return len; } // ============================================================================ inline bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size) { bool result; LG_LOCK(channel->lock); result = spice_write_msg_nl(channel, type, buffer, size, 0); LG_UNLOCK(channel->lock); return result; } // ============================================================================ bool spice_write_msg_nl(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size, const ssize_t extra) { if (!channel->ready) { DEBUG_ERROR("channel not ready"); return false; } SpiceMiniDataHeader header; ++channel->serial; header.type = type; header.size = size + extra; if (spice_write_nl(channel, &header, sizeof(header)) != sizeof(header)) { DEBUG_ERROR("failed to write message header"); return false; } if (buffer && size) { if (spice_write_nl(channel, buffer, size) != size) { DEBUG_ERROR("failed to write message body"); return false; } } return true; } // ============================================================================ bool spice_read_nl(const struct SpiceChannel * channel, void * buffer, const ssize_t size) { if (!channel->connected) { DEBUG_ERROR("not connected"); return -1; } if (!buffer) { DEBUG_ERROR("invalid buffer argument supplied"); return false; } size_t left = size; uint8_t * buf = (uint8_t *)buffer; while(left) { ssize_t len = read(channel->socket, buf, left); if (len <= 0) { if (len == 0) DEBUG_ERROR("remote end closd connection after %ld byte(s)", size - left); return false; } left -= len; buf += len; } return true; } // ============================================================================ bool spice_discard_nl(const struct SpiceChannel * channel, ssize_t size) { void *c = malloc(8192); ssize_t left = size; while(left) { size_t len = read(channel->socket, c, left > 8192 ? 8192 : left); if (len <= 0) { if (len == 0) DEBUG_ERROR("remote end closed connection after %ld byte(s)", size - left); free(c); return false; } left -= len; } free(c); return true; } // ============================================================================ bool spice_key_down(uint32_t code) { DEBUG_KEYBOARD("%u", code); if (!spice.scInputs.connected) { DEBUG_ERROR("not connected"); return false; } if (code > 0x100) code = 0xe0 | ((code - 0x100) << 8); SpiceMsgcKeyDown msg; msg.code = code; return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_DOWN, &msg, sizeof(msg)); } // ============================================================================ bool spice_key_up(uint32_t code) { DEBUG_KEYBOARD("%u", code); if (!spice.scInputs.connected) { DEBUG_ERROR("not connected"); return false; } if (code < 0x100) code |= 0x80; else code = 0x80e0 | ((code - 0x100) << 8); SpiceMsgcKeyDown msg; msg.code = code; return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_UP, &msg, sizeof(msg)); } // ============================================================================ bool spice_mouse_mode(bool server) { DEBUG_MOUSE("%s", server ? "server" : "client"); if (!spice.scMain.connected) { DEBUG_ERROR("not connected"); return false; } SpiceMsgcMainMouseModeRequest msg; msg.mouse_mode = server ? SPICE_MOUSE_MODE_SERVER : SPICE_MOUSE_MODE_CLIENT; return spice_write_msg(&spice.scMain, SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, &msg, sizeof(msg)); } // ============================================================================ bool spice_mouse_position(uint32_t x, uint32_t y) { DEBUG_MOUSE("x=%u, y=%u", x, y); if (!spice.scInputs.connected) { DEBUG_ERROR("not connected"); return false; } SpiceMsgcMousePosition msg; msg.x = x; msg.y = y; msg.button_state = spice.mouse.buttonState; msg.display_id = 0; __sync_fetch_and_add(&spice.mouse.sentCount, 1); return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_POSITION, &msg, sizeof(msg)); } // ============================================================================ bool spice_mouse_motion(int32_t x, int32_t y) { DEBUG_MOUSE("x=%d, y=%d", x, y); if (!spice.scInputs.connected) { DEBUG_ERROR("not connected"); return false; } SpiceMsgcMouseMotion msg; msg.x = x; msg.y = y; msg.button_state = spice.mouse.buttonState; __sync_fetch_and_add(&spice.mouse.sentCount, 1); return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_MOTION, &msg, sizeof(msg)); } // ============================================================================ bool spice_mouse_press(uint32_t button) { DEBUG_MOUSE("%u", button); if (!spice.scInputs.connected) { DEBUG_ERROR("not connected"); return false; } switch(button) { case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_LEFT ; break; case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_MIDDLE; break; case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_RIGHT ; break; } SpiceMsgcMousePress msg; msg.button = button; msg.button_state = spice.mouse.buttonState; return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_PRESS, &msg, sizeof(msg)); } // ============================================================================ bool spice_mouse_release(uint32_t button) { DEBUG_MOUSE("%u", button); if (!spice.scInputs.connected) { DEBUG_ERROR("not connected"); return false; } switch(button) { case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_LEFT ; break; case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE; break; case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT ; break; } SpiceMsgcMouseRelease msg; msg.button = button; msg.button_state = spice.mouse.buttonState; return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_RELEASE, &msg, sizeof(msg)); } // ============================================================================ static uint32_t spice_type_to_agent_type(SpiceDataType type) { switch(type) { case SPICE_DATA_TEXT: return VD_AGENT_CLIPBOARD_UTF8_TEXT ; break; case SPICE_DATA_PNG : return VD_AGENT_CLIPBOARD_IMAGE_PNG ; break; case SPICE_DATA_BMP : return VD_AGENT_CLIPBOARD_IMAGE_BMP ; break; case SPICE_DATA_TIFF: return VD_AGENT_CLIPBOARD_IMAGE_TIFF; break; case SPICE_DATA_JPEG: return VD_AGENT_CLIPBOARD_IMAGE_JPG ; break; default: DEBUG_ERROR("unsupported spice data type specified"); return VD_AGENT_CLIPBOARD_NONE; } } static SpiceDataType agent_type_to_spice_type(uint32_t type) { switch(type) { case VD_AGENT_CLIPBOARD_UTF8_TEXT : return SPICE_DATA_TEXT; break; case VD_AGENT_CLIPBOARD_IMAGE_PNG : return SPICE_DATA_PNG ; break; case VD_AGENT_CLIPBOARD_IMAGE_BMP : return SPICE_DATA_BMP ; break; case VD_AGENT_CLIPBOARD_IMAGE_TIFF: return SPICE_DATA_TIFF; break; case VD_AGENT_CLIPBOARD_IMAGE_JPG : return SPICE_DATA_JPEG; break; default: DEBUG_ERROR("unsupported agent data type specified"); return SPICE_DATA_NONE; } } // ============================================================================ bool spice_clipboard_request(SpiceDataType type) { VDAgentClipboardRequest req; if (!spice.cbAgentGrabbed) { DEBUG_ERROR("the agent has not grabbed any data yet"); return false; } if (type != spice.cbType) { DEBUG_ERROR("data type requested doesn't match reported data type"); return false; } req.type = spice_type_to_agent_type(type); if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_REQUEST, &req, sizeof(req))) { DEBUG_ERROR("failed to request clipboard data"); return false; } return true; } // ============================================================================ bool spice_set_clipboard_cb(SpiceClipboardNotice cbNoticeFn, SpiceClipboardData cbDataFn, SpiceClipboardRelease cbReleaseFn, SpiceClipboardRequest cbRequestFn) { if ((cbNoticeFn && !cbDataFn) || (cbDataFn && !cbNoticeFn)) { DEBUG_ERROR("clipboard callback notice and data callbacks must be specified"); return false; } spice.cbNoticeFn = cbNoticeFn; spice.cbDataFn = cbDataFn; spice.cbReleaseFn = cbReleaseFn; spice.cbRequestFn = cbRequestFn; return true; } // ============================================================================ bool spice_clipboard_grab(SpiceDataType type) { if (type == SPICE_DATA_NONE) { DEBUG_ERROR("grab type is invalid"); return false; } if (spice.cbSelection) { uint8_t req[8] = { VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD }; ((uint32_t*)req)[1] = spice_type_to_agent_type(type); if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_GRAB, req, sizeof(req))) { DEBUG_ERROR("failed to grab the clipboard"); return false; } spice.cbClientGrabbed = true; return true; } uint32_t req = spice_type_to_agent_type(type); if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_GRAB, &req, sizeof(req))) { DEBUG_ERROR("failed to grab the clipboard"); return false; } spice.cbClientGrabbed = true; return true; } // ============================================================================ bool spice_clipboard_release() { // check if if there is anything to release first if (!spice.cbClientGrabbed) return true; if (spice.cbSelection) { uint8_t req[4] = { VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD }; if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_RELEASE, req, sizeof(req))) { DEBUG_ERROR("failed to release the clipboard"); return false; } spice.cbClientGrabbed = false; return true; } if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_RELEASE, NULL, 0)) { DEBUG_ERROR("failed to release the clipboard"); return false; } spice.cbClientGrabbed = false; return true; } // ============================================================================ bool spice_clipboard_data(SpiceDataType type, uint8_t * data, size_t size) { uint8_t * buffer; size_t bufSize; if (spice.cbSelection) { bufSize = 8 + size; buffer = (uint8_t *)malloc(bufSize); buffer[0] = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; buffer[1] = buffer[2] = buffer[3] = 0; ((uint32_t*)buffer)[1] = spice_type_to_agent_type(type); memcpy(buffer + 8, data, size); } else { bufSize = 4 + size; buffer = (uint8_t *)malloc(bufSize); ((uint32_t*)buffer)[0] = spice_type_to_agent_type(type); memcpy(buffer + 4, data, size); } if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD, buffer, bufSize)) { DEBUG_ERROR("failed to write the clipboard data"); free(buffer); return false; } free(buffer); return true; }looking-glass-0+b1/client/src/000077500000000000000000000000001351530562300163325ustar00rootroot00000000000000looking-glass-0+b1/client/src/app.c000066400000000000000000000036011351530562300172560ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "main.h" #include "common/debug.h" #include void app_alert(LG_MsgAlert type, const char * fmt, ...) { if (!state.lgr || !params.showAlerts) return; va_list args; va_start(args, fmt); const int length = vsnprintf(NULL, 0, fmt, args); va_end(args); char *buffer = malloc(length + 1); va_start(args, fmt); vsnprintf(buffer, length + 1, fmt, args); va_end(args); state.lgr->on_alert( state.lgrData, type, buffer, NULL ); free(buffer); } KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque) { // don't allow duplicate binds if (state.bindings[key]) { DEBUG_INFO("Key already bound"); return NULL; } KeybindHandle handle = (KeybindHandle)malloc(sizeof(struct KeybindHandle)); handle->key = key; handle->callback = callback; handle->opaque = opaque; state.bindings[key] = handle; return handle; } void app_release_keybind(KeybindHandle * handle) { if (!*handle) return; state.bindings[(*handle)->key] = NULL; free(*handle); *handle = NULL; }looking-glass-0+b1/client/src/config.c000066400000000000000000000413211351530562300177440ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "main.h" #include "config.h" #include "common/option.h" #include "common/debug.h" #include "common/stringutils.h" #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 char * optScancodeToString (struct Option * opt); 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 = "shmFile", .description = "The path to the shared memory file", .shortopt = 'f', .type = OPTION_TYPE_STRING, .value.x_string = "/dev/shm/looking-glass", }, { .module = "app", .name = "shmSize", .description = "Specify the size in MB of the shared memory file (0 = detect)", .shortopt = 'L', .type = OPTION_TYPE_INT, .value.x_int = 0, }, { .module = "app", .name = "renderer", .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 }, // 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 = "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 = true, }, { .module = "win", .name = "fpsLimit", .description = "Frame rate limit (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 = "alerts", .description = "Show on screen alert messages", .shortopt = 'q', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, // 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 = "escapeKey", .description = "Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values", .shortopt = 'm', .type = OPTION_TYPE_INT, .value.x_int = SDL_SCANCODE_SCROLLLOCK, .toString = optScancodeToString }, { .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, }, // 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 }, {0} }; void config_init() { params.center = true; params.w = 1024; 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 params.shmFile = option_get_string("app", "shmFile" ); params.shmSize = option_get_int ("app", "shmSize" ) * 1048576; params.cursorPollInterval = option_get_int ("app", "cursorPollInterval"); params.framePollInterval = option_get_int ("app", "framePollInterval" ); params.windowTitle = option_get_string("win", "title" ); params.autoResize = option_get_bool ("win", "autoResize" ); params.allowResize = option_get_bool ("win", "allowResize" ); params.keepAspect = option_get_bool ("win", "keepAspect" ); params.borderless = option_get_bool ("win", "borderless" ); params.fullscreen = option_get_bool ("win", "fullScreen" ); params.maximize = option_get_bool ("win", "maximize" ); params.fpsLimit = option_get_int ("win", "fpsLimit" ); params.showFPS = option_get_bool ("win", "showFPS" ); params.ignoreQuit = option_get_bool ("win", "ignoreQuit" ); params.noScreensaver = option_get_bool ("win", "noScreensaver"); params.showAlerts = option_get_bool ("win", "alerts" ); params.grabKeyboard = option_get_bool ("input", "grabKeyboard"); params.escapeKey = option_get_int ("input", "escapeKey" ); params.hideMouse = option_get_bool ("input", "hideCursor" ); params.mouseSens = option_get_int ("input", "mouseSens" ); params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss"); if (option_get_bool("spice", "enable")) { params.spiceHost = option_get_string("spice", "host"); params.spicePort = option_get_int ("spice", "port"); params.useSpiceInput = option_get_bool("spice", "input" ); params.useSpiceClipboard = option_get_bool("spice", "clipboard"); if (params.useSpiceClipboard) { params.clipboardToVM = option_get_bool("spice", "clipboardToVM" ); params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal"); if (!params.clipboardToVM && !params.clipboardToLocal) params.useSpiceClipboard = false; } params.scaleMouseInput = option_get_bool("spice", "scaleCursor"); } return true; } void config_free() { option_free(); } static void doLicense() { fprintf(stderr, "\n" "Looking Glass - KVM FrameRelay (KVMFR) Client\n" "Copyright(C) 2017-2019 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 (strcasecmp(str, "auto") == 0) { params.forceRenderer = false; return true; } for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) if (strcasecmp(str, LG_Renderers[i]->get_name()) == 0) { params.forceRenderer = true; 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 (!params.forceRenderer) return strdup("auto"); if (params.forceRendererIndex >= LG_RENDERER_COUNT) return NULL; return strdup(LG_Renderers[params.forceRendererIndex]->get_name()); } static bool optPosParse(struct Option * opt, const char * str) { if (strcmp(str, "center") == 0) { params.center = true; return true; } if (sscanf(str, "%dx%d", ¶ms.x, ¶ms.y) == 2) { 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 (params.center) return strdup("center"); int len = snprintf(NULL, 0, "%dx%d", params.x, params.y); char * str = malloc(len + 1); sprintf(str, "%dx%d", params.x, params.y); return str; } static bool optSizeParse(struct Option * opt, const char * str) { if (sscanf(str, "%dx%d", ¶ms.w, ¶ms.h) == 2) { if (params.w < 1 || 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", params.w, params.h); char * str = malloc(len + 1); sprintf(str, "%dx%d", params.w, params.h); return str; } static char * optScancodeToString(struct Option * opt) { char * str; alloc_sprintf(&str, "%d = %s", opt->value.x_int, SDL_GetScancodeName(opt->value.x_int)); return str; } looking-glass-0+b1/client/src/config.h000066400000000000000000000016441351530562300177550ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include void config_init(); bool config_load(int argc, char * argv[]); void config_free();looking-glass-0+b1/client/src/kb.h000066400000000000000000000062621351530562300171050ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ static uint32_t usb_to_ps2[] = { 0x000000, 0x000000, 0x000000, 0x000000, 0x00001e, 0x000030, 0x00002e, 0x000020, 0x000012, 0x000021, 0x000022, 0x000023, 0x000017, 0x000024, 0x000025, 0x000026, 0x000032, 0x000031, 0x000018, 0x000019, 0x000010, 0x000013, 0x00001f, 0x000014, 0x000016, 0x00002f, 0x000011, 0x00002d, 0x000015, 0x00002c, 0x000002, 0x000003, 0x000004, 0x000005, 0x000006, 0x000007, 0x000008, 0x000009, 0x00000a, 0x00000b, 0x00001c, 0x000001, 0x00000e, 0x00000f, 0x000039, 0x00000c, 0x00000d, 0x00001a, 0x00001b, 0x00002b, 0x00002b, 0x000027, 0x000028, 0x000029, 0x000033, 0x000034, 0x000035, 0x00003a, 0x00003b, 0x00003c, 0x00003d, 0x00003e, 0x00003f, 0x000040, 0x000041, 0x000042, 0x000043, 0x000044, 0x000057, 0x000058, 0x00e037, 0x000046, 0x00e046, 0x00e052, 0x00e047, 0x00e049, 0x00e053, 0x00e04f, 0x00e051, 0x00e04d, 0x00e04b, 0x00e050, 0x00e048, 0x000045, 0x00e035, 0x000037, 0x00004a, 0x00004e, 0x00e01c, 0x00004f, 0x000050, 0x000051, 0x00004b, 0x00004c, 0x00004d, 0x000047, 0x000048, 0x000049, 0x000052, 0x000053, 0x000056, 0x00e05d, 0x000000, 0x000059, 0x00005d, 0x00005e, 0x00005f, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x00007e, 0x000000, 0x000073, 0x000070, 0x00007d, 0x000079, 0x00007b, 0x00005c, 0x000000, 0x000000, 0x000000, 0x0000f2, 0x0000f1, 0x000078, 0x000077, 0x000076, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x00001d, 0x00002a, 0x000038, 0x00e05b, 0x00e01d, 0x000036, 0x00e038, 0x00e05c };looking-glass-0+b1/client/src/lg-renderer.c000066400000000000000000000032041351530562300207030ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include 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+b1/client/src/ll.c000066400000000000000000000056211351530562300171110ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "ll.h" #include "utils.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() { struct ll * list = malloc(sizeof(struct ll)); list->head = NULL; list->tail = NULL; list->pos = NULL; 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); if (!list->head) { list->head = item; list->tail = item; LG_UNLOCK(list->lock); return; } ++list->count; 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; 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; } 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+b1/client/src/main.c000066400000000000000000001062561351530562300174340ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "main.h" #include "config.h" #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 "utils.h" #include "kb.h" #include "ll.h" // forwards static int cursorThread(void * unused); static int renderThread(void * unused); static int frameThread (void * unused); struct AppState state; // this structure is initialized in config.c struct AppParams params = { 0 }; static void updatePositionInfo() { if (state.haveSrcSize) { if (params.keepAspect) { const float srcAspect = (float)state.srcSize.y / (float)state.srcSize.x; const float wndAspect = (float)state.windowH / (float)state.windowW; if (wndAspect < srcAspect) { state.dstRect.w = (float)state.windowH / srcAspect; state.dstRect.h = state.windowH; state.dstRect.x = (state.windowW >> 1) - (state.dstRect.w >> 1); state.dstRect.y = 0; } else { state.dstRect.w = state.windowW; state.dstRect.h = (float)state.windowW * srcAspect; state.dstRect.x = 0; state.dstRect.y = (state.windowH >> 1) - (state.dstRect.h >> 1); } } else { state.dstRect.x = 0; state.dstRect.y = 0; state.dstRect.w = state.windowW; state.dstRect.h = state.windowH; } state.dstRect.valid = true; state.scaleX = (float)state.srcSize.y / (float)state.dstRect.h; state.scaleY = (float)state.srcSize.x / (float)state.dstRect.w; } state.lgrResize = true; } static int renderThread(void * unused) { if (!state.lgr->render_startup(state.lgrData, state.window)) { state.running = false; return 1; } // start the cursor thread after render startup to prevent a race condition SDL_Thread *t_cursor = NULL; if (!(t_cursor = SDL_CreateThread(cursorThread, "cursorThread", NULL))) { DEBUG_ERROR("cursor create thread failed"); return 1; } struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); while(state.running) { if (state.lgrResize) { if (state.lgr) state.lgr->on_resize(state.lgrData, state.windowW, state.windowH, state.dstRect); state.lgrResize = false; } if (!state.lgr->render(state.lgrData, state.window)) break; if (params.showFPS) { const uint64_t t = nanotime(); state.renderTime += t - state.lastFrameTime; state.lastFrameTime = t; ++state.renderCount; if (state.renderTime > 1e9) { const float avgUPS = 1000.0f / (((float)state.renderTime / state.frameCount ) / 1e6f); const float avgFPS = 1000.0f / (((float)state.renderTime / state.renderCount) / 1e6f); state.lgr->update_fps(state.lgrData, avgUPS, avgFPS); state.renderTime = 0; state.frameCount = 0; state.renderCount = 0; } } uint64_t nsec = time.tv_nsec + state.frameTime; if (nsec > 1e9) { time.tv_nsec = nsec - 1e9; ++time.tv_sec; } else time.tv_nsec = nsec; clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &time, NULL); } state.running = false; SDL_WaitThread(t_cursor, NULL); return 0; } static int cursorThread(void * unused) { KVMFRCursor header; LG_RendererCursor cursorType = LG_CURSOR_COLOR; uint32_t version = 0; memset(&header, 0, sizeof(KVMFRCursor)); while(state.running) { // poll until we have cursor data if(!(state.shm->cursor.flags & KVMFR_CURSOR_FLAG_UPDATE) && !(state.shm->cursor.flags & KVMFR_CURSOR_FLAG_POS)) { if (!state.running) return 0; usleep(params.cursorPollInterval); continue; } // if the cursor was moved bool moved = false; if (state.shm->cursor.flags & KVMFR_CURSOR_FLAG_POS) { state.cursor.x = state.shm->cursor.x; state.cursor.y = state.shm->cursor.y; state.haveCursorPos = true; moved = true; } // if this was only a move event if (!(state.shm->cursor.flags & KVMFR_CURSOR_FLAG_UPDATE)) { // turn off the pos flag, trigger the event and continue __sync_and_and_fetch(&state.shm->cursor.flags, ~KVMFR_CURSOR_FLAG_POS); state.lgr->on_mouse_event ( state.lgrData, state.cursorVisible, state.cursor.x, state.cursor.y ); continue; } // we must take a copy of the header to prevent the contained arguments // from being abused to overflow buffers. memcpy(&header, &state.shm->cursor, sizeof(struct KVMFRCursor)); if (header.flags & KVMFR_CURSOR_FLAG_SHAPE && header.version != version) { version = header.version; bool bad = false; switch(header.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"); bad = true; break; } if (bad) break; // check the data position is sane const uint64_t dataSize = header.height * header.pitch; if (header.dataPos + dataSize > state.shmSize) { DEBUG_ERROR("The guest sent an invalid mouse dataPos"); break; } const uint8_t * data = (const uint8_t *)state.shm + header.dataPos; if (!state.lgr->on_mouse_shape( state.lgrData, cursorType, header.width, header.height, header.pitch, data) ) { DEBUG_ERROR("Failed to update mouse shape"); break; } } // now we have taken the mouse data, we can flag to the host we are ready state.shm->cursor.flags = 0; bool showCursor = header.flags & KVMFR_CURSOR_FLAG_VISIBLE; if (showCursor != state.cursorVisible || moved) { state.cursorVisible = showCursor; state.lgr->on_mouse_event ( state.lgrData, state.cursorVisible, state.cursor.x, state.cursor.y ); } } return 0; } static int frameThread(void * unused) { bool error = false; KVMFRFrame header; memset(&header, 0, sizeof(struct KVMFRFrame)); SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); while(state.running) { // poll until we have a new frame while(!(state.shm->frame.flags & KVMFR_FRAME_FLAG_UPDATE)) { if (!state.running) break; usleep(params.framePollInterval); continue; } // we must take a copy of the header to prevent the contained // arguments from being abused to overflow buffers. memcpy(&header, &state.shm->frame, sizeof(struct KVMFRFrame)); // tell the host to continue as the host buffers up to one frame // we can be sure the data for this frame wont be touched __sync_and_and_fetch(&state.shm->frame.flags, ~KVMFR_FRAME_FLAG_UPDATE); // sainty check of the frame format if ( header.type >= FRAME_TYPE_MAX || header.width == 0 || header.height == 0 || header.pitch == 0 || header.dataPos == 0 || header.dataPos > state.shmSize || header.pitch < header.width ){ DEBUG_WARN("Bad header"); DEBUG_WARN(" width : %u" , header.width ); DEBUG_WARN(" height : %u" , header.height ); DEBUG_WARN(" pitch : %u" , header.pitch ); DEBUG_WARN(" dataPos: 0x%08lx", header.dataPos); usleep(1000); continue; } // setup the renderer format with the frame format details LG_RendererFormat lgrFormat; lgrFormat.type = header.type; lgrFormat.width = header.width; lgrFormat.height = header.height; lgrFormat.stride = header.stride; lgrFormat.pitch = header.pitch; size_t dataSize; switch(header.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_YUV420: dataSize = lgrFormat.height * lgrFormat.width; dataSize += (dataSize / 4) * 2; lgrFormat.bpp = 12; break; default: DEBUG_ERROR("Unsupported frameType"); error = true; break; } if (error) break; // check the header's dataPos is sane if (header.dataPos + dataSize > state.shmSize) { DEBUG_ERROR("The guest sent an invalid dataPos"); break; } if (header.width != state.srcSize.x || header.height != state.srcSize.y) { state.srcSize.x = header.width; state.srcSize.y = header.height; state.haveSrcSize = true; if (params.autoResize) SDL_SetWindowSize(state.window, header.width, header.height); updatePositionInfo(); } const uint8_t * data = (const uint8_t *)state.shm + header.dataPos; if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, data)) { DEBUG_ERROR("renderer on frame event returned failure"); break; } ++state.frameCount; } state.running = false; return 0; } int spiceThread(void * arg) { while(state.running) if (!spice_process()) { if (state.running) { state.running = false; DEBUG_ERROR("failed to process spice messages"); } break; } state.running = false; return 0; } static inline const uint32_t mapScancode(SDL_Scancode scancode) { uint32_t ps2; if (scancode > (sizeof(usb_to_ps2) / sizeof(uint32_t)) || (ps2 = usb_to_ps2[scancode]) == 0) { DEBUG_WARN("Unable to map USB scan code: %x\n", scancode); return 0; } return ps2; } static LG_ClipboardData spice_type_to_clipboard_type(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; } } static SpiceDataType clipboard_type_to_spice_type(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 clipboardRelease() { if (!params.clipboardToVM) return; spice_clipboard_release(); } void clipboardNotify(const LG_ClipboardData type) { if (!params.clipboardToVM) return; if (type == LG_CLIPBOARD_DATA_NONE) { spice_clipboard_release(); return; } spice_clipboard_grab(clipboard_type_to_spice_type(type)); } void clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size) { if (!params.clipboardToVM) return; uint8_t * buffer = data; // unix2dos if (type == LG_CLIPBOARD_DATA_TEXT) { // TODO: make this more memory efficent size_t newSize = 0; buffer = malloc(size * 2); uint8_t * p = buffer; for(uint32_t i = 0; i < size; ++i) { uint8_t c = data[i]; if (c == '\n') { *p++ = '\r'; ++newSize; } *p++ = c; ++newSize; } size = newSize; } spice_clipboard_data(clipboard_type_to_spice_type(type), buffer, (uint32_t)size); if (buffer != data) free(buffer); } void clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque) { if (!params.clipboardToLocal) return; struct CBRequest * cbr = (struct CBRequest *)malloc(sizeof(struct CBRequest())); cbr->type = state.cbType; cbr->replyFn = replyFn; cbr->opaque = opaque; ll_push(state.cbRequestList, cbr); spice_clipboard_request(state.cbType); } void spiceClipboardNotice(const SpiceDataType type) { if (!params.clipboardToLocal) return; if (!state.lgc || !state.lgc->notice) return; state.cbType = type; state.lgc->notice(clipboardRequest, spice_type_to_clipboard_type(type)); } void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t size) { if (!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(state.cbRequestList, (void **)&cbr)) { cbr->replyFn(cbr->opaque, type, buffer, size); free(cbr); } } void spiceClipboardRelease() { if (!params.clipboardToLocal) return; if (state.lgc && state.lgc->release) state.lgc->release(); } void spiceClipboardRequest(const SpiceDataType type) { if (!params.clipboardToVM) return; if (state.lgc && state.lgc->request) state.lgc->request(spice_type_to_clipboard_type(type)); } int eventFilter(void * userdata, SDL_Event * event) { static bool serverMode = false; static bool realignGuest = true; switch(event->type) { case SDL_QUIT: { if (!params.ignoreQuit) state.running = false; return 0; } case SDL_WINDOWEVENT: { switch(event->window.event) { case SDL_WINDOWEVENT_ENTER: realignGuest = true; break; case SDL_WINDOWEVENT_SIZE_CHANGED: SDL_GetWindowSize(state.window, &state.windowW, &state.windowH); updatePositionInfo(); realignGuest = true; break; // allow a window close event to close the application even if ignoreQuit is set case SDL_WINDOWEVENT_CLOSE: state.running = false; break; } return 0; } case SDL_SYSWMEVENT: { if (params.useSpiceClipboard && state.lgc && state.lgc->wmevent) state.lgc->wmevent(event->syswm.msg); return 0; } case SDL_MOUSEMOTION: { if (state.ignoreInput || !params.useSpiceInput) break; if ( !serverMode && ( event->motion.x < state.dstRect.x || event->motion.x > state.dstRect.x + state.dstRect.w || event->motion.y < state.dstRect.y || event->motion.y > state.dstRect.y + state.dstRect.h ) ) { realignGuest = true; break; } int x = 0; int y = 0; if (realignGuest && state.haveCursorPos) { x = event->motion.x - state.dstRect.x; y = event->motion.y - state.dstRect.y; if (params.scaleMouseInput && !serverMode) { x = (float)x * state.scaleX; y = (float)y * state.scaleY; } x -= state.cursor.x; y -= state.cursor.y; realignGuest = false; state.accX = 0; state.accY = 0; state.sensX = 0; state.sensY = 0; if (!spice_mouse_motion(x, y)) DEBUG_ERROR("SDL_MOUSEMOTION: failed to send message"); break; } x = event->motion.xrel; y = event->motion.yrel; if (x != 0 || y != 0) { if (params.scaleMouseInput && !serverMode) { state.accX += (float)x * state.scaleX; state.accY += (float)y * state.scaleY; x = floor(state.accX); y = floor(state.accY); state.accX -= x; state.accY -= y; } if (serverMode && state.mouseSens != 0) { state.sensX += ((float)x / 10.0f) * (state.mouseSens + 10); state.sensY += ((float)y / 10.0f) * (state.mouseSens + 10); x = floor(state.sensX); y = floor(state.sensY); state.sensX -= x; state.sensY -= y; } if (!spice_mouse_motion(x, y)) { DEBUG_ERROR("SDL_MOUSEMOTION: failed to send message"); break; } } break; } case SDL_KEYDOWN: { SDL_Scancode sc = event->key.keysym.scancode; if (sc == params.escapeKey) { state.escapeActive = true; state.escapeAction = -1; break; } if (state.escapeActive) { state.escapeAction = sc; break; } if (state.ignoreInput || !params.useSpiceInput) break; uint32_t scancode = mapScancode(sc); if (scancode == 0) break; if (!state.keyDown[sc]) { if (spice_key_down(scancode)) state.keyDown[sc] = true; else { DEBUG_ERROR("SDL_KEYDOWN: failed to send message"); break; } } break; } case SDL_KEYUP: { SDL_Scancode sc = event->key.keysym.scancode; if (state.escapeActive) { if (state.escapeAction == -1) { if (params.useSpiceInput) { serverMode = !serverMode; spice_mouse_mode(serverMode); SDL_SetRelativeMouseMode(serverMode); SDL_SetWindowGrab(state.window, serverMode); DEBUG_INFO("Server Mode: %s", serverMode ? "on" : "off"); app_alert( serverMode ? LG_ALERT_SUCCESS : LG_ALERT_WARNING, serverMode ? "Capture Enabled" : "Capture Disabled" ); if (!serverMode) realignGuest = true; } } else { KeybindHandle handle = state.bindings[sc]; if (handle) handle->callback(sc, handle->opaque); } if (sc == params.escapeKey) state.escapeActive = false; } if (state.ignoreInput || !params.useSpiceInput) break; // avoid sending key up events when we didn't send a down if (!state.keyDown[sc]) break; uint32_t scancode = mapScancode(sc); if (scancode == 0) break; if (spice_key_up(scancode)) state.keyDown[sc] = false; else { DEBUG_ERROR("SDL_KEYUP: failed to send message"); break; } break; } case SDL_MOUSEWHEEL: if (state.ignoreInput || !params.useSpiceInput) break; if ( !spice_mouse_press (event->wheel.y == 1 ? 4 : 5) || !spice_mouse_release(event->wheel.y == 1 ? 4 : 5) ) { DEBUG_ERROR("SDL_MOUSEWHEEL: failed to send messages"); break; } break; case SDL_MOUSEBUTTONDOWN: if (state.ignoreInput || !params.useSpiceInput) break; // The SPICE protocol doesn't support more than a standard PS/2 3 button mouse if (event->button.button > 3) break; if ( !spice_mouse_position(event->button.x, event->button.y) || !spice_mouse_press(event->button.button) ) { DEBUG_ERROR("SDL_MOUSEBUTTONDOWN: failed to send message"); break; } break; case SDL_MOUSEBUTTONUP: if (state.ignoreInput || !params.useSpiceInput) break; // The SPICE protocol doesn't support more than a standard PS/2 3 button mouse if (event->button.button > 3) break; if ( !spice_mouse_position(event->button.x, event->button.y) || !spice_mouse_release(event->button.button) ) { DEBUG_ERROR("SDL_MOUSEBUTTONUP: failed to send message"); break; } break; } // consume all events return 0; } void int_handler(int signal) { switch(signal) { case SIGINT: case SIGTERM: DEBUG_INFO("Caught signal, shutting down..."); state.running = false; break; } } static void * map_memory() { struct stat st; if (stat(params.shmFile, &st) < 0) { DEBUG_ERROR("Failed to stat the shared memory file: %s", params.shmFile); return NULL; } state.shmSize = params.shmSize ? params.shmSize : st.st_size; state.shmFD = open(params.shmFile, O_RDWR, (mode_t)0600); if (state.shmFD < 0) { DEBUG_ERROR("Failed to open the shared memory file: %s", params.shmFile); return NULL; } void * map = mmap(0, state.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, state.shmFD, 0); if (map == MAP_FAILED) { DEBUG_ERROR("Failed to map the shared memory file: %s", params.shmFile); close(state.shmFD); state.shmFD = 0; return NULL; } return map; } static bool try_renderer(const int index, const LG_RendererParams lgrParams, Uint32 * sdlFlags) { 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 state.lgrData = NULL; if (!r->create(&state.lgrData, lgrParams)) return false; // initialize the renderer if (!r->initialize(state.lgrData, sdlFlags)) { r->deinitialize(state.lgrData); return false; } DEBUG_INFO("Using Renderer: %s", r->get_name()); return true; } static void toggle_fullscreen(SDL_Scancode key, void * opaque) { SDL_SetWindowFullscreen(state.window, params.fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP); params.fullscreen = !params.fullscreen; } static void toggle_input(SDL_Scancode key, void * opaque) { state.ignoreInput = !state.ignoreInput; app_alert( LG_ALERT_INFO, state.ignoreInput ? "Input Disabled" : "Input Enabled" ); } static void mouse_sens_inc(SDL_Scancode key, void * opaque) { char * msg; if (state.mouseSens < 9) ++state.mouseSens; alloc_sprintf(&msg, "Sensitivity: %s%d", state.mouseSens > 0 ? "+" : "", state.mouseSens); app_alert( LG_ALERT_INFO, msg ); free(msg); } static void mouse_sens_dec(SDL_Scancode key, void * opaque) { char * msg; if (state.mouseSens > -9) --state.mouseSens; alloc_sprintf(&msg, "Sensitivity: %s%d", state.mouseSens > 0 ? "+" : "", state.mouseSens); app_alert( LG_ALERT_INFO, msg ); free(msg); } static void ctrl_alt_fn(SDL_Scancode key, void * opaque) { const uint32_t ctrl = mapScancode(SDL_SCANCODE_LCTRL); const uint32_t alt = mapScancode(SDL_SCANCODE_LALT ); const uint32_t fn = mapScancode(key); 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 register_key_binds() { state.kbFS = app_register_keybind(SDL_SCANCODE_F , toggle_fullscreen, NULL); state.kbInput = app_register_keybind(SDL_SCANCODE_I , toggle_input , NULL); state.kbMouseSensInc = app_register_keybind(SDL_SCANCODE_INSERT, mouse_sens_inc , NULL); state.kbMouseSensDec = app_register_keybind(SDL_SCANCODE_DELETE, mouse_sens_dec , NULL); state.kbCtrlAltFn[0 ] = app_register_keybind(SDL_SCANCODE_F1 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[1 ] = app_register_keybind(SDL_SCANCODE_F2 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[2 ] = app_register_keybind(SDL_SCANCODE_F3 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[3 ] = app_register_keybind(SDL_SCANCODE_F4 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[4 ] = app_register_keybind(SDL_SCANCODE_F5 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[5 ] = app_register_keybind(SDL_SCANCODE_F6 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[6 ] = app_register_keybind(SDL_SCANCODE_F7 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[7 ] = app_register_keybind(SDL_SCANCODE_F8 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[8 ] = app_register_keybind(SDL_SCANCODE_F9 , ctrl_alt_fn, NULL); state.kbCtrlAltFn[9 ] = app_register_keybind(SDL_SCANCODE_F10, ctrl_alt_fn, NULL); state.kbCtrlAltFn[10] = app_register_keybind(SDL_SCANCODE_F11, ctrl_alt_fn, NULL); state.kbCtrlAltFn[11] = app_register_keybind(SDL_SCANCODE_F12, ctrl_alt_fn, NULL); } static void release_key_binds() { app_release_keybind(&state.kbFS); app_release_keybind(&state.kbInput); for(int i = 0; i < 12; ++i) app_release_keybind(&state.kbCtrlAltFn[i]); } int run() { DEBUG_INFO("Looking Glass (" BUILD_VERSION ")"); DEBUG_INFO("Locking Method: " LG_LOCK_MODE); memset(&state, 0, sizeof(state)); state.running = true; state.scaleX = 1.0f; state.scaleY = 1.0f; state.mouseSens = params.mouseSens; if (state.mouseSens < -9) state.mouseSens = -9; else if (state.mouseSens > 9) state.mouseSens = 9; char* XDG_SESSION_TYPE = getenv("XDG_SESSION_TYPE"); if (XDG_SESSION_TYPE == NULL) XDG_SESSION_TYPE = "unspecified"; if (strcmp(XDG_SESSION_TYPE, "wayland") == 0) { DEBUG_INFO("Wayland detected"); if (getenv("SDL_VIDEODRIVER") == NULL) { int err = setenv("SDL_VIDEODRIVER", "wayland", 1); if (err < 0) { DEBUG_ERROR("Unable to set the env variable SDL_VIDEODRIVER: %d", err); return -1; } DEBUG_INFO("SDL_VIDEODRIVER has been set to wayland"); } } // warn about using FPS display until we can fix the font rendering to prevent lag spikes if (params.showFPS) { DEBUG_WARN("================================================================================"); DEBUG_WARN("WARNING: The FPS display causes microstutters, this is a known issue" ); DEBUG_WARN("================================================================================"); } if (SDL_Init(SDL_INIT_VIDEO) < 0) { DEBUG_ERROR("SDL_Init Failed"); return -1; } // override SDL's SIGINIT handler so that we can tell the difference between // SIGINT and the user sending a close event, such as ALT+F4 signal(SIGINT , int_handler); signal(SIGTERM, int_handler); LG_RendererParams lgrParams; lgrParams.showFPS = params.showFPS; Uint32 sdlFlags; if (params.forceRenderer) { DEBUG_INFO("Trying forced renderer"); sdlFlags = 0; if (!try_renderer(params.forceRendererIndex, lgrParams, &sdlFlags)) { DEBUG_ERROR("Forced renderer failed to iniailize"); return -1; } state.lgr = LG_Renderers[params.forceRendererIndex]; } else { // probe for a a suitable renderer for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) { sdlFlags = 0; if (try_renderer(i, lgrParams, &sdlFlags)) { state.lgr = LG_Renderers[i]; break; } } } if (!state.lgr) { DEBUG_INFO("Unable to find a suitable renderer"); return -1; } state.window = SDL_CreateWindow( params.windowTitle, params.center ? SDL_WINDOWPOS_CENTERED : params.x, params.center ? SDL_WINDOWPOS_CENTERED : params.y, params.w, params.h, ( SDL_WINDOW_SHOWN | (params.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) | (params.allowResize ? SDL_WINDOW_RESIZABLE : 0) | (params.borderless ? SDL_WINDOW_BORDERLESS : 0) | (params.maximize ? SDL_WINDOW_MAXIMIZED : 0) | sdlFlags ) ); if (state.window == NULL) { DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError()); return 1; } if (params.fullscreen || !params.minimizeOnFocusLoss) SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); if (!params.noScreensaver) { SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); SDL_EnableScreenSaver(); } if (!params.center) SDL_SetWindowPosition(state.window, params.x, params.y); // ensure the initial window size is stored in the state SDL_GetWindowSize(state.window, &state.windowW, &state.windowH); // ensure renderer viewport is aware of the current window size updatePositionInfo(); //Auto detect active monitor refresh rate for FPS Limit if no FPS Limit was passed. if (params.fpsLimit == -1) { SDL_DisplayMode current; if (SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(state.window), ¤t) == 0) { state.frameTime = 1e9 / (current.refresh_rate * 2); } else { DEBUG_WARN("Unable to capture monitor refresh rate using the default FPS Limit: 200"); state.frameTime = 1e9 / 200; } } else { DEBUG_INFO("Using the FPS Limit from args: %d", params.fpsLimit); state.frameTime = 1e9 / params.fpsLimit; } register_key_binds(); // set the compositor hint to bypass for low latency SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); if (SDL_GetWindowWMInfo(state.window, &wminfo)) { if (wminfo.subsystem == SDL_SYSWM_X11) { Atom NETWM_BYPASS_COMPOSITOR = XInternAtom( wminfo.info.x11.display, "NETWM_BYPASS_COMPOSITOR", False); unsigned long value = 1; XChangeProperty( wminfo.info.x11.display, wminfo.info.x11.window, NETWM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&value, 1 ); state.lgc = LG_Clipboards[0]; } } else { DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError()); return -1; } if (!state.window) { DEBUG_ERROR("failed to create window"); return -1; } if (state.lgc) { DEBUG_INFO("Using Clipboard: %s", state.lgc->getName()); if (!state.lgc->init(&wminfo, clipboardRelease, clipboardNotify, clipboardData)) { DEBUG_WARN("Failed to initialize the clipboard interface, continuing anyway"); state.lgc = NULL; } state.cbRequestList = ll_new(); } SDL_Cursor *cursor = NULL; if (params.hideMouse) { // work around SDL_ShowCursor being non functional int32_t cursorData[2] = {0, 0}; cursor = SDL_CreateCursor((uint8_t*)cursorData, (uint8_t*)cursorData, 8, 8, 4, 4); SDL_SetCursor(cursor); SDL_ShowCursor(SDL_DISABLE); } SDL_Thread *t_spice = NULL; SDL_Thread *t_frame = NULL; SDL_Thread *t_render = NULL; while(1) { state.shm = (struct KVMFRHeader *)map_memory(); if (!state.shm) { DEBUG_ERROR("Failed to map memory"); break; } // start the renderThread so we don't just display junk if (!(t_render = SDL_CreateThread(renderThread, "renderThread", NULL))) { DEBUG_ERROR("render create thread failed"); break; } if (params.useSpiceInput || params.useSpiceClipboard) { spice_set_clipboard_cb( spiceClipboardNotice, spiceClipboardData, spiceClipboardRelease, spiceClipboardRequest); if (!spice_connect(params.spiceHost, params.spicePort, "")) { DEBUG_ERROR("Failed to connect to spice server"); return 0; } while(state.running && !spice_ready()) if (!spice_process()) { state.running = false; DEBUG_ERROR("Failed to process spice messages"); break; } if (!(t_spice = SDL_CreateThread(spiceThread, "spiceThread", NULL))) { DEBUG_ERROR("spice create thread failed"); break; } } // ensure mouse acceleration is identical in server mode SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE); SDL_SetEventFilter(eventFilter, NULL); // flag the host that we are starting up this is important so that // the host wakes up if it is waiting on an interrupt, the host will // also send us the current mouse shape since we won't know it yet DEBUG_INFO("Waiting for host to signal it's ready..."); __sync_or_and_fetch(&state.shm->flags, KVMFR_HEADER_FLAG_RESTART); while(state.running && (state.shm->flags & KVMFR_HEADER_FLAG_RESTART)) SDL_WaitEventTimeout(NULL, 1000); if (!state.running) break; DEBUG_INFO("Host ready, starting session"); // check the header's magic and version are valid if (memcmp(state.shm->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0) { DEBUG_ERROR("Invalid header magic, is the host running?"); break; } if (state.shm->version != KVMFR_HEADER_VERSION) { DEBUG_ERROR("KVMFR version missmatch, expected %u but got %u", KVMFR_HEADER_VERSION, state.shm->version); DEBUG_ERROR("This is not a bug, ensure you have the right version of looking-glass-host.exe on the guest"); break; } if (!(t_frame = SDL_CreateThread(frameThread, "frameThread", NULL))) { DEBUG_ERROR("frame create thread failed"); break; } bool *closeAlert = NULL; while(state.running) { SDL_WaitEventTimeout(NULL, 1000); if (closeAlert == NULL) { if (state.shm->flags & KVMFR_HEADER_FLAG_PAUSED) { if (state.lgr && params.showAlerts) state.lgr->on_alert( state.lgrData, LG_ALERT_WARNING, "Stream Paused", &closeAlert ); } } else { if (!(state.shm->flags & KVMFR_HEADER_FLAG_PAUSED)) { *closeAlert = true; closeAlert = NULL; } } } break; } state.running = false; if (t_render) SDL_WaitThread(t_render, NULL); if (t_frame) SDL_WaitThread(t_frame, NULL); // if spice is still connected send key up events for any pressed keys if (params.useSpiceInput && spice_ready()) { for(int i = 0; i < SDL_NUM_SCANCODES; ++i) if (state.keyDown[i]) { uint32_t scancode = mapScancode(i); if (scancode == 0) continue; state.keyDown[i] = false; spice_key_up(scancode); } if (t_spice) SDL_WaitThread(t_spice, NULL); spice_disconnect(); } if (state.lgr) state.lgr->deinitialize(state.lgrData); if (state.lgc) { state.lgc->free(); struct CBRequest *cbr; while(ll_shift(state.cbRequestList, (void **)&cbr)) free(cbr); ll_free(state.cbRequestList); } if (state.window) SDL_DestroyWindow(state.window); if (cursor) SDL_FreeCursor(cursor); if (state.shm) { munmap(state.shm, state.shmSize); close(state.shmFD); } SDL_Quit(); return 0; } int main(int argc, char * argv[]) { if (!installCrashHandler("/proc/self/exe")) DEBUG_WARN("Failed to install the crash handler"); config_init(); // early renderer setup for option registration for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) LG_Renderers[i]->setup(); if (!config_load(argc, argv)) return -1; if (params.grabKeyboard) SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); const int ret = run(); release_key_binds(); config_free(); return ret; } looking-glass-0+b1/client/src/main.h000066400000000000000000000066321351530562300174360ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include "interface/app.h" #include "dynamic/renderers.h" #include "dynamic/clipboards.h" #include "spice/spice.h" struct AppState { bool running; bool ignoreInput; bool escapeActive; SDL_Scancode escapeAction; KeybindHandle bindings[SDL_NUM_SCANCODES]; bool keyDown[SDL_NUM_SCANCODES]; bool haveSrcSize; int windowW, windowH; SDL_Point srcSize; LG_RendererRect dstRect; SDL_Point cursor; bool cursorVisible; bool haveCursorPos; float scaleX, scaleY; float accX, accY; const LG_Renderer * lgr; void * lgrData; bool lgrResize; const LG_Clipboard * lgc; SpiceDataType cbType; struct ll * cbRequestList; SDL_Window * window; int shmFD; struct KVMFRHeader * shm; unsigned int shmSize; uint64_t frameTime; uint64_t lastFrameTime; uint64_t renderTime; uint64_t frameCount; uint64_t renderCount; KeybindHandle kbFS; KeybindHandle kbInput; KeybindHandle kbMouseSensInc; KeybindHandle kbMouseSensDec; KeybindHandle kbCtrlAltFn[12]; int mouseSens; float sensX, sensY; }; struct AppParams { bool autoResize; bool allowResize; bool keepAspect; bool borderless; bool fullscreen; bool maximize; bool minimizeOnFocusLoss; bool center; int x, y; unsigned int w, h; const char * shmFile; unsigned int shmSize; unsigned int fpsLimit; bool showFPS; bool useSpiceInput; bool useSpiceClipboard; const char * spiceHost; unsigned int spicePort; bool clipboardToVM; bool clipboardToLocal; bool scaleMouseInput; bool hideMouse; bool ignoreQuit; bool noScreensaver; bool grabKeyboard; SDL_Scancode escapeKey; bool showAlerts; unsigned int cursorPollInterval; unsigned int framePollInterval; bool forceRenderer; unsigned int forceRendererIndex; const char * windowTitle; int mouseSens; }; struct CBRequest { SpiceDataType type; LG_ClipboardReplyFn replyFn; void * opaque; }; struct KeybindHandle { SDL_Scancode key; SuperEventFn callback; void * opaque; }; // forwards extern struct AppState state; extern struct AppParams params; looking-glass-0+b1/client/src/utils.c000066400000000000000000000033471351530562300176450ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "utils.h" #include "common/debug.h" #include #include bool file_get_contents(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; }looking-glass-0+b1/common/000077500000000000000000000000001351530562300155555ustar00rootroot00000000000000looking-glass-0+b1/common/CMakeLists.txt000066400000000000000000000013721351530562300203200ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common LANGUAGES C) include_directories( ${PROJECT_SOURCE_DIR}/include ) if(ENABLE_BACKTRACE) add_definitions(-DENABLE_BACKTRACE) endif() set(COMMON_SOURCES src/stringutils.c src/stringlist.c src/option.c ) set(LINUX_SOURCES src/crash.linux.c src/sysinfo.linux.c ) set(WINDOWS_SOURCES src/crash.windows.c src/sysinfo.windows.c ) if(WIN32) set(SOURCES ${COMMON_SOURCES} ${WINDOWS_SOURCES}) add_library(lg_common STATIC ${SOURCES}) else() set(SOURCES ${COMMON_SOURCES} ${LINUX_SOURCES}) add_library(lg_common STATIC ${SOURCES}) if(ENABLE_BACKTRACE) target_link_libraries(lg_common bfd) endif() endif() target_include_directories(lg_common INTERFACE include PRIVATE src ) looking-glass-0+b1/common/include/000077500000000000000000000000001351530562300172005ustar00rootroot00000000000000looking-glass-0+b1/common/include/common/000077500000000000000000000000001351530562300204705ustar00rootroot00000000000000looking-glass-0+b1/common/include/common/KVMFR.h000066400000000000000000000057761351530562300215450ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #define KVMFR_HEADER_MAGIC "[[KVMFR]]" #define KVMFR_HEADER_VERSION 8 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_YUV420 , // YUV420 FRAME_TYPE_MAX , // sentinel value } FrameType; typedef enum CursorType { CURSOR_TYPE_COLOR , CURSOR_TYPE_MONOCHROME , CURSOR_TYPE_MASKED_COLOR } CursorType; #define KVMFR_CURSOR_FLAG_UPDATE 1 // cursor update available #define KVMFR_CURSOR_FLAG_VISIBLE 2 // cursor is visible #define KVMFR_CURSOR_FLAG_SHAPE 4 // shape updated #define KVMFR_CURSOR_FLAG_POS 8 // position updated typedef struct KVMFRCursor { uint8_t flags; // KVMFR_CURSOR_FLAGS int16_t x, y; // cursor x & y position uint32_t version; // shape version CursorType type; // shape buffer data type uint32_t width; // width of the shape uint32_t height; // height of the shape uint32_t pitch; // row length in bytes of the shape uint64_t dataPos; // offset to the shape data } KVMFRCursor; #define KVMFR_FRAME_FLAG_UPDATE 1 // frame update available typedef struct KVMFRFrame { uint8_t flags; // KVMFR_FRAME_FLAGS FrameType type; // the frame data type uint32_t width; // the width uint32_t height; // the height uint32_t stride; // the row stride (zero if compressed data) uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size) uint64_t dataPos; // offset to the frame } KVMFRFrame; #define KVMFR_HEADER_FLAG_RESTART 1 // restart signal from client #define KVMFR_HEADER_FLAG_READY 2 // ready signal from client #define KVMFR_HEADER_FLAG_PAUSED 4 // capture has been paused by the host typedef struct KVMFRHeader { char magic[sizeof(KVMFR_HEADER_MAGIC)]; uint32_t version; // version of this structure uint8_t flags; // KVMFR_HEADER_FLAGS KVMFRFrame frame; // the frame information KVMFRCursor cursor; // the cursor information } KVMFRHeader;looking-glass-0+b1/common/include/common/crash.h000066400000000000000000000016001351530562300217360ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include bool installCrashHandler(const char * exe);looking-glass-0+b1/common/include/common/debug.h000066400000000000000000000064711351530562300217370ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #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(type, fmt, ...) do {fprintf(stderr, type " %20s:%-4u | %-30s | " fmt "\n", STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0) #define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__) #define DEBUG_WARN(fmt, ...) DEBUG_PRINT("[W]", fmt, ##__VA_ARGS__) #define DEBUG_ERROR(fmt, ...) DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__) #define DEBUG_FIXME(fmt, ...) DEBUG_PRINT("[F]", fmt, ##__VA_ARGS__) #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) #endiflooking-glass-0+b1/common/include/common/locking.h000066400000000000000000000020511351530562300222650ustar00rootroot00000000000000/* Looking Glass - KVM FrameRelay (KVMFR) Client Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #if defined(__GCC__) || defined(__GNUC__) #define INTERLOCKED_AND8 __sync_fetch_and_and #define INTERLOCKED_OR8 __sync_fetch_and_or #else #define INTERLOCKED_OR8 InterlockedOr8 #define INTERLOCKED_AND8 InterlockedAnd8 #endiflooking-glass-0+b1/common/include/common/memcpySSE.h000066400000000000000000000102031351530562300225020ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include #include #include #include #include "debug.h" #if defined(NATIVE_MEMCPY) #define memcpySSE memcpy #elif defined(_MSC_VER) extern "C" void * memcpySSE(void *dst, const void * src, size_t length); #elif (defined(__GNUC__) || defined(__GNUG__)) && defined(__i386__) inline static void * memcpySSE(void *dst, const void * src, size_t length) { if (length == 0 || dst == src) return; // copies under 1MB are faster with the inlined memcpy // tell the dev to use that instead if (length < 1048576) { static bool smallBufferWarn = false; if (!smallBufferWarn) { DEBUG_WARN("Do not use memcpySSE for copies under 1MB in size!"); smallBufferWarn = true; } memcpy(dst, src, length); return; } const void * end = dst + (length & ~0x7F); const size_t off = (7 - ((length & 0x7F) >> 4)) * 9; __asm__ __volatile__ ( "cmp %[dst],%[end] \n\t" "je Remain_%= \n\t" // perform SIMD block copy "loop_%=: \n\t" "movaps 0x00(%[src]),%%xmm0 \n\t" "movaps 0x10(%[src]),%%xmm1 \n\t" "movaps 0x20(%[src]),%%xmm2 \n\t" "movaps 0x30(%[src]),%%xmm3 \n\t" "movaps 0x40(%[src]),%%xmm4 \n\t" "movaps 0x50(%[src]),%%xmm5 \n\t" "movaps 0x60(%[src]),%%xmm6 \n\t" "movaps 0x70(%[src]),%%xmm7 \n\t" "movntdq %%xmm0 ,0x00(%[dst]) \n\t" "movntdq %%xmm1 ,0x10(%[dst]) \n\t" "movntdq %%xmm2 ,0x20(%[dst]) \n\t" "movntdq %%xmm3 ,0x30(%[dst]) \n\t" "movntdq %%xmm4 ,0x40(%[dst]) \n\t" "movntdq %%xmm5 ,0x50(%[dst]) \n\t" "movntdq %%xmm6 ,0x60(%[dst]) \n\t" "movntdq %%xmm7 ,0x70(%[dst]) \n\t" "add $0x80,%[dst] \n\t" "add $0x80,%[src] \n\t" "cmp %[dst],%[end] \n\t" "jne loop_%= \n\t" "Remain_%=: \n\t" // copy any remaining 16 byte blocks "call GetPC_%=\n\t" "Offset_%=:\n\t" "add $(BlockTable_%= - Offset_%=), %%eax \n\t" "add %[off],%%eax \n\t" "jmp *%%eax \n\t" "GetPC_%=:\n\t" "mov (%%esp), %%eax \n\t" "ret \n\t" "BlockTable_%=:\n\t" "movaps 0x60(%[src]),%%xmm6 \n\t" "movntdq %%xmm6 ,0x60(%[dst]) \n\t" "movaps 0x50(%[src]),%%xmm5 \n\t" "movntdq %%xmm5 ,0x50(%[dst]) \n\t" "movaps 0x40(%[src]),%%xmm4 \n\t" "movntdq %%xmm4 ,0x40(%[dst]) \n\t" "movaps 0x30(%[src]),%%xmm3 \n\t" "movntdq %%xmm3 ,0x30(%[dst]) \n\t" "movaps 0x20(%[src]),%%xmm2 \n\t" "movntdq %%xmm2 ,0x20(%[dst]) \n\t" "movaps 0x10(%[src]),%%xmm1 \n\t" "movntdq %%xmm1 ,0x10(%[dst]) \n\t" "movaps 0x00(%[src]),%%xmm0 \n\t" "movntdq %%xmm0 ,0x00(%[dst]) \n\t" "nop\n\t" "nop\n\t" : [dst]"+r" (dst), [src]"+r" (src) : [off]"r" (off), [end]"r" (end) : "eax", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "memory" ); //copy any remaining bytes memcpy(dst, src, length & 0xF); } #else #define memcpySSE memcpy #endif looking-glass-0+b1/common/include/common/option.h000066400000000000000000000045671351530562300221650ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "common/stringlist.h" enum OptionType { OPTION_TYPE_NONE = 0, OPTION_TYPE_INT, OPTION_TYPE_STRING, OPTION_TYPE_BOOL, OPTION_TYPE_CUSTOM }; 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)(); // 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(); // print out the options, help, and their current values void option_print(); // final cleanup void option_free();looking-glass-0+b1/common/include/common/stringlist.h000066400000000000000000000022111351530562300230370ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include 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);looking-glass-0+b1/common/include/common/stringutils.h000066400000000000000000000016361351530562300232360ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // sprintf but with buffer allocation int alloc_sprintf(char ** str, const char * format, ...);looking-glass-0+b1/common/include/common/sysinfo.h000066400000000000000000000016471351530562300223430ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // returns the maximum number of multisamples supported by the system int sysinfo_gfx_max_multisample();looking-glass-0+b1/common/src/000077500000000000000000000000001351530562300163445ustar00rootroot00000000000000looking-glass-0+b1/common/src/bfd.inc.h000066400000000000000000000005271351530562300200240ustar00rootroot00000000000000#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+b1/common/src/crash.linux.c000066400000000000000000000126131351530562300207510ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE #include "common/crash.h" #include "common/debug.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; }; static struct crash crash = {0}; static void load_symbols() { bfd_init(); crash.fd = bfd_openr(crash.exe, NULL); if (!crash.fd) { DEBUG_ERROR("failed to open '%s'", crash.exe); return; } 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; } crash.section = bfd_get_section_by_name(crash.fd, ".text"); if (!crash.section) { DEBUG_ERROR("failed to find .text section"); return; } if ((bfd_get_file_flags(crash.fd) & HAS_SYMS) == 0) { DEBUG_ERROR("executable '%s' has no symbols", crash.exe); return; } 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; } } static bool lookup_address(bfd_vma pc, const char ** filename, const char ** function, unsigned int * line, unsigned int * discriminator) { if ((bfd_get_section_flags(crash.fd, crash.section) & SEC_ALLOC) == 0) return false; bfd_size_type size = bfd_get_section_size(crash.section); 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; } static void cleanup() { 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; } static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) { void * array[50]; char ** messages; int size, i; dl_iterate_phdr(dl_iterate_phdr_callback, NULL); load_symbols(); DEBUG_ERROR("==== FATAL CRASH (" BUILD_VERSION ") ===="); DEBUG_ERROR("signal %d (%s), address is %p", sig_num, strsignal(sig_num), info->si_addr); 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); cleanup(); exit(EXIT_FAILURE); } bool installCrashHandler(const char * exe) { struct sigaction sigact = { 0 }; crash.exe = realpath(exe, 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; } #endiflooking-glass-0+b1/common/src/crash.windows.c000066400000000000000000000016401351530562300213020ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/crash.h" bool installCrashHandler(const char * exe) { //TODO return true; }looking-glass-0+b1/common/src/memcpySSE.asm000066400000000000000000000116651351530562300207240ustar00rootroot00000000000000.code memcpySSE proc ; dst = rcx ; src = rdx ; len = r8 mov rax, rcx test r8, r8 jz @Exit cmp rcx, rdx je @Exit sub rsp, 8 + 2*16 + 4*8 movdqa oword ptr [rsp + 4*8 + 00 ], xmm6 movdqa oword ptr [rsp + 4*8 + 16 ], xmm7 ; void * end = dst + (length & ~0x7F); ; end = r10 mov r9 , r8 and r9 , 0FFFFFFFFFFFFFF80h jz @RemainingBlocks mov r10, rcx add r10, r9 @FullLoop: movaps xmm0 , xmmword ptr [rdx + 000h] movaps xmm1 , xmmword ptr [rdx + 010h] movaps xmm2 , xmmword ptr [rdx + 020h] movaps xmm3 , xmmword ptr [rdx + 030h] movaps xmm4 , xmmword ptr [rdx + 040h] movaps xmm5 , xmmword ptr [rdx + 050h] movaps xmm6 , xmmword ptr [rdx + 060h] movaps xmm7 , xmmword ptr [rdx + 070h] movntdq xmmword ptr [rcx + 000h], xmm0 movntdq xmmword ptr [rcx + 010h], xmm1 movntdq xmmword ptr [rcx + 020h], xmm2 movntdq xmmword ptr [rcx + 030h], xmm3 movntdq xmmword ptr [rcx + 040h], xmm4 movntdq xmmword ptr [rcx + 050h], xmm5 movntdq xmmword ptr [rcx + 060h], xmm6 movntdq xmmword ptr [rcx + 070h], xmm7 add rdx, 080h add rcx, 080h cmp rcx, r10 jne @FullLoop @RemainingBlocks: ; size_t rem = (length & 0x7F) >> 4); ; rem = r11 mov r11, r8 and r11, 07Fh jz @RestoreExit shr r11, 4 jz @FinalBytes mov r10, 7 sub r10, r11 imul r10, 9 lea r9 , @FinalBlocks add r9 , r10 jmp r9 @RestoreExit: movdqa xmm6 , oword ptr [rsp + 4*8 + 00] movdqa xmm7 , oword ptr [rsp + 4*8 + 16] add rsp, 8 + 2*16 + 4*8 @Exit: ret @FinalBlocks: movaps xmm6 , xmmword ptr [rdx + 060h] movntdq xmmword ptr [rcx + 060h], xmm6 movaps xmm5 , xmmword ptr [rdx + 050h] movntdq xmmword ptr [rcx + 050h], xmm5 movaps xmm4 , xmmword ptr [rdx + 040h] movntdq xmmword ptr [rcx + 040h], xmm4 movaps xmm3 , xmmword ptr [rdx + 030h] movntdq xmmword ptr [rcx + 030h], xmm3 movaps xmm2 , xmmword ptr [rdx + 020h] movntdq xmmword ptr [rcx + 020h], xmm2 movaps xmm1 , xmmword ptr [rdx + 010h] movntdq xmmword ptr [rcx + 010h], xmm1 movaps xmm0 , xmmword ptr [rdx + 000h] movntdq xmmword ptr [rcx + 000h], xmm0 movdqa xmm6 , oword ptr [rsp + 4*8 + 00] movdqa xmm7 , oword ptr [rsp + 4*8 + 16] add rsp, 8 + 2*16 + 4*8 sfence shl r11, 4 add rdx, r11 add rcx, r11 @FinalBytes: and r8, 0Fh jz @Exit imul r8, 5 lea r9, @FinalBytesTable add r9, r8 jmp r9 @FinalBytesTable: jmp @Copy1 jmp @Copy2 jmp @Copy3 jmp @Copy4 jmp @Copy5 jmp @Copy6 jmp @Copy7 jmp @Copy8 jmp @Copy9 jmp @Copy10 jmp @Copy11 jmp @Copy12 jmp @Copy13 jmp @Copy14 jmp @Copy15 db 128 DUP(0CCh) ; fall through - 1 byte @Copy1: mov al, byte ptr [rdx] mov byte ptr [rcx], al ret @Copy2: mov r10w, word ptr [rdx] mov word ptr [rcx], r10w ret @Copy3: mov r10w, word ptr [rdx] mov word ptr [rcx], r10w mov r11b, byte ptr [rdx + 02h] mov byte ptr [rcx + 02h], r11b ret @Copy4: mov r9d, dword ptr [rdx] mov dword ptr [rcx], r9d ret @Copy5: mov r9d, dword ptr [rdx ] mov r11b , byte ptr [rdx + 04h] mov dword ptr [rcx ], r9d mov byte ptr [rcx + 04h], r11b ret @Copy6: mov r9d , dword ptr [rdx ] mov r10w, word ptr [rdx + 04h] mov dword ptr [rcx ], r9d mov word ptr [rcx + 04h], r10w ret @Copy7: mov r9d , dword ptr [rdx ] mov r10w, word ptr [rdx + 04h] mov r11b, byte ptr [rdx + 06h] mov dword ptr [rcx ], r9d mov word ptr [rcx + 04h], r10w mov byte ptr [rcx + 06h], r11b ret @Copy8: mov r8, qword ptr [rdx] mov qword ptr [rcx], r8 ret @Copy9: mov r8 , qword ptr [rdx ] mov r11b, byte ptr [rdx + 08h] mov qword ptr [rcx ], r8 mov byte ptr [rcx + 08h], r11b ret @Copy10: mov r8 , qword ptr [rdx ] mov r10w, word ptr [rdx + 08h] mov qword ptr [rcx ], r8 mov word ptr [rcx + 08h], r10w ret @Copy11: mov r8 , qword ptr [rdx ] mov r10w, word ptr [rdx + 08h] mov r11b, byte ptr [rdx + 0Ah] mov qword ptr [rcx ], r8 mov word ptr [rcx + 08h], r10w mov byte ptr [rcx + 0Ah], r11b ret @Copy12: mov r8 , qword ptr [rdx ] mov r9d, dword ptr [rdx + 08h] mov qword ptr [rcx ], r8 mov dword ptr [rcx + 08h], r9d ret @Copy13: mov r8 , qword ptr [rdx ] mov r9d , dword ptr [rdx + 08h] mov r11b, byte ptr [rdx + 0Ch] mov qword ptr [rcx ], r8 mov dword ptr [rcx + 08h], r9d mov byte ptr [rcx + 0Ch], r11b ret @Copy14: mov r8 , qword ptr [rdx ] mov r9d , dword ptr [rdx + 08h] mov r10w, word ptr [rdx + 0Ch] mov qword ptr [rcx ], r8 mov dword ptr [rcx + 08h], r9d mov word ptr [rcx + 0Ch], r10w ret ; copy 15 @Copy15: mov r8 , qword ptr [rdx + 00h] mov r9d , dword ptr [rdx + 08h] mov r10w, word ptr [rdx + 0Ch] mov r11b, byte ptr [rdx + 0Eh] mov qword ptr [rcx + 00h], r8 mov dword ptr [rcx + 08h], r9d mov word ptr [rcx + 0Ch], r10w mov byte ptr [rcx + 0Eh], r11b ret memcpySSE endp endlooking-glass-0+b1/common/src/option.c000066400000000000000000000364561351530562300200360ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/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 { bool doHelp; struct Option ** options; int oCount; struct OptionGroup * groups; int gCount; }; static struct State state = { .doHelp = false, .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() { 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; 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 = true; 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; } 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 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)) { 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) { if (!module) { DEBUG_ERROR("Syntax error on line %d, module not specified for option", lineno); result = false; goto exit; } 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"); } } 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; default: 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; } } exit: fclose(fp); free(module); free(name ); free(value ); return result; } bool option_validate() { if (state.doHelp) { 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() { 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; // 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", strlen(state.groups[g].module) + state.groups[g].pad + 1, "Long", valueLen, "Value" ); assert(maxLen > 0); 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) { // print a horizontal rule printf(" |"); for(int i = 0; i < maxLen + 2; ++i) putc('-', stdout); printf("|\n"); } char * line = stringlist_at(lines, i); printf(" | %-*s |\n", maxLen, line); if (i == 0) { // print a horizontal rule printf(" |"); for(int i = 0; i < maxLen + 2; ++i) putc('-', stdout); printf("|\n"); } } // print a horizontal rule printf(" |"); for(int i = 0; i < maxLen + 2; ++i) putc('-', stdout); printf("|\n"); 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+b1/common/src/stringlist.c000066400000000000000000000035631351530562300207210ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/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+b1/common/src/stringutils.c000066400000000000000000000024541351530562300211040ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include int alloc_sprintf(char ** str, const char * format, ...) { if (!str) return -1; *str = NULL; va_list ap; va_start(ap, format); int len = vsnprintf(NULL, 0, format, ap); va_end(ap); if (len < 0) return len; *str = malloc(len+1); va_start(ap, format); int ret = vsnprintf(*str, len + 1, format, ap); va_end(ap); if (ret < 0) { free(*str); *str = NULL; return ret; } return ret; }looking-glass-0+b1/common/src/sysinfo.linux.c000066400000000000000000000036101351530562300213400ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include int sysinfo_gfx_max_multisample() { Display * dpy = XOpenDisplay(NULL); if (!dpy) return -1; 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; } XCloseDisplay(dpy); return maxSamples; }looking-glass-0+b1/common/src/sysinfo.windows.c000066400000000000000000000016121351530562300216730ustar00rootroot00000000000000/* KVMGFX Client - A KVM Client for VGA Passthrough Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ int sysinfo_gfx_max_multisample() { //FIXME: Implement this return 4; }looking-glass-0+b1/contrib/000077500000000000000000000000001351530562300157255ustar00rootroot00000000000000looking-glass-0+b1/contrib/redhat/000077500000000000000000000000001351530562300171745ustar00rootroot00000000000000looking-glass-0+b1/contrib/redhat/qemu-ivshmem-sock.pp000066400000000000000000000034211351530562300231070ustar00rootroot00000000000000||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+b1/module/000077500000000000000000000000001351530562300155525ustar00rootroot00000000000000looking-glass-0+b1/module/Makefile000066400000000000000000000005511351530562300172130ustar00rootroot00000000000000obj-m += kvmfr.o USER := $(shell whoami) KVER ?= $(shell uname -r) KDIR ?= /lib/modules/$(KVER)/build all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean test: all grep -q '^uio' /proc/modules || sudo modprobe uio grep -q '^kvmfr' /proc/modules && sudo rmmod kvmfr || true sudo insmod ./kvmfr.ko sudo chown $(USER) /dev/uio0 looking-glass-0+b1/module/README.md000066400000000000000000000021641351530562300170340ustar00rootroot00000000000000This kernel module implements a basic interface to the IVSHMEM device for LookingGlass when using LookingGlass in VM->VM mode. ## Compiling (Manual) Make sure you have your kernel headers installed first, on Debian/Ubuntu use the following command. apt-get install linux-headers-$(uname -r) Then simply run `make` and you're done. ### Loading This module requires the `uio` module to be loaded first, loading it is as simple as: modprobe uio insmod kvmfr.ko ## Compiling & Installing (DKMS) You can install this module into DKMS so that it persists across kernel upgrades. Simply run: dkms install . ### Loading Simply modprobe the module: modprobe kvmfr ## Usage This will create the `/dev/uio0` node that represents the KVMFR interface. To use the interface you need permission to access it by either creating a udev rule to ensure your user can read and write to it, or simply change it's ownership manually, ie: sudo chown user:user /dev/uid0 Usage with looking glass is simple, you only need to specify the path to the device node, for example: ./looking-glass-client -f /dev/uio0 -L 16 looking-glass-0+b1/module/dkms.conf000066400000000000000000000003531351530562300173600ustar00rootroot00000000000000PACKAGE_NAME="kvmfr" PACKAGE_VERSION="0.0.1" BUILT_MODULE_NAME[0]="${PACKAGE_NAME}" MAKE[0]="make KDIR=${kernel_source_dir}" CLEAN="make KDIR=${kernel_source_dir} clean" DEST_MODULE_LOCATION[0]="/kernel/drivers/misc" AUTOINSTALL="yes" looking-glass-0+b1/module/kvmfr.c000066400000000000000000000056761351530562300170610ustar00rootroot00000000000000/* UIO KVMFR Driver Copyright (C) 2017-2019 Geoffrey McRae https://looking-glass.hostfission.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define PCI_KVMFR_VENDOR_ID 0x1af4 //Red Hat Inc, #define PCI_KVMFR_DEVICE_ID 0x1110 //Inter-VM shared memory #include #include #include #include #include #include static int kvmfr_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct uio_info *info; info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); if (!info) return -ENOMEM; if (pci_enable_device(dev)) goto out_free; if (pci_request_regions(dev, "kvmfr")) goto out_disable; info->mem[0].addr = pci_resource_start(dev, 2); if (!info->mem[0].addr) goto out_release; info->mem[0].internal_addr = ioremap_wt( pci_resource_start(dev, 2), pci_resource_len (dev, 2) ); if (!info->mem[0].internal_addr) goto out_release; info->mem[0].size = pci_resource_len(dev, 2); info->mem[0].memtype = UIO_MEM_PHYS; info->name = "KVMFR"; info->version = "0.0.1"; info->irq = 0; info->irq_flags = 0; info->handler = NULL; if (uio_register_device(&dev->dev, info)) goto out_unmap; pci_set_drvdata(dev, info); return 0; out_unmap: printk("unmap\n"); iounmap(info->mem[0].internal_addr); out_release: printk("release\n"); pci_release_regions(dev); out_disable: printk("disable\n"); pci_disable_device(dev); out_free: printk("free\n"); kfree(info); return -ENODEV; } static void kvmfr_pci_remove(struct pci_dev *dev) { struct uio_info *info = pci_get_drvdata(dev); uio_unregister_device(info); pci_release_regions(dev); pci_disable_device(dev); iounmap(info->mem[0].internal_addr); kfree(info); } static struct pci_device_id kvmfr_pci_ids[] = { { .vendor = PCI_KVMFR_VENDOR_ID, .device = PCI_KVMFR_DEVICE_ID, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID }, { 0, } }; static struct pci_driver kvmfr_pci_driver = { .name = "kvmfr", .id_table = kvmfr_pci_ids, .probe = kvmfr_pci_probe, .remove = kvmfr_pci_remove }; module_pci_driver(kvmfr_pci_driver); MODULE_DEVICE_TABLE(pci, kvmfr_pci_ids); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Geoffrey McRae");looking-glass-0+b1/pre-commit000077500000000000000000000001701351530562300162650ustar00rootroot00000000000000#!/bin/bash ( git describe --always --long --abbrev=10 --tags | tr -d '\n' echo -n '+1' ) > VERSION git add VERSIONlooking-glass-0+b1/resources/000077500000000000000000000000001351530562300162775ustar00rootroot00000000000000looking-glass-0+b1/resources/icon-128x128.png000066400000000000000000000537231351530562300207020ustar00rootroot00000000000000PNG  IHDR>a)zTXtRaw profile type exifxڭirH)/U`?:@&nyE$D[T ac9R61[Ζbw~l~yuK5J|__7eAWq=Qݴ}=~?Ӿ'{,c%;`*:p⫿Pl;}v066z_w2ۊܯ+Cھد}91clޛL2_?c oqy}\tuwܾߧ,1zwo~^D} nX&T|2Zew\y9􎓹_O/7_':G뜭b]^129Q8ĝצ~/qc86t\nsܯ ρ㒍>zOvb1.]H.;[/aNJ:+!\J~9sMTk>eGC5$ Ί1?%VbI)TRM-r)\0PbI%RjiPcM5Rkm7ZnŴZ띋vNywއađFeFό3<ˬ; _yjo wy]wk'xɧz^{k5zMk\)$gxGNj<@@{V3{jנ\ڥS1a9ᅉv1Mot?)`؆Ppߏ=y@=ﻭ5|_8ǿ~rv9sx +z`Rm~'6}GIvڻ/+>~zno;5~O6Oc?qv柌wO6*>35- d#Fo߶N.NW4l$J@"R  * T!c˃7'#8 ݢQR:% N\] XkbdY'KX/Eo@ճ()ՓaQulAL Hi_Sҁm&BM{_#>BYeܸSߨ'm Ǵ:䭧?Fv;&3|kYYV\39nTgIE4I 4;]Z-á@}9N$D"]B=CXtZ¶"3akK-S3\(>S S]:Rsަë--Y燊-Wq#bvϨNᨺ$alsNc!IadiМA0| I*l8{iɍcU܃J_ȍ[$f Q?1^ ]'?I!f;gUh{NOy~=QA$nw |4 ⼄OÙn׫T b0QJh.`rh8֠'l0;؇ و 7vbF%m&aU6lK- a8h\Wv+P86Gm0)W;.W-lh[N0Nω%)o*24x+tl!\ˮ}AeZNz@HA1FxH⥂s&zSU%7):)5 P\. )Go _FWNdW 3e>rʙЄ`*%ŋ׶_PNgqA ט+O,-}ҘTpC >";۲*`biPgXYg1LRbaP.p<3:ʣ9ظn ߳"⟠E%C%#ȩD)7{BK>o'-23TzV`lT 3B5J/XEvnެ1K 5Ӧ85HXz 90ekN!9M!aĪ7;AUЮ"DmUD^BAagpXc=j$Sa %փK좶g" g\<^'W|`)R$!%zD 9pTQڋt^D YG3;%Z(] ɬ Ԟz"Z3n}܂짃>ͧ&Y~⢈X 4  D1D`9%HۛԀ] O P&I7(}Bz*AFSq"aĺo\C\S|J&PdPRiȶ$BsPi#MB93OT#a%D\|1ߪ(& |W锗u= 7 & 8 tɅBRnBX$ *PsDGT%J*QA2 )bLIGX\M Q37C BDhZ  >}VB=V<|Ȅ.uw sdxkbԳ5?. Bǝ''VzCj'clnlFv-*#G 2hjUH],1Rߚt@̂0c԰ʇ 홠@N;pA{"@jI.޼o^ {b N;V0})nPNח_k/AxXD/A șAqo|jDh簆(\@:,J+@"YPq3x7UVv[)hlT"XP vJ9YPbmu s1V) ]ù̞sۘ!Ezj]T";)Wv>h; v8i|D.l(D8Ɣ3uc9QQT.J^vFRCMjt52 (Ⱦ̖ ԫi,k_#dMQjoϩԝ$<]6ULSM d`ih;dH >|\6myS]<^8l$7py&>D"b=9(BDPcrRwzy:_|@#u Pbli;u'H.K>ga;5 C=JC.8W->*ǒ2!LQ $woryóKbZ6%Aۤ~ ,QɚQrOV53rVQ^ 7ޝ l! KTlz?Iul)/J!$:Vu܈Q-R8 I"@Rv%PN%nwqY\D"}[ۈ⦛Fy@*B~DEQL*s  @5@ރO(g 9SмsD uۣYXF0\_:H@q+NS3"$1p޽_`́mA&Bq#Kvԟ` |0lވԘ&BYX#0 mޖ{,TT2+&F ޢO @% )lO~#6Dm|OuxvF!#m)X2:P$U uw֏ ~f1nk"vWMƨ=B&Z(R#f( adߚOٚiJ;S1?d}pp7.Q4\F$ jSiT<  &λ@ŶSS5ԉ)Re)Mx/orQp.Il$Ubh7I٩Jt(2Ӯf/uxvH-Qg"u"|<J@4D+A6:T䨀! VnU6^YLw!C&QTu c:%;Dk0R ͂+bE7A[ܾlUm6g:LWs(/ 055#rBHoH0QC E- Pz(Es4*W~JPdؘ' 6U-82}JX%j,8̥⽘5  ΦG9 _:N'QܠA_E&@tPI%+  j.UcAMf&c9S ̥()g[B\!A9>p- OaJݽQ+.pn!PڎK apMNܟ|{#ڢ wF[UA5y۟S[6`^G}jPZIOa{ExjU CLjѪMZuSp~ԴZB\D!&ds[ h[m"["HjSCQ d>t i܁85EcN+`Y֪qBՊ´mjRgI)8D}Y MDCUpFi0K"+>tnaq(v (RX׌ ZM593-l` 'n$QtK iѴ*k+jǂ#9I"9KSMwǬ]U5 ?DE?Sjn#en?Cڼ;,?ގoqJ Y]6A J@ 凐P "b#@*HBFJ)O & 4!컗J$ْpG^e B 2gu'gAsWӸs>T&1mb9zƭ@ц iJ<z!L*SBmId9C\@ J AQ+CnV?%4QSldIڸ!5qޓ|**9Gk||P\8G^jH8B3n2#$Х]a?bWr ff솕7VC7ՒjCK{@\c%X;{Ƅkͫ,_"9aƥkԢڛ MP;Zn@EAC"$GhgJ܃ZWeOhM,u:-G{E@95M } رSQ'B"\"N0" :(7mSx+ֳջW70`}yXm31aM G w"cZ]4IH㎊&x;Զ/az0e(s^ B^t#~wI:x[tJJn(~]IӦ[ҏKM*Nikf3]co} 5mJ’$+S\M~|776DtBB{Nm vDB@n#Qr2 ͙"*>sQ_VPH%&oR</sp8.ϔQޟc0uY'́!"8MOW>ݦ }h͂*pV&>6!H^I !>=qJ*E+#2J&_ؑ"5jS#%/#15jbtz0/4ONSwM@8Ɠ\&kU~DEZ$K<˓~*xI2_B!sl5yM$1r!ΆۡT%y^-RyeRγkxP-h<XJ B$N*5TܛWH%A nj3t6dtAY}6* W'1i{ZnRz8b)Yu44BQCV[a=EI.[yKI:qBv{u8 c @:4i٨11m=$dV>M4CB]h/Wcqg8HɓwG5:ͣwknts~+bi>^lr%Ž=mr M,SWR0A_wUX Ј_\}G=dVnjx CWTřbQ&; 8[O~eԈ?I&;m`k(ir#vmi Y}6C<:P;lhɐ,q-W>zN{ JnC{pO:^8ughYQ)996(vN/fyo9ޭWfmTޤ~>u@ yV@T /+Y%j娙OHC)ޔHI+y}15/L}E$tc:P;\BʷZL"=ql>|Hߟݲ${[xi- ('!ҳxż )eҠq9jA-.h׃g[g)zP1 }qS $NJ1׶4 :YBP-kC`]*js 2AQj 6z'=ߟƦfDдj,cڵIY .E ί0fw$l\sz ͬ멲>֐E Fa%uvK dhAw(*~ކ A q7\\)AHIU*4i y hn?Q.> 7iӍVwY'pyZk򜽝7Y=hyӖsۈ ~)=|^LGEFM}M?'"&%s:~K]so6ӂ$2[t:}ʼ Џ=U^_ڵmuՋϷpɰn?4и*Z7BPx ,.-.9NvGE %= h2k:4u`6M\ D}#? `J6˥rɀA(+[ϵqOk((Lz%| 6XeKm-mSeR21OM~ mp:3#ܺ B_}VBtU8SLEbtr/w " 2uyQAk nHw{I&Z)+)I_Gp@( ¢Ss8OswPW9 z_&r _86`.4uBw`-;߮{@w\NA dp*畑Wrjw`¤B$*Ikvm[/>]7lsdD. QRi"9{"ܺvme/A@T?)S)ԓu,(p1tsV  DvI,DDžT"C&P(Ht:=F@]e%*)+؎2փ(:|ע}O}{ۮ-( %.wB=YR#˹@ nDcDFNL&6> _J9iVשG7b60OR)2a]d4cTQ9we|J7`FHTlQaL>[8s f׺<4tnGjLzuMՄDtʕjL"XkJpxW})7>6K#4"A"QÆwr,Vpͮ;Cv=l/cgf¸)錛N=UVVCM/8H^5X@&I7lޟ(+w;|cC,~h2L&$,C'~NtuptPgv~^5JF]ϵ$)u rżڗXQ讽QM`/XdCfmMS[wGlgqQ(>wױzZu\ xg SnK'' ȗ܃<(n챧WKR3)s&e6ti1FDQD.!Wl6SS@2vowY1y14=&}Akr 7]w}`rw x^T2\x/(7X'2'+I FEi5EeSx6tmFɌU?>ą΀ą @ue&aJ{m/h?g0D;gX! ̴i:ŷE5`S0V$͊;f0rPj:س0~fn]FrtB:qM\6㧦sA>ygj,^k>ZZ2[RUOlF4N ztCkNKp+z`[c $d<ZGW.bii:˟уYuEYA(Qdߺ d!D>"v$HX??9`5.#$F{WA`}7:}fAYYWL:_=<~Ǜ41<I&#߸2e_3P'lVso>x77=}M˩Nv7<T ~[~f_} 78բ}{wkoMxt=#8KKfĎ#s;L}04 kϮ~_zub E7deّu`no9U*$]Iдk/bPz;6.;r@ Ǯ'F-"o/V&]IHR?q~뙹xxS6[9w "Cص Bg+SNsV{%EtgLʢg v׊Md8W?\3ny;C QlucYJ#>!|v@}TlsIHaMݷ#šSO渡( u [u􂁄Eq vY_qa?b{ikƞf?bQjgNkpĮ[k8.T&aǖML\$*Vn eg٢Ʒr):QF\bȶʤL=h̵״߹?0>ɱp4Jc6B;1-i`6ٞB)g±=6}Cgh$r%r&?`lA~TRҭN+eĨ5lYw MÜuNzeݚ=hZ;ḨJ +  izdZRb9CQ>gQBp?%䯯q|{#{#ma`ȼ,/\kYw.',ǺO -oʂTA<#zn*dA 0m~E'c\q"2}?x=.ŵ mV@~ {Oo5cS;1n%;ޠD h;t\2(]y.[pĺ=x=2hHE]IA =Ϳ=6? cB1HeRNH蕬{عux_n}놁D-ZNk&dЈ@4١B/A3LZZA%֯`Z|}yv䶇/L(rxVq_d. A #/nY " D`ZOYq-w 64mkuۏ^T5n)!(}e4]biij}B&'exDJ;Wt8#V(PbdPorF  1&֭o.m`fRR Sx!-_ZN`mZsvM'q"8u`Ү0ry`vZy#)>QAMAEA1:69ju!QxzG47c!qjsܸh:|ݩ U?!VopV Ñzu '0ďڃy)-b^DC985O+zSǶKHDMkt FNǒ# Q*i `xO߀Sǥst:>w΍!`̜7V[ԏ*K8p4T*%9;r.`L&FOHE3H$󮛈 +|QG .C>Q[$p+Wρ=GiIzk&"6 2]m`ܔ Fvlq|Y4zR2xjՑ~l?MdJ&yVDĬYIe'08tyMgkF Mz$$RSU sngn؋\!cc͚_P\wLdV Zɝ^Cml~UTj,¬: )]wLTJ~j uڭr+dr}ѭ.CG$|]*HLbq^}ճٿ <էCmM0ۊ'/.bPR vzD]M#>Ѫ (166-?| ߅Eΐ S- a #mgY( n``Yaf_?W.=zX^@-\AcꖖV˸ ~*U̹ya/Z}; D̪7a4}$! [/=t!X%m+hd 3Vk3A7:ŷΡ7[EfK~  ȡ|jU"C̾r?^] N t{8G`(Fe/|FGy:/ʢMJ zs%7rz~D:9um[IƂioୗ8frk@Ԁpvoa' |2}$u:u\6o"Vk6Ko |eCp<]uP4yˡE,u:=^<2(ً_OJ^o'c􃟬N>8yVY* Q$ _z_G8"AD=+Ck'wWSaȰ8RG$:1c'OT#dzoetZxYCϧI>9xLf"J&{agqN ILDIr-ZLz3IIOx<~1$ Q9QM~R~lMAIL_0(trJq @hz?FM'gr 2sA~V޿p_'ȴƠimGc6!\Xy6moQq֟[onjXoD⡑Ѵȝ"R3ᡧo"2&#xvhRʥwgLRRկk3z*K먮ې)xG`}B@~]+;ڵwMg{+KGEi <>>i̫cyէ>`{ٵ黁Yߝϕ͠Dݱ6c$[H>ڪFV k_ζ8Af81IDdž{vgg>=`m0#O;i.Hw!0Ln]8>_]Ǒ&ʿ{Zi 𞃩p$>-f e/~ulA*0|n4 oFJ |1LTWSTXB#(hG}/9u+9r GEL&}y~g=ߗSWqcáxip.! 𿺀[!@/ 8x,{z/^gG?lAdo/Lgҥ JD5QU^G]]54׵X׊Vyy{7ГAa?65>MfZ=Ņla7?v#JmL_=J[y/i-ֺ 8MG3p6л\w\<<>lOCd KeAdBL\PviXgz Orpqv~w-5ҟtkͯXs s^~$53gQx{*2 oz~f3&ٮmbmn6kFPȈ $mrG@[Kks-8$T©,{i ;V;7J3~_a|2W;9 '>*i .1 -f7DNJO”kRw'"J(//썭tTǝ.`w-pDΆUĬwX]^݇ܥWg1ɌJP?i7, M>uoV<%C5<87i&19//Z5**c:AYv[4=l1&A ATFaA)6ٴ: ./,! 璹dJdL( A6meME1k3m5" =xQ(PDGO?B 9>˳8̀Tx`mn鎭غZbDŽyI۟%F!,j F  IQ*( dr9)b2l`\Tioᔢز;v%Mz/j ijR|d$ `XB‚ 'j%@jѴkuitR]YO]}6P[cs+Qt:2h9s^5f5d* ;k%R~|]qs(P(Dt'3eZM7JpM&t`'sF7Q#] sRoR=[aOBAv'݄;ZcDZ#}s P c/_:υ|ᴸž'~o[%!wpz߀tUp ݺG@EAcVQC o]Ykػɘ_z6; x,ӽ\g{<Tci`O]t8a7aMC#}z2t,.,}ϝ+sNE-Z @E/qs㮭(uI_N6_ђn> ;tKxއ̱aUtf=RUHL${Xvi\! KW߆(`0QY\Ǿ]GyDZ/d樦K IKi`?"v\{RRPLfDcyFf'dS,Rߔ+䖚>v/Դ2jhw!>ʩelZʲ::Z}b*q"0-)νܻ6]n$['(]&)AQ}>C?sѡlW!<NKseoxfхܲogDQDb4-eo/[jkAjF5<z?ٿ]RdzOEi SH$ʩ;F,r Cbԛl;{^0if:J:.[8Lj*뉈 aԡX{"&zL!%/̢NovʴrOwBQ"%cD:wjwisqC"|Zd2&_@hTKS1wTb93;eZ}gp~e\Dɦ}Ofo=ot}q Vk, OWr[-~R N8VPɦMj5uV7'F%vp݁_y-m zne6vpU5u6ma[2 l=+;#|~ 6х4TLQaa^Tt,OW 2%UQRR7"5'LO(d?[\{@TUg+/SikvŠ|݇Qo ޣ aT1"[( V0W0W0W0W0W0W0W0W0W0FsM|SطY^`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^YSإM|FsW0W0W0W0W0W0W0W0W0V0W0W0W0W0W0W0W0W0IvPS`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`S؟IvPW0W0W0W0W0W0W0W0W0W0W0W0W0W0W0QaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaQW0W0W0W0W0W0W0W0W0W0W0W0IvPYaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaYIvPW0W0W0W0W0W0W0X0X0Cp^^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^Cp^X0X0W0W0X0X0X0IvP^aaaaaaaa_N~>iy,SgA[4Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1gAy,S>iN~_aaaaaaaa^IvPX0X0X0X0X0X0YaaaaaaaY>i\ 5Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1\ 5>iYaaaaaaaYX0X0X0X0X1QaaaaaaaBoY1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1BoaaaaaaaQX1X0X1IwPaaaaaaYy,SY1Y1Y1Y1Y1Y1Y1Y1Y1Z1Z1Z1Z1Z1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1Z1Z1Z1Z1Y1Y1Y1Y1Y1Y1Y1Y1Y1y,SYaaaaaaIwPX1Y1Saaaaaay,SY1Y1Y1Y1Y1Y1Z1Z1Z1Z1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1Z1Z1Z1Y1Y1Y1Y1Y1Y1y,SaaaaaaSY1Ft`aaaaaBoY1Y1Y1Y1Z1Z1Z1Z1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1Z1Z1Z1Y1Y1Y1Y1Boaaaaa`FtM|aaaaaYY1Z1Z1Z1Z1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2vv[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1Z1Z1Z1Y1YaaaaaM|Saaaaa>iZ1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2\2\2\2\2\2\2\2\2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1>iaaaaaSطYaaaa_] 5Z1Z2Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2\2\2\2\2\2\2\2\3\3\3\3\3\3\3\3\2\2\2\2\2\2\2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2Z2Z1] 5_aaaaY^aaaaN~Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2\2\2\2\2\2\2\3\3\3\3\3\3\3\3\3\3yP`h,F\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3i.H}Se\3\3\3\3\3\3\3\3\3\3\2\2\2\2\2\2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2N~aaaa^`aaaa>iZ2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2\2\2\2\2\2\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3^ 7ʾa;\3\3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3\3\3c?˿̾]5\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\2\2\2\2\2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2>iaaaa`aaaaaz,TZ2[2[2[2[2[2[2[2[2[2[2[2[2[2[2\2\2\2\2\2\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3]3]3]3]3]3l/Jvhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwp|p8P]3]3]3]3]3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\2\2\2\2\2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2z,TaaaaaaaaaahA[2[2[2[2[2[2[2[2[2[2[2\2\2\2\2\2\3\3\3\3\3\3\3\3\3\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3r?UtBW]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\2\2\2\2\2[2[2[2[2[2[2[2[2[2[2[2hAaaaaaaaaaa]5[2[2[2[2[2[2[2[2[2\2\2\2\3\3\3\3\3\3\3\3\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3^4`nǼǽdr`8]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3\3\3\3\3\3\3\3\3\2\2\2[2[2[2[2[2[2[2[2[2]5aaaaaaaaaa[2[2[2[2[2[2\2\2\2\2\3\3\3\3\3\3\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3^3^3^3^3^3^3^3^4^4f@f@^4^4^3^3^3^3^3^3^3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3\3\3\3\3\3\3\2\2\2\2[2[2[2[2[2[2aaaaaaaaaa[2[2[2[2\2\2\2\3\3\3\3\3\3\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3^3^3^3^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4f@f@^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^3^3^3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3\3\3\3\3\3\3\2\2\2[2[2[2[2aaaaaaaaaa[2\2\2\2\3\3\3\3\3\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3^3^3^3^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4f@f@^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^3^3^3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3\3\3\3\3\3\2\2\2[2aaaaaaaaaa\2\2\3\3\3\3\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3^3^3^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4f@f@^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^3^3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3\3\3\3\3\2\2aaaaaaaaaa\3\3\3\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3^3^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4g@g@_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^3]3]3]3]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3\3\3\3aaaaaaaaaa\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3]3]3^3^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4g@g@_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^3]3]3]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3aaaaaaaaaa\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3^3^3^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4g@g@_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^3^3]3]3]3]3]3]3]3]3]3]3]3]3\3\3\3aaaaaaaaaa]3]3]3]3]3]3]3]3]3]3]3]3^3^3^4^4^4^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4gAgA_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^3^3]3]3]3]3]3]3]3]3]3]3]3]3aaaaaaaaaa]3]3]3]3]3]3]3]3]3]3^3^4^4^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_5_5_5_5_5`5`5`5`5`5`5`5`5hAhA`5`5`5`5`5`5`5`5_5_5_5_5_5_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^4^4^3]3]3]3]3]3]3]3]3]3]3aaaaaaaaaa]3]3]3]3]3]3]3^3^4^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_5_5_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5hAhA`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_5_5_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^4^3]3]3]3]3]3]3]3aaaaaaaaaa]3]3]3]3]3^3^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_5_5_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5hAhA`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_5_5_4_4_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^3]3]3]3]3]3aaaaaaaaaa]3]3^3^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_5_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5hAhA`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_5_4_4_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^3]3]3aaaaaaaaaa^3^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5hAhAa5a5a5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^3aaaaaaaaaa^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_5_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5iAiAa5a5a5a5a5a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_5_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4aaaaaaaaaa^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_5_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5iBiBa5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_5_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4aaaaaaaaaa^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a6a6a6a6a6a6a6b8l#Fl#Fa7a6a6a6a6a6a6a6a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4aaaaaaaaaa^4^4_4_4_4_4_4_4_4_4_4_4_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a5a5a6a6a6a6a6a6a6a6a6c:l%FVmVml)Gc:a6a6a6a6a6a6a6a6a6a5a5a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_4_4_4_4_4_4_4_4_4_4^4^4aaaaaaaaaa^4_4_4_4_4_4_4_4_4_4_5`5`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a5a6a6a6a6a6a6a6a6b6b6b7y?Yʽŵq0Nb6b6b6a6a6a6a6a6a6a6a6a5a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_4_4_4_4_4_4_4_4_4^4aaaaaaaaaa_4_4_4_4_4_4_4_4_5`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a6a6a6a6a6a6b6b6b6b6b6b6|M`{ZiyM]t@Up4Nl&Fm)Hp3MuBW{Q`Ziy̽v@Wb6b6b6b6b6b6a6a6a6a6a6a6a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5`5_5_4_4_4_4_4_4_4_4aaaaaaaaaa_4_4_4_4_4_4_5`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a6a6a6a6a6a6b6b6b6b6b6b6b6m*HyĴ|t;Sb6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6q3Owj)Eb6b6b6b6b6b6b6a6a6a6a6a6a6a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5`5_5_4_4_4_4_4_4aaaaaaaaaa_4_4_4_4_5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a6a6a6a6a6b6b6b6b6b6b6b6b6b6g>~Nbb6b6b6b6b6b6b6b6c6c6c6c6c6c6c6c6c6c6b6b6b6b6b6b6b6b6{K_e;b6b6b6b6b6b6b6b6b6a6a6a6a6a6a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5_5_4_4_4_4aaaaaaaaaa_4_4_5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a6a6a6a6a6b6b6b6b6b6b6b6b6b6b6h?hye;c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6e;o}g@b6b6b6b6b6b6b6b6b6b6a6a6a6a6a6a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5_5_4_4aaaaaaaaaa_5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6brete:c6c6c6c6c6c6c6c6c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c6c6c6c6c6c6c6c6e:kybqb6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5_5aaaaaaaaaa`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6k&Efuc7c6c6c6c6c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c6c6c6c6c7l{l,Hb6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5aaaaaaaaaa`5`5`5`5`5`5`5`5a5a5a5a5a5a5a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6c6c6_qe :c7c7c7c7c7c7c7c7c7c7c7c7c7d 9h@|@\ɷǴï}~D^g>e :c7c7c7c7c7c7c7c7c7c7c7c7c7e :dsc6c6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a5a5a5a5a5a5`5`5`5`5`5`5`5`5aaaaaaaaaa`5`5`5`5`5`5a5a5a5a5a5a5a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6c6c6c7u:Tc7c7c7c7c7c7c7c7d7d7d7d7t1PzĴŴs2Pd7d7d7d7c7c7c7c7c7c7c7c7u;Uc6c6c6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a5a5a5a5a5a5`5`5`5`5`5`5aaaaaaaaaa`5`5`5`5a5a5a5a5a5a5a5a6a6a6b6b6b6b6b6b6b6b6b6b6b6c6c6c6c6j"Bfc7c7c7c7c7c6c6c6c6c6b6b6b6b6b6b6b6b6a6a6a6a5a5a5aaaaaaaaaaa5a6a6a6b6b6b6b6b6b6b6b6b6c6c6c6c6c7c7c7c7c7d7d7p.Ld7d7d7d7d7d7e7t0N~f :e8e8e8e8e8e8e8e9o'H̼o(Ie9e8e8e8e8e8e8e8f9}u9Re7d7d7d7d7d7d7q/Ld7d7c7c7c7c7c7c6c6c6c6b6b6b6b6b6b6b6b6b6a6a6a6a5aaaaaaaaaaa6a6a6b6b6b6b6b6b6b6b6c6c6c6c6c6c7c7c7c7c7d7d7{Sfd7d7d7d7e7e7e7\kQce8e8e8e8e8e8e8h e8e7e7d7d7d7d7d7d7d7c7c7c7c6c6c6aaaaaaaaaac6c6c7c7c7c7d7d7d7d7d7d7d7e7e7e8m{f9f8f8f8f8f8̽h:g9g9g9h:̽h:h9h9h9h9h9h9h9h9h9i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:h9h9h9h9h9h9h9h9h9h:̿g9g9g9g9h:f8f8f8f8f8f9ixe8e7e7d7d7d7d7d7d7d7c7c7c7c7c6c6aaaaaaaaaac6c7c7c7c7d7d7d7d7d7d7d7e7e7e8e8°f8f8f8f8f8h ;`sg9g9g9g9Zlo"Eh9h9h9h9h9h9h9h:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:h:h9h9h9h9h9h9h9o"E[lg9g9g9g9fwg9f8f8f8f8f8e8e8e7e7d7d7d7d7d7d7d7c7c7c7c7c6aaaaaaaaaac6c7c7c7d7d7d7d7d7d7d7e7e7e8e8e8}B\f8f8f8f8f8]mi\g9g8f8f8f8e8e8e8e8e8e7e7d7d7d7d7d7d7c7aaaaaaaaaac7d7d7d7d7d7d7e7e7e8e8e8e8e8}f8f8f8g8g9Mhͽg9g9h9h9_r˸h9i:i:i:i:i:i:j:j:j:j:j:j:j;k;k;k;k;k;k;l;l;l;l;l;lh9h9g9p"Hi=g8f8f8Ʒbtj?e8e7e7d7d7d7d7d7d7c7c7aaaaaaaaaac7c7d7d7d7d7d7d7e7e7e8e8wy?Wf8f8g8g:Vig9g9h9h9dth9h9i:i:i:i:i:i:i:j:j:j:j:j:j:j;j;k;k;k;k;k;k;k;k;k;k;k;k;k;k;k;k;k;k;j;j;j:j:j:j:j:j:i:i:i:i:i:i:i:h9h9`rh9h9g9g9Sgg9g8f8f8y?Wse8e8e7e7d7d7d7d7d7d7c7c7aaaaaaaaaac7c7d7d7d7d7d7d7e7e7e8e8e8Zku8Qf8f8f8f8g8Ƶzg9g9h9h9h9h9i:i:i:i:i:i:i:i:j:j:j:j:j:j:j;j;j;k;k;k;k;k;k;k;k;k;k;k;k;k;k;j;j;j;j:j:j:j:j:j:i:i:i:i:i:i:i:i:h9h9h9h9g9g9yİg8f8f8f8f8u:STge8e8e8e7e7d7d7d7d7d7d7c7c7aaaaaaaaaac7c7d7d7d7d7d7d7d7e7e7e8e8e8e8e8f8f8f8f8f8f8g8g9g9g9h9Hah9h9h9i:i:i:i:i:i:i:i:j:j:j:j:j:j:j:j:j;j;j;k;k;k;k;k;k;k;k;j;j;j;j:j:j:j:j:j:j:j:i:i:i:i:i:i:i:i:h9h9h9Mdh9g9g9g9g8f8f8f8f8f8f8e8e8e8e8e8e7e7d7d7d7d7d7d7d7c7c7aaaaaaaaaac7c7c7d7d7d7d7d7d7e7e7e8e8e8e8e8f8f8f8f8f8f8f8{?Yg9g9g9g9j=L`h9h9h9i:i:i:i:i:i:i:i:j:j:j:j:j:j:j:j:j:j:j:j;j;j;j;j;j;j:j:j:j:j:j:j:j:j:j:j:i:i:i:i:i:i:i:i:h9h9h9|AZj=g9g9g9g9Oef8f8f8f8f8f8f8e8e8e8e8e8e7e7d7d7d7d7d7d7c7c7c7aaaaaaaaaac7c7c7d7d7d7d7d7d7d7e7e7e8e8e8e8e8f8f8f8f8f8f8f8v1Qg9g9g9g9h9h9h9h9i:i:i:i:i:i:i:i:i:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:i:i:i:i:i:i:i:i:i:h9h9h9h9g9g9g9g9v3Qh :f8f8f8f8f8f8e8e8e8e8e8e7e7d7d7d7d7d7d7d7c7c7c7aaaaaaaaaac7c7c7c7d7d7d7d7d7d7e7e7e8e8e8e8e8f8f8f8f8f8f8f8ǹg9g9g9g9Vji ;h9h9h9h9h:i:i:i:i:i:i:i:i:i:i:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:j:i:i:i:i:i:i:i:i:i:i:h:h9h9h9h9i ;Vig9g9g9g9ǹf8f8f8f8f8f8f8e8e8e8e8e8e7e7d7d7d7d7d7d7c7c7c7c7aaaaaaaaaac6c7c7c7d7d7d7d7d7d7d7e7e7e8e8e8e8e8f8f8f8f8f8f8lzh:g9g9g9g9qh9h9h9h9h9h9i:i:i:i:i:i:i:i:i:i:i:i:i:j:j:j:j:j:j:j:j:j:j:i:i:i:i:i:i:i:i:i:i:i:i:i:h9h9h9h9h9h9qg9g9g9g9h:m{f8f8f8f8f8f8e8e8e8e8e8e7e7d7d7d7d7d7d7d7c7c7c7c6aaaaaaaaaac6c7c7c7c7d7d7d7d7d7d7d7e7e7e8e8e8e8e8f8f8f8f8f8kA}B\g9g9g9g9ziwg9g9g9g9}A[j>f8f8f8f8f8e8e8e8e8e8e7e7d7d7d7d7d7d7d7c7c7c7c7c6aaaaaaaaaac6c6c7c7c7d7d7d7d7d7d7d7e7e7e8e8e8e8e8e8f8f8f8f8f8g9g9g9g9i=h9h9h9h9h9h9h9h9h9h:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:i:h:h9h9h9h9h9h9h9h9h9j>g9g9g9g9f8f8f8f8f8e8e8e8e8e8e8e7e7d7d7d7d7d7d7d7c7c7c7c6c6aaaaaaaaaac6c6c7c7c7c7d7d7d7d7d7d7d7e7e7e8e8e8e8e8e8f8f8f8f8}J^vWo}g9g9g9g8x8T|H]f8f8f8f8e8e8e8e8e8e8e7e7d7d7d7d7d7d7d7c7c7c7c7c6c6aaaaaaaaaac6c6c6c7c7c7c7d7d7d7d7d7d7d7e7e7e8e8e8e8e8e8f8f8f8f8óf8g8g9g9h:ð̽h:g9h9h9h9h9h9h9h9h9h9h9h9h9h9h9i:i:i:i:i:i:i:i:i:i:i:i:h9h9h9h9h9h9h9h9h9h9h9h9h9h9g9i <˻ıh ;g9g9g8f8±f8f8f8f8e8e8e8e8e8e8e7e7d7d7d7d7d7d7d7c7c7c7c7c6c6c6aaaaaaaaaac6c6c6c6c7c7c7c7d7d7d7d7d7d7d7e7e7e8e8e8e8e8e8f8f8f8t0OxCXf8g8g9g9p$Gg9g9g9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9g9g9g9p$Hg9g9g8f8y=Wt0Of8f8f8e8e8e8e8e8e8e7e7d7d7d7d7d7d7d7c7c7c7c7c6c6c6c6aaaaaaaaaab6c6c6c6c6c7c7c7d7d7d7d7d7d7d7d7e7e7e8e8e8e8e8e8f8f8f8ȸf8f8f8g8g9kyo}g9g9g9g9g9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9g9g9g9g9g9o~eug9g8f8f8g9ȷf8f8f8e8e8e8e8e8e8e7e7d7d7d7d7d7d7d7d7c7c7c7c6c6c6c6b6aaaaaaaaaab6b6c6c6c6c7c7c7c7d7d7d7d7d7d7d7d7e7e7e8e8e8e8e8e8f8f8s2Nxf8f8f8f8g8fug9g9g9g9g9g9g9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9g9g9g9g9g9g9g9Rjg8f8f8f8f8qu:Rf8f8e8e8e8e8e8e8e7e7d7d7d7d7d7d7d7d7c7c7c7c7c6c6c6b6b6aaaaaaaaaab6b6b6c6c6c6c7c7c7c7d7d7d7d7d7d7d7d7e7e7e7e8e8e8e8e8e8f8wo%Gf8f8f8f8g9fug9g9g9g9g9g9g9g9g9g9g9h9h9h9h9h9h9h9h9h9h9h9h9h9h9g9g9g9g9g9g9g9g9g9g9g9Rjg9f8f8f8f8lC{f8e8e8e8e8e8e8e7e7e7d7d7d7d7d7d7d7d7c7c7c7c7c6c6c6b6b6b6aaaaaaaaaab6b6b6b6c6c6c6c7c7c7c7c7d7d7d7d7d7d7d7d7e7e7e8e8e8e8e8e8f9̽;i=f8f8f8f8g :}g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9~g :f8f8f8f8j>Ͽ̽f9e8e8e8e8e8e8e7e7d7d7d7d7d7d7d7d7c7c7c7c7c7c6c6c6b6b6b6b6aaaaaaaaaab6b6b6b6b6c6c6c6c7c7c7c7c7d7d7d7d7d7d7d7d7e7e7e8e8e8e8e8e8t:Rf8f8f8f8f8g :j>g8g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g9g8i d7d7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7d7d7jA~i@d7d7d7d7d7d7d7d7d7c7c7c7c7c7c7c6c6c6c6c6b6b6b6b6b6b6b6b6b6b6a6a6a6a5a5a5a5a5a5},Uaaaaa`aaaa>j`5a5a5a5a5a5a5a6a6a6a6b6b6b6b6b6b6b6b6b6b6c6c6c6c6c6c7c7c7c7c7c7c7d7d7d7d7d7d7d7d7g=IJp(Jd7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7t3Pƴgjaaaa`^aaaaN`5`5`5a5a5a5a5a5a5a6a6a6b6b6b6b6b6b6b6b6b6b6b6c6c6c6c6c6c6c7c7c7c7c7c7c7c7d7d7d7d7d7d7l%En|˾Ƹx[kz@Zu3Qn Fn Fr-Mz@ZTgxʾ˼n}k%Dd7d7d7d7d7d7c7c7c7c7c7c7c7c7c6c6c6c6c6c6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a5a5a5a5a5a5`5`5`5Naaaa^Yaaaa_c 8`5`5`5a5a5a5a5a5a5a5a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6c6c6c6c6c6c6c7c7c7c7c7c7c7c7c7c7d7d7d7d7u9S}ɼȸv@Vd7d7d7d7c7c7c7c7c7c7c7c7c7c7c6c6c6c6c6c6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a5a5a5a5a5a5a5`5`5`5c 8_aaaaYSaaaaa>j`5`5`5`5`5a5a5a5a5a5a5a5a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6c6c6c6c6c6c6c6c7c7c7c7c7c7c7c7c7c7c7c7c7d7w7U|ȺǶux:Wd7c7c7c7c7c7c7c7c7c7c7c7c7c7c6c6c6c6c6c6c6b6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a5a5a5a5a5a5a5`5`5`5`5`5>jaaaaaSئM|aaaaaY`5`5`5`5`5`5`5a5a5a5a5a5a5a5a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6c6c6c6c6c6c6c6c6c6c7c7c7c7c7c7c7c7c7c7c7c7c7c7d8d 9e:e:d 9d8c7c7c7c7c7c7c7c7c7c7c7c7c7c7c6c6c6c6c6c6c6c6c6b6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a5a5a5a5a5a5a5`5`5`5`5`5`5`5YaaaaaM|Ft`aaaaaBo`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6c6c6c6c6c6c6c6c6c6c6c6c6c6c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c6c6c6c6c6c6c6c6c6c6c6c6c6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5Boaaaaa`Ft_4Saaaaaa},U`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a6a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a6a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5},UaaaaaaS_4_4IwPaaaaaaY},U`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a6a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a6a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5},UYaaaaaaIwP_4^4_4QaaaaaaaBo`5`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a6a6a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a6a6a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5`5BoaaaaaaaQ_4^4^4^4^4YaaaaaaaY>jc 8`5`5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a6a6a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a6a6a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5`5c 8>jYaaaaaaaY^4^4^4^4^4^4IwP^aaaaaaaa_N>j},UlDb 7`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a5a6a6a6a6a6a6a6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6a6a6a6a6a6a6a6a5a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5`5lD},U>jN_aaaaaaaa^IwP^4^4^4^4^4^4^4Cq^^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^Cq^^4^4^4^4^4^4^4^4^4IwPYaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaYIwP^4^4^4^4^4]3^3^4^4^4^4^4QaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaQ^4^4^4^4^4^3]3]3]3]3^3^4^4^4^4IwPS`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`SؠIwP^4^4^4^4^3]3]3]3]3]3]3]3]3^3^4^4^4^4FtM|SظY^`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^YSئM|Ft^4^4^4^4^3]3]3]3]3]3(@ W0W0W0W0PJ\\`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`\\PJW0W0W0W0W0W0IvZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZIvW0W0X0Iv\aaaaUJyFsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsEsJyUaaaa\֟IvX0X0ZaaaJykEY1Y1Y1Y1Y1Z1Z1Z1Z1Z1Z2Z2Z2Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z2Z2Z2Z1Z1Z1Z1Z1Y1Y1Y1Y1Y1kEJyaaaZX0PJaaa7`Y1Y1Y1Z1Z1Z1Z2Z2Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z2Z2Z1Z1Z1Y1Y1Y17`aaaPJ\aaJyZ1Z1Z1Z1Z2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2[2ju{{{{{{{{{{{{{{{{ju[2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2Z1Z1Z1Z1Jyaa\\aakEZ2Z2Z2Z2[2[2[2[2[2[2[2[2[2[2\2\2\2\3\3\3\3\2\2\2[2[2[2[2[2[2[2[2[2[2Z2Z2Z2Z2kEaa\`aUZ2Z2[2[2[2[2[2[2[2[2\2\2\3\3\3\3\3\3\3\3] 5\3]3]3]3]3]3]3]3]3]3]3\3^ 6\3\3\3\3\3\3\3\3\2\2[2[2[2[2[2[2[2[2Z2Z2Ua`aaJy[2[2[2[2[2[2\2\2\3\3\3\3\3\3\3]3]3]3]3]3a:b<]3]3]3]3]3\3\3\3\3\3\3\3\2\2[2[2[2[2[2[2JyaaaaFs[2[2[2\2\2\3\3\3\3\3]3]3]3]3]3]3]3]3]3]3]3]3\j`m^4]3]3]3]3]3]3]3]3]3]3]3\3\3\3\3\3\2\2[2[2[2FsaaaaEs\2\2\3\3\3\3\3]3]3]3]3]3]3]3]3^3^3^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^4^3^3]3]3]3]3]3]3]3]3\3\3\3\3\3\2\2EsaaaaEs\3\3\3\3]3]3]3]3]3]3]3^3^4^4^4^4^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^4^4^4^4^3]3]3]3]3]3]3]3\3\3\3\3EsaaaaEs\3]3]3]3]3]3]3^3^4^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4^4^3]3]3]3]3]3]3\3EsaaaaEs]3]3]3]3]3^4^4^4^4^4^4^4_4_4_4_4_4_4_4_4_4_4_5_5`5`5`5`5`5`5`5`5_5_5_4_4_4_4_4_4_4_4_4_4^4^4^4^4^4^4^4]3]3]3]3]3EsaaaaEs]3]3^3^4^4^4^4^4_4_4_4_4_4_4_4_5_5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_5_5_4_4_4_4_4_4_4^4^4^4^4^4^3]3]3EsaaaaEs^4^4^4^4^4^4_4_4_4_4_4_4`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5a5a5`5`5`5`5`5`5`5`5`5`5`5`5`5`5`5_4_4_4_4_4_4^4^4^4^4^4^4EsaaaaEs^4^4^4_4_4_4_4_4_5`5`5`5`5`5`5`5`5`5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5`5`5`5`5`5`5`5`5`5_5_4_4_4_4_4^4^4^4EsaaaaEs^4_4_4_4_4_4`5`5`5`5`5`5`5`5a5a5a5a5a5a5a6a6a6a6d;Xh[kdh ;f8g8Rdg9{h9i:i:i:i:j:j:j:j;k;k;k;k;k;k;k;k;j;j:j:j:i:i:i:i:h9zg9Seg8f8h e8e7d7d7d7d7FtaaaaFtd7d7d7e7e8e8Sff8f8iUg9`og:f8f8e8e8e8e7d7d7d7c7c7FtaaaaFtc6c7c7d7d7d7e7e8e8e8f8f8g8g9h9h9h9h9h9h9i:i:i:i:i:i:i:i:i:i:h9h9h9h9h9h:g9g8f8f8e8e8e8e7d7d7d7c7c7c6FtaaaaFtc6c6c7d7d7d7d7e7e8e8e8f8j?gsg8i=g9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9h9g9i=g8esj?f8e8e8e8e7d7d7d7d7c7c6c6FtaaaaFtb6c6c7c7d7d7d7d7e7e8e8e8f8hTe8õfrd7d7c7c6c6b6b6a63]a`Cpa5a5a6b6b6b6c6c7c7d7d7c7c7c6b6b6b6a6a5a5Cp`^\lC`5a5a5a6b6b6b6b6c6c6c6c7c7c7c7c6c6c6b6b6b6b6a6a5a5`5lC\^X1`\Cp3]1[1[1[1[1[1[1[1[1[1[1[1[1[1[1[1[1[1[1[1[1[1[3]Cp\`X1^3X1^`aaaaaaaaaaaaaaaaaaaaaaaa`^X1^3(  ^VXL|L{L{L{L{L{L{L{L{L{L{L{X^VX^ 6Z2[2[2tN\vvtN\[2[2Z2^ 6XL|\2\3]3]3^4v~v^4]3]3\3\2L|L{]3^4_4_4_4`5]i]i`5_4_4_4^4]3L{L{_4`5`5a5tFWtGWa5`5`5_4L{L|a5a6b6yv|{{|wzb6a6a5L|L|b6c6ynyxxnyyc6b6L|L|c7s?Sxn(Eh9h9n(Eyt@Tc7L|L|d7yp,Hi:j:j:i:p,Hyd7L|L|d7{ui:j;k;k;j;i:u{d7L|L|d7vi:j;k;k;j;i:vd7L|L|d7e8s~t:Pi:i:i:i:t9Ps~e8d7L|L|c7d7e8u>Sg9g9u>Se8d7c7L|L|b6c6d7uCVxxtATd7c6b6L|Xd 9a6b6b6c7nynyc7b6b6a6d 9X^VXL|L|L|L|L|L|L|L|L|L|L|L|X^Vlooking-glass-0+b1/vendor/000077500000000000000000000000001351530562300155625ustar00rootroot00000000000000looking-glass-0+b1/vendor/getopt/000077500000000000000000000000001351530562300170645ustar00rootroot00000000000000looking-glass-0+b1/vendor/getopt/README.txt000066400000000000000000000001251351530562300205600ustar00rootroot00000000000000This source comes from: https://github.com/Stichting-MINIX-Research-Foundation/minix looking-glass-0+b1/vendor/getopt/getopt.c000066400000000000000000000100501351530562300205260ustar00rootroot00000000000000/* $NetBSD: getopt.c,v 1.29 2014/06/05 22:00:22 christos Exp $ */ /* * Copyright (c) 1987, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include int opterr = 1, /* if error message should be printed */ optind = 1, /* index into parent argv vector */ optopt, /* character checked for validity */ optreset; /* reset getopt */ char *optarg; /* argument associated with option */ #define BADCH (int)'?' #define BADARG (int)':' #define EMSG "" /* * getopt -- * Parse argc/argv argument vector. */ int getopt(int nargc, char * const nargv[], const char *ostr) { static char *place = EMSG; /* option letter processing */ char *oli; /* option letter list index */ if (optreset || *place == 0) { /* update scanning pointer */ optreset = 0; place = nargv[optind]; if (optind >= nargc || *place++ != '-') { /* Argument is absent or is not an option */ place = EMSG; return (-1); } optopt = *place++; if (optopt == '-' && *place == 0) { /* "--" => end of options */ ++optind; place = EMSG; return (-1); } if (optopt == 0) { /* Solitary '-', treat as a '-' option if the program (eg su) is looking for it. */ place = EMSG; if (strchr(ostr, '-') == NULL) return -1; optopt = '-'; } } else optopt = *place++; /* See if option letter is one the caller wanted... */ if (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) { if (*place == 0) ++optind; if (opterr && *ostr != ':') (void)fprintf(stderr, "unknown option -- %c\n", optopt); return (BADCH); } /* Does this option need an argument? */ if (oli[1] != ':') { /* don't need argument */ optarg = NULL; if (*place == 0) ++optind; } else { /* Option-argument is either the rest of this argument or the entire next argument. */ if (*place) optarg = place; else if (oli[2] == ':') /* * GNU Extension, for optional arguments if the rest of * the argument is empty, we return NULL */ optarg = NULL; else if (nargc > ++optind) optarg = nargv[optind]; else { /* option-argument absent */ place = EMSG; if (*ostr == ':') return (BADARG); if (opterr) (void)fprintf(stderr, "option requires an argument -- %c\n", optopt); return (BADCH); } place = EMSG; ++optind; } return (optopt); /* return option letter */ }looking-glass-0+b1/vendor/getopt/getopt.h000066400000000000000000000007371351530562300205460ustar00rootroot00000000000000#ifndef GETOPT_H #define GETOPT_H #ifdef __cplusplus extern "C" { #endif extern int opterr; /* if error message should be printed */ extern int optind; /* index into parent argv vector */ extern int optopt; /* character checked for validity */ extern int optreset; /* reset getopt */ extern char *optarg; /* argument associated with option */ int getopt(int nargc, char * const nargv[], const char *ostr); #ifdef __cplusplus } #endif #endiflooking-glass-0+b1/vendor/ivshmem/000077500000000000000000000000001351530562300172325ustar00rootroot00000000000000looking-glass-0+b1/vendor/ivshmem/ivshmem.h000066400000000000000000000043501351530562300210550ustar00rootroot00000000000000#include DEFINE_GUID (GUID_DEVINTERFACE_IVSHMEM, 0xdf576976,0x569d,0x4672,0x95,0xa0,0xf5,0x7e,0x4e,0xa0,0xb2,0x10); // {df576976-569d-4672-95a0-f57e4ea0b210} typedef UINT16 IVSHMEM_PEERID; typedef UINT64 IVSHMEM_SIZE; /* This structure is for use with the IOCTL_IVSHMEM_REQUEST_MMAP IOCTL */ typedef struct IVSHMEM_MMAP { IVSHMEM_PEERID peerID; // our peer id IVSHMEM_SIZE size; // the size of the memory region PVOID ptr; // pointer to the memory region UINT16 vectors; // the number of vectors available } IVSHMEM_MMAP, *PIVSHMEM_MMAP; /* This structure is for use with the IOCTL_IVSHMEM_RING_DOORBELL IOCTL */ typedef struct IVSHMEM_RING { IVSHMEM_PEERID peerID; // the id of the peer to ring UINT16 vector; // the doorbell to ring } IVSHMEM_RING, *PIVSHMEM_RING; /* This structure is for use with the IOCTL_IVSHMEM_REGISTER_EVENT IOCTL Please Note: - The IVSHMEM driver has a hard limit of 32 events. - Events that are singleShot are released after they have been set. - At this time repeating events are only released when the driver device handle is closed, closing the event handle doesn't release it from the drivers list. While this won't cause a problem in the driver, it will cause you to run out of event slots. */ typedef struct IVSHMEM_EVENT { UINT16 vector; // the vector that triggers the event HANDLE event; // the event to trigger BOOLEAN singleShot; // set to TRUE if you want the driver to only trigger this event once } IVSHMEM_EVENT, *PIVSHMEM_EVENT; #define IOCTL_IVSHMEM_REQUEST_PEERID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IVSHMEM_REQUEST_SIZE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IVSHMEM_REQUEST_MMAP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IVSHMEM_RELEASE_MMAP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IVSHMEM_RING_DOORBELL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IVSHMEM_REGISTER_EVENT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS)