mirror of
https://github.com/gnif/LookingGlass.git
synced 2025-04-26 00:26:32 +00:00
[client] major restructure of platform specific code
This commit is contained in:
parent
062c4e32cb
commit
27a38294ea
@ -21,12 +21,6 @@ add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.")
|
|||||||
option(ENABLE_EGL "Enable the EGL renderer" ON)
|
option(ENABLE_EGL "Enable the EGL renderer" ON)
|
||||||
add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.")
|
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)
|
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
||||||
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
|
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)
|
option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF)
|
||||||
add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.")
|
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)
|
option(ENABLE_WAYLAND "Build with Wayland support" ON)
|
||||||
add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
|
add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
|
||||||
|
|
||||||
@ -69,11 +66,6 @@ endif()
|
|||||||
find_package(PkgConfig)
|
find_package(PkgConfig)
|
||||||
pkg_check_modules(PKGCONFIG REQUIRED
|
pkg_check_modules(PKGCONFIG REQUIRED
|
||||||
sdl2
|
sdl2
|
||||||
x11
|
|
||||||
)
|
|
||||||
|
|
||||||
pkg_check_modules(PKGCONFIG_OPT
|
|
||||||
xi
|
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(GMP)
|
find_package(GMP)
|
||||||
@ -112,15 +104,14 @@ set(SOURCES
|
|||||||
src/lg-renderer.c
|
src/lg-renderer.c
|
||||||
src/ll.c
|
src/ll.c
|
||||||
src/utils.c
|
src/utils.c
|
||||||
src/wm.c
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" )
|
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/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" )
|
||||||
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
|
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
|
||||||
|
|
||||||
|
add_subdirectory(displayservers)
|
||||||
add_subdirectory(renderers)
|
add_subdirectory(renderers)
|
||||||
add_subdirectory(clipboards)
|
|
||||||
add_subdirectory(fonts)
|
add_subdirectory(fonts)
|
||||||
|
|
||||||
add_executable(looking-glass-client ${SOURCES})
|
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
|
target_link_libraries(looking-glass-client
|
||||||
${EXE_FLAGS}
|
${EXE_FLAGS}
|
||||||
lg_common
|
lg_common
|
||||||
|
displayservers
|
||||||
lgmp
|
lgmp
|
||||||
purespice
|
purespice
|
||||||
renderers
|
renderers
|
||||||
clipboards
|
|
||||||
fonts
|
fonts
|
||||||
)
|
)
|
||||||
|
|
||||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-client DESTINATION bin/ COMPONENT binary)
|
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)
|
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)
|
||||||
|
@ -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 <stddef.h>\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})
|
|
@ -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
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
||||||
$<INSTALL_INTERFACE:include>
|
|
||||||
PRIVATE
|
|
||||||
src
|
|
||||||
${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS}
|
|
||||||
)
|
|
@ -1,442 +0,0 @@
|
|||||||
/*
|
|
||||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
|
||||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
|
||||||
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 <assert.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <wayland-client.h>
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
@ -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
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
||||||
$<INSTALL_INTERFACE:include>
|
|
||||||
PRIVATE
|
|
||||||
src
|
|
||||||
${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS}
|
|
||||||
)
|
|
@ -1,466 +0,0 @@
|
|||||||
/*
|
|
||||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
|
||||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
|
||||||
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 <X11/extensions/Xfixes.h>
|
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
50
client/displayservers/CMakeLists.txt
Normal file
50
client/displayservers/CMakeLists.txt
Normal file
@ -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 <stddef.h>\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})
|
24
client/displayservers/SDL/CMakeLists.txt
Normal file
24
client/displayservers/SDL/CMakeLists.txt
Normal file
@ -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}
|
||||||
|
)
|
154
client/displayservers/SDL/sdl.c
Normal file
154
client/displayservers/SDL/sdl.c
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||||
|
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||||
|
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 <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#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,
|
||||||
|
};
|
58
client/displayservers/Wayland/CMakeLists.txt
Normal file
58
client/displayservers/Wayland/CMakeLists.txt
Normal file
@ -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")
|
||||||
|
|
715
client/displayservers/Wayland/wayland.c
Normal file
715
client/displayservers/Wayland/wayland.c
Normal file
@ -0,0 +1,715 @@
|
|||||||
|
/*
|
||||||
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||||
|
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||||
|
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 <stdbool.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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
|
||||||
|
};
|
29
client/displayservers/X11/CMakeLists.txt
Normal file
29
client/displayservers/X11/CMakeLists.txt
Normal file
@ -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}
|
||||||
|
)
|
792
client/displayservers/X11/x11.c
Normal file
792
client/displayservers/X11/x11.c
Normal file
@ -0,0 +1,792 @@
|
|||||||
|
/*
|
||||||
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||||
|
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||||
|
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 <SDL2/SDL.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <GL/glx.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if SDL_VIDEO_DRIVER_X11_XINPUT2
|
||||||
|
#include <X11/extensions/XInput2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <X11/extensions/Xfixes.h>
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
};
|
43
client/include/app.h
Normal file
43
client/include/app.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||||
|
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||||
|
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 <stdbool.h>
|
||||||
|
|
||||||
|
#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);
|
@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
|
||||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
|
||||||
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 <stdbool.h>
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
#include <SDL2/SDL_syswm.h>
|
|
||||||
|
|
||||||
typedef enum LG_ClipboardData
|
|
||||||
{
|
|
||||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
|
||||||
LG_CLIPBOARD_DATA_PNG,
|
|
||||||
LG_CLIPBOARD_DATA_BMP,
|
|
||||||
LG_CLIPBOARD_DATA_TIFF,
|
|
||||||
LG_CLIPBOARD_DATA_JPEG,
|
|
||||||
|
|
||||||
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
|
||||||
}
|
|
||||||
LG_ClipboardData;
|
|
||||||
|
|
||||||
typedef 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;
|
|
86
client/include/interface/displayserver.h
Normal file
86
client/include/interface/displayserver.h
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||||
|
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||||
|
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 <stdbool.h>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_syswm.h>
|
||||||
|
|
||||||
|
typedef enum LG_ClipboardData
|
||||||
|
{
|
||||||
|
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||||
|
LG_CLIPBOARD_DATA_PNG,
|
||||||
|
LG_CLIPBOARD_DATA_BMP,
|
||||||
|
LG_CLIPBOARD_DATA_TIFF,
|
||||||
|
LG_CLIPBOARD_DATA_JPEG,
|
||||||
|
|
||||||
|
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
||||||
|
}
|
||||||
|
LG_ClipboardData;
|
||||||
|
|
||||||
|
typedef enum LG_DSProperty
|
||||||
|
{
|
||||||
|
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
|
@ -34,6 +34,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include <wayland-egl.h>
|
#include <wayland-egl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "app.h"
|
||||||
#include "model.h"
|
#include "model.h"
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
#include "desktop.h"
|
#include "desktop.h"
|
||||||
@ -218,25 +219,6 @@ bool egl_initialize(void * opaque, Uint32 * sdlFlags)
|
|||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,12 +488,24 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
|
|||||||
return false;
|
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[] =
|
EGLint attr[] =
|
||||||
{
|
{
|
||||||
EGL_BUFFER_SIZE , 32,
|
EGL_BUFFER_SIZE , 32,
|
||||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||||
EGL_SAMPLE_BUFFERS , 1,
|
EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0,
|
||||||
EGL_SAMPLES , 4,
|
EGL_SAMPLES , maxSamples,
|
||||||
EGL_NONE
|
EGL_NONE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,10 +36,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
|
|
||||||
#if SDL_VIDEO_DRIVER_X11_XINPUT2
|
|
||||||
#include <X11/extensions/XInput2.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/crash.h"
|
#include "common/crash.h"
|
||||||
#include "common/KVMFR.h"
|
#include "common/KVMFR.h"
|
||||||
@ -54,7 +50,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "kb.h"
|
#include "kb.h"
|
||||||
#include "ll.h"
|
#include "ll.h"
|
||||||
#include "wm.h"
|
|
||||||
|
|
||||||
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
|
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
|
||||||
|
|
||||||
@ -71,7 +66,6 @@ static LGThread *t_cursor = NULL;
|
|||||||
static LGThread *t_frame = NULL;
|
static LGThread *t_frame = NULL;
|
||||||
static SDL_Cursor *cursor = NULL;
|
static SDL_Cursor *cursor = NULL;
|
||||||
|
|
||||||
static int g_XInputOp; // XInput Opcode
|
|
||||||
static Uint32 e_SDLEvent; // our SDL event
|
static Uint32 e_SDLEvent; // our SDL event
|
||||||
|
|
||||||
enum
|
enum
|
||||||
@ -101,12 +95,43 @@ static void lgInit(void)
|
|||||||
g_cursor.guest.valid = false;
|
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 &&
|
return params.useSpiceInput && !g_state.ignoreInput &&
|
||||||
((g_cursor.grab && params.captureInputOnly) || !params.captureInputOnly);
|
((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)
|
static void alignToGuest(void)
|
||||||
{
|
{
|
||||||
if (SDL_HasEvent(e_SDLEvent))
|
if (SDL_HasEvent(e_SDLEvent))
|
||||||
@ -398,7 +423,7 @@ static int cursorThread(void * unused)
|
|||||||
g_cursor.guest.valid = true;
|
g_cursor.guest.valid = true;
|
||||||
|
|
||||||
// if the state just became valid
|
// if the state just became valid
|
||||||
if (valid != true && inputEnabled())
|
if (valid != true && app_inputEnabled())
|
||||||
alignToGuest();
|
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)
|
if (!params.clipboardToVM)
|
||||||
return;
|
return;
|
||||||
@ -720,7 +745,7 @@ void clipboardRelease(void)
|
|||||||
spice_clipboard_release();
|
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)
|
if (!params.clipboardToVM)
|
||||||
return;
|
return;
|
||||||
@ -741,7 +766,7 @@ void clipboardNotify(const LG_ClipboardData type, size_t size)
|
|||||||
spice_clipboard_data_start(g_state.cbType, 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)
|
if (!params.clipboardToVM)
|
||||||
return;
|
return;
|
||||||
@ -759,7 +784,7 @@ void clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
|||||||
g_state.cbXfer -= 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)
|
if (!params.clipboardToLocal)
|
||||||
return;
|
return;
|
||||||
@ -779,11 +804,11 @@ void spiceClipboardNotice(const SpiceDataType type)
|
|||||||
if (!params.clipboardToLocal)
|
if (!params.clipboardToLocal)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!g_state.lgc || !g_state.lgc->notice)
|
if (!g_state.cbAvailable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
g_state.cbType = type;
|
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)
|
void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t size)
|
||||||
@ -822,8 +847,8 @@ void spiceClipboardRelease(void)
|
|||||||
if (!params.clipboardToLocal)
|
if (!params.clipboardToLocal)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (g_state.lgc && g_state.lgc->release)
|
if (g_state.cbAvailable)
|
||||||
g_state.lgc->release();
|
g_state.ds->cbRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
void spiceClipboardRequest(const SpiceDataType type)
|
void spiceClipboardRequest(const SpiceDataType type)
|
||||||
@ -831,22 +856,22 @@ void spiceClipboardRequest(const SpiceDataType type)
|
|||||||
if (!params.clipboardToVM)
|
if (!params.clipboardToVM)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (g_state.lgc && g_state.lgc->request)
|
if (g_state.cbAvailable)
|
||||||
g_state.lgc->request(spice_type_to_clipboard_type(type));
|
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)
|
if (g_cursor.warpState == WARP_STATE_OFF)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (disable)
|
if (exiting)
|
||||||
g_cursor.warpState = WARP_STATE_OFF;
|
g_cursor.warpState = WARP_STATE_OFF;
|
||||||
|
|
||||||
if (g_cursor.pos.x == x && g_cursor.pos.y == y)
|
if (g_cursor.pos.x == x && g_cursor.pos.y == y)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wmWarpMouse(x, y);
|
g_state.ds->warpMouse(x, y, exiting);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isValidCursorLocation(int x, int y)
|
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;
|
*y = (int)ey;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleMouseGrabbed(double ex, double ey)
|
void app_handleMouseGrabbed(double ex, double ey)
|
||||||
{
|
{
|
||||||
int x, y;
|
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
|
// 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
|
// 1:1 mouse movement patch applied in the guest. For anything fancy, use
|
||||||
// capture mode.
|
// capture mode.
|
||||||
static void handleMouseWayland(void)
|
static void app_handleMouseWayland(void)
|
||||||
{
|
{
|
||||||
if (g_cursor.guest.dpiScale == 0)
|
if (g_cursor.guest.dpiScale == 0)
|
||||||
return;
|
return;
|
||||||
@ -940,19 +965,19 @@ static void handleMouseWayland(void)
|
|||||||
DEBUG_ERROR("failed to send mouse motion message");
|
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 we don't have the current cursor pos just send cursor movements */
|
||||||
if (!g_cursor.guest.valid)
|
if (!g_cursor.guest.valid)
|
||||||
{
|
{
|
||||||
if (g_cursor.grab)
|
if (g_cursor.grab)
|
||||||
handleMouseGrabbed(ex, ey);
|
app_handleMouseGrabbed(ex, ey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_state.wminfo.subsystem == SDL_SYSWM_WAYLAND)
|
if (g_state.wminfo.subsystem == SDL_SYSWM_WAYLAND)
|
||||||
{
|
{
|
||||||
handleMouseWayland();
|
app_handleMouseWayland();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -987,7 +1012,7 @@ void handleMouseNormal(double ex, double ey)
|
|||||||
g_cursor.redraw = true;
|
g_cursor.redraw = true;
|
||||||
|
|
||||||
g_cursor.warpState = WARP_STATE_ON;
|
g_cursor.warpState = WARP_STATE_ON;
|
||||||
wmGrabPointer();
|
g_state.ds->grabPointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DoublePoint guest =
|
struct DoublePoint guest =
|
||||||
@ -1053,7 +1078,7 @@ void handleMouseNormal(double ex, double ey)
|
|||||||
g_cursor.inWindow = false;
|
g_cursor.inWindow = false;
|
||||||
|
|
||||||
/* ungrab the pointer and move the local cursor to the exit point */
|
/* ungrab the pointer and move the local cursor to the exit point */
|
||||||
wmUngrabPointer();
|
g_state.ds->ungrabPointer();
|
||||||
warpMouse(tx, ty, true);
|
warpMouse(tx, ty, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1093,7 +1118,13 @@ void handleMouseNormal(double ex, double ey)
|
|||||||
DEBUG_ERROR("failed to send mouse motion message");
|
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,
|
SDL_GetWindowBordersSize(g_state.window,
|
||||||
&g_state.border.y,
|
&g_state.border.y,
|
||||||
@ -1108,7 +1139,7 @@ static void handleResizeEvent(unsigned int w, unsigned int h)
|
|||||||
g_state.windowCY = h / 2;
|
g_state.windowCY = h / 2;
|
||||||
updatePositionInfo();
|
updatePositionInfo();
|
||||||
|
|
||||||
if (inputEnabled())
|
if (app_inputEnabled())
|
||||||
{
|
{
|
||||||
/* if the window is moved/resized causing a loss of focus while grabbed, it
|
/* 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
|
* 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.inWindow = false;
|
||||||
g_cursor.inView = false;
|
g_cursor.inView = false;
|
||||||
|
|
||||||
if (!inputEnabled())
|
if (!app_inputEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!params.alwaysShowCursor)
|
if (!params.alwaysShowCursor)
|
||||||
@ -1136,10 +1167,10 @@ static void handleWindowLeave(void)
|
|||||||
g_cursor.redraw = true;
|
g_cursor.redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleWindowEnter(void)
|
void app_handleWindowEnter(void)
|
||||||
{
|
{
|
||||||
g_cursor.inWindow = true;
|
g_cursor.inWindow = true;
|
||||||
if (!inputEnabled())
|
if (!app_inputEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
g_cursor.draw = true;
|
g_cursor.draw = true;
|
||||||
@ -1171,20 +1202,20 @@ static void setGrabQuiet(bool enable)
|
|||||||
|
|
||||||
if (enable)
|
if (enable)
|
||||||
{
|
{
|
||||||
wmGrabPointer();
|
g_state.ds->grabPointer();
|
||||||
|
|
||||||
if (params.grabKeyboard)
|
if (params.grabKeyboard)
|
||||||
wmGrabKeyboard();
|
g_state.ds->grabKeyboard();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (params.grabKeyboard)
|
if (params.grabKeyboard)
|
||||||
{
|
{
|
||||||
if (!g_state.focused || !params.grabKeyboardOnFocus)
|
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
|
// 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)
|
int eventFilter(void * userdata, SDL_Event * event)
|
||||||
{
|
{
|
||||||
|
if (g_state.ds->eventFilter(event))
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (event->type == e_SDLEvent)
|
if (event->type == e_SDLEvent)
|
||||||
{
|
{
|
||||||
switch(event->user.code)
|
switch(event->user.code)
|
||||||
@ -1232,55 +1266,38 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
switch(event->window.event)
|
switch(event->window.event)
|
||||||
{
|
{
|
||||||
case SDL_WINDOWEVENT_ENTER:
|
case SDL_WINDOWEVENT_ENTER:
|
||||||
if (g_state.wminfo.subsystem == SDL_SYSWM_WAYLAND)
|
app_handleWindowEnter();
|
||||||
g_cursor.inView = true;
|
|
||||||
if (g_state.wminfo.subsystem != SDL_SYSWM_X11)
|
|
||||||
handleWindowEnter();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_LEAVE:
|
case SDL_WINDOWEVENT_LEAVE:
|
||||||
if (g_state.wminfo.subsystem == SDL_SYSWM_WAYLAND)
|
app_handleWindowLeave();
|
||||||
g_cursor.inView = false;
|
|
||||||
if (g_state.wminfo.subsystem != SDL_SYSWM_X11)
|
|
||||||
handleWindowLeave();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||||
if (g_state.wminfo.subsystem != SDL_SYSWM_X11)
|
g_state.focused = true;
|
||||||
{
|
|
||||||
g_state.focused = true;
|
|
||||||
|
|
||||||
if (!inputEnabled())
|
if (!app_inputEnabled())
|
||||||
break;
|
break;
|
||||||
if (params.grabKeyboardOnFocus)
|
if (params.grabKeyboardOnFocus)
|
||||||
wmGrabKeyboard();
|
g_state.ds->grabKeyboard();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||||
if (g_state.wminfo.subsystem != SDL_SYSWM_X11)
|
g_state.focused = false;
|
||||||
{
|
|
||||||
g_state.focused = false;
|
|
||||||
|
|
||||||
if (!inputEnabled())
|
if (!app_inputEnabled())
|
||||||
break;
|
break;
|
||||||
if (params.grabKeyboardOnFocus)
|
if (params.grabKeyboardOnFocus)
|
||||||
wmUngrabKeyboard();
|
g_state.ds->ungrabKeyboard();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
case SDL_WINDOWEVENT_RESIZED:
|
case SDL_WINDOWEVENT_RESIZED:
|
||||||
if (g_state.wminfo.subsystem != SDL_SYSWM_X11)
|
app_handleResizeEvent(event->window.data1, event->window.data2);
|
||||||
handleResizeEvent(event->window.data1, event->window.data2);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_MOVED:
|
case SDL_WINDOWEVENT_MOVED:
|
||||||
if (g_state.wminfo.subsystem != SDL_SYSWM_X11)
|
app_updateWindowPos(event->window.data1, event->window.data2);
|
||||||
{
|
|
||||||
g_state.windowPos.x = event->window.data1;
|
|
||||||
g_state.windowPos.y = event->window.data2;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_CLOSE:
|
case SDL_WINDOWEVENT_CLOSE:
|
||||||
@ -1291,201 +1308,8 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
return 0;
|
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:
|
case SDL_MOUSEMOTION:
|
||||||
{
|
{
|
||||||
if (g_state.wminfo.subsystem == SDL_SYSWM_X11)
|
|
||||||
break;
|
|
||||||
|
|
||||||
g_cursor.pos.x = event->motion.x;
|
g_cursor.pos.x = event->motion.x;
|
||||||
g_cursor.pos.y = event->motion.y;
|
g_cursor.pos.y = event->motion.y;
|
||||||
|
|
||||||
@ -1495,10 +1319,10 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
if (g_cursor.grab)
|
if (g_cursor.grab)
|
||||||
{
|
{
|
||||||
if (g_state.wminfo.subsystem != SDL_SYSWM_WAYLAND)
|
if (g_state.wminfo.subsystem != SDL_SYSWM_WAYLAND)
|
||||||
handleMouseGrabbed(event->motion.xrel, event->motion.yrel);
|
app_handleMouseGrabbed(event->motion.xrel, event->motion.yrel);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
handleMouseNormal(event->motion.xrel, event->motion.yrel);
|
app_handleMouseNormal(event->motion.xrel, event->motion.yrel);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1519,7 +1343,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inputEnabled())
|
if (!app_inputEnabled())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (params.ignoreWindowsKeys &&
|
if (params.ignoreWindowsKeys &&
|
||||||
@ -1568,7 +1392,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
g_state.escapeActive = false;
|
g_state.escapeActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inputEnabled())
|
if (!app_inputEnabled())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// avoid sending key up events when we didn't send a down
|
// 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:
|
case SDL_MOUSEWHEEL:
|
||||||
if (!inputEnabled() || !g_cursor.inView)
|
if (!app_inputEnabled() || !g_cursor.inView)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -1609,7 +1433,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
|
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
{
|
{
|
||||||
if (!inputEnabled() || !g_cursor.inView)
|
if (!app_inputEnabled() || !g_cursor.inView)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
int button = event->button.button;
|
int button = event->button.button;
|
||||||
@ -1626,7 +1450,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
|
|
||||||
case SDL_MOUSEBUTTONUP:
|
case SDL_MOUSEBUTTONUP:
|
||||||
{
|
{
|
||||||
if (!inputEnabled() || !g_cursor.inView)
|
if (!app_inputEnabled() || !g_cursor.inView)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
int button = event->button.button;
|
int button = event->button.button;
|
||||||
@ -1966,6 +1790,45 @@ static int lg_run(void)
|
|||||||
return 1;
|
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,
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
|
||||||
params.minimizeOnFocusLoss ? "1" : "0");
|
params.minimizeOnFocusLoss ? "1" : "0");
|
||||||
|
|
||||||
@ -1997,42 +1860,6 @@ static int lg_run(void)
|
|||||||
|
|
||||||
register_key_binds();
|
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();
|
initSDLCursor();
|
||||||
if (params.hideMouse)
|
if (params.hideMouse)
|
||||||
SDL_ShowCursor(SDL_DISABLE);
|
SDL_ShowCursor(SDL_DISABLE);
|
||||||
@ -2066,22 +1893,10 @@ static int lg_run(void)
|
|||||||
// the end of the output
|
// the end of the output
|
||||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||||
|
|
||||||
wmInit();
|
g_state.ds->startup();
|
||||||
|
g_state.cbAvailable = g_state.ds->cbInit && g_state.ds->cbInit();
|
||||||
for (LG_Clipboard ** clipboard = LG_Clipboards; *clipboard; clipboard++)
|
if (g_state.cbAvailable)
|
||||||
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.cbRequestList = ll_new();
|
g_state.cbRequestList = ll_new();
|
||||||
}
|
|
||||||
else
|
|
||||||
DEBUG_WARN("Failed to initialize the clipboard interface, continuing anyway");
|
|
||||||
|
|
||||||
LGMP_STATUS status;
|
LGMP_STATUS status;
|
||||||
|
|
||||||
@ -2263,19 +2078,18 @@ static void lg_shutdown(void)
|
|||||||
lgJoinThread(t_spice, NULL);
|
lgJoinThread(t_spice, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_state.lgc)
|
if (g_state.ds)
|
||||||
{
|
g_state.ds->shutdown();
|
||||||
g_state.lgc->free();
|
|
||||||
|
|
||||||
struct CBRequest *cbr;
|
if (g_state.cbRequestList)
|
||||||
while(ll_shift(g_state.cbRequestList, (void **)&cbr))
|
{
|
||||||
free(cbr);
|
|
||||||
ll_free(g_state.cbRequestList);
|
ll_free(g_state.cbRequestList);
|
||||||
|
g_state.cbRequestList = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_state.window)
|
if (g_state.window)
|
||||||
{
|
{
|
||||||
wmFree();
|
g_state.ds->free();
|
||||||
SDL_DestroyWindow(g_state.window);
|
SDL_DestroyWindow(g_state.window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "interface/app.h"
|
#include "interface/app.h"
|
||||||
|
#include "dynamic/displayservers.h"
|
||||||
#include "dynamic/renderers.h"
|
#include "dynamic/renderers.h"
|
||||||
#include "dynamic/clipboards.h"
|
|
||||||
#include "common/ivshmem.h"
|
#include "common/ivshmem.h"
|
||||||
|
|
||||||
#include "spice/spice.h"
|
#include "spice/spice.h"
|
||||||
@ -38,7 +38,10 @@ enum RunState
|
|||||||
|
|
||||||
struct AppState
|
struct AppState
|
||||||
{
|
{
|
||||||
enum RunState state;
|
enum RunState state;
|
||||||
|
|
||||||
|
struct LG_DisplayServerOps * ds;
|
||||||
|
|
||||||
bool stopVideo;
|
bool stopVideo;
|
||||||
bool ignoreInput;
|
bool ignoreInput;
|
||||||
bool escapeActive;
|
bool escapeActive;
|
||||||
@ -59,7 +62,7 @@ struct AppState
|
|||||||
void * lgrData;
|
void * lgrData;
|
||||||
atomic_int lgrResize;
|
atomic_int lgrResize;
|
||||||
|
|
||||||
const LG_Clipboard * lgc;
|
bool cbAvailable;
|
||||||
SpiceDataType cbType;
|
SpiceDataType cbType;
|
||||||
bool cbChunked;
|
bool cbChunked;
|
||||||
size_t cbXfer;
|
size_t cbXfer;
|
||||||
@ -236,6 +239,3 @@ struct CursorState
|
|||||||
// forwards
|
// forwards
|
||||||
extern struct AppState g_state;
|
extern struct AppState g_state;
|
||||||
extern struct AppParams params;
|
extern struct AppParams params;
|
||||||
|
|
||||||
void handleMouseGrabbed(double, double);
|
|
||||||
void handleMouseNormal(double, double);
|
|
||||||
|
495
client/src/wm.c
495
client/src/wm.c
@ -1,495 +0,0 @@
|
|||||||
/*
|
|
||||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
|
||||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
|
||||||
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 <stdbool.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
#include <wayland-client.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 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);
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
|
||||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
|
||||||
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 <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
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);
|
|
@ -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
|
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
|
// returns the page size
|
||||||
long sysinfo_getPageSize();
|
long sysinfo_getPageSize();
|
@ -18,51 +18,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <GL/glx.h>
|
|
||||||
|
|
||||||
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)
|
long sysinfo_getPageSize(void)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user