From 27a38294eafac4c3e6f8e18be59968f544d52703 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 15 Jan 2021 20:30:03 +1100 Subject: [PATCH] [client] major restructure of platform specific code --- client/CMakeLists.txt | 51 +- client/clipboards/CMakeLists.txt | 47 -- client/clipboards/Wayland/CMakeLists.txt | 25 - client/clipboards/Wayland/src/wayland.c | 442 ----------- client/clipboards/X11/CMakeLists.txt | 26 - client/clipboards/X11/src/x11.c | 466 ----------- client/displayservers/CMakeLists.txt | 50 ++ client/displayservers/SDL/CMakeLists.txt | 24 + client/displayservers/SDL/sdl.c | 154 ++++ client/displayservers/Wayland/CMakeLists.txt | 58 ++ client/displayservers/Wayland/wayland.c | 715 +++++++++++++++++ client/displayservers/X11/CMakeLists.txt | 29 + client/displayservers/X11/x11.c | 792 +++++++++++++++++++ client/include/app.h | 43 + client/include/interface/clipboard.h | 62 -- client/include/interface/displayserver.h | 86 ++ client/renderers/EGL/egl.c | 36 +- client/src/main.c | 468 ++++------- client/src/main.h | 12 +- client/src/wm.c | 495 ------------ client/src/wm.h | 65 -- common/include/common/sysinfo.h | 5 +- common/src/platform/linux/sysinfo.c | 47 +- 23 files changed, 2120 insertions(+), 2078 deletions(-) delete mode 100644 client/clipboards/CMakeLists.txt delete mode 100644 client/clipboards/Wayland/CMakeLists.txt delete mode 100644 client/clipboards/Wayland/src/wayland.c delete mode 100644 client/clipboards/X11/CMakeLists.txt delete mode 100644 client/clipboards/X11/src/x11.c create mode 100644 client/displayservers/CMakeLists.txt create mode 100644 client/displayservers/SDL/CMakeLists.txt create mode 100644 client/displayservers/SDL/sdl.c create mode 100644 client/displayservers/Wayland/CMakeLists.txt create mode 100644 client/displayservers/Wayland/wayland.c create mode 100644 client/displayservers/X11/CMakeLists.txt create mode 100644 client/displayservers/X11/x11.c create mode 100644 client/include/app.h delete mode 100644 client/include/interface/clipboard.h create mode 100644 client/include/interface/displayserver.h delete mode 100644 client/src/wm.c delete mode 100644 client/src/wm.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 1b981586..d16cff95 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -21,12 +21,6 @@ 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_CB_WAYLAND "Enable Wayland clipboard integration" ON) -add_feature_info(ENABLE_CB_WAYLAND ENABLE_CB_WAYLAND "Wayland Clipboard Integration.") - option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON) add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.") @@ -36,6 +30,9 @@ add_feature_info(ENABLE_ASAN ENABLE_ASAN "AddressSanitizer support.") option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF) add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.") +option(ENABLE_X11 "Build with X11 support" ON) +add_feature_info(ENABLE_WAYLAND ENABLE_X11 "X11 support.") + option(ENABLE_WAYLAND "Build with Wayland support" ON) add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.") @@ -69,11 +66,6 @@ endif() find_package(PkgConfig) pkg_check_modules(PKGCONFIG REQUIRED sdl2 - x11 -) - -pkg_check_modules(PKGCONFIG_OPT - xi ) find_package(GMP) @@ -112,15 +104,14 @@ set(SOURCES src/lg-renderer.c src/ll.c src/utils.c - src/wm.c ) add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" ) add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" ) add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice") +add_subdirectory(displayservers) add_subdirectory(renderers) -add_subdirectory(clipboards) add_subdirectory(fonts) add_executable(looking-glass-client ${SOURCES}) @@ -128,45 +119,13 @@ target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER} ${P target_link_libraries(looking-glass-client ${EXE_FLAGS} lg_common + displayservers lgmp purespice renderers - clipboards fonts ) install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-client DESTINATION bin/ COMPONENT binary) -if(ENABLE_WAYLAND) - find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner) - pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15) - pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir) - - macro(wayland_generate protocol_file output_file) - add_custom_command(OUTPUT "${output_file}.h" - COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h" - DEPENDS "${protocol_file}" - VERBATIM) - - add_custom_command(OUTPUT "${output_file}.c" - COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c" - DEPENDS "${protocol_file}" - VERBATIM) - - target_sources(looking-glass-client PRIVATE "${output_file}.h" "${output_file}.c") - endmacro() - - file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland") - include_directories("${CMAKE_BINARY_DIR}/wayland") - wayland_generate( - "${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml" - "${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol") - wayland_generate( - "${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml" - "${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol") - wayland_generate( - "${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml" - "${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol") -endif() - feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES) diff --git a/client/clipboards/CMakeLists.txt b/client/clipboards/CMakeLists.txt deleted file mode 100644 index 31b24583..00000000 --- a/client/clipboards/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -cmake_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() - -if (ENABLE_CB_WAYLAND) - add_clipboard(Wayland) -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}) diff --git a/client/clipboards/Wayland/CMakeLists.txt b/client/clipboards/Wayland/CMakeLists.txt deleted file mode 100644 index fa676a65..00000000 --- a/client/clipboards/Wayland/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(clipboard_Wayland LANGUAGES C) - -find_package(PkgConfig) -pkg_check_modules(CLIPBOARD_PKGCONFIG REQUIRED - wayland-client -) - -add_library(clipboard_Wayland STATIC - src/wayland.c -) - -target_link_libraries(clipboard_Wayland - ${CLIPBOARD_PKGCONFIG_LIBRARIES} - lg_common -) - -target_include_directories(clipboard_Wayland - PUBLIC - $ - $ - PRIVATE - src - ${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS} -) diff --git a/client/clipboards/Wayland/src/wayland.c b/client/clipboards/Wayland/src/wayland.c deleted file mode 100644 index 74c6a35b..00000000 --- a/client/clipboards/Wayland/src/wayland.c +++ /dev/null @@ -1,442 +0,0 @@ -/* -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 "../../client/src/wm.h" - -#include -#include -#include -#include -#include - -struct WCBTransfer -{ - void * data; - size_t size; - const char ** mimetypes; -}; - -struct WCBState -{ - enum LG_ClipboardData stashedType; - char * stashedMimetype; - uint8_t * stashedContents; - ssize_t stashedSize; - bool isReceiving; - bool isSelfCopy; - - LG_ClipboardReleaseFn releaseFn; - LG_ClipboardRequestFn requestFn; - LG_ClipboardNotifyFn notifyFn; - LG_ClipboardDataFn dataFn; - LG_ClipboardData type; -}; - -static struct WCBState * this = NULL; - -static const char * textMimetypes[] = -{ - "text/plain", - "text/plain;charset=utf-8", - "TEXT", - "STRING", - "UTF8_STRING", - NULL, -}; - -static const char * pngMimetypes[] = -{ - "image/png", - NULL, -}; - -static const char * bmpMimetypes[] = -{ - "image/bmp", - "image/x-bmp", - "image/x-MS-bmp", - "image/x-win-bitmap", - NULL, -}; - -static const char * tiffMimetypes[] = -{ - "image/tiff", - NULL, -}; - -static const char * jpegMimetypes[] = -{ - "image/jpeg", - NULL, -}; - -static const char ** cbTypeToMimetypes(enum LG_ClipboardData type) -{ - switch (type) - { - case LG_CLIPBOARD_DATA_TEXT: - return textMimetypes; - case LG_CLIPBOARD_DATA_PNG: - return pngMimetypes; - case LG_CLIPBOARD_DATA_BMP: - return bmpMimetypes; - case LG_CLIPBOARD_DATA_TIFF: - return tiffMimetypes; - case LG_CLIPBOARD_DATA_JPEG: - return jpegMimetypes; - default: - DEBUG_ERROR("invalid clipboard type"); - abort(); - } -} - -static bool containsMimetype(const char ** mimetypes, - const char * needle) -{ - for (const char ** mimetype = mimetypes; *mimetype; mimetype++) - if (!strcmp(needle, *mimetype)) - return true; - - return false; -} - -static bool mimetypeEndswith(const char * mimetype, const char * what) -{ - size_t mimetypeLen = strlen(mimetype); - size_t whatLen = strlen(what); - - if (mimetypeLen < whatLen) - return false; - - return !strcmp(mimetype + mimetypeLen - whatLen, what); -} - -static bool isTextMimetype(const char * mimetype) -{ - if (containsMimetype(textMimetypes, mimetype)) - return true; - - char * text = "text/"; - if (!strncmp(mimetype, text, strlen(text))) - return true; - - if (mimetypeEndswith(mimetype, "script") || - mimetypeEndswith(mimetype, "xml") || - mimetypeEndswith(mimetype, "yaml")) - return true; - - if (strstr(mimetype, "json")) - return true; - - return false; -} - -static enum LG_ClipboardData mimetypeToCbType(const char * mimetype) -{ - if (isTextMimetype(mimetype)) - return LG_CLIPBOARD_DATA_TEXT; - - if (containsMimetype(pngMimetypes, mimetype)) - return LG_CLIPBOARD_DATA_PNG; - - if (containsMimetype(bmpMimetypes, mimetype)) - return LG_CLIPBOARD_DATA_BMP; - - if (containsMimetype(tiffMimetypes, mimetype)) - return LG_CLIPBOARD_DATA_TIFF; - - if (containsMimetype(jpegMimetypes, mimetype)) - return LG_CLIPBOARD_DATA_JPEG; - - return LG_CLIPBOARD_DATA_NONE; -} - -static const char * wayland_cb_getName(void) -{ - return "Wayland"; -} - -// Destination client handlers. - -static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer, - const char * mimetype) -{ - enum LG_ClipboardData type = mimetypeToCbType(mimetype); - // Oftentimes we'll get text/html alongside text/png, but would prefer to send - // image/png. In general, prefer images over text content. - if (type != LG_CLIPBOARD_DATA_NONE && - (this->stashedType == LG_CLIPBOARD_DATA_NONE || - this->stashedType == LG_CLIPBOARD_DATA_TEXT)) - { - this->stashedType = type; - if (this->stashedMimetype) - free(this->stashedMimetype); - this->stashedMimetype = strdup(mimetype); - } -} - -static void dataOfferHandleSourceActions(void * data, - struct wl_data_offer * offer, uint32_t sourceActions) -{ - // Do nothing. -} - -static void dataOfferHandleAction(void * data, struct wl_data_offer * offer, - uint32_t dndAction) -{ - // Do nothing. -} - -static const struct wl_data_offer_listener dataOfferListener = { - .offer = dataOfferHandleOffer, - .source_actions = dataOfferHandleSourceActions, - .action = dataOfferHandleAction, -}; - -static void dataDeviceHandleDataOffer(void * data, - struct wl_data_device * dataDevice, struct wl_data_offer * offer) -{ - this->stashedType = LG_CLIPBOARD_DATA_NONE; - wl_data_offer_add_listener(offer, &dataOfferListener, NULL); -} - -static void dataDeviceHandleSelection(void * data, - struct wl_data_device * dataDevice, struct wl_data_offer * offer) -{ - if (this->stashedType == LG_CLIPBOARD_DATA_NONE || !offer) - return; - - int fds[2]; - if (pipe(fds) < 0) - { - DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno)); - abort(); - } - - this->isReceiving = true; - this->isSelfCopy = false; - wl_data_offer_receive(offer, this->stashedMimetype, fds[1]); - close(fds[1]); - free(this->stashedMimetype); - this->stashedMimetype = NULL; - - struct WMDataWayland * wm = g_wmState.opaque; - wl_display_roundtrip(wm->display); - - if (this->stashedContents) - { - free(this->stashedContents); - this->stashedContents = NULL; - } - - size_t size = 4096, numRead = 0; - uint8_t * buf = (uint8_t *) malloc(size); - while (true) - { - ssize_t result = read(fds[0], buf + numRead, size - numRead); - if (result < 0) - { - DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno)); - abort(); - } - - if (result == 0) - { - buf[numRead] = 0; - break; - } - - numRead += result; - if (numRead >= size) - { - size *= 2; - void * nbuf = realloc(buf, size); - if (!nbuf) { - DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno)); - abort(); - } - - buf = nbuf; - } - } - - this->stashedSize = numRead; - this->stashedContents = buf; - this->isReceiving = false; - - close(fds[0]); - wl_data_offer_destroy(offer); - - if (!this->isSelfCopy) - this->notifyFn(this->stashedType, 0); -} - -static const struct wl_data_device_listener dataDeviceListener = { - .data_offer = dataDeviceHandleDataOffer, - .selection = dataDeviceHandleSelection, -}; - -static void wayland_cb_request(LG_ClipboardData type) -{ - // We only notified once, so it must be this. - assert(type == this->stashedType); - this->dataFn(this->stashedType, this->stashedContents, this->stashedSize); -} - -// End of Wayland handlers. - -static bool wayland_cb_init( - SDL_SysWMinfo * wminfo, - LG_ClipboardReleaseFn releaseFn, - LG_ClipboardNotifyFn notifyFn, - LG_ClipboardDataFn dataFn) -{ - if (wminfo->subsystem != SDL_SYSWM_WAYLAND) - return false; - - this = (struct WCBState *)malloc(sizeof(struct WCBState)); - memset(this, 0, sizeof(struct WCBState)); - - this->releaseFn = releaseFn; - this->notifyFn = notifyFn; - this->dataFn = dataFn; - this->stashedType = LG_CLIPBOARD_DATA_NONE; - - struct WMDataWayland * wm = g_wmState.opaque; - wl_data_device_add_listener(wm->dataDevice, &dataDeviceListener, NULL); - return true; -} - -static void wayland_cb_free(void) -{ - free(this); - this = NULL; -} - -static void wayland_cb_wmevent(SDL_SysWMmsg * msg) -{ -} - -static void dataSourceHandleSend(void * data, struct wl_data_source * source, - const char * mimetype, int fd) -{ - struct WCBTransfer * transfer = (struct WCBTransfer *) data; - if (this->isReceiving) - this->isSelfCopy = true; - else if (containsMimetype(transfer->mimetypes, mimetype)) - { - // Consider making this do non-blocking sends to not stall the Wayland - // event loop if it becomes a problem. This is "fine" in the sense that - // wl-copy also stalls like this, but it's not necessary. - fcntl(fd, F_SETFL, 0); - - size_t pos = 0; - while (pos < transfer->size) - { - ssize_t written = write(fd, transfer->data + pos, transfer->size - pos); - if (written < 0) - { - if (errno != EPIPE) - DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno)); - goto error; - } - - pos += written; - } - } - -error: - close(fd); -} - -static void dataSourceHandleCancelled(void * data, - struct wl_data_source * source) -{ - struct WCBTransfer * transfer = (struct WCBTransfer *) data; - free(transfer->data); - free(transfer); - wl_data_source_destroy(source); -} - -static const struct wl_data_source_listener dataSourceListener = { - .send = dataSourceHandleSend, - .cancelled = dataSourceHandleCancelled, -}; - -static void wayland_cb_reply_fn(void * opaque, LG_ClipboardData type, - uint8_t * data, uint32_t size) -{ - struct WMDataWayland * wm = g_wmState.opaque; - - struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer)); - void * dataCopy = malloc(size); - memcpy(dataCopy, data, size); - *transfer = (struct WCBTransfer) { - .data = dataCopy, - .size = size, - .mimetypes = cbTypeToMimetypes(type), - }; - - struct wl_data_source * source = - wl_data_device_manager_create_data_source(wm->dataDeviceManager); - wl_data_source_add_listener(source, &dataSourceListener, transfer); - for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++) - wl_data_source_offer(source, *mimetype); - - wl_data_device_set_selection(wm->dataDevice, source, - wm->keyboardEnterSerial); -} - -static void wayland_cb_notice(LG_ClipboardRequestFn requestFn, - LG_ClipboardData type) -{ - this->requestFn = requestFn; - this->type = type; - - if (!this->requestFn) - return; - - // Won't have a keyboard enter serial if we don't have the keyboard - // capability. - struct WMDataWayland * wm = g_wmState.opaque; - if (!wm->keyboard) - return; - - this->requestFn(wayland_cb_reply_fn, NULL); -} - -static void wayland_cb_release(void) -{ - this->requestFn = NULL; -} - -const LG_Clipboard LGC_Wayland = -{ - .getName = wayland_cb_getName, - .init = wayland_cb_init, - .free = wayland_cb_free, - .wmevent = wayland_cb_wmevent, - .notice = wayland_cb_notice, - .release = wayland_cb_release, - .request = wayland_cb_request, -}; diff --git a/client/clipboards/X11/CMakeLists.txt b/client/clipboards/X11/CMakeLists.txt deleted file mode 100644 index ed12fad9..00000000 --- a/client/clipboards/X11/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_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} -) diff --git a/client/clipboards/X11/src/x11.c b/client/clipboards/X11/src/x11.c deleted file mode 100644 index ae77950e..00000000 --- a/client/clipboards/X11/src/x11.c +++ /dev/null @@ -1,466 +0,0 @@ -/* -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; - - bool incrStart; - unsigned int lowerBound; - - // 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(void) -{ - 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) - 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(void) -{ - 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_selection_request(const XSelectionRequestEvent e) -{ - XEvent * s = (XEvent *)malloc(sizeof(XEvent)); - s->xselection.type = SelectionNotify; - s->xselection.requestor = e.requestor; - s->xselection.selection = e.selection; - s->xselection.target = e.target; - s->xselection.property = e.property; - s->xselection.time = e.time; - - if (!this->requestFn) - goto nodata; - - // target list requested - if (e.target == this->aTargets) - { - Atom targets[2]; - targets[0] = this->aTargets; - targets[1] = this->aTypes[this->type]; - - XChangeProperty( - e.display, - e.requestor, - e.property, - XA_ATOM, - 32, - PropModeReplace, - (unsigned char*)targets, - sizeof(targets) / sizeof(Atom)); - - goto send; - } - - // look to see if we can satisfy the data type - for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) - if (this->aTypes[i] == e.target && this->type == i) - { - // request the data - this->requestFn(x11_cb_reply_fn, s); - return; - } - -nodata: - // report no data - s->xselection.property = None; - -send: - XSendEvent(this->display, e.requestor, 0, 0, s); - XFlush(this->display); - free(s); -} - -static void x11_cb_selection_clear(const XSelectionClearEvent e) -{ - if (e.selection != XA_PRIMARY && e.selection != this->aSelection) - return; - - this->aCurSelection = BadValue; - this->releaseFn(); - return; -} - -static void x11_cb_xfixes_selection_notify(const XFixesSelectionNotifyEvent e) -{ - // check if the selection is valid and it isn't ourself - if ((e.selection != XA_PRIMARY && e.selection != this->aSelection) || - e.owner == this->window || e.owner == 0) - { - return; - } - - // remember which selection we are working with - this->aCurSelection = e.selection; - XConvertSelection( - this->display, - e.selection, - this->aTargets, - this->aTargets, - this->window, - CurrentTime); - - return; -} - -static void x11_cb_selection_incr(const XPropertyEvent e) -{ - Atom type; - int format; - unsigned long itemCount, after; - unsigned char *data; - - if (XGetWindowProperty( - e.display, - e.window, - e.atom, - 0, ~0L, // start and length - True, // delete the property - this->aIncr, - &type, - &format, - &itemCount, - &after, - &data) != Success) - { - DEBUG_INFO("GetProp Failed"); - this->notifyFn(LG_CLIPBOARD_DATA_NONE, 0); - goto out; - } - - LG_ClipboardData dataType; - for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType) - if (this->aTypes[dataType] == type) - break; - - if (dataType == LG_CLIPBOARD_DATA_NONE) - { - DEBUG_WARN("clipboard data (%s) not in a supported format", - XGetAtomName(this->display, type)); - - this->lowerBound = 0; - this->notifyFn(LG_CLIPBOARD_DATA_NONE, 0); - goto out; - } - - if (this->incrStart) - { - this->notifyFn(dataType, this->lowerBound); - this->incrStart = false; - } - - XFree(data); - data = NULL; - - if (XGetWindowProperty( - e.display, - e.window, - e.atom, - 0, ~0L, // start and length - True, // delete the property - type, - &type, - &format, - &itemCount, - &after, - &data) != Success) - { - DEBUG_ERROR("XGetWindowProperty Failed"); - this->notifyFn(LG_CLIPBOARD_DATA_NONE, 0); - goto out; - } - - this->dataFn(dataType, data, itemCount); - this->lowerBound -= itemCount; - -out: - if (data) - XFree(data); -} - -static void x11_cb_selection_notify(const XSelectionEvent e) -{ - if (e.property == None) - return; - - Atom type; - int format; - unsigned long itemCount, after; - unsigned char *data; - - if (XGetWindowProperty( - e.display, - e.requestor, - e.property, - 0, ~0L, // start and length - True , // delete the property - AnyPropertyType, - &type, - &format, - &itemCount, - &after, - &data) != Success) - { - this->notifyFn(LG_CLIPBOARD_DATA_NONE, 0); - goto out; - } - - if (type == this->aIncr) - { - this->incrStart = true; - this->lowerBound = *(unsigned int *)data; - goto out; - } - - // the target list - if (e.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) - goto out; - - // 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, 0); - goto out; - } - } - - // no matches - this->notifyFn(LG_CLIPBOARD_DATA_NONE, 0); - goto out; - } - - if (e.property == this->aSelData) - { - LG_ClipboardData dataType; - for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType) - if (this->aTypes[dataType] == type) - break; - - if (dataType == LG_CLIPBOARD_DATA_NONE) - { - DEBUG_WARN("clipboard data (%s) not in a supported format", - XGetAtomName(this->display, type)); - goto out; - } - - this->dataFn(dataType, data, itemCount); - goto out; - } - -out: - if (data) - XFree(data); -} - -static void x11_cb_wmevent(SDL_SysWMmsg * msg) -{ - XEvent e = msg->msg.x11.event; - - switch(e.type) - { - case SelectionRequest: - x11_cb_selection_request(e.xselectionrequest); - break; - - case SelectionClear: - x11_cb_selection_clear(e.xselectionclear); - break; - - case SelectionNotify: - x11_cb_selection_notify(e.xselection); - break; - - case PropertyNotify: - if (e.xproperty.display != this->display || - e.xproperty.window != this->window || - e.xproperty.atom != this->aSelData || - e.xproperty.state != PropertyNewValue || - this->lowerBound == 0) - break; - - x11_cb_selection_incr(e.xproperty); - break; - - default: - if (e.type == this->eventBase + XFixesSelectionNotify) - { - XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&e; - x11_cb_xfixes_selection_notify(*sne); - } - break; - } -} - -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(void) -{ - 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 -}; diff --git a/client/displayservers/CMakeLists.txt b/client/displayservers/CMakeLists.txt new file mode 100644 index 00000000..dafeffdc --- /dev/null +++ b/client/displayservers/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.0) +project(displayservers LANGUAGES C) + +set(DISPLAYSERVER_H "${CMAKE_BINARY_DIR}/include/dynamic/displayservers.h") +set(DISPLAYSERVER_C "${CMAKE_BINARY_DIR}/src/displayservers.c") + +file(WRITE ${DISPLAYSERVER_H} "#include \"interface/displayserver.h\"\n\n") +file(APPEND ${DISPLAYSERVER_H} "extern struct LG_DisplayServerOps * LG_DisplayServers[];\n\n") + +file(WRITE ${DISPLAYSERVER_C} "#include \"interface/displayserver.h\"\n\n") +file(APPEND ${DISPLAYSERVER_C} "#include \n\n") + +set(DISPLAYSERVERS "_") +set(DISPLAYSERVERS_LINK "_") +function(add_displayserver name) + set(DISPLAYSERVERS "${DISPLAYSERVERS};${name}" PARENT_SCOPE) + set(DISPLAYSERVERS_LINK "${DISPLAYSERVERS_LINK};displayserver_${name}" PARENT_SCOPE) + add_subdirectory(${name}) +endfunction() + +# SDL must be first as it's the default implementation! +add_displayserver(SDL) + +# Add/remove displayservers here! +if (ENABLE_X11) + add_displayserver(X11) +endif() + +if (ENABLE_WAYLAND) + add_displayserver(Wayland) +endif() + +list(REMOVE_AT DISPLAYSERVERS 0) +list(REMOVE_AT DISPLAYSERVERS_LINK 0) + +list(LENGTH DISPLAYSERVERS DISPLAYSERVER_COUNT) +file(APPEND ${DISPLAYSERVER_H} "#define LG_DISPLAYSERVER_COUNT ${DISPLAYSERVER_COUNT}\n") + +foreach(displayserver ${DISPLAYSERVERS}) + file(APPEND ${DISPLAYSERVER_C} "extern struct LG_DisplayServerOps LGDS_${displayserver};\n") +endforeach() + +file(APPEND ${DISPLAYSERVER_C} "\nconst struct LG_DisplayServerOps * LG_DisplayServers[] =\n{\n") +foreach(displayserver ${DISPLAYSERVERS}) + file(APPEND ${DISPLAYSERVER_C} " &LGDS_${displayserver},\n") +endforeach() +file(APPEND ${DISPLAYSERVER_C} " NULL\n};") + +add_library(displayservers STATIC ${DISPLAYSERVER_C}) +target_link_libraries(displayservers ${DISPLAYSERVERS_LINK}) diff --git a/client/displayservers/SDL/CMakeLists.txt b/client/displayservers/SDL/CMakeLists.txt new file mode 100644 index 00000000..89f01492 --- /dev/null +++ b/client/displayservers/SDL/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.0) +project(displayserver_SDL LANGUAGES C) + +#find_package(PkgConfig) +#pkg_check_modules(DISPLAYSERVER_SDL_PKGCONFIG REQUIRED +# #sdl2 +#) + +add_library(displayserver_SDL STATIC + sdl.c +) + +target_link_libraries(displayserver_SDL + ${DISPLAYSERVER_SDL_PKGCONFIG_LIBRARIES} + ${DISPLAYSERVER_SDL_OPT_PKGCONFIG_LIBRARIES} + lg_common +) + +target_include_directories(displayserver_SDL + PRIVATE + src + ${DISPLAYSERVER_SDL_PKGCONFIG_INCLUDE_DIRS} + ${DISPLAYSERVER_SDL_OPT_PKGCONFIG_INCLUDE_DIRS} +) diff --git a/client/displayservers/SDL/sdl.c b/client/displayservers/SDL/sdl.c new file mode 100644 index 00000000..d935ca7e --- /dev/null +++ b/client/displayservers/SDL/sdl.c @@ -0,0 +1,154 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2021 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/displayserver.h" + +#include + +#include "app.h" +#include "common/debug.h" + +struct SDLDSState +{ + bool keyboardGrabbed; + bool pointerGrabbed; + bool exiting; +}; + +static struct SDLDSState sdl; + +static void sdlInit(SDL_SysWMinfo * info) +{ + memset(&sdl, 0, sizeof(sdl)); +} + +static void sdlStartup(void) +{ +} + +static void sdlShutdown(void) +{ +} + +static void sdlFree(void) +{ +} + +static bool sdlGetProp(LG_DSProperty prop, void * ret) +{ + return false; +} + +static bool sdlEventFilter(SDL_Event * event) +{ + switch(event->type) + { + case SDL_MOUSEMOTION: + // stop motion events during the warp out of the window + if (sdl.exiting) + return true; + break; + + case SDL_WINDOWEVENT: + { + switch(event->window.event) + { + /* after leave re-enable warp and cursor processing */ + case SDL_WINDOWEVENT_LEAVE: + sdl.exiting = false; + return false; + } + break; + } + } + + return false; +} + +static void sdlGrabPointer(void) +{ + SDL_SetWindowGrab(app_getWindow(), SDL_TRUE); + SDL_SetRelativeMouseMode(SDL_TRUE); + sdl.pointerGrabbed = true; +} + +static void sdlUngrabPointer(void) +{ + SDL_SetWindowGrab(app_getWindow(), SDL_FALSE); + SDL_SetRelativeMouseMode(SDL_FALSE); + sdl.pointerGrabbed = false; +} + +static void sdlGrabKeyboard(void) +{ + if (sdl.pointerGrabbed) + SDL_SetWindowGrab(app_getWindow(), SDL_FALSE); + else + { + DEBUG_WARN("SDL does not support grabbing only the keyboard, grabbing all"); + sdl.pointerGrabbed = true; + } + + SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); + SDL_SetWindowGrab(app_getWindow(), SDL_TRUE); + sdl.keyboardGrabbed = true; +} + +static void sdlUngrabKeyboard(void) +{ + SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0"); + SDL_SetWindowGrab(app_getWindow(), SDL_FALSE); + if (sdl.pointerGrabbed) + SDL_SetWindowGrab(app_getWindow(), SDL_TRUE); + sdl.keyboardGrabbed = false; +} + +static void sdlWarpMouse(int x, int y, bool exiting) +{ + if (sdl.exiting) + return; + + sdl.exiting = exiting; + + // if exiting turn off relative mode + if (exiting) + SDL_SetRelativeMouseMode(SDL_FALSE); + + // issue the warp + SDL_WarpMouseInWindow(app_getWindow(), x, y); +} + +struct LG_DisplayServerOps LGDS_SDL = +{ + .subsystem = SDL_SYSWM_UNKNOWN, + .init = sdlInit, + .startup = sdlStartup, + .shutdown = sdlShutdown, + .free = sdlFree, + .getProp = sdlGetProp, + .eventFilter = sdlEventFilter, + .grabPointer = sdlGrabPointer, + .ungrabPointer = sdlUngrabPointer, + .grabKeyboard = sdlGrabKeyboard, + .ungrabKeyboard = sdlUngrabKeyboard, + .warpMouse = sdlWarpMouse, + + /* SDL does not have clipboard support */ + .cbInit = NULL, +}; diff --git a/client/displayservers/Wayland/CMakeLists.txt b/client/displayservers/Wayland/CMakeLists.txt new file mode 100644 index 00000000..06d82817 --- /dev/null +++ b/client/displayservers/Wayland/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 3.0) +project(displayserver_Wayland LANGUAGES C) + +find_package(PkgConfig) +pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED + wayland-client +) + +#pkg_check_modules(DISPLAYSERVER_Wayland_OPT_PKGCONFIG +#) + +add_library(displayserver_Wayland STATIC + wayland.c +) + +target_link_libraries(displayserver_Wayland + ${DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES} + ${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES} + lg_common +) + +target_include_directories(displayserver_Wayland + PRIVATE + src + ${DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS} + ${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_INCLUDE_DIRS} +) + +find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner) +pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15) +pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir) + +macro(wayland_generate protocol_file output_file) + add_custom_command(OUTPUT "${output_file}.h" + COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h" + DEPENDS "${protocol_file}" + VERBATIM) + + add_custom_command(OUTPUT "${output_file}.c" + COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c" + DEPENDS "${protocol_file}" + VERBATIM) + + target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c") +endmacro() + +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland") +include_directories("${CMAKE_BINARY_DIR}/wayland") +wayland_generate( + "${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml" + "${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol") +wayland_generate( + "${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml" + "${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol") +wayland_generate( + "${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml" + "${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol") + diff --git a/client/displayservers/Wayland/wayland.c b/client/displayservers/Wayland/wayland.c new file mode 100644 index 00000000..fe5f37d7 --- /dev/null +++ b/client/displayservers/Wayland/wayland.c @@ -0,0 +1,715 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2021 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 +#include +#include + +#include +#include + +#include "app.h" +#include "common/debug.h" + +#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" +#include "wayland-pointer-constraints-unstable-v1-client-protocol.h" +#include "wayland-relative-pointer-unstable-v1-client-protocol.h" + +struct WaylandDSState +{ + bool pointerGrabbed; + bool keyboardGrabbed; + + struct wl_display * display; + struct wl_surface * surface; + struct wl_registry * registry; + struct wl_seat * seat; + + struct wl_data_device_manager * dataDeviceManager; + struct wl_data_device * dataDevice; + + uint32_t capabilities; + + struct wl_keyboard * keyboard; + struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager; + struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor; + uint32_t keyboardEnterSerial; + + struct wl_pointer * pointer; + struct zwp_relative_pointer_manager_v1 * relativePointerManager; + struct zwp_pointer_constraints_v1 * pointerConstraints; + struct zwp_relative_pointer_v1 * relativePointer; + struct zwp_confined_pointer_v1 * confinedPointer; +}; + +struct WCBTransfer +{ + void * data; + size_t size; + const char ** mimetypes; +}; + +struct WCBState +{ + enum LG_ClipboardData stashedType; + char * stashedMimetype; + uint8_t * stashedContents; + ssize_t stashedSize; + bool isReceiving; + bool isSelfCopy; + + bool haveRequest; + LG_ClipboardData type; +}; + +static struct WaylandDSState wm; +static struct WCBState wcb; + +// Wayland support. + +// Registry-handling listeners. + +static void registryGlobalHandler(void * data, struct wl_registry * registry, + uint32_t name, const char * interface, uint32_t version) +{ + if (!strcmp(interface, wl_seat_interface.name) && !wm.seat) + wm.seat = wl_registry_bind(wm.registry, name, &wl_seat_interface, 1); + else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) + wm.relativePointerManager = wl_registry_bind(wm.registry, name, + &zwp_relative_pointer_manager_v1_interface, 1); + else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name)) + wm.pointerConstraints = wl_registry_bind(wm.registry, name, + &zwp_pointer_constraints_v1_interface, 1); + else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name)) + wm.keyboardInhibitManager = wl_registry_bind(wm.registry, name, + &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); + else if (!strcmp(interface, wl_data_device_manager_interface.name)) + wm.dataDeviceManager = wl_registry_bind(wm.registry, name, + &wl_data_device_manager_interface, 3); +} + +static void registryGlobalRemoveHandler(void * data, + struct wl_registry * registry, uint32_t name) +{ + // Do nothing. +} + +static const struct wl_registry_listener registryListener = { + .global = registryGlobalHandler, + .global_remove = registryGlobalRemoveHandler, +}; + +// Mouse-handling listeners. + +static void pointerMotionHandler(void * data, struct wl_pointer * pointer, + uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW) +{ + int sx = wl_fixed_to_int(sxW); + int sy = wl_fixed_to_int(syW); + app_handleMouseNormal(sx, sy); +} + +static void pointerEnterHandler(void * data, struct wl_pointer * pointer, + uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, + wl_fixed_t syW) +{ + int sx = wl_fixed_to_int(sxW); + int sy = wl_fixed_to_int(syW); + app_handleMouseNormal(sx, sy); +} + +static void pointerLeaveHandler(void * data, struct wl_pointer * pointer, + uint32_t serial, struct wl_surface * surface) +{ + // Do nothing. +} + +static void pointerAxisHandler(void * data, struct wl_pointer * pointer, + uint32_t serial, uint32_t axis, wl_fixed_t value) +{ + // Do nothing. +} + +static void pointerButtonHandler(void *data, struct wl_pointer *pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW) +{ + // Do nothing. +} + +static const struct wl_pointer_listener pointerListener = { + .enter = pointerEnterHandler, + .leave = pointerLeaveHandler, + .motion = pointerMotionHandler, + .button = pointerButtonHandler, + .axis = pointerAxisHandler, +}; + +// Keyboard-handling listeners. + +static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard, + uint32_t format, int fd, uint32_t size) +{ + close(fd); +} + +static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard, + uint32_t serial, struct wl_surface * surface, struct wl_array * keys) +{ + wm.keyboardEnterSerial = serial; +} + +static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard, + uint32_t serial, struct wl_surface * surface) +{ + // Do nothing. +} + +static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + // Do nothing. +} + +static void keyboardModifiersHandler(void * data, + struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed, + uint32_t modsLatched, uint32_t modsLocked, uint32_t group) +{ + // Do nothing. +} + +static const struct wl_keyboard_listener keyboardListener = { + .keymap = keyboardKeymapHandler, + .enter = keyboardEnterHandler, + .leave = keyboardLeaveHandler, + .key = keyboardKeyHandler, + .modifiers = keyboardModifiersHandler, +}; + +// Seat-handling listeners. + +static void handlePointerCapability(uint32_t capabilities) +{ + bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; + if (!hasPointer && wm.pointer) + { + wl_pointer_destroy(wm.pointer); + wm.pointer = NULL; + } + else if (hasPointer && !wm.pointer) + { + wm.pointer = wl_seat_get_pointer(wm.seat); + wl_pointer_add_listener(wm.pointer, &pointerListener, NULL); + } +} + +static void handleKeyboardCapability(uint32_t capabilities) +{ + bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; + if (!hasKeyboard && wm.keyboard) + { + wl_keyboard_destroy(wm.keyboard); + wm.keyboard = NULL; + } + else if (hasKeyboard && !wm.keyboard) + { + wm.keyboard = wl_seat_get_keyboard(wm.seat); + wl_keyboard_add_listener(wm.keyboard, &keyboardListener, NULL); + } +} + +static void seatCapabilitiesHandler(void * data, struct wl_seat * seat, + uint32_t capabilities) +{ + wm.capabilities = capabilities; + handlePointerCapability(capabilities); + handleKeyboardCapability(capabilities); +} + +static void seatNameHandler(void * data, struct wl_seat * seat, + const char * name) +{ + // Do nothing. +} + +static const struct wl_seat_listener seatListener = { + .capabilities = seatCapabilitiesHandler, + .name = seatNameHandler, +}; + +static void waylandInit(SDL_SysWMinfo * info) +{ + memset(&wm, 0, sizeof(wm)); + + wm.display = info->info.wl.display; + wm.surface = info->info.wl.surface; + wm.registry = wl_display_get_registry(wm.display); + + wl_registry_add_listener(wm.registry, ®istryListener, NULL); + wl_display_roundtrip(wm.display); + + wl_seat_add_listener(wm.seat, &seatListener, NULL); + wl_display_roundtrip(wm.display); + + wm.dataDevice = wl_data_device_manager_get_data_device( + wm.dataDeviceManager, wm.seat); +} + +static void waylandStartup(void) +{ +} + +static void relativePointerMotionHandler(void * data, + struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo, + wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW, + wl_fixed_t dyUnaccelW) +{ + double dxUnaccel = wl_fixed_to_double(dxUnaccelW); + double dyUnaccel = wl_fixed_to_double(dyUnaccelW); + app_handleMouseGrabbed(dxUnaccel, dyUnaccel); +} + +static const struct zwp_relative_pointer_v1_listener relativePointerListener = { + .relative_motion = relativePointerMotionHandler, +}; + +static void waylandGrabPointer(void) +{ + if (!wm.relativePointer) + { + wm.relativePointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + wm.relativePointerManager, wm.pointer); + zwp_relative_pointer_v1_add_listener(wm.relativePointer, + &relativePointerListener, NULL); + } + + if (!wm.confinedPointer) + { + wm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( + wm.pointerConstraints, wm.surface, wm.pointer, NULL, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + } +} + +static void waylandUngrabPointer(void) +{ + if (wm.relativePointer) + { + zwp_relative_pointer_v1_destroy(wm.relativePointer); + wm.relativePointer = NULL; + } + + if (wm.confinedPointer) + { + zwp_confined_pointer_v1_destroy(wm.confinedPointer); + wm.confinedPointer = NULL; + } +} + +static void waylandGrabKeyboard(void) +{ + if (wm.keyboardInhibitManager && !wm.keyboardInhibitor) + { + wm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts( + wm.keyboardInhibitManager, wm.surface, wm.seat); + } +} + +static void waylandUngrabKeyboard(void) +{ + if (wm.keyboardInhibitor) + { + zwp_keyboard_shortcuts_inhibitor_v1_destroy(wm.keyboardInhibitor); + wm.keyboardInhibitor = NULL; + } +} + +static void waylandFree(void) +{ + waylandUngrabPointer(); + + // TODO: these also need to be freed, but are currently owned by SDL. + // wl_display_destroy(wm.display); + // wl_surface_destroy(wm.surface); + wl_pointer_destroy(wm.pointer); + wl_seat_destroy(wm.seat); + wl_registry_destroy(wm.registry); +} + +static bool waylandEventFilter(SDL_Event * event) +{ + return false; +} + +//asdasd + +static const char * textMimetypes[] = +{ + "text/plain", + "text/plain;charset=utf-8", + "TEXT", + "STRING", + "UTF8_STRING", + NULL, +}; + +static const char * pngMimetypes[] = +{ + "image/png", + NULL, +}; + +static const char * bmpMimetypes[] = +{ + "image/bmp", + "image/x-bmp", + "image/x-MS-bmp", + "image/x-win-bitmap", + NULL, +}; + +static const char * tiffMimetypes[] = +{ + "image/tiff", + NULL, +}; + +static const char * jpegMimetypes[] = +{ + "image/jpeg", + NULL, +}; + +static const char ** cbTypeToMimetypes(enum LG_ClipboardData type) +{ + switch (type) + { + case LG_CLIPBOARD_DATA_TEXT: + return textMimetypes; + case LG_CLIPBOARD_DATA_PNG: + return pngMimetypes; + case LG_CLIPBOARD_DATA_BMP: + return bmpMimetypes; + case LG_CLIPBOARD_DATA_TIFF: + return tiffMimetypes; + case LG_CLIPBOARD_DATA_JPEG: + return jpegMimetypes; + default: + DEBUG_ERROR("invalid clipboard type"); + abort(); + } +} + +static bool containsMimetype(const char ** mimetypes, + const char * needle) +{ + for (const char ** mimetype = mimetypes; *mimetype; mimetype++) + if (!strcmp(needle, *mimetype)) + return true; + + return false; +} + +static bool mimetypeEndswith(const char * mimetype, const char * what) +{ + size_t mimetypeLen = strlen(mimetype); + size_t whatLen = strlen(what); + + if (mimetypeLen < whatLen) + return false; + + return !strcmp(mimetype + mimetypeLen - whatLen, what); +} + +static bool isTextMimetype(const char * mimetype) +{ + if (containsMimetype(textMimetypes, mimetype)) + return true; + + char * text = "text/"; + if (!strncmp(mimetype, text, strlen(text))) + return true; + + if (mimetypeEndswith(mimetype, "script") || + mimetypeEndswith(mimetype, "xml") || + mimetypeEndswith(mimetype, "yaml")) + return true; + + if (strstr(mimetype, "json")) + return true; + + return false; +} + +static enum LG_ClipboardData mimetypeToCbType(const char * mimetype) +{ + if (isTextMimetype(mimetype)) + return LG_CLIPBOARD_DATA_TEXT; + + if (containsMimetype(pngMimetypes, mimetype)) + return LG_CLIPBOARD_DATA_PNG; + + if (containsMimetype(bmpMimetypes, mimetype)) + return LG_CLIPBOARD_DATA_BMP; + + if (containsMimetype(tiffMimetypes, mimetype)) + return LG_CLIPBOARD_DATA_TIFF; + + if (containsMimetype(jpegMimetypes, mimetype)) + return LG_CLIPBOARD_DATA_JPEG; + + return LG_CLIPBOARD_DATA_NONE; +} + +// Destination client handlers. + +static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer, + const char * mimetype) +{ + enum LG_ClipboardData type = mimetypeToCbType(mimetype); + // Oftentimes we'll get text/html alongside text/png, but would prefer to send + // image/png. In general, prefer images over text content. + if (type != LG_CLIPBOARD_DATA_NONE && + (wcb.stashedType == LG_CLIPBOARD_DATA_NONE || + wcb.stashedType == LG_CLIPBOARD_DATA_TEXT)) + { + wcb.stashedType = type; + if (wcb.stashedMimetype) + free(wcb.stashedMimetype); + wcb.stashedMimetype = strdup(mimetype); + } +} + +static void dataOfferHandleSourceActions(void * data, + struct wl_data_offer * offer, uint32_t sourceActions) +{ + // Do nothing. +} + +static void dataOfferHandleAction(void * data, struct wl_data_offer * offer, + uint32_t dndAction) +{ + // Do nothing. +} + +static const struct wl_data_offer_listener dataOfferListener = { + .offer = dataOfferHandleOffer, + .source_actions = dataOfferHandleSourceActions, + .action = dataOfferHandleAction, +}; + +static void dataDeviceHandleDataOffer(void * data, + struct wl_data_device * dataDevice, struct wl_data_offer * offer) +{ + wcb.stashedType = LG_CLIPBOARD_DATA_NONE; + wl_data_offer_add_listener(offer, &dataOfferListener, NULL); +} + +static void dataDeviceHandleSelection(void * data, + struct wl_data_device * dataDevice, struct wl_data_offer * offer) +{ + if (wcb.stashedType == LG_CLIPBOARD_DATA_NONE || !offer) + return; + + int fds[2]; + if (pipe(fds) < 0) + { + DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno)); + abort(); + } + + wcb.isReceiving = true; + wcb.isSelfCopy = false; + wl_data_offer_receive(offer, wcb.stashedMimetype, fds[1]); + close(fds[1]); + free(wcb.stashedMimetype); + wcb.stashedMimetype = NULL; + + wl_display_roundtrip(wm.display); + + if (wcb.stashedContents) + { + free(wcb.stashedContents); + wcb.stashedContents = NULL; + } + + size_t size = 4096, numRead = 0; + uint8_t * buf = (uint8_t *) malloc(size); + while (true) + { + ssize_t result = read(fds[0], buf + numRead, size - numRead); + if (result < 0) + { + DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno)); + abort(); + } + + if (result == 0) + { + buf[numRead] = 0; + break; + } + + numRead += result; + if (numRead >= size) + { + size *= 2; + void * nbuf = realloc(buf, size); + if (!nbuf) { + DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno)); + abort(); + } + + buf = nbuf; + } + } + + wcb.stashedSize = numRead; + wcb.stashedContents = buf; + wcb.isReceiving = false; + + close(fds[0]); + wl_data_offer_destroy(offer); + + if (!wcb.isSelfCopy) + app_clipboardNotify(wcb.stashedType, 0); +} + +static const struct wl_data_device_listener dataDeviceListener = { + .data_offer = dataDeviceHandleDataOffer, + .selection = dataDeviceHandleSelection, +}; + +static void waylandCBRequest(LG_ClipboardData type) +{ + // We only notified once, so it must be this. + assert(type == wcb.stashedType); + app_clipboardData(wcb.stashedType, wcb.stashedContents, wcb.stashedSize); +} + +static bool waylandCBInit(void) +{ + memset(&wcb, 0, sizeof(wcb)); + + wcb.stashedType = LG_CLIPBOARD_DATA_NONE; + wl_data_device_add_listener(wm.dataDevice, &dataDeviceListener, NULL); + + return true; +} + +static void dataSourceHandleSend(void * data, struct wl_data_source * source, + const char * mimetype, int fd) +{ + struct WCBTransfer * transfer = (struct WCBTransfer *) data; + if (wcb.isReceiving) + wcb.isSelfCopy = true; + else if (containsMimetype(transfer->mimetypes, mimetype)) + { + // Consider making this do non-blocking sends to not stall the Wayland + // event loop if it becomes a problem. This is "fine" in the sense that + // wl-copy also stalls like this, but it's not necessary. + fcntl(fd, F_SETFL, 0); + + size_t pos = 0; + while (pos < transfer->size) + { + ssize_t written = write(fd, transfer->data + pos, transfer->size - pos); + if (written < 0) + { + if (errno != EPIPE) + DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno)); + goto error; + } + + pos += written; + } + } + +error: + close(fd); +} + +static void dataSourceHandleCancelled(void * data, + struct wl_data_source * source) +{ + struct WCBTransfer * transfer = (struct WCBTransfer *) data; + free(transfer->data); + free(transfer); + wl_data_source_destroy(source); +} + +static const struct wl_data_source_listener dataSourceListener = { + .send = dataSourceHandleSend, + .cancelled = dataSourceHandleCancelled, +}; + +static void waylandCBReplyFn(void * opaque, LG_ClipboardData type, + uint8_t * data, uint32_t size) +{ + struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer)); + void * dataCopy = malloc(size); + memcpy(dataCopy, data, size); + *transfer = (struct WCBTransfer) { + .data = dataCopy, + .size = size, + .mimetypes = cbTypeToMimetypes(type), + }; + + struct wl_data_source * source = + wl_data_device_manager_create_data_source(wm.dataDeviceManager); + wl_data_source_add_listener(source, &dataSourceListener, transfer); + for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++) + wl_data_source_offer(source, *mimetype); + + wl_data_device_set_selection(wm.dataDevice, source, + wm.keyboardEnterSerial); +} + +static void waylandCBNotice(LG_ClipboardData type) +{ + wcb.haveRequest = true; + wcb.type = type; + app_clipboardRequest(waylandCBReplyFn, NULL); +} + +static void waylandCBRelease(void) +{ + wcb.haveRequest = false; +} + +struct LG_DisplayServerOps LGDS_Wayland = +{ + .subsystem = SDL_SYSWM_WAYLAND, + .init = waylandInit, + .startup = waylandStartup, + .free = waylandFree, + .eventFilter = waylandEventFilter, + .grabPointer = waylandGrabPointer, + .ungrabPointer = waylandUngrabPointer, + .grabKeyboard = waylandGrabKeyboard, + .ungrabKeyboard = waylandUngrabKeyboard, + .warpMouse = NULL, // fallback to SDL + + .cbInit = waylandCBInit, + .cbNotice = waylandCBNotice, + .cbRelease = waylandCBRelease, + .cbRequest = waylandCBRequest +}; diff --git a/client/displayservers/X11/CMakeLists.txt b/client/displayservers/X11/CMakeLists.txt new file mode 100644 index 00000000..bbebecb3 --- /dev/null +++ b/client/displayservers/X11/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.0) +project(displayserver_X11 LANGUAGES C) + +find_package(PkgConfig) +pkg_check_modules(DISPLAYSERVER_X11_PKGCONFIG REQUIRED + x11 + xfixes +) + +pkg_check_modules(DISPLAYSERVER_X11_OPT_PKGCONFIG + xi +) + +add_library(displayserver_X11 STATIC + x11.c +) + +target_link_libraries(displayserver_X11 + ${DISPLAYSERVER_X11_PKGCONFIG_LIBRARIES} + ${DISPLAYSERVER_X11_OPT_PKGCONFIG_LIBRARIES} + lg_common +) + +target_include_directories(displayserver_X11 + PRIVATE + src + ${DISPLAYSERVER_X11_PKGCONFIG_INCLUDE_DIRS} + ${DISPLAYSERVER_X11_OPT_PKGCONFIG_INCLUDE_DIRS} +) diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c new file mode 100644 index 00000000..6c53ecb1 --- /dev/null +++ b/client/displayservers/X11/x11.c @@ -0,0 +1,792 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2021 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/displayserver.h" + +#include +#include +#include +#include +#include +#include + +#if SDL_VIDEO_DRIVER_X11_XINPUT2 +#include +#endif + +#include + + +#include "app.h" +#include "common/debug.h" + +struct X11DSState +{ + Display * display; + Window window; + int xinputOp; + + bool pointerGrabbed; + bool keyboardGrabbed; + + // clipboard members + Atom aSelection; + Atom aCurSelection; + Atom aTargets; + Atom aSelData; + Atom aIncr; + Atom aTypes[LG_CLIPBOARD_DATA_NONE]; + LG_ClipboardData type; + bool haveRequest; + + bool incrStart; + unsigned int lowerBound; + + // XFixes vars + int eventBase; + int errorBase; +}; + +static const char * atomTypes[] = +{ + "UTF8_STRING", + "image/png", + "image/bmp", + "image/tiff", + "image/jpeg" +}; + +static struct X11DSState x11; + +// forwards +static void x11CBSelectionRequest(const XSelectionRequestEvent e); +static void x11CBSelectionClear(const XSelectionClearEvent e); +static void x11CBSelectionIncr(const XPropertyEvent e); +static void x11CBSelectionNotify(const XSelectionEvent e); +static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e); + +static void x11Init(SDL_SysWMinfo * info) +{ + memset(&x11, 0, sizeof(x11)); + x11.display = info->info.x11.display; + x11.window = info->info.x11.window; + + int event, error; + + // enable X11 events to work around SDL2 bugs + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + +#if SDL_VIDEO_DRIVER_X11_XINPUT2 + XQueryExtension(x11.display, "XInputExtension", &x11.xinputOp, &event, &error); +#endif + + Atom NETWM_BYPASS_COMPOSITOR = XInternAtom(x11.display, + "NETWM_BYPASS_COMPOSITOR", False); + + unsigned long value = 1; + XChangeProperty( + x11.display, + x11.window, + NETWM_BYPASS_COMPOSITOR, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char *)&value, + 1 + ); +} + +static void x11Startup(void) +{ +} + +static void x11Shutdown(void) +{ +} + +static void x11Free(void) +{ +} + +static bool x11GetProp(LG_DSProperty prop, void *ret) +{ + if (prop != LG_DS_MAX_MULTISAMPLE) + return false; + + Display * dpy = XOpenDisplay(NULL); + if (!dpy) + return false; + + XVisualInfo queryTemplate; + queryTemplate.screen = 0; + + int visualCount; + int maxSamples = -1; + XVisualInfo * visuals = XGetVisualInfo(dpy, VisualScreenMask, + &queryTemplate, &visualCount); + + for (int i = 0; i < visualCount; i++) + { + XVisualInfo * visual = &visuals[i]; + + int res, supportsGL; + // Some GLX visuals do not use GL, and these must be ignored in our search. + if ((res = glXGetConfig(dpy, visual, GLX_USE_GL, &supportsGL)) != 0 || !supportsGL) + continue; + + int sampleBuffers, samples; + if ((res = glXGetConfig(dpy, visual, GLX_SAMPLE_BUFFERS, &sampleBuffers)) != 0) + continue; + + // Will be 1 if this visual supports multisampling + if (sampleBuffers != 1) + continue; + + if ((res = glXGetConfig(dpy, visual, GLX_SAMPLES, &samples)) != 0) + continue; + + // Track the largest number of samples supported + if (samples > maxSamples) + maxSamples = samples; + } + + XCloseDisplay(dpy); + + *(int*)ret = maxSamples; + return true; +} + +static bool x11EventFilter(SDL_Event * event) +{ + /* prevent the default processing for the following events */ + switch(event->type) + { + case SDL_WINDOWEVENT: + switch(event->window.event) + { + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_CLOSE: + return true; + } + return false; + +#if SDL_VIDEO_DRIVER_X11_XINPUT2 + case SDL_MOUSEMOTION: + return true; +#endif + } + + if (event->type != SDL_SYSWMEVENT) + return false; + + XEvent xe = event->syswm.msg->msg.x11.event; + switch(xe.type) + { + case ConfigureNotify: + { + int x, y; + + /* the window may have been re-parented so we need to translate to + * ensure we get the screen top left position of the window */ + Window child; + XTranslateCoordinates( + x11.display, + x11.window, + DefaultRootWindow(x11.display), + 0, 0, + &x, + &y, + &child); + + app_updateWindowPos(x, y); + app_handleResizeEvent(xe.xconfigure.width, xe.xconfigure.height); + return true; + } + + case EnterNotify: + { + int x, y; + Window child; + XTranslateCoordinates( + x11.display, + DefaultRootWindow(x11.display), + x11.window, + xe.xcrossing.x_root, xe.xcrossing.y_root, + &x, &y, + &child); + + app_updateCursorPos(x, y); + app_handleWindowEnter(); + return true; + } + + case LeaveNotify: + { + if (xe.xcrossing.mode != NotifyNormal) + return true; + + int x, y; + Window child; + XTranslateCoordinates(x11.display, + DefaultRootWindow(x11.display), + x11.window, + xe.xcrossing.x_root, xe.xcrossing.y_root, + &x, &y, + &child); + + app_updateCursorPos(x, y); + app_handleWindowLeave(); + return true; + } + +#if SDL_VIDEO_DRIVER_X11_XINPUT2 + /* support movements via XInput2 */ + case GenericEvent: + { + XGenericEventCookie *cookie = (XGenericEventCookie*)&xe.xcookie; + if (cookie->extension != x11.xinputOp) + return false; + + if (!app_inputEnabled()) + return true; + + switch(cookie->evtype) + { + case XI_Motion: + { + if (!app_cursorInWindow()) + return true; + + XIDeviceEvent *device = cookie->data; + app_updateCursorPos(device->event_x, device->event_y); + return true; + } + + case XI_RawMotion: + { + if (!app_cursorInWindow()) + return true; + + XIRawEvent *raw = cookie->data; + double raw_axis[2]; + double axis[2]; + + /* select the active validators for the X & Y axis */ + double *valuator = raw->valuators.values; + double *r_value = raw->raw_values; + int count = 0; + for(int i = 0; i < raw->valuators.mask_len * 8; ++i) + { + if (XIMaskIsSet(raw->valuators.mask, i)) + { + raw_axis[count] = *r_value; + axis [count] = *valuator; + ++count; + + if (count == 2) + break; + + ++valuator; + ++r_value; + } + } + + /* filter out scroll wheel and other events */ + if (count < 2) + return true; + + /* filter out duplicate events */ + static Time prev_time = 0; + static double prev_axis[2] = {0}; + if (raw->time == prev_time && + axis[0] == prev_axis[0] && + axis[1] == prev_axis[1]) + return true; + + prev_time = raw->time; + prev_axis[0] = axis[0]; + prev_axis[1] = axis[1]; + + if (app_cursorIsGrabbed()) + { + if (app_cursorWantsRaw()) + app_handleMouseGrabbed(raw_axis[0], raw_axis[1]); + else + app_handleMouseGrabbed(axis[0], axis[1]); + } + else + if (app_cursorInWindow()) + app_handleMouseNormal(axis[0], axis[1]); + + return true; + } + } + + return false; + } +#endif + + // clipboard events + case SelectionRequest: + x11CBSelectionRequest(xe.xselectionrequest); + return true; + + case SelectionClear: + x11CBSelectionClear(xe.xselectionclear); + return true; + + case SelectionNotify: + x11CBSelectionNotify(xe.xselection); + return true; + + case PropertyNotify: + if (xe.xproperty.display != x11.display || + xe.xproperty.window != x11.window || + xe.xproperty.atom != x11.aSelData || + xe.xproperty.state != PropertyNewValue || + x11.lowerBound == 0) + return false; + + x11CBSelectionIncr(xe.xproperty); + return true; + + default: + if (xe.type == x11.eventBase + XFixesSelectionNotify) + { + XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&xe; + x11CBXFixesSelectionNotify(*sne); + return true; + } + return false; + } +} + +static void x11GrabPointer(void) +{ + if (x11.pointerGrabbed) + return; + + XGrabPointer( + x11.display, + x11.window, + true, + None, + GrabModeAsync, + GrabModeAsync, + x11.window, + None, + CurrentTime); + + x11.pointerGrabbed = true; +} + +static void x11UngrabPointer(void) +{ + if (!x11.pointerGrabbed) + return; + + XUngrabPointer(x11.display, CurrentTime); + x11.pointerGrabbed = false; +} + +static void x11GrabKeyboard(void) +{ + if (x11.keyboardGrabbed) + return; + + XGrabKeyboard( + x11.display, + x11.window, + true, + GrabModeAsync, + GrabModeAsync, + CurrentTime); + + x11.keyboardGrabbed = true; +} + +static void x11UngrabKeyboard(void) +{ + if (!x11.keyboardGrabbed) + return; + + XUngrabKeyboard(x11.display, CurrentTime); + x11.keyboardGrabbed = false; +} + +static void x11WarpMouse(int x, int y, bool exiting) +{ + XWarpPointer( + x11.display, + None, + x11.window, + 0, 0, 0, 0, + x, y); + + XSync(x11.display, False); +} + +static bool x11CBInit() +{ + x11.aSelection = XInternAtom(x11.display, "CLIPBOARD" , False); + x11.aTargets = XInternAtom(x11.display, "TARGETS" , False); + x11.aSelData = XInternAtom(x11.display, "SEL_DATA" , False); + x11.aIncr = XInternAtom(x11.display, "INCR" , False); + x11.aCurSelection = BadValue; + + for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) + { + x11.aTypes[i] = XInternAtom(x11.display, atomTypes[i], False); + if (x11.aTypes[i] == BadAlloc || x11.aTypes[i] == BadValue) + { + DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]); + return false; + } + } + + // use xfixes to get clipboard change notifications + if (!XFixesQueryExtension(x11.display, &x11.eventBase, &x11.errorBase)) + { + DEBUG_ERROR("failed to initialize xfixes"); + return false; + } + + XFixesSelectSelectionInput(x11.display, x11.window, + XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask); + XFixesSelectSelectionInput(x11.display, x11.window, + x11.aSelection, XFixesSetSelectionOwnerNotifyMask); + + return true; +} + +static void x11CBReplyFn(void * opaque, LG_ClipboardData type, + uint8_t * data, uint32_t size) +{ + XEvent *s = (XEvent *)opaque; + + XChangeProperty( + x11.display , + s->xselection.requestor, + s->xselection.property , + s->xselection.target , + 8, + PropModeReplace, + data, + size); + + XSendEvent(x11.display, s->xselection.requestor, 0, 0, s); + XFlush(x11.display); + free(s); +} + +static void x11CBSelectionRequest(const XSelectionRequestEvent e) +{ + XEvent * s = (XEvent *)malloc(sizeof(XEvent)); + s->xselection.type = SelectionNotify; + s->xselection.requestor = e.requestor; + s->xselection.selection = e.selection; + s->xselection.target = e.target; + s->xselection.property = e.property; + s->xselection.time = e.time; + + if (!x11.haveRequest) + goto nodata; + + // target list requested + if (e.target == x11.aTargets) + { + Atom targets[2]; + targets[0] = x11.aTargets; + targets[1] = x11.aTypes[x11.type]; + + XChangeProperty( + e.display, + e.requestor, + e.property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)targets, + sizeof(targets) / sizeof(Atom)); + + goto send; + } + + // look to see if we can satisfy the data type + for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) + if (x11.aTypes[i] == e.target && x11.type == i) + { + // request the data + app_clipboardRequest(x11CBReplyFn, s); + return; + } + +nodata: + // report no data + s->xselection.property = None; + +send: + XSendEvent(x11.display, e.requestor, 0, 0, s); + XFlush(x11.display); + free(s); +} + +static void x11CBSelectionClear(const XSelectionClearEvent e) +{ + if (e.selection != XA_PRIMARY && e.selection != x11.aSelection) + return; + + x11.aCurSelection = BadValue; + app_clipboardRelease(); + return; +} + +static void x11CBSelectionIncr(const XPropertyEvent e) +{ + Atom type; + int format; + unsigned long itemCount, after; + unsigned char *data; + + if (XGetWindowProperty( + e.display, + e.window, + e.atom, + 0, ~0L, // start and length + True, // delete the property + x11.aIncr, + &type, + &format, + &itemCount, + &after, + &data) != Success) + { + DEBUG_INFO("GetProp Failed"); + app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0); + goto out; + } + + LG_ClipboardData dataType; + for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType) + if (x11.aTypes[dataType] == type) + break; + + if (dataType == LG_CLIPBOARD_DATA_NONE) + { + DEBUG_WARN("clipboard data (%s) not in a supported format", + XGetAtomName(x11.display, type)); + + x11.lowerBound = 0; + app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0); + goto out; + } + + if (x11.incrStart) + { + app_clipboardNotify(dataType, x11.lowerBound); + x11.incrStart = false; + } + + XFree(data); + data = NULL; + + if (XGetWindowProperty( + e.display, + e.window, + e.atom, + 0, ~0L, // start and length + True, // delete the property + type, + &type, + &format, + &itemCount, + &after, + &data) != Success) + { + DEBUG_ERROR("XGetWindowProperty Failed"); + app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0); + goto out; + } + + app_clipboardData(dataType, data, itemCount); + x11.lowerBound -= itemCount; + +out: + if (data) + XFree(data); +} + +static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e) +{ + // check if the selection is valid and it isn't ourself + if ((e.selection != XA_PRIMARY && e.selection != x11.aSelection) || + e.owner == x11.window || e.owner == 0) + { + return; + } + + // remember which selection we are working with + x11.aCurSelection = e.selection; + XConvertSelection( + x11.display, + e.selection, + x11.aTargets, + x11.aTargets, + x11.window, + CurrentTime); + + return; +} + +static void x11CBSelectionNotify(const XSelectionEvent e) +{ + if (e.property == None) + return; + + Atom type; + int format; + unsigned long itemCount, after; + unsigned char *data; + + if (XGetWindowProperty( + e.display, + e.requestor, + e.property, + 0, ~0L, // start and length + True , // delete the property + AnyPropertyType, + &type, + &format, + &itemCount, + &after, + &data) != Success) + { + app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0); + goto out; + } + + if (type == x11.aIncr) + { + x11.incrStart = true; + x11.lowerBound = *(unsigned int *)data; + goto out; + } + + // the target list + if (e.property == x11.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) + goto out; + + // 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 (x11.aTypes[n] == targets[i]) + { + // we have a match, so send the notification + app_clipboardNotify(n, 0); + goto out; + } + } + + // no matches + app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0); + goto out; + } + + if (e.property == x11.aSelData) + { + LG_ClipboardData dataType; + for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType) + if (x11.aTypes[dataType] == type) + break; + + if (dataType == LG_CLIPBOARD_DATA_NONE) + { + DEBUG_WARN("clipboard data (%s) not in a supported format", + XGetAtomName(x11.display, type)); + goto out; + } + + app_clipboardData(dataType, data, itemCount); + goto out; + } + +out: + if (data) + XFree(data); +} + +static void x11CBNotice(LG_ClipboardData type) +{ + x11.haveRequest = true; + x11.type = type; + XSetSelectionOwner(x11.display, XA_PRIMARY , x11.window, CurrentTime); + XSetSelectionOwner(x11.display, x11.aSelection, x11.window, CurrentTime); + XFlush(x11.display); +} + +static void x11CBRelease(void) +{ + x11.haveRequest = false; + XSetSelectionOwner(x11.display, XA_PRIMARY , None, CurrentTime); + XSetSelectionOwner(x11.display, x11.aSelection, None, CurrentTime); + XFlush(x11.display); +} + +static void x11CBRequest(LG_ClipboardData type) +{ + if (x11.aCurSelection == BadValue) + return; + + XConvertSelection( + x11.display, + x11.aCurSelection, + x11.aTypes[type], + x11.aSelData, + x11.window, + CurrentTime); +} + +struct LG_DisplayServerOps LGDS_X11 = +{ + .subsystem = SDL_SYSWM_X11, + .init = x11Init, + .startup = x11Startup, + .shutdown = x11Shutdown, + .free = x11Free, + .getProp = x11GetProp, + .eventFilter = x11EventFilter, + .grabPointer = x11GrabPointer, + .ungrabPointer = x11UngrabPointer, + .grabKeyboard = x11GrabKeyboard, + .ungrabKeyboard = x11UngrabKeyboard, + .warpMouse = x11WarpMouse, + + .cbInit = x11CBInit, + .cbNotice = x11CBNotice, + .cbRelease = x11CBRelease, + .cbRequest = x11CBRequest +}; diff --git a/client/include/app.h b/client/include/app.h new file mode 100644 index 00000000..f60b4a0e --- /dev/null +++ b/client/include/app.h @@ -0,0 +1,43 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2020 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 "interface/displayserver.h" + + +SDL_Window * app_getWindow(void); + +bool app_getProp(LG_DSProperty prop, void * ret); +bool app_inputEnabled(void); +bool app_cursorIsGrabbed(void); +bool app_cursorWantsRaw(void); +bool app_cursorInWindow(void); +void app_updateCursorPos(double x, double y); +void app_updateWindowPos(int x, int y); +void app_handleResizeEvent(int w, int h); +void app_handleMouseGrabbed(double ex, double ey); +void app_handleMouseNormal(double ex, double ey); +void app_handleWindowEnter(); +void app_handleWindowLeave(); + +void app_clipboardRelease(void); +void app_clipboardNotify(const LG_ClipboardData type, size_t size); +void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size); +void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque); diff --git a/client/include/interface/clipboard.h b/client/include/interface/clipboard.h deleted file mode 100644 index 78250567..00000000 --- a/client/include/interface/clipboard.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -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, size_t size); -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; diff --git a/client/include/interface/displayserver.h b/client/include/interface/displayserver.h new file mode 100644 index 00000000..28b5abde --- /dev/null +++ b/client/include/interface/displayserver.h @@ -0,0 +1,86 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2017-2021 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 +*/ + +#ifndef _H_I_DISPLAYSERVER_ +#define _H_I_DISPLAYSERVER_ + +#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 enum LG_DSProperty +{ + LG_DS_MAX_MULTISAMPLE // data type is `int` +} +LG_DSProperty; + +typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type, + uint8_t * data, uint32_t size); + +struct LG_DisplayServerOps +{ + const SDL_SYSWM_TYPE subsystem; + + /* early initialization */ + void (*init)(SDL_SysWMinfo * info); + + /* called at startup after window creation, renderer and/or SPICE is ready */ + void (*startup)(); + + /* called just before final window destruction, before final free */ + void (*shutdown)(); + + /* final free */ + void (*free)(); + + /* return a system specific property, returns false if unsupported or failure */ + bool (*getProp)(LG_DSProperty prop, void * ret); + + /* event filter, return true if the event has been handled */ + bool (*eventFilter)(SDL_Event * event); + + /* dm specific cursor implementations */ + void (*grabPointer)(); + void (*ungrabPointer)(); + void (*grabKeyboard)(); + void (*ungrabKeyboard)(); + + //exiting = true if the warp is to leave the window + void (*warpMouse)(int x, int y, bool exiting); + + /* clipboard support */ + bool (* cbInit)(void); + void (* cbNotice)(LG_ClipboardData type); + void (* cbRelease)(void); + void (* cbRequest)(LG_ClipboardData type); +}; + +#endif diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index 7ec1909d..d03dd08d 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -34,6 +34,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include #endif +#include "app.h" #include "model.h" #include "shader.h" #include "desktop.h" @@ -218,25 +219,6 @@ bool egl_initialize(void * opaque, Uint32 * sdlFlags) { struct Inst * this = (struct Inst *)opaque; DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off"); - - *sdlFlags = SDL_WINDOW_OPENGL; - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , this->opt.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("Multisampling enabled, max samples: %d", maxSamples); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples); - } - } - return true; } @@ -506,12 +488,24 @@ bool egl_render_startup(void * opaque, SDL_Window * window) return false; } + int maxSamples = 1; + if (option_get_bool("egl", "multisample")) + { + if (app_getProp(LG_DS_MAX_MULTISAMPLE, &maxSamples) && maxSamples > 1) + { + if (maxSamples > 4) + maxSamples = 4; + + DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples); + } + } + EGLint attr[] = { EGL_BUFFER_SIZE , 32, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_SAMPLE_BUFFERS , 1, - EGL_SAMPLES , 4, + EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0, + EGL_SAMPLES , maxSamples, EGL_NONE }; diff --git a/client/src/main.c b/client/src/main.c index 2a485e04..64a1420f 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -36,10 +36,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include #include -#if SDL_VIDEO_DRIVER_X11_XINPUT2 -#include -#endif - #include "common/debug.h" #include "common/crash.h" #include "common/KVMFR.h" @@ -54,7 +50,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include "utils.h" #include "kb.h" #include "ll.h" -#include "wm.h" #define RESIZE_TIMEOUT (10 * 1000) // 10ms @@ -71,7 +66,6 @@ static LGThread *t_cursor = NULL; static LGThread *t_frame = NULL; static SDL_Cursor *cursor = NULL; -static int g_XInputOp; // XInput Opcode static Uint32 e_SDLEvent; // our SDL event enum @@ -101,12 +95,43 @@ static void lgInit(void) g_cursor.guest.valid = false; } -static bool inputEnabled(void) +bool app_getProp(LG_DSProperty prop, void * ret) +{ + return g_state.ds->getProp(prop, ret); +} + +SDL_Window * app_getWindow(void) +{ + return g_state.window; +} + +bool app_inputEnabled(void) { return params.useSpiceInput && !g_state.ignoreInput && ((g_cursor.grab && params.captureInputOnly) || !params.captureInputOnly); } +bool app_cursorInWindow(void) +{ + return g_cursor.inWindow; +} + +bool app_cursorIsGrabbed(void) +{ + return g_cursor.grab; +} + +bool app_cursorWantsRaw(void) +{ + return params.rawMouse; +} + +void app_updateCursorPos(double x, double y) +{ + g_cursor.pos.x = x; + g_cursor.pos.y = y; +} + static void alignToGuest(void) { if (SDL_HasEvent(e_SDLEvent)) @@ -398,7 +423,7 @@ static int cursorThread(void * unused) g_cursor.guest.valid = true; // if the state just became valid - if (valid != true && inputEnabled()) + if (valid != true && app_inputEnabled()) alignToGuest(); } @@ -712,7 +737,7 @@ static SpiceDataType clipboard_type_to_spice_type(const LG_ClipboardData type) } } -void clipboardRelease(void) +void app_clipboardRelease(void) { if (!params.clipboardToVM) return; @@ -720,7 +745,7 @@ void clipboardRelease(void) spice_clipboard_release(); } -void clipboardNotify(const LG_ClipboardData type, size_t size) +void app_clipboardNotify(const LG_ClipboardData type, size_t size) { if (!params.clipboardToVM) return; @@ -741,7 +766,7 @@ void clipboardNotify(const LG_ClipboardData type, size_t size) spice_clipboard_data_start(g_state.cbType, size); } -void clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size) +void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size) { if (!params.clipboardToVM) return; @@ -759,7 +784,7 @@ void clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size) g_state.cbXfer -= size; } -void clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque) +void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque) { if (!params.clipboardToLocal) return; @@ -779,11 +804,11 @@ void spiceClipboardNotice(const SpiceDataType type) if (!params.clipboardToLocal) return; - if (!g_state.lgc || !g_state.lgc->notice) + if (!g_state.cbAvailable) return; g_state.cbType = type; - g_state.lgc->notice(clipboardRequest, spice_type_to_clipboard_type(type)); + g_state.ds->cbNotice(spice_type_to_clipboard_type(type)); } void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t size) @@ -822,8 +847,8 @@ void spiceClipboardRelease(void) if (!params.clipboardToLocal) return; - if (g_state.lgc && g_state.lgc->release) - g_state.lgc->release(); + if (g_state.cbAvailable) + g_state.ds->cbRelease(); } void spiceClipboardRequest(const SpiceDataType type) @@ -831,22 +856,22 @@ void spiceClipboardRequest(const SpiceDataType type) if (!params.clipboardToVM) return; - if (g_state.lgc && g_state.lgc->request) - g_state.lgc->request(spice_type_to_clipboard_type(type)); + if (g_state.cbAvailable) + g_state.ds->cbRequest(spice_type_to_clipboard_type(type)); } -static void warpMouse(int x, int y, bool disable) +static void warpMouse(int x, int y, bool exiting) { if (g_cursor.warpState == WARP_STATE_OFF) return; - if (disable) + if (exiting) g_cursor.warpState = WARP_STATE_OFF; if (g_cursor.pos.x == x && g_cursor.pos.y == y) return; - wmWarpMouse(x, y); + g_state.ds->warpMouse(x, y, exiting); } static bool isValidCursorLocation(int x, int y) @@ -883,7 +908,7 @@ static void cursorToInt(double ex, double ey, int *x, int *y) *y = (int)ey; } -void handleMouseGrabbed(double ex, double ey) +void app_handleMouseGrabbed(double ex, double ey) { int x, y; @@ -918,7 +943,7 @@ static void guestCurToLocal(struct DoublePoint *local) // warp support. Instead, we attempt a best-effort emulation which works with a // 1:1 mouse movement patch applied in the guest. For anything fancy, use // capture mode. -static void handleMouseWayland(void) +static void app_handleMouseWayland(void) { if (g_cursor.guest.dpiScale == 0) return; @@ -940,19 +965,19 @@ static void handleMouseWayland(void) DEBUG_ERROR("failed to send mouse motion message"); } -void handleMouseNormal(double ex, double ey) +void app_handleMouseNormal(double ex, double ey) { /* if we don't have the current cursor pos just send cursor movements */ if (!g_cursor.guest.valid) { if (g_cursor.grab) - handleMouseGrabbed(ex, ey); + app_handleMouseGrabbed(ex, ey); return; } if (g_state.wminfo.subsystem == SDL_SYSWM_WAYLAND) { - handleMouseWayland(); + app_handleMouseWayland(); return; } @@ -987,7 +1012,7 @@ void handleMouseNormal(double ex, double ey) g_cursor.redraw = true; g_cursor.warpState = WARP_STATE_ON; - wmGrabPointer(); + g_state.ds->grabPointer(); } struct DoublePoint guest = @@ -1053,7 +1078,7 @@ void handleMouseNormal(double ex, double ey) g_cursor.inWindow = false; /* ungrab the pointer and move the local cursor to the exit point */ - wmUngrabPointer(); + g_state.ds->ungrabPointer(); warpMouse(tx, ty, true); return; } @@ -1093,7 +1118,13 @@ void handleMouseNormal(double ex, double ey) DEBUG_ERROR("failed to send mouse motion message"); } -static void handleResizeEvent(unsigned int w, unsigned int h) +void app_updateWindowPos(int x, int y) +{ + g_state.windowPos.x = x; + g_state.windowPos.y = y; +} + +void app_handleResizeEvent(int w, int h) { SDL_GetWindowBordersSize(g_state.window, &g_state.border.y, @@ -1108,7 +1139,7 @@ static void handleResizeEvent(unsigned int w, unsigned int h) g_state.windowCY = h / 2; updatePositionInfo(); - if (inputEnabled()) + if (app_inputEnabled()) { /* if the window is moved/resized causing a loss of focus while grabbed, it * makes it impossible to re-focus the window, so we quietly re-enter @@ -1122,12 +1153,12 @@ static void handleResizeEvent(unsigned int w, unsigned int h) } } -static void handleWindowLeave(void) +void app_handleWindowLeave(void) { g_cursor.inWindow = false; g_cursor.inView = false; - if (!inputEnabled()) + if (!app_inputEnabled()) return; if (!params.alwaysShowCursor) @@ -1136,10 +1167,10 @@ static void handleWindowLeave(void) g_cursor.redraw = true; } -static void handleWindowEnter(void) +void app_handleWindowEnter(void) { g_cursor.inWindow = true; - if (!inputEnabled()) + if (!app_inputEnabled()) return; g_cursor.draw = true; @@ -1171,20 +1202,20 @@ static void setGrabQuiet(bool enable) if (enable) { - wmGrabPointer(); + g_state.ds->grabPointer(); if (params.grabKeyboard) - wmGrabKeyboard(); + g_state.ds->grabKeyboard(); } else { if (params.grabKeyboard) { if (!g_state.focused || !params.grabKeyboardOnFocus) - wmUngrabKeyboard(); + g_state.ds->ungrabKeyboard(); } - wmUngrabPointer(); + g_state.ds->ungrabPointer(); } // if exiting capture when input on capture only, we want to show the cursor @@ -1197,6 +1228,9 @@ static void setGrabQuiet(bool enable) int eventFilter(void * userdata, SDL_Event * event) { + if (g_state.ds->eventFilter(event)) + return 0; + if (event->type == e_SDLEvent) { switch(event->user.code) @@ -1232,55 +1266,38 @@ int eventFilter(void * userdata, SDL_Event * event) switch(event->window.event) { case SDL_WINDOWEVENT_ENTER: - if (g_state.wminfo.subsystem == SDL_SYSWM_WAYLAND) - g_cursor.inView = true; - if (g_state.wminfo.subsystem != SDL_SYSWM_X11) - handleWindowEnter(); + app_handleWindowEnter(); break; case SDL_WINDOWEVENT_LEAVE: - if (g_state.wminfo.subsystem == SDL_SYSWM_WAYLAND) - g_cursor.inView = false; - if (g_state.wminfo.subsystem != SDL_SYSWM_X11) - handleWindowLeave(); + app_handleWindowLeave(); break; case SDL_WINDOWEVENT_FOCUS_GAINED: - if (g_state.wminfo.subsystem != SDL_SYSWM_X11) - { - g_state.focused = true; + g_state.focused = true; - if (!inputEnabled()) - break; - if (params.grabKeyboardOnFocus) - wmGrabKeyboard(); - } + if (!app_inputEnabled()) + break; + if (params.grabKeyboardOnFocus) + g_state.ds->grabKeyboard(); break; case SDL_WINDOWEVENT_FOCUS_LOST: - if (g_state.wminfo.subsystem != SDL_SYSWM_X11) - { - g_state.focused = false; + g_state.focused = false; - if (!inputEnabled()) - break; - if (params.grabKeyboardOnFocus) - wmUngrabKeyboard(); - } + if (!app_inputEnabled()) + break; + if (params.grabKeyboardOnFocus) + g_state.ds->ungrabKeyboard(); break; case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_RESIZED: - if (g_state.wminfo.subsystem != SDL_SYSWM_X11) - handleResizeEvent(event->window.data1, event->window.data2); + app_handleResizeEvent(event->window.data1, event->window.data2); break; case SDL_WINDOWEVENT_MOVED: - if (g_state.wminfo.subsystem != SDL_SYSWM_X11) - { - g_state.windowPos.x = event->window.data1; - g_state.windowPos.y = event->window.data2; - } + app_updateWindowPos(event->window.data1, event->window.data2); break; case SDL_WINDOWEVENT_CLOSE: @@ -1291,201 +1308,8 @@ int eventFilter(void * userdata, SDL_Event * event) return 0; } - case SDL_SYSWMEVENT: - { - // When the window manager forces the window size after calling SDL_SetWindowSize, SDL - // ignores this update and caches the incorrect window size. As such all related details - // are incorect including mouse movement information as it clips to the old window size. - if (g_state.wminfo.subsystem == SDL_SYSWM_X11) - { - XEvent xe = event->syswm.msg->msg.x11.event; - - switch(xe.type) - { - case ConfigureNotify: - { - /* the window may have been re-parented so we need to translate to - * ensure we get the screen top left position of the window */ - Window child; - XTranslateCoordinates(g_state.wminfo.info.x11.display, - g_state.wminfo.info.x11.window, - DefaultRootWindow(g_state.wminfo.info.x11.display), - 0, 0, &g_state.windowPos.x, &g_state.windowPos.y, - &child); - - handleResizeEvent(xe.xconfigure.width, xe.xconfigure.height); - break; - } - -#if SDL_VIDEO_DRIVER_X11_XINPUT2 - /* support movements via XInput2 */ - case GenericEvent: - { - if (!inputEnabled()) - break; - - XGenericEventCookie *cookie = (XGenericEventCookie*)&xe.xcookie; - if (cookie->extension != g_XInputOp) - break; - - switch(cookie->evtype) - { - case XI_Motion: - { - if (!g_cursor.inWindow) - break; - - XIDeviceEvent *device = cookie->data; - g_cursor.pos.x = device->event_x; - g_cursor.pos.y = device->event_y; - break; - } - - case XI_RawMotion: - { - if (!g_cursor.inWindow) - break; - - XIRawEvent *raw = cookie->data; - double raw_axis[2]; - double axis[2]; - - /* select the active validators for the X & Y axis */ - double *valuator = raw->valuators.values; - double *r_value = raw->raw_values; - int count = 0; - for(int i = 0; i < raw->valuators.mask_len * 8; ++i) - { - if (XIMaskIsSet(raw->valuators.mask, i)) - { - raw_axis[count] = *r_value; - axis [count] = *valuator; - ++count; - - if (count == 2) - break; - - ++valuator; - ++r_value; - } - } - - /* filter out scroll wheel and other events */ - if (count < 2) - break; - - /* filter out duplicate events */ - static Time prev_time = 0; - static double prev_axis[2] = {0}; - if (raw->time == prev_time && - axis[0] == prev_axis[0] && - axis[1] == prev_axis[1]) - break; - - prev_time = raw->time; - prev_axis[0] = axis[0]; - prev_axis[1] = axis[1]; - - if (g_cursor.grab) - { - if (params.rawMouse) - handleMouseGrabbed(raw_axis[0], raw_axis[1]); - else - handleMouseGrabbed(axis[0], axis[1]); - } - else - if (g_cursor.inWindow) - handleMouseNormal(axis[0], axis[1]); - break; - } - } - - break; - } -#endif - - case EnterNotify: - { - int x, y; - Window child; - XTranslateCoordinates(g_state.wminfo.info.x11.display, - DefaultRootWindow(g_state.wminfo.info.x11.display), - g_state.wminfo.info.x11.window, - xe.xcrossing.x_root, xe.xcrossing.y_root, - &x, &y, - &child); - - g_cursor.pos.x = x; - g_cursor.pos.y = y; - handleWindowEnter(); - break; - } - - case LeaveNotify: - { - if (xe.xcrossing.mode != NotifyNormal) - break; - - int x, y; - Window child; - XTranslateCoordinates(g_state.wminfo.info.x11.display, - DefaultRootWindow(g_state.wminfo.info.x11.display), - g_state.wminfo.info.x11.window, - xe.xcrossing.x_root, xe.xcrossing.y_root, - &x, &y, - &child); - - g_cursor.pos.x = x; - g_cursor.pos.y = y; - handleWindowLeave(); - break; - } - - case FocusIn: - g_state.focused = true; - - if (!inputEnabled()) - break; - - if (xe.xfocus.mode == NotifyNormal || - xe.xfocus.mode == NotifyUngrab) - { - if (params.grabKeyboardOnFocus) - wmGrabKeyboard(); - } - break; - - case FocusOut: - g_state.focused = false; - - if (!inputEnabled()) - break; - - if (xe.xfocus.mode == NotifyNormal || - xe.xfocus.mode == NotifyWhileGrabbed) - { - if (g_cursor.grab) - setGrab(false); - else - { - if (params.grabKeyboardOnFocus) - wmUngrabKeyboard(); - } - } - break; - } - } - - if (params.useSpiceClipboard && g_state.lgc && g_state.lgc->wmevent) - g_state.lgc->wmevent(event->syswm.msg); - return 0; - } - case SDL_MOUSEMOTION: { - if (g_state.wminfo.subsystem == SDL_SYSWM_X11) - break; - g_cursor.pos.x = event->motion.x; g_cursor.pos.y = event->motion.y; @@ -1495,10 +1319,10 @@ int eventFilter(void * userdata, SDL_Event * event) if (g_cursor.grab) { if (g_state.wminfo.subsystem != SDL_SYSWM_WAYLAND) - handleMouseGrabbed(event->motion.xrel, event->motion.yrel); + app_handleMouseGrabbed(event->motion.xrel, event->motion.yrel); } else - handleMouseNormal(event->motion.xrel, event->motion.yrel); + app_handleMouseNormal(event->motion.xrel, event->motion.yrel); } break; } @@ -1519,7 +1343,7 @@ int eventFilter(void * userdata, SDL_Event * event) break; } - if (!inputEnabled()) + if (!app_inputEnabled()) break; if (params.ignoreWindowsKeys && @@ -1568,7 +1392,7 @@ int eventFilter(void * userdata, SDL_Event * event) g_state.escapeActive = false; } - if (!inputEnabled()) + if (!app_inputEnabled()) break; // avoid sending key up events when we didn't send a down @@ -1594,7 +1418,7 @@ int eventFilter(void * userdata, SDL_Event * event) } case SDL_MOUSEWHEEL: - if (!inputEnabled() || !g_cursor.inView) + if (!app_inputEnabled() || !g_cursor.inView) break; if ( @@ -1609,7 +1433,7 @@ int eventFilter(void * userdata, SDL_Event * event) case SDL_MOUSEBUTTONDOWN: { - if (!inputEnabled() || !g_cursor.inView) + if (!app_inputEnabled() || !g_cursor.inView) break; int button = event->button.button; @@ -1626,7 +1450,7 @@ int eventFilter(void * userdata, SDL_Event * event) case SDL_MOUSEBUTTONUP: { - if (!inputEnabled() || !g_cursor.inView) + if (!app_inputEnabled() || !g_cursor.inView) break; int button = event->button.button; @@ -1966,6 +1790,45 @@ static int lg_run(void) return 1; } + SDL_VERSION(&g_state.wminfo.version); + if (!SDL_GetWindowWMInfo(g_state.window, &g_state.wminfo)) + { + DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError()); + return -1; + } + + // search for the best displayserver ops to use + for(int i = 0; i < LG_DISPLAYSERVER_COUNT; ++i) + if (LG_DisplayServers[i]->subsystem == g_state.wminfo.subsystem) + { + g_state.ds = LG_DisplayServers[i]; + break; + } + + if (!g_state.ds) + g_state.ds = LG_DisplayServers[0]; + + // set any null methods to the fallback +#define SET_FALLBACK(x) \ + if (!g_state.ds->x) g_state.ds->x = LG_DisplayServers[0]->x; + SET_FALLBACK(getProp); + SET_FALLBACK(init); + SET_FALLBACK(startup); + SET_FALLBACK(shutdown); + SET_FALLBACK(free); + SET_FALLBACK(eventFilter); + SET_FALLBACK(grabPointer); + SET_FALLBACK(ungrabKeyboard); + SET_FALLBACK(warpMouse); + SET_FALLBACK(cbInit); + SET_FALLBACK(cbNotice); + SET_FALLBACK(cbRelease); + SET_FALLBACK(cbRequest); +#undef SET_FALLBACK + + // init the subsystem + g_state.ds->init(&g_state.wminfo); + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, params.minimizeOnFocusLoss ? "1" : "0"); @@ -1997,42 +1860,6 @@ static int lg_run(void) register_key_binds(); - // set the compositor hint to bypass for low latency - SDL_VERSION(&g_state.wminfo.version); - if (SDL_GetWindowWMInfo(g_state.window, &g_state.wminfo)) - { - if (g_state.wminfo.subsystem == SDL_SYSWM_X11) - { - int event, error; - - // enable X11 events to work around SDL2 bugs - SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); - - XQueryExtension(g_state.wminfo.info.x11.display, "XInputExtension", - &g_XInputOp, &event, &error); - - Atom NETWM_BYPASS_COMPOSITOR = XInternAtom( - g_state.wminfo.info.x11.display, - "NETWM_BYPASS_COMPOSITOR", - False); - - unsigned long value = 1; - XChangeProperty( - g_state.wminfo.info.x11.display, - g_state.wminfo.info.x11.window, - NETWM_BYPASS_COMPOSITOR, - XA_CARDINAL, - 32, - PropModeReplace, - (unsigned char *)&value, - 1 - ); - } - } else { - DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError()); - return -1; - } - initSDLCursor(); if (params.hideMouse) SDL_ShowCursor(SDL_DISABLE); @@ -2066,22 +1893,10 @@ static int lg_run(void) // the end of the output lgWaitEvent(e_startup, TIMEOUT_INFINITE); - wmInit(); - - for (LG_Clipboard ** clipboard = LG_Clipboards; *clipboard; clipboard++) - if ((*clipboard)->init(&g_state.wminfo, clipboardRelease, clipboardNotify, clipboardData)) - { - g_state.lgc = *clipboard; - break; - } - - if (g_state.lgc) - { - DEBUG_INFO("Using Clipboard: %s", g_state.lgc->getName()); + g_state.ds->startup(); + g_state.cbAvailable = g_state.ds->cbInit && g_state.ds->cbInit(); + if (g_state.cbAvailable) g_state.cbRequestList = ll_new(); - } - else - DEBUG_WARN("Failed to initialize the clipboard interface, continuing anyway"); LGMP_STATUS status; @@ -2263,19 +2078,18 @@ static void lg_shutdown(void) lgJoinThread(t_spice, NULL); } - if (g_state.lgc) - { - g_state.lgc->free(); + if (g_state.ds) + g_state.ds->shutdown(); - struct CBRequest *cbr; - while(ll_shift(g_state.cbRequestList, (void **)&cbr)) - free(cbr); + if (g_state.cbRequestList) + { ll_free(g_state.cbRequestList); + g_state.cbRequestList = NULL; } if (g_state.window) { - wmFree(); + g_state.ds->free(); SDL_DestroyWindow(g_state.window); } diff --git a/client/src/main.h b/client/src/main.h index d19f302a..04198768 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -22,8 +22,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include #include "interface/app.h" +#include "dynamic/displayservers.h" #include "dynamic/renderers.h" -#include "dynamic/clipboards.h" #include "common/ivshmem.h" #include "spice/spice.h" @@ -38,7 +38,10 @@ enum RunState struct AppState { - enum RunState state; + enum RunState state; + + struct LG_DisplayServerOps * ds; + bool stopVideo; bool ignoreInput; bool escapeActive; @@ -59,7 +62,7 @@ struct AppState void * lgrData; atomic_int lgrResize; - const LG_Clipboard * lgc; + bool cbAvailable; SpiceDataType cbType; bool cbChunked; size_t cbXfer; @@ -236,6 +239,3 @@ struct CursorState // forwards extern struct AppState g_state; extern struct AppParams params; - -void handleMouseGrabbed(double, double); -void handleMouseNormal(double, double); diff --git a/client/src/wm.c b/client/src/wm.c deleted file mode 100644 index a2dfa43b..00000000 --- a/client/src/wm.c +++ /dev/null @@ -1,495 +0,0 @@ -/* -Looking Glass - KVM FrameRelay (KVMFR) Client -Copyright (C) 2017-2021 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 "wm.h" -#include "main.h" - -#include -#include - -#include -#include - -#include "common/debug.h" - -#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" -#include "wayland-pointer-constraints-unstable-v1-client-protocol.h" -#include "wayland-relative-pointer-unstable-v1-client-protocol.h" - -struct WMState g_wmState; - -static void wmWaylandInit(); -static void wmWaylandFree(); -static void wmWaylandGrabKeyboard(); -static void wmWaylandUngrabKeyboard(); -static void wmWaylandGrabPointer(); -static void wmWaylandUngrabPointer(); - -void wmInit(void) -{ - switch (g_state.wminfo.subsystem) - { - case SDL_SYSWM_WAYLAND: - wmWaylandInit(); - break; - - default: - break; - } -} - -void wmFree(void) -{ - switch (g_state.wminfo.subsystem) - { - case SDL_SYSWM_WAYLAND: - wmWaylandFree(); - break; - - default: - break; - } -} - -void wmGrabPointer(void) -{ - switch(g_state.wminfo.subsystem) - { - case SDL_SYSWM_X11: - XGrabPointer( - g_state.wminfo.info.x11.display, - g_state.wminfo.info.x11.window, - true, - None, - GrabModeAsync, - GrabModeAsync, - g_state.wminfo.info.x11.window, - None, - CurrentTime); - break; - - case SDL_SYSWM_WAYLAND: - wmWaylandGrabPointer(); - break; - - default: - SDL_SetWindowGrab(g_state.window, SDL_TRUE); - break; - } - - g_wmState.pointerGrabbed = true; -} - -void wmUngrabPointer(void) -{ - switch(g_state.wminfo.subsystem) - { - case SDL_SYSWM_X11: - XUngrabPointer(g_state.wminfo.info.x11.display, CurrentTime); - break; - - case SDL_SYSWM_WAYLAND: - wmWaylandUngrabPointer(); - break; - - default: - SDL_SetWindowGrab(g_state.window, SDL_FALSE); - break; - } - - g_wmState.pointerGrabbed = false; -} - -void wmGrabKeyboard(void) -{ - switch(g_state.wminfo.subsystem) - { - case SDL_SYSWM_X11: - XGrabKeyboard( - g_state.wminfo.info.x11.display, - g_state.wminfo.info.x11.window, - true, - GrabModeAsync, - GrabModeAsync, - CurrentTime); - break; - - case SDL_SYSWM_WAYLAND: - wmWaylandGrabKeyboard(); - break; - - default: - if (g_wmState.pointerGrabbed) - SDL_SetWindowGrab(g_state.window, SDL_FALSE); - else - { - DEBUG_WARN("SDL does not support grabbing only the keyboard, grabbing all"); - g_wmState.pointerGrabbed = true; - } - - SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); - SDL_SetWindowGrab(g_state.window, SDL_TRUE); - break; - } - - g_wmState.keyboardGrabbed = true; -} - -void wmUngrabKeyboard(void) -{ - switch(g_state.wminfo.subsystem) - { - case SDL_SYSWM_X11: - XUngrabKeyboard(g_state.wminfo.info.x11.display, CurrentTime); - break; - - case SDL_SYSWM_WAYLAND: - wmWaylandUngrabKeyboard(); - break; - - default: - SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0"); - SDL_SetWindowGrab(g_state.window, SDL_FALSE); - if (g_wmState.pointerGrabbed) - SDL_SetWindowGrab(g_state.window, SDL_TRUE); - break; - } - - g_wmState.keyboardGrabbed = false; -} - -void wmGrabAll(void) -{ - wmGrabPointer(); - wmGrabKeyboard(); -} - -void wmUngrabAll(void) -{ - wmUngrabPointer(); - wmUngrabKeyboard(); -} - -void wmWarpMouse(int x, int y) -{ - switch(g_state.wminfo.subsystem) - { - case SDL_SYSWM_X11: - XWarpPointer( - g_state.wminfo.info.x11.display, - None, - g_state.wminfo.info.x11.window, - 0, 0, 0, 0, - x, y); - XSync(g_state.wminfo.info.x11.display, False); - break; - - default: - SDL_WarpMouseInWindow(g_state.window, x, y); - break; - } -} - -// Wayland support. - -// Registry-handling listeners. - -static void registryGlobalHandler(void * data, struct wl_registry * registry, - uint32_t name, const char * interface, uint32_t version) -{ - struct WMDataWayland * wm = data; - - if (!strcmp(interface, wl_seat_interface.name) && !wm->seat) - wm->seat = wl_registry_bind(wm->registry, name, &wl_seat_interface, 1); - else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) - wm->relativePointerManager = wl_registry_bind(wm->registry, name, - &zwp_relative_pointer_manager_v1_interface, 1); - else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name)) - wm->pointerConstraints = wl_registry_bind(wm->registry, name, - &zwp_pointer_constraints_v1_interface, 1); - else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name)) - wm->keyboardInhibitManager = wl_registry_bind(wm->registry, name, - &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); - else if (!strcmp(interface, wl_data_device_manager_interface.name)) - wm->dataDeviceManager = wl_registry_bind(wm->registry, name, - &wl_data_device_manager_interface, 3); -} - -static void registryGlobalRemoveHandler(void * data, - struct wl_registry * registry, uint32_t name) -{ - // Do nothing. -} - -static const struct wl_registry_listener registryListener = { - .global = registryGlobalHandler, - .global_remove = registryGlobalRemoveHandler, -}; - -// Mouse-handling listeners. - -static void pointerMotionHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW) -{ - int sx = wl_fixed_to_int(sxW); - int sy = wl_fixed_to_int(syW); - handleMouseNormal(sx, sy); -} - -static void pointerEnterHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, - wl_fixed_t syW) -{ - int sx = wl_fixed_to_int(sxW); - int sy = wl_fixed_to_int(syW); - handleMouseNormal(sx, sy); -} - -static void pointerLeaveHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, struct wl_surface * surface) -{ - // Do nothing. -} - -static void pointerAxisHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, uint32_t axis, wl_fixed_t value) -{ - // Do nothing. -} - -static void pointerButtonHandler(void *data, struct wl_pointer *pointer, - uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW) -{ - // Do nothing. -} - -static const struct wl_pointer_listener pointerListener = { - .enter = pointerEnterHandler, - .leave = pointerLeaveHandler, - .motion = pointerMotionHandler, - .button = pointerButtonHandler, - .axis = pointerAxisHandler, -}; - -// Keyboard-handling listeners. - -static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard, - uint32_t format, int fd, uint32_t size) -{ - close(fd); -} - -static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard, - uint32_t serial, struct wl_surface * surface, struct wl_array * keys) -{ - struct WMDataWayland * wm = data; - wm->keyboardEnterSerial = serial; -} - -static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard, - uint32_t serial, struct wl_surface * surface) -{ - // Do nothing. -} - -static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard, - uint32_t serial, uint32_t time, uint32_t key, uint32_t state) -{ - // Do nothing. -} - -static void keyboardModifiersHandler(void * data, - struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed, - uint32_t modsLatched, uint32_t modsLocked, uint32_t group) -{ - // Do nothing. -} - -static const struct wl_keyboard_listener keyboardListener = { - .keymap = keyboardKeymapHandler, - .enter = keyboardEnterHandler, - .leave = keyboardLeaveHandler, - .key = keyboardKeyHandler, - .modifiers = keyboardModifiersHandler, -}; - -// Seat-handling listeners. - -static void handlePointerCapability(struct WMDataWayland * wm, - uint32_t capabilities) -{ - bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; - if (!hasPointer && wm->pointer) - { - wl_pointer_destroy(wm->pointer); - wm->pointer = NULL; - } - else if (hasPointer && !wm->pointer) - { - wm->pointer = wl_seat_get_pointer(wm->seat); - wl_pointer_add_listener(wm->pointer, &pointerListener, wm); - } -} - -static void handleKeyboardCapability(struct WMDataWayland * wm, - uint32_t capabilities) -{ - bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; - if (!hasKeyboard && wm->keyboard) - { - wl_keyboard_destroy(wm->keyboard); - wm->keyboard = NULL; - } - else if (hasKeyboard && !wm->keyboard) - { - wm->keyboard = wl_seat_get_keyboard(wm->seat); - wl_keyboard_add_listener(wm->keyboard, &keyboardListener, wm); - } -} - -static void seatCapabilitiesHandler(void * data, struct wl_seat * seat, - uint32_t capabilities) -{ - struct WMDataWayland * wm = data; - wm->capabilities = capabilities; - handlePointerCapability(wm, capabilities); - handleKeyboardCapability(wm, capabilities); -} - -static void seatNameHandler(void * data, struct wl_seat * seat, - const char * name) -{ - // Do nothing. -} - -static const struct wl_seat_listener seatListener = { - .capabilities = seatCapabilitiesHandler, - .name = seatNameHandler, -}; - -void wmWaylandInit(void) -{ - struct WMDataWayland * wm = malloc(sizeof(struct WMDataWayland)); - memset(wm, 0, sizeof(struct WMDataWayland)); - - wm->display = g_state.wminfo.info.wl.display; - wm->surface = g_state.wminfo.info.wl.surface; - wm->registry = wl_display_get_registry(wm->display); - - wl_registry_add_listener(wm->registry, ®istryListener, wm); - wl_display_roundtrip(wm->display); - - wl_seat_add_listener(wm->seat, &seatListener, wm); - wl_display_roundtrip(wm->display); - - wm->dataDevice = wl_data_device_manager_get_data_device( - wm->dataDeviceManager, wm->seat); - - g_wmState.opaque = wm; -} - -static void relativePointerMotionHandler(void * data, - struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo, - wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW, - wl_fixed_t dyUnaccelW) -{ - double dxUnaccel = wl_fixed_to_double(dxUnaccelW); - double dyUnaccel = wl_fixed_to_double(dyUnaccelW); - handleMouseGrabbed(dxUnaccel, dyUnaccel); -} - -static const struct zwp_relative_pointer_v1_listener relativePointerListener = { - .relative_motion = relativePointerMotionHandler, -}; - -static void wmWaylandGrabPointer(void) -{ - struct WMDataWayland * wm = g_wmState.opaque; - - if (!wm->relativePointer) - { - wm->relativePointer = - zwp_relative_pointer_manager_v1_get_relative_pointer( - wm->relativePointerManager, wm->pointer); - zwp_relative_pointer_v1_add_listener(wm->relativePointer, - &relativePointerListener, wm); - } - - if (!wm->confinedPointer) - { - wm->confinedPointer = zwp_pointer_constraints_v1_confine_pointer( - wm->pointerConstraints, wm->surface, wm->pointer, NULL, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); - } -} - -static void wmWaylandUngrabPointer(void) -{ - struct WMDataWayland * wm = g_wmState.opaque; - - if (wm->relativePointer) - { - zwp_relative_pointer_v1_destroy(wm->relativePointer); - wm->relativePointer = NULL; - } - - if (wm->confinedPointer) - { - zwp_confined_pointer_v1_destroy(wm->confinedPointer); - wm->confinedPointer = NULL; - } -} - -static void wmWaylandGrabKeyboard(void) -{ - struct WMDataWayland * wm = g_wmState.opaque; - - if (wm->keyboardInhibitManager && !wm->keyboardInhibitor) - { - wm->keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts( - wm->keyboardInhibitManager, wm->surface, wm->seat); - } -} - -static void wmWaylandUngrabKeyboard(void) -{ - struct WMDataWayland * wm = g_wmState.opaque; - - if (wm->keyboardInhibitor) - { - zwp_keyboard_shortcuts_inhibitor_v1_destroy(wm->keyboardInhibitor); - wm->keyboardInhibitor = NULL; - } -} - -static void wmWaylandFree(void) -{ - struct WMDataWayland * wm = g_wmState.opaque; - - wmWaylandUngrabPointer(); - - // TODO: these also need to be freed, but are currently owned by SDL. - // wl_display_destroy(wm->display); - // wl_surface_destroy(wm->surface); - wl_pointer_destroy(wm->pointer); - wl_seat_destroy(wm->seat); - wl_registry_destroy(wm->registry); - - free(g_wmState.opaque); -} diff --git a/client/src/wm.h b/client/src/wm.h deleted file mode 100644 index 7c2bcf45..00000000 --- a/client/src/wm.h +++ /dev/null @@ -1,65 +0,0 @@ -/* -Looking Glass - KVM FrameRelay (KVMFR) Client -Copyright (C) 2017-2021 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 WMState -{ - bool pointerGrabbed; - bool keyboardGrabbed; - - void * opaque; -}; - -struct WMDataWayland -{ - struct wl_display * display; - struct wl_surface * surface; - struct wl_registry * registry; - struct wl_seat * seat; - - struct wl_data_device_manager * dataDeviceManager; - struct wl_data_device * dataDevice; - - uint32_t capabilities; - - struct wl_keyboard * keyboard; - struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager; - struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor; - uint32_t keyboardEnterSerial; - - struct wl_pointer * pointer; - struct zwp_relative_pointer_manager_v1 * relativePointerManager; - struct zwp_pointer_constraints_v1 * pointerConstraints; - struct zwp_relative_pointer_v1 * relativePointer; - struct zwp_confined_pointer_v1 * confinedPointer; -}; - -extern struct WMState g_wmState; - -void wmInit(); -void wmFree(); -void wmGrabPointer(); -void wmUngrabPointer(); -void wmGrabKeyboard(); -void wmUngrabKeyboard(); -void wmGrabAll(); -void wmUngrabAll(); -void wmWarpMouse(int x, int y); diff --git a/common/include/common/sysinfo.h b/common/include/common/sysinfo.h index 4296be67..d929674f 100644 --- a/common/include/common/sysinfo.h +++ b/common/include/common/sysinfo.h @@ -17,8 +17,5 @@ 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(); - // returns the page size -long sysinfo_getPageSize(); \ No newline at end of file +long sysinfo_getPageSize(); diff --git a/common/src/platform/linux/sysinfo.c b/common/src/platform/linux/sysinfo.c index a39dea38..6536bc6c 100644 --- a/common/src/platform/linux/sysinfo.c +++ b/common/src/platform/linux/sysinfo.c @@ -18,53 +18,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA */ #include -#include - -int sysinfo_gfx_max_multisample(void) -{ - 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; -} long sysinfo_getPageSize(void) { return sysconf(_SC_PAGESIZE); -} \ No newline at end of file +}