mirror of
https://github.com/gnif/LookingGlass.git
synced 2025-08-09 20:24:14 +00:00
[client] major restructure of platform specific code
This commit is contained in:
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
|
||||
};
|
Reference in New Issue
Block a user