GHOST: initial Wayland support
Usable with the CMake option 'WITH_GHOST_WAYLAND' The following functionality is working: - Building with X11 and Wayland at the same time, wayland is used when available. - Keyboard, pointer handling. - Cursor handling. - Dedicated off-screen windows. - Drag & drop. - Copy & paste. - Pointer grabbing. See D6567 for further details.
This commit is contained in:
committed by
Campbell Barton
parent
00e0034b13
commit
66e70fe299
@@ -207,6 +207,10 @@ mark_as_advanced(WITH_GHOST_DEBUG)
|
||||
option(WITH_GHOST_SDL "Enable building Blender against SDL for windowing rather than the native APIs" OFF)
|
||||
mark_as_advanced(WITH_GHOST_SDL)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
option(WITH_GHOST_WAYLAND "Enable building Blender against Wayland for windowing" OFF)
|
||||
endif()
|
||||
|
||||
if(WITH_X11)
|
||||
option(WITH_GHOST_XDND "Enable drag'n'drop support on X11 using XDND protocol" ON)
|
||||
endif()
|
||||
|
||||
@@ -440,6 +440,14 @@ function(SETUP_LIBDIRS)
|
||||
link_directories(${HDF5_LIBPATH})
|
||||
endif()
|
||||
|
||||
if(WITH_GHOST_WAYLAND)
|
||||
link_directories(
|
||||
${wayland-client_LIBRARY_DIRS}
|
||||
${wayland-egl_LIBRARY_DIRS}
|
||||
${xkbcommon_LIBRARY_DIRS}
|
||||
${wayland-cursor_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(WIN32 AND NOT UNIX)
|
||||
link_directories(${PTHREADS_LIBPATH})
|
||||
endif()
|
||||
|
||||
@@ -504,6 +504,26 @@ if(WITH_SYSTEM_AUDASPACE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_GHOST_WAYLAND)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(wayland-client REQUIRED wayland-client>=1.12)
|
||||
pkg_check_modules(wayland-egl REQUIRED wayland-egl)
|
||||
pkg_check_modules(wayland-scanner REQUIRED wayland-scanner)
|
||||
pkg_check_modules(xkbcommon REQUIRED xkbcommon)
|
||||
pkg_check_modules(wayland-cursor REQUIRED wayland-cursor)
|
||||
|
||||
set(WITH_GL_EGL ON)
|
||||
|
||||
if(WITH_GHOST_WAYLAND)
|
||||
list(APPEND PLATFORM_LINKLIBS
|
||||
${wayland-client_LIBRARIES}
|
||||
${wayland-egl_LIBRARIES}
|
||||
${xkbcommon_LIBRARIES}
|
||||
${wayland-cursor_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_X11)
|
||||
find_package(X11 REQUIRED)
|
||||
|
||||
|
||||
@@ -177,73 +177,141 @@ elseif(APPLE AND NOT WITH_X11)
|
||||
)
|
||||
endif()
|
||||
|
||||
elseif(WITH_X11)
|
||||
list(APPEND INC_SYS
|
||||
${X11_X11_INCLUDE_PATH}
|
||||
)
|
||||
|
||||
list(APPEND SRC
|
||||
intern/GHOST_DisplayManagerX11.cpp
|
||||
intern/GHOST_SystemX11.cpp
|
||||
intern/GHOST_TaskbarX11.cpp
|
||||
intern/GHOST_WindowX11.cpp
|
||||
|
||||
intern/GHOST_DisplayManagerX11.h
|
||||
intern/GHOST_IconX11.h
|
||||
intern/GHOST_SystemX11.h
|
||||
intern/GHOST_TaskbarX11.h
|
||||
intern/GHOST_WindowX11.h
|
||||
)
|
||||
|
||||
if(NOT WITH_GL_EGL)
|
||||
list(APPEND SRC
|
||||
intern/GHOST_ContextGLX.cpp
|
||||
|
||||
intern/GHOST_ContextGLX.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_GHOST_XDND)
|
||||
add_definitions(-DWITH_XDND)
|
||||
|
||||
list(APPEND LIB
|
||||
extern_xdnd
|
||||
)
|
||||
|
||||
list(APPEND INC
|
||||
../../extern/xdnd
|
||||
elseif(WITH_X11 OR WITH_GHOST_WAYLAND)
|
||||
if(WITH_X11)
|
||||
list(APPEND INC_SYS
|
||||
${X11_X11_INCLUDE_PATH}
|
||||
)
|
||||
|
||||
list(APPEND SRC
|
||||
intern/GHOST_DropTargetX11.cpp
|
||||
intern/GHOST_DisplayManagerX11.cpp
|
||||
intern/GHOST_SystemX11.cpp
|
||||
intern/GHOST_TaskbarX11.cpp
|
||||
intern/GHOST_WindowX11.cpp
|
||||
|
||||
intern/GHOST_DropTargetX11.h
|
||||
intern/GHOST_DisplayManagerX11.h
|
||||
intern/GHOST_IconX11.h
|
||||
intern/GHOST_SystemX11.h
|
||||
intern/GHOST_TaskbarX11.h
|
||||
intern/GHOST_WindowX11.h
|
||||
)
|
||||
|
||||
if(NOT WITH_GL_EGL)
|
||||
list(APPEND SRC
|
||||
intern/GHOST_ContextGLX.cpp
|
||||
|
||||
intern/GHOST_ContextGLX.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_GHOST_XDND)
|
||||
add_definitions(-DWITH_XDND)
|
||||
|
||||
list(APPEND LIB
|
||||
extern_xdnd
|
||||
)
|
||||
|
||||
list(APPEND INC
|
||||
../../extern/xdnd
|
||||
)
|
||||
|
||||
list(APPEND SRC
|
||||
intern/GHOST_DropTargetX11.cpp
|
||||
|
||||
intern/GHOST_DropTargetX11.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(X11_XF86keysym_INCLUDE_PATH)
|
||||
add_definitions(-DWITH_XF86KEYSYM)
|
||||
list(APPEND INC_SYS
|
||||
${X11_XF86keysym_INCLUDE_PATH}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_X11_XF86VMODE)
|
||||
add_definitions(-DWITH_X11_XF86VMODE)
|
||||
list(APPEND INC_SYS
|
||||
${X11_xf86vmode_INCLUDE_PATH}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_X11_XFIXES)
|
||||
add_definitions(-DWITH_X11_XFIXES)
|
||||
list(APPEND INC_SYS
|
||||
${X11_Xfixes_INCLUDE_PATH}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_X11_ALPHA)
|
||||
add_definitions(-DWITH_X11_ALPHA)
|
||||
endif()
|
||||
|
||||
if(WITH_X11_XINPUT)
|
||||
add_definitions(-DWITH_X11_XINPUT)
|
||||
list(APPEND INC_SYS
|
||||
${X11_Xinput_INCLUDE_PATH}
|
||||
)
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_X11)
|
||||
endif()
|
||||
|
||||
if(X11_XF86keysym_INCLUDE_PATH)
|
||||
add_definitions(-DWITH_XF86KEYSYM)
|
||||
if(WITH_GHOST_WAYLAND)
|
||||
list(APPEND INC_SYS
|
||||
${X11_XF86keysym_INCLUDE_PATH}
|
||||
${wayland-client_INCLUDE_DIRS}
|
||||
${wayland-egl_INCLUDE_DIRS}
|
||||
${xkbcommon_INCLUDE_DIRS}
|
||||
${wayland-cursor_INCLUDE_DIRS}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_X11_XF86VMODE)
|
||||
add_definitions(-DWITH_X11_XF86VMODE)
|
||||
list(APPEND INC_SYS
|
||||
${X11_xf86vmode_INCLUDE_PATH}
|
||||
list(APPEND SRC
|
||||
intern/GHOST_SystemWayland.cpp
|
||||
intern/GHOST_WindowWayland.cpp
|
||||
|
||||
intern/GHOST_SystemWayland.h
|
||||
intern/GHOST_WindowWayland.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_X11_XFIXES)
|
||||
add_definitions(-DWITH_X11_XFIXES)
|
||||
list(APPEND INC_SYS
|
||||
${X11_Xfixes_INCLUDE_PATH}
|
||||
pkg_get_variable(WAYLAND_SCANNER wayland-scanner wayland_scanner)
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||
|
||||
# Generate protocols bindings.
|
||||
macro(generate_protocol_bindings NAME PROT_DEF)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${NAME}-client-protocol.h
|
||||
COMMAND ${WAYLAND_SCANNER} client-header ${PROT_DEF} ${NAME}-client-protocol.h
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${NAME}-client-protocol.c
|
||||
COMMAND ${WAYLAND_SCANNER} private-code ${PROT_DEF} ${NAME}-client-protocol.c
|
||||
DEPENDS ${NAME}-client-protocol.h
|
||||
)
|
||||
list(APPEND SRC
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${NAME}-client-protocol.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${NAME}-client-protocol.h
|
||||
)
|
||||
endmacro()
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# xdg-shell.
|
||||
generate_protocol_bindings(
|
||||
xdg-shell
|
||||
"${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml"
|
||||
)
|
||||
# Pointer-constraints.
|
||||
generate_protocol_bindings(
|
||||
pointer-constraints
|
||||
"${WAYLAND_PROTOCOLS_DIR}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
|
||||
)
|
||||
# Relative-pointer.
|
||||
generate_protocol_bindings(
|
||||
relative-pointer
|
||||
"${WAYLAND_PROTOCOLS_DIR}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_X11_ALPHA)
|
||||
add_definitions(-DWITH_X11_ALPHA)
|
||||
add_definitions(-DWITH_GHOST_WAYLAND)
|
||||
endif()
|
||||
|
||||
if(WITH_INPUT_NDOF)
|
||||
|
||||
@@ -27,8 +27,13 @@
|
||||
|
||||
#include "GHOST_ISystem.h"
|
||||
|
||||
#ifdef WITH_X11
|
||||
# include "GHOST_SystemX11.h"
|
||||
#if defined(WITH_X11) || defined(WITH_GHOST_WAYLAND)
|
||||
# ifdef WITH_X11
|
||||
# include "GHOST_SystemX11.h"
|
||||
# endif
|
||||
# ifdef WITH_GHOST_WAYLAND
|
||||
# include "GHOST_SystemWayland.h"
|
||||
# endif
|
||||
#else
|
||||
# ifdef WITH_HEADLESS
|
||||
# include "GHOST_SystemNULL.h"
|
||||
@@ -49,8 +54,19 @@ GHOST_TSuccess GHOST_ISystem::createSystem()
|
||||
{
|
||||
GHOST_TSuccess success;
|
||||
if (!m_system) {
|
||||
#ifdef WITH_X11
|
||||
m_system = new GHOST_SystemX11();
|
||||
#if defined(WITH_X11) || defined(WITH_GHOST_WAYLAND)
|
||||
# ifdef WITH_GHOST_WAYLAND
|
||||
try {
|
||||
m_system = new GHOST_SystemWayland();
|
||||
}
|
||||
catch (const std::exception &) {
|
||||
}
|
||||
# endif
|
||||
# ifdef WITH_X11
|
||||
if (!m_system) {
|
||||
m_system = new GHOST_SystemX11();
|
||||
}
|
||||
# endif
|
||||
#else
|
||||
# ifdef WITH_HEADLESS
|
||||
m_system = new GHOST_SystemNULL();
|
||||
|
||||
1648
intern/ghost/intern/GHOST_SystemWayland.cpp
Normal file
1648
intern/ghost/intern/GHOST_SystemWayland.cpp
Normal file
@@ -0,0 +1,1648 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
#include "GHOST_SystemWayland.h"
|
||||
#include "GHOST_Event.h"
|
||||
#include "GHOST_EventButton.h"
|
||||
#include "GHOST_EventCursor.h"
|
||||
#include "GHOST_EventDragnDrop.h"
|
||||
#include "GHOST_EventKey.h"
|
||||
#include "GHOST_EventWheel.h"
|
||||
#include "GHOST_WindowManager.h"
|
||||
|
||||
#include "GHOST_ContextEGL.h"
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-egl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <pointer-constraints-client-protocol.h>
|
||||
#include <relative-pointer-client-protocol.h>
|
||||
#include <wayland-cursor.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
struct output_t {
|
||||
struct wl_output *output;
|
||||
int32_t width, height;
|
||||
int transform;
|
||||
int scale;
|
||||
std::string make;
|
||||
std::string model;
|
||||
};
|
||||
|
||||
struct buffer_t {
|
||||
void *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct cursor_t {
|
||||
bool visible;
|
||||
struct wl_surface *surface = nullptr;
|
||||
struct wl_buffer *buffer;
|
||||
struct wl_cursor_image image;
|
||||
struct buffer_t *file_buffer = nullptr;
|
||||
};
|
||||
|
||||
struct data_offer_t {
|
||||
std::unordered_set<std::string> types;
|
||||
uint32_t source_actions;
|
||||
uint32_t dnd_action;
|
||||
struct wl_data_offer *id;
|
||||
std::atomic<bool> in_use;
|
||||
struct {
|
||||
int x, y;
|
||||
} dnd;
|
||||
};
|
||||
|
||||
struct data_source_t {
|
||||
struct wl_data_source *data_source;
|
||||
/** Last device that was active. */
|
||||
uint32_t source_serial;
|
||||
char *buffer_out;
|
||||
};
|
||||
|
||||
struct input_t {
|
||||
GHOST_SystemWayland *system;
|
||||
|
||||
std::string name;
|
||||
struct wl_seat *seat;
|
||||
struct wl_pointer *pointer = nullptr;
|
||||
struct wl_keyboard *keyboard = nullptr;
|
||||
|
||||
uint32_t pointer_serial;
|
||||
int x, y;
|
||||
GHOST_Buttons buttons;
|
||||
struct cursor_t cursor;
|
||||
|
||||
struct zwp_relative_pointer_v1 *relative_pointer;
|
||||
struct zwp_locked_pointer_v1 *locked_pointer;
|
||||
|
||||
struct xkb_context *xkb_context;
|
||||
struct xkb_state *xkb_state;
|
||||
|
||||
struct wl_data_device *data_device = nullptr;
|
||||
struct data_offer_t *data_offer_dnd; /* Drag & Drop. */
|
||||
struct data_offer_t *data_offer_copy_paste; /* Copy & Paste. */
|
||||
|
||||
struct data_source_t *data_source;
|
||||
};
|
||||
|
||||
struct display_t {
|
||||
GHOST_SystemWayland *system;
|
||||
|
||||
struct wl_display *display;
|
||||
struct wl_registry *registry;
|
||||
struct wl_compositor *compositor = nullptr;
|
||||
struct xdg_wm_base *xdg_shell = nullptr;
|
||||
struct wl_shm *shm = nullptr;
|
||||
std::vector<output_t *> outputs;
|
||||
std::vector<input_t *> inputs;
|
||||
struct wl_cursor_theme *cursor_theme = nullptr;
|
||||
struct wl_data_device_manager *data_device_manager = nullptr;
|
||||
struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr;
|
||||
struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr;
|
||||
|
||||
std::vector<struct wl_surface *> os_surfaces;
|
||||
std::vector<struct wl_egl_window *> os_egl_windows;
|
||||
};
|
||||
|
||||
static void display_destroy(display_t *d)
|
||||
{
|
||||
if (d->data_device_manager) {
|
||||
wl_data_device_manager_destroy(d->data_device_manager);
|
||||
}
|
||||
|
||||
for (output_t *output : d->outputs) {
|
||||
wl_output_destroy(output->output);
|
||||
delete output;
|
||||
}
|
||||
|
||||
for (input_t *input : d->inputs) {
|
||||
if (input->data_source) {
|
||||
free(input->data_source->buffer_out);
|
||||
if (input->data_source->data_source) {
|
||||
wl_data_source_destroy(input->data_source->data_source);
|
||||
}
|
||||
delete input->data_source;
|
||||
}
|
||||
if (input->data_offer_copy_paste) {
|
||||
wl_data_offer_destroy(input->data_offer_copy_paste->id);
|
||||
delete input->data_offer_copy_paste;
|
||||
}
|
||||
if (input->data_device) {
|
||||
wl_data_device_release(input->data_device);
|
||||
}
|
||||
if (input->pointer) {
|
||||
if (input->cursor.file_buffer) {
|
||||
munmap(input->cursor.file_buffer->data, input->cursor.file_buffer->size);
|
||||
delete input->cursor.file_buffer;
|
||||
}
|
||||
if (input->cursor.surface) {
|
||||
wl_surface_destroy(input->cursor.surface);
|
||||
}
|
||||
if (input->pointer) {
|
||||
wl_pointer_destroy(input->pointer);
|
||||
}
|
||||
}
|
||||
if (input->keyboard) {
|
||||
wl_keyboard_destroy(input->keyboard);
|
||||
}
|
||||
if (input->xkb_state) {
|
||||
xkb_state_unref(input->xkb_state);
|
||||
}
|
||||
if (input->xkb_context) {
|
||||
xkb_context_unref(input->xkb_context);
|
||||
}
|
||||
wl_seat_destroy(input->seat);
|
||||
delete input;
|
||||
}
|
||||
|
||||
if (d->cursor_theme) {
|
||||
wl_cursor_theme_destroy(d->cursor_theme);
|
||||
}
|
||||
|
||||
if (d->shm) {
|
||||
wl_shm_destroy(d->shm);
|
||||
}
|
||||
|
||||
if (d->relative_pointer_manager) {
|
||||
zwp_relative_pointer_manager_v1_destroy(d->relative_pointer_manager);
|
||||
}
|
||||
|
||||
if (d->pointer_constraints) {
|
||||
zwp_pointer_constraints_v1_destroy(d->pointer_constraints);
|
||||
}
|
||||
|
||||
for (wl_egl_window *os_egl_window : d->os_egl_windows) {
|
||||
wl_egl_window_destroy(os_egl_window);
|
||||
}
|
||||
|
||||
for (wl_surface *os_surface : d->os_surfaces) {
|
||||
wl_surface_destroy(os_surface);
|
||||
}
|
||||
|
||||
if (d->compositor) {
|
||||
wl_compositor_destroy(d->compositor);
|
||||
}
|
||||
|
||||
if (d->xdg_shell) {
|
||||
xdg_wm_base_destroy(d->xdg_shell);
|
||||
}
|
||||
|
||||
if (eglGetDisplay) {
|
||||
::eglTerminate(eglGetDisplay(EGLNativeDisplayType(d->display)));
|
||||
}
|
||||
|
||||
if (d->display) {
|
||||
wl_display_disconnect(d->display);
|
||||
}
|
||||
|
||||
delete d;
|
||||
}
|
||||
|
||||
static GHOST_TKey xkb_map_gkey(const xkb_keysym_t &sym)
|
||||
{
|
||||
static const std::unordered_map<xkb_keysym_t, GHOST_TKey> special_keys = {
|
||||
{XKB_KEY_BackSpace, GHOST_kKeyBackSpace},
|
||||
{XKB_KEY_Tab, GHOST_kKeyTab},
|
||||
{XKB_KEY_Linefeed, GHOST_kKeyLinefeed},
|
||||
{XKB_KEY_Clear, GHOST_kKeyClear},
|
||||
{XKB_KEY_Return, GHOST_kKeyEnter},
|
||||
|
||||
{XKB_KEY_Escape, GHOST_kKeyEsc},
|
||||
{XKB_KEY_space, GHOST_kKeySpace},
|
||||
{XKB_KEY_comma, GHOST_kKeyComma},
|
||||
{XKB_KEY_minus, GHOST_kKeyMinus},
|
||||
{XKB_KEY_plus, GHOST_kKeyPlus},
|
||||
{XKB_KEY_period, GHOST_kKeyPeriod},
|
||||
{XKB_KEY_slash, GHOST_kKeySlash},
|
||||
|
||||
{XKB_KEY_semicolon, GHOST_kKeySemicolon},
|
||||
{XKB_KEY_equal, GHOST_kKeyEqual},
|
||||
|
||||
{XKB_KEY_bracketleft, GHOST_kKeyLeftBracket},
|
||||
{XKB_KEY_bracketright, GHOST_kKeyRightBracket},
|
||||
{XKB_KEY_backslash, GHOST_kKeyBackslash},
|
||||
{XKB_KEY_grave, GHOST_kKeyAccentGrave},
|
||||
|
||||
{XKB_KEY_Shift_L, GHOST_kKeyLeftShift},
|
||||
{XKB_KEY_Shift_R, GHOST_kKeyRightShift},
|
||||
{XKB_KEY_Control_L, GHOST_kKeyLeftControl},
|
||||
{XKB_KEY_Control_R, GHOST_kKeyRightControl},
|
||||
{XKB_KEY_Alt_L, GHOST_kKeyLeftAlt},
|
||||
{XKB_KEY_Alt_R, GHOST_kKeyRightAlt},
|
||||
{XKB_KEY_Super_L, GHOST_kKeyOS},
|
||||
{XKB_KEY_Super_R, GHOST_kKeyOS},
|
||||
{XKB_KEY_Menu, GHOST_kKeyApp},
|
||||
|
||||
{XKB_KEY_Caps_Lock, GHOST_kKeyCapsLock},
|
||||
{XKB_KEY_Num_Lock, GHOST_kKeyNumLock},
|
||||
{XKB_KEY_Scroll_Lock, GHOST_kKeyScrollLock},
|
||||
|
||||
{XKB_KEY_Left, GHOST_kKeyLeftArrow},
|
||||
{XKB_KEY_KP_Left, GHOST_kKeyLeftArrow},
|
||||
{XKB_KEY_Right, GHOST_kKeyRightArrow},
|
||||
{XKB_KEY_KP_Right, GHOST_kKeyRightArrow},
|
||||
{XKB_KEY_Up, GHOST_kKeyUpArrow},
|
||||
{XKB_KEY_KP_Up, GHOST_kKeyUpArrow},
|
||||
{XKB_KEY_Down, GHOST_kKeyDownArrow},
|
||||
{XKB_KEY_KP_Down, GHOST_kKeyDownArrow},
|
||||
|
||||
{XKB_KEY_Print, GHOST_kKeyPrintScreen},
|
||||
{XKB_KEY_Pause, GHOST_kKeyPause},
|
||||
|
||||
{XKB_KEY_Insert, GHOST_kKeyInsert},
|
||||
{XKB_KEY_KP_Insert, GHOST_kKeyInsert},
|
||||
{XKB_KEY_Delete, GHOST_kKeyDelete},
|
||||
{XKB_KEY_KP_Delete, GHOST_kKeyDelete},
|
||||
{XKB_KEY_Home, GHOST_kKeyHome},
|
||||
{XKB_KEY_KP_Home, GHOST_kKeyHome},
|
||||
{XKB_KEY_End, GHOST_kKeyEnd},
|
||||
{XKB_KEY_KP_End, GHOST_kKeyEnd},
|
||||
{XKB_KEY_Page_Up, GHOST_kKeyUpPage},
|
||||
{XKB_KEY_KP_Page_Up, GHOST_kKeyUpPage},
|
||||
{XKB_KEY_Page_Down, GHOST_kKeyDownPage},
|
||||
{XKB_KEY_KP_Page_Down, GHOST_kKeyDownPage},
|
||||
|
||||
{XKB_KEY_KP_Decimal, GHOST_kKeyNumpadPeriod},
|
||||
{XKB_KEY_KP_Enter, GHOST_kKeyNumpadEnter},
|
||||
{XKB_KEY_KP_Add, GHOST_kKeyNumpadPlus},
|
||||
{XKB_KEY_KP_Subtract, GHOST_kKeyNumpadMinus},
|
||||
{XKB_KEY_KP_Multiply, GHOST_kKeyNumpadAsterisk},
|
||||
{XKB_KEY_KP_Divide, GHOST_kKeyNumpadSlash},
|
||||
|
||||
{XKB_KEY_XF86AudioPlay, GHOST_kKeyMediaPlay},
|
||||
{XKB_KEY_XF86AudioStop, GHOST_kKeyMediaStop},
|
||||
{XKB_KEY_XF86AudioPrev, GHOST_kKeyMediaFirst},
|
||||
{XKB_KEY_XF86AudioNext, GHOST_kKeyMediaLast},
|
||||
};
|
||||
|
||||
GHOST_TKey gkey = GHOST_kKeyUnknown;
|
||||
if (special_keys.count(sym)) {
|
||||
gkey = special_keys.at(sym);
|
||||
}
|
||||
else if (sym >= XKB_KEY_0 && sym <= XKB_KEY_9) {
|
||||
gkey = GHOST_TKey(sym);
|
||||
}
|
||||
else if (sym >= XKB_KEY_KP_0 && sym <= XKB_KEY_KP_9) {
|
||||
gkey = GHOST_TKey(GHOST_kKeyNumpad0 + sym - XKB_KEY_KP_0);
|
||||
}
|
||||
else if (sym >= XKB_KEY_A && sym <= XKB_KEY_Z) {
|
||||
gkey = GHOST_TKey(sym);
|
||||
}
|
||||
else if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) {
|
||||
gkey = GHOST_TKey(sym - XKB_KEY_a + XKB_KEY_A);
|
||||
}
|
||||
else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F24) {
|
||||
gkey = GHOST_TKey(GHOST_kKeyF1 + sym - XKB_KEY_F1);
|
||||
}
|
||||
else {
|
||||
GHOST_PRINT("unhandled key: " << sym << std::endl);
|
||||
}
|
||||
|
||||
return gkey;
|
||||
}
|
||||
|
||||
static const int default_cursor_size = 24;
|
||||
|
||||
static const std::unordered_map<GHOST_TStandardCursor, std::string> cursors = {
|
||||
{GHOST_kStandardCursorDefault, "left_ptr"},
|
||||
{GHOST_kStandardCursorRightArrow, "right_ptr"},
|
||||
{GHOST_kStandardCursorLeftArrow, "left_ptr"},
|
||||
{GHOST_kStandardCursorInfo, ""},
|
||||
{GHOST_kStandardCursorDestroy, ""},
|
||||
{GHOST_kStandardCursorHelp, "question_arrow"},
|
||||
{GHOST_kStandardCursorWait, "watch"},
|
||||
{GHOST_kStandardCursorText, "xterm"},
|
||||
{GHOST_kStandardCursorCrosshair, "crosshair"},
|
||||
{GHOST_kStandardCursorCrosshairA, ""},
|
||||
{GHOST_kStandardCursorCrosshairB, ""},
|
||||
{GHOST_kStandardCursorCrosshairC, ""},
|
||||
{GHOST_kStandardCursorPencil, ""},
|
||||
{GHOST_kStandardCursorUpArrow, "sb_up_arrow"},
|
||||
{GHOST_kStandardCursorDownArrow, "sb_down_arrow"},
|
||||
{GHOST_kStandardCursorVerticalSplit, ""},
|
||||
{GHOST_kStandardCursorHorizontalSplit, ""},
|
||||
{GHOST_kStandardCursorEraser, ""},
|
||||
{GHOST_kStandardCursorKnife, ""},
|
||||
{GHOST_kStandardCursorEyedropper, ""},
|
||||
{GHOST_kStandardCursorZoomIn, ""},
|
||||
{GHOST_kStandardCursorZoomOut, ""},
|
||||
{GHOST_kStandardCursorMove, "move"},
|
||||
{GHOST_kStandardCursorNSEWScroll, ""},
|
||||
{GHOST_kStandardCursorNSScroll, ""},
|
||||
{GHOST_kStandardCursorEWScroll, ""},
|
||||
{GHOST_kStandardCursorStop, ""},
|
||||
{GHOST_kStandardCursorUpDown, "sb_v_double_arrow"},
|
||||
{GHOST_kStandardCursorLeftRight, "sb_h_double_arrow"},
|
||||
{GHOST_kStandardCursorTopSide, "top_side"},
|
||||
{GHOST_kStandardCursorBottomSide, "bottom_side"},
|
||||
{GHOST_kStandardCursorLeftSide, "left_side"},
|
||||
{GHOST_kStandardCursorRightSide, "right_side"},
|
||||
{GHOST_kStandardCursorTopLeftCorner, "top_left_corner"},
|
||||
{GHOST_kStandardCursorTopRightCorner, "top_right_corner"},
|
||||
{GHOST_kStandardCursorBottomRightCorner, "bottom_right_corner"},
|
||||
{GHOST_kStandardCursorBottomLeftCorner, "bottom_left_corner"},
|
||||
{GHOST_kStandardCursorCopy, "copy"},
|
||||
};
|
||||
|
||||
static constexpr const char *mime_text_plain = "text/plain";
|
||||
static constexpr const char *mime_text_utf8 = "text/plain;charset=utf-8";
|
||||
static constexpr const char *mime_text_uri = "text/uri-list";
|
||||
|
||||
static const std::unordered_map<std::string, GHOST_TDragnDropTypes> mime_dnd = {
|
||||
{mime_text_plain, GHOST_kDragnDropTypeString},
|
||||
{mime_text_utf8, GHOST_kDragnDropTypeString},
|
||||
{mime_text_uri, GHOST_kDragnDropTypeFilenames},
|
||||
};
|
||||
|
||||
static const std::vector<std::string> mime_preference_order = {
|
||||
mime_text_uri,
|
||||
mime_text_utf8,
|
||||
mime_text_plain,
|
||||
};
|
||||
|
||||
static const std::vector<std::string> mime_send = {
|
||||
"UTF8_STRING",
|
||||
"COMPOUND_TEXT",
|
||||
"TEXT",
|
||||
"STRING",
|
||||
"text/plain;charset=utf-8",
|
||||
"text/plain",
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Interface Callbacks
|
||||
*
|
||||
* These callbacks are registered for Wayland interfaces and called when
|
||||
* an event is received from the compositor.
|
||||
* \{ */
|
||||
|
||||
static void relative_pointer_relative_motion(
|
||||
void *data,
|
||||
struct zwp_relative_pointer_v1 * /*zwp_relative_pointer_v1*/,
|
||||
uint32_t /*utime_hi*/,
|
||||
uint32_t /*utime_lo*/,
|
||||
wl_fixed_t dx,
|
||||
wl_fixed_t dy,
|
||||
wl_fixed_t /*dx_unaccel*/,
|
||||
wl_fixed_t /*dy_unaccel*/)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
|
||||
input->x += wl_fixed_to_int(dx);
|
||||
input->y += wl_fixed_to_int(dy);
|
||||
|
||||
input->system->pushEvent(
|
||||
new GHOST_EventCursor(input->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
input->system->getWindowManager()->getActiveWindow(),
|
||||
input->x,
|
||||
input->y,
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
|
||||
static const zwp_relative_pointer_v1_listener relative_pointer_listener = {
|
||||
relative_pointer_relative_motion};
|
||||
|
||||
static void dnd_events(const input_t *const input, const GHOST_TEventType event)
|
||||
{
|
||||
const GHOST_TUns64 time = input->system->getMilliSeconds();
|
||||
GHOST_IWindow *const window = input->system->getWindowManager()->getActiveWindow();
|
||||
for (const std::string &type : mime_preference_order) {
|
||||
input->system->pushEvent(new GHOST_EventDragnDrop(time,
|
||||
event,
|
||||
mime_dnd.at(type),
|
||||
window,
|
||||
input->data_offer_dnd->dnd.x,
|
||||
input->data_offer_dnd->dnd.y,
|
||||
nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
static std::string read_pipe(data_offer_t *data_offer, const std::string mime_receive)
|
||||
{
|
||||
int pipefd[2];
|
||||
pipe(pipefd);
|
||||
wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
|
||||
std::string data;
|
||||
ssize_t len;
|
||||
char buffer[4096];
|
||||
while ((len = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
|
||||
data.insert(data.end(), buffer, buffer + len);
|
||||
}
|
||||
close(pipefd[0]);
|
||||
data_offer->in_use.store(false);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* A target accepts an offered mime type.
|
||||
*
|
||||
* Sent when a target accepts pointer_focus or motion events. If
|
||||
* a target does not accept any of the offered types, type is NULL.
|
||||
*/
|
||||
static void data_source_target(void * /*data*/,
|
||||
struct wl_data_source * /*wl_data_source*/,
|
||||
const char * /*mime_type*/)
|
||||
{
|
||||
/* pass */
|
||||
}
|
||||
|
||||
static void data_source_send(void *data,
|
||||
struct wl_data_source * /*wl_data_source*/,
|
||||
const char * /*mime_type*/,
|
||||
int32_t fd)
|
||||
{
|
||||
const char *const buffer = static_cast<char *>(data);
|
||||
write(fd, buffer, strlen(buffer) + 1);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void data_source_cancelled(void * /*data*/, struct wl_data_source *wl_data_source)
|
||||
{
|
||||
wl_data_source_destroy(wl_data_source);
|
||||
}
|
||||
|
||||
/**
|
||||
* The drag-and-drop operation physically finished.
|
||||
*
|
||||
* The user performed the drop action. This event does not
|
||||
* indicate acceptance, #wl_data_source.cancelled may still be
|
||||
* emitted afterwards if the drop destination does not accept any mime type.
|
||||
*/
|
||||
static void data_source_dnd_drop_performed(void * /*data*/,
|
||||
struct wl_data_source * /*wl_data_source*/)
|
||||
{
|
||||
/* pass */
|
||||
}
|
||||
|
||||
/**
|
||||
* The drag-and-drop operation concluded.
|
||||
*
|
||||
* The drop destination finished interoperating with this data
|
||||
* source, so the client is now free to destroy this data source
|
||||
* and free all associated data.
|
||||
*/
|
||||
static void data_source_dnd_finished(void * /*data*/, struct wl_data_source * /*wl_data_source*/)
|
||||
{
|
||||
/* pass */
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the selected action.
|
||||
*
|
||||
* This event indicates the action selected by the compositor
|
||||
* after matching the source/destination side actions. Only one
|
||||
* action (or none) will be offered here.
|
||||
*/
|
||||
static void data_source_action(void * /*data*/,
|
||||
struct wl_data_source * /*wl_data_source*/,
|
||||
uint32_t /*dnd_action*/)
|
||||
{
|
||||
/* pass */
|
||||
}
|
||||
|
||||
static const struct wl_data_source_listener data_source_listener = {
|
||||
data_source_target,
|
||||
data_source_send,
|
||||
data_source_cancelled,
|
||||
data_source_dnd_drop_performed,
|
||||
data_source_dnd_finished,
|
||||
data_source_action,
|
||||
};
|
||||
|
||||
static void data_offer_offer(void *data,
|
||||
struct wl_data_offer * /*wl_data_offer*/,
|
||||
const char *mime_type)
|
||||
{
|
||||
static_cast<data_offer_t *>(data)->types.insert(mime_type);
|
||||
}
|
||||
|
||||
static void data_offer_source_actions(void *data,
|
||||
struct wl_data_offer * /*wl_data_offer*/,
|
||||
uint32_t source_actions)
|
||||
{
|
||||
static_cast<data_offer_t *>(data)->source_actions = source_actions;
|
||||
}
|
||||
|
||||
static void data_offer_action(void *data,
|
||||
struct wl_data_offer * /*wl_data_offer*/,
|
||||
uint32_t dnd_action)
|
||||
{
|
||||
static_cast<data_offer_t *>(data)->dnd_action = dnd_action;
|
||||
}
|
||||
|
||||
static const struct wl_data_offer_listener data_offer_listener = {
|
||||
data_offer_offer,
|
||||
data_offer_source_actions,
|
||||
data_offer_action,
|
||||
};
|
||||
|
||||
static void data_device_data_offer(void * /*data*/,
|
||||
struct wl_data_device * /*wl_data_device*/,
|
||||
struct wl_data_offer *id)
|
||||
{
|
||||
data_offer_t *data_offer = new data_offer_t;
|
||||
data_offer->id = id;
|
||||
wl_data_offer_add_listener(id, &data_offer_listener, data_offer);
|
||||
}
|
||||
|
||||
static void data_device_enter(void *data,
|
||||
struct wl_data_device * /*wl_data_device*/,
|
||||
uint32_t serial,
|
||||
struct wl_surface * /*surface*/,
|
||||
wl_fixed_t x,
|
||||
wl_fixed_t y,
|
||||
struct wl_data_offer *id)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
input->data_offer_dnd = static_cast<data_offer_t *>(wl_data_offer_get_user_data(id));
|
||||
data_offer_t *data_offer = input->data_offer_dnd;
|
||||
|
||||
data_offer->in_use.store(true);
|
||||
data_offer->dnd.x = wl_fixed_to_int(x);
|
||||
data_offer->dnd.y = wl_fixed_to_int(y);
|
||||
|
||||
wl_data_offer_set_actions(id,
|
||||
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
|
||||
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
|
||||
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
|
||||
|
||||
for (const std::string &type : mime_preference_order) {
|
||||
wl_data_offer_accept(id, serial, type.c_str());
|
||||
}
|
||||
|
||||
dnd_events(input, GHOST_kEventDraggingEntered);
|
||||
}
|
||||
|
||||
static void data_device_leave(void *data, struct wl_data_device * /*wl_data_device*/)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
|
||||
dnd_events(input, GHOST_kEventDraggingExited);
|
||||
|
||||
if (input->data_offer_dnd && !input->data_offer_dnd->in_use.load()) {
|
||||
wl_data_offer_destroy(input->data_offer_dnd->id);
|
||||
delete input->data_offer_dnd;
|
||||
input->data_offer_dnd = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void data_device_motion(void *data,
|
||||
struct wl_data_device * /*wl_data_device*/,
|
||||
uint32_t /*time*/,
|
||||
wl_fixed_t x,
|
||||
wl_fixed_t y)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
input->data_offer_dnd->dnd.x = wl_fixed_to_int(x);
|
||||
input->data_offer_dnd->dnd.y = wl_fixed_to_int(y);
|
||||
dnd_events(input, GHOST_kEventDraggingUpdated);
|
||||
}
|
||||
|
||||
static void data_device_drop(void *data, struct wl_data_device * /*wl_data_device*/)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
data_offer_t *data_offer = input->data_offer_dnd;
|
||||
|
||||
const std::string mime_receive = *std::find_first_of(mime_preference_order.begin(),
|
||||
mime_preference_order.end(),
|
||||
data_offer->types.begin(),
|
||||
data_offer->types.end());
|
||||
|
||||
auto read_uris = [](GHOST_SystemWayland *const system,
|
||||
data_offer_t *data_offer,
|
||||
const std::string mime_receive) {
|
||||
const int x = data_offer->dnd.x;
|
||||
const int y = data_offer->dnd.y;
|
||||
|
||||
const std::string data = read_pipe(data_offer, mime_receive);
|
||||
|
||||
wl_data_offer_finish(data_offer->id);
|
||||
wl_data_offer_destroy(data_offer->id);
|
||||
|
||||
delete data_offer;
|
||||
data_offer = nullptr;
|
||||
|
||||
if (mime_receive == mime_text_uri) {
|
||||
static constexpr const char *file_proto = "file://";
|
||||
static constexpr const char *crlf = "\r\n";
|
||||
|
||||
std::vector<std::string> uris;
|
||||
|
||||
size_t pos = 0;
|
||||
while (true) {
|
||||
pos = data.find(file_proto, pos);
|
||||
const size_t start = pos + sizeof(file_proto) - 1;
|
||||
pos = data.find(crlf, pos);
|
||||
const size_t end = pos;
|
||||
|
||||
if (pos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
uris.push_back(data.substr(start, end - start));
|
||||
}
|
||||
|
||||
GHOST_TStringArray *flist = static_cast<GHOST_TStringArray *>(
|
||||
malloc(sizeof(GHOST_TStringArray)));
|
||||
flist->count = int(uris.size());
|
||||
flist->strings = static_cast<GHOST_TUns8 **>(malloc(uris.size() * sizeof(GHOST_TUns8 *)));
|
||||
for (size_t i = 0; i < uris.size(); i++) {
|
||||
flist->strings[i] = static_cast<GHOST_TUns8 *>(
|
||||
malloc((uris[i].size() + 1) * sizeof(GHOST_TUns8)));
|
||||
memcpy(flist->strings[i], uris[i].data(), uris[i].size() + 1);
|
||||
}
|
||||
system->pushEvent(new GHOST_EventDragnDrop(system->getMilliSeconds(),
|
||||
GHOST_kEventDraggingDropDone,
|
||||
GHOST_kDragnDropTypeFilenames,
|
||||
system->getWindowManager()->getActiveWindow(),
|
||||
x,
|
||||
y,
|
||||
flist));
|
||||
}
|
||||
else if (mime_receive == mime_text_plain || mime_receive == mime_text_utf8) {
|
||||
/* TODO: enable use of internal functions 'txt_insert_buf' and
|
||||
* 'text_update_edited' to behave like dropped text was pasted. */
|
||||
}
|
||||
wl_display_roundtrip(system->display());
|
||||
};
|
||||
|
||||
std::thread read_thread(read_uris, input->system, data_offer, mime_receive);
|
||||
read_thread.detach();
|
||||
}
|
||||
|
||||
static void data_device_selection(void *data,
|
||||
struct wl_data_device * /*wl_data_device*/,
|
||||
struct wl_data_offer *id)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
data_offer_t *data_offer = input->data_offer_copy_paste;
|
||||
|
||||
/* Delete old data offer. */
|
||||
if (data_offer != nullptr) {
|
||||
wl_data_offer_destroy(data_offer->id);
|
||||
delete data_offer;
|
||||
data_offer = nullptr;
|
||||
}
|
||||
|
||||
if (id == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get new data offer. */
|
||||
data_offer = static_cast<data_offer_t *>(wl_data_offer_get_user_data(id));
|
||||
input->data_offer_copy_paste = data_offer;
|
||||
|
||||
std::string mime_receive;
|
||||
for (const std::string &type : {mime_text_utf8, mime_text_plain}) {
|
||||
if (data_offer->types.count(type)) {
|
||||
mime_receive = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto read_selection = [](GHOST_SystemWayland *const system,
|
||||
data_offer_t *data_offer,
|
||||
const std::string mime_receive) {
|
||||
const std::string data = read_pipe(data_offer, mime_receive);
|
||||
system->setSelection(data);
|
||||
};
|
||||
|
||||
std::thread read_thread(read_selection, input->system, data_offer, mime_receive);
|
||||
read_thread.detach();
|
||||
}
|
||||
|
||||
static const struct wl_data_device_listener data_device_listener = {
|
||||
data_device_data_offer,
|
||||
data_device_enter,
|
||||
data_device_leave,
|
||||
data_device_motion,
|
||||
data_device_drop,
|
||||
data_device_selection,
|
||||
};
|
||||
|
||||
static void buffer_release(void * /*data*/, struct wl_buffer *wl_buffer)
|
||||
{
|
||||
wl_buffer_destroy(wl_buffer);
|
||||
}
|
||||
|
||||
const struct wl_buffer_listener buffer_listener = {
|
||||
buffer_release,
|
||||
};
|
||||
|
||||
static void pointer_enter(void *data,
|
||||
struct wl_pointer * /*wl_pointer*/,
|
||||
uint32_t serial,
|
||||
struct wl_surface *surface,
|
||||
wl_fixed_t surface_x,
|
||||
wl_fixed_t surface_y)
|
||||
{
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
input->pointer_serial = serial;
|
||||
input->x = wl_fixed_to_int(surface_x);
|
||||
input->y = wl_fixed_to_int(surface_y);
|
||||
|
||||
static_cast<GHOST_WindowWayland *>(wl_surface_get_user_data(surface))->activate();
|
||||
}
|
||||
|
||||
static void pointer_leave(void * /*data*/,
|
||||
struct wl_pointer * /*wl_pointer*/,
|
||||
uint32_t /*serial*/,
|
||||
struct wl_surface *surface)
|
||||
{
|
||||
if (surface != nullptr) {
|
||||
static_cast<GHOST_WindowWayland *>(wl_surface_get_user_data(surface))->deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
static void pointer_motion(void *data,
|
||||
struct wl_pointer * /*wl_pointer*/,
|
||||
uint32_t /*time*/,
|
||||
wl_fixed_t surface_x,
|
||||
wl_fixed_t surface_y)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
|
||||
input->x = wl_fixed_to_int(surface_x);
|
||||
input->y = wl_fixed_to_int(surface_y);
|
||||
|
||||
input->system->pushEvent(
|
||||
new GHOST_EventCursor(input->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
input->system->getWindowManager()->getActiveWindow(),
|
||||
wl_fixed_to_int(surface_x),
|
||||
wl_fixed_to_int(surface_y),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
|
||||
static void pointer_button(void *data,
|
||||
struct wl_pointer * /*wl_pointer*/,
|
||||
uint32_t serial,
|
||||
uint32_t /*time*/,
|
||||
uint32_t button,
|
||||
uint32_t state)
|
||||
{
|
||||
GHOST_TEventType etype = GHOST_kEventUnknown;
|
||||
switch (state) {
|
||||
case WL_POINTER_BUTTON_STATE_RELEASED:
|
||||
etype = GHOST_kEventButtonUp;
|
||||
break;
|
||||
case WL_POINTER_BUTTON_STATE_PRESSED:
|
||||
etype = GHOST_kEventButtonDown;
|
||||
break;
|
||||
}
|
||||
|
||||
GHOST_TButtonMask ebutton = GHOST_kButtonMaskLeft;
|
||||
switch (button) {
|
||||
case BTN_LEFT:
|
||||
ebutton = GHOST_kButtonMaskLeft;
|
||||
break;
|
||||
case BTN_MIDDLE:
|
||||
ebutton = GHOST_kButtonMaskMiddle;
|
||||
break;
|
||||
case BTN_RIGHT:
|
||||
ebutton = GHOST_kButtonMaskRight;
|
||||
break;
|
||||
}
|
||||
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
input->data_source->source_serial = serial;
|
||||
input->buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
|
||||
input->system->pushEvent(
|
||||
new GHOST_EventButton(input->system->getMilliSeconds(),
|
||||
etype,
|
||||
input->system->getWindowManager()->getActiveWindow(),
|
||||
ebutton,
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
|
||||
static void pointer_axis(void *data,
|
||||
struct wl_pointer * /*wl_pointer*/,
|
||||
uint32_t /*time*/,
|
||||
uint32_t axis,
|
||||
wl_fixed_t value)
|
||||
{
|
||||
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) {
|
||||
return;
|
||||
}
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
input->system->pushEvent(
|
||||
new GHOST_EventWheel(input->system->getMilliSeconds(),
|
||||
input->system->getWindowManager()->getActiveWindow(),
|
||||
std::signbit(value) ? +1 : -1));
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointer_listener = {
|
||||
pointer_enter,
|
||||
pointer_leave,
|
||||
pointer_motion,
|
||||
pointer_button,
|
||||
pointer_axis,
|
||||
};
|
||||
|
||||
static void keyboard_keymap(
|
||||
void *data, struct wl_keyboard * /*wl_keyboard*/, uint32_t format, int32_t fd, uint32_t size)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
|
||||
if ((!data) || (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
char *map_str = static_cast<char *>(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0));
|
||||
if (map_str == MAP_FAILED) {
|
||||
close(fd);
|
||||
throw std::runtime_error("keymap mmap failed: " + std::string(std::strerror(errno)));
|
||||
}
|
||||
|
||||
struct xkb_keymap *keymap = xkb_keymap_new_from_string(
|
||||
input->xkb_context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
munmap(map_str, size);
|
||||
close(fd);
|
||||
|
||||
if (!keymap) {
|
||||
return;
|
||||
}
|
||||
|
||||
input->xkb_state = xkb_state_new(keymap);
|
||||
|
||||
xkb_keymap_unref(keymap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter event.
|
||||
*
|
||||
* Notification that this seat's keyboard focus is on a certain
|
||||
* surface.
|
||||
*/
|
||||
static void keyboard_enter(void * /*data*/,
|
||||
struct wl_keyboard * /*wl_keyboard*/,
|
||||
uint32_t /*serial*/,
|
||||
struct wl_surface * /*surface*/,
|
||||
struct wl_array * /*keys*/)
|
||||
{
|
||||
/* pass */
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave event.
|
||||
*
|
||||
* Notification that this seat's keyboard focus is no longer on a
|
||||
* certain surface.
|
||||
*/
|
||||
static void keyboard_leave(void * /*data*/,
|
||||
struct wl_keyboard * /*wl_keyboard*/,
|
||||
uint32_t /*serial*/,
|
||||
struct wl_surface * /*surface*/)
|
||||
{
|
||||
/* pass */
|
||||
}
|
||||
|
||||
static void keyboard_key(void *data,
|
||||
struct wl_keyboard * /*wl_keyboard*/,
|
||||
uint32_t serial,
|
||||
uint32_t /*time*/,
|
||||
uint32_t key,
|
||||
uint32_t state)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
|
||||
GHOST_TEventType etype = GHOST_kEventUnknown;
|
||||
switch (state) {
|
||||
case WL_KEYBOARD_KEY_STATE_RELEASED:
|
||||
etype = GHOST_kEventKeyUp;
|
||||
break;
|
||||
case WL_KEYBOARD_KEY_STATE_PRESSED:
|
||||
etype = GHOST_kEventKeyDown;
|
||||
break;
|
||||
}
|
||||
|
||||
const xkb_keysym_t sym = xkb_state_key_get_one_sym(input->xkb_state, key + 8);
|
||||
if (sym == XKB_KEY_NoSymbol) {
|
||||
return;
|
||||
}
|
||||
const GHOST_TKey gkey = xkb_map_gkey(sym);
|
||||
|
||||
GHOST_TEventKeyData key_data;
|
||||
|
||||
if (etype == GHOST_kEventKeyDown) {
|
||||
xkb_state_key_get_utf8(
|
||||
input->xkb_state, key + 8, key_data.utf8_buf, sizeof(GHOST_TEventKeyData::utf8_buf));
|
||||
}
|
||||
else {
|
||||
key_data.utf8_buf[0] = '\0';
|
||||
}
|
||||
|
||||
input->data_source->source_serial = serial;
|
||||
input->system->pushEvent(new GHOST_EventKey(input->system->getMilliSeconds(),
|
||||
etype,
|
||||
input->system->getWindowManager()->getActiveWindow(),
|
||||
gkey,
|
||||
'\0',
|
||||
key_data.utf8_buf,
|
||||
false));
|
||||
}
|
||||
|
||||
static void keyboard_modifiers(void *data,
|
||||
struct wl_keyboard * /*wl_keyboard*/,
|
||||
uint32_t /*serial*/,
|
||||
uint32_t mods_depressed,
|
||||
uint32_t mods_latched,
|
||||
uint32_t mods_locked,
|
||||
uint32_t group)
|
||||
{
|
||||
xkb_state_update_mask(static_cast<input_t *>(data)->xkb_state,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
0,
|
||||
0,
|
||||
group);
|
||||
}
|
||||
|
||||
static const struct wl_keyboard_listener keyboard_listener = {
|
||||
keyboard_keymap,
|
||||
keyboard_enter,
|
||||
keyboard_leave,
|
||||
keyboard_key,
|
||||
keyboard_modifiers,
|
||||
};
|
||||
|
||||
static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities)
|
||||
{
|
||||
input_t *input = static_cast<input_t *>(data);
|
||||
input->pointer = nullptr;
|
||||
input->keyboard = nullptr;
|
||||
|
||||
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
|
||||
input->pointer = wl_seat_get_pointer(wl_seat);
|
||||
input->cursor.surface = wl_compositor_create_surface(input->system->compositor());
|
||||
input->cursor.visible = true;
|
||||
input->cursor.buffer = nullptr;
|
||||
input->cursor.file_buffer = new buffer_t;
|
||||
wl_pointer_add_listener(input->pointer, &pointer_listener, data);
|
||||
}
|
||||
|
||||
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
|
||||
input->keyboard = wl_seat_get_keyboard(wl_seat);
|
||||
wl_keyboard_add_listener(input->keyboard, &keyboard_listener, data);
|
||||
}
|
||||
}
|
||||
|
||||
static void seat_name(void *data, struct wl_seat * /*wl_seat*/, const char *name)
|
||||
{
|
||||
static_cast<input_t *>(data)->name = std::string(name);
|
||||
}
|
||||
|
||||
static const struct wl_seat_listener seat_listener = {
|
||||
seat_capabilities,
|
||||
seat_name,
|
||||
};
|
||||
|
||||
static void output_geometry(void *data,
|
||||
struct wl_output * /*wl_output*/,
|
||||
int32_t /*x*/,
|
||||
int32_t /*y*/,
|
||||
int32_t /*physical_width*/,
|
||||
int32_t /*physical_height*/,
|
||||
int32_t /*subpixel*/,
|
||||
const char *make,
|
||||
const char *model,
|
||||
int32_t transform)
|
||||
{
|
||||
output_t *output = static_cast<output_t *>(data);
|
||||
output->transform = transform;
|
||||
output->make = std::string(make);
|
||||
output->model = std::string(model);
|
||||
}
|
||||
|
||||
static void output_mode(void *data,
|
||||
struct wl_output * /*wl_output*/,
|
||||
uint32_t /*flags*/,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t /*refresh*/)
|
||||
{
|
||||
output_t *output = static_cast<output_t *>(data);
|
||||
output->width = width;
|
||||
output->height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent all information about output.
|
||||
*
|
||||
* This event is sent after all other properties have been sent
|
||||
* after binding to the output object and after any other property
|
||||
* changes done after that. This allows changes to the output
|
||||
* properties to be seen as atomic, even if they happen via multiple events.
|
||||
*/
|
||||
static void output_done(void * /*data*/, struct wl_output * /*wl_output*/)
|
||||
{
|
||||
}
|
||||
|
||||
static void output_scale(void *data, struct wl_output * /*wl_output*/, int32_t factor)
|
||||
{
|
||||
static_cast<output_t *>(data)->scale = factor;
|
||||
}
|
||||
|
||||
static const struct wl_output_listener output_listener = {
|
||||
output_geometry,
|
||||
output_mode,
|
||||
output_done,
|
||||
output_scale,
|
||||
};
|
||||
|
||||
static void shell_ping(void * /*data*/, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
|
||||
{
|
||||
xdg_wm_base_pong(xdg_wm_base, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_wm_base_listener shell_listener = {
|
||||
shell_ping,
|
||||
};
|
||||
|
||||
static void global_add(void *data,
|
||||
struct wl_registry *wl_registry,
|
||||
uint32_t name,
|
||||
const char *interface,
|
||||
uint32_t /*version*/)
|
||||
{
|
||||
struct display_t *display = static_cast<struct display_t *>(data);
|
||||
if (!strcmp(interface, wl_compositor_interface.name)) {
|
||||
display->compositor = static_cast<wl_compositor *>(
|
||||
wl_registry_bind(wl_registry, name, &wl_compositor_interface, 1));
|
||||
}
|
||||
else if (!strcmp(interface, xdg_wm_base_interface.name)) {
|
||||
display->xdg_shell = static_cast<xdg_wm_base *>(
|
||||
wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1));
|
||||
xdg_wm_base_add_listener(display->xdg_shell, &shell_listener, nullptr);
|
||||
}
|
||||
else if (!strcmp(interface, wl_output_interface.name)) {
|
||||
output_t *output = new output_t;
|
||||
output->scale = 1;
|
||||
output->output = static_cast<wl_output *>(
|
||||
wl_registry_bind(wl_registry, name, &wl_output_interface, 2));
|
||||
display->outputs.push_back(output);
|
||||
wl_output_add_listener(output->output, &output_listener, output);
|
||||
}
|
||||
else if (!strcmp(interface, wl_seat_interface.name)) {
|
||||
input_t *input = new input_t;
|
||||
input->system = display->system;
|
||||
input->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
input->xkb_state = nullptr;
|
||||
input->data_offer_dnd = nullptr;
|
||||
input->data_offer_copy_paste = nullptr;
|
||||
input->data_source = new data_source_t;
|
||||
input->data_source->data_source = nullptr;
|
||||
input->data_source->buffer_out = nullptr;
|
||||
input->relative_pointer = nullptr;
|
||||
input->locked_pointer = nullptr;
|
||||
input->seat = static_cast<wl_seat *>(
|
||||
wl_registry_bind(wl_registry, name, &wl_seat_interface, 2));
|
||||
display->inputs.push_back(input);
|
||||
wl_seat_add_listener(input->seat, &seat_listener, input);
|
||||
}
|
||||
else if (!strcmp(interface, wl_shm_interface.name)) {
|
||||
display->shm = static_cast<wl_shm *>(
|
||||
wl_registry_bind(wl_registry, name, &wl_shm_interface, 1));
|
||||
}
|
||||
else if (!strcmp(interface, wl_data_device_manager_interface.name)) {
|
||||
display->data_device_manager = static_cast<wl_data_device_manager *>(
|
||||
wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 1));
|
||||
}
|
||||
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) {
|
||||
display->relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>(
|
||||
wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1));
|
||||
}
|
||||
else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name)) {
|
||||
display->pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>(
|
||||
wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Announce removal of global object.
|
||||
*
|
||||
* Notify the client of removed global objects.
|
||||
*
|
||||
* This event notifies the client that the global identified by
|
||||
* name is no longer available. If the client bound to the global
|
||||
* using the bind request, the client should now destroy that object.
|
||||
*/
|
||||
static void global_remove(void * /*data*/, struct wl_registry * /*wl_registry*/, uint32_t /*name*/)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
global_add,
|
||||
global_remove,
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Ghost Implementation
|
||||
*
|
||||
* Wayland specific implementation of the GHOST_System interface.
|
||||
* \{ */
|
||||
|
||||
GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t)
|
||||
{
|
||||
d->system = this;
|
||||
/* Connect to the Wayland server. */
|
||||
d->display = wl_display_connect(nullptr);
|
||||
if (!d->display) {
|
||||
display_destroy(d);
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
/* Register interfaces. */
|
||||
struct wl_registry *registry = wl_display_get_registry(d->display);
|
||||
wl_registry_add_listener(registry, ®istry_listener, d);
|
||||
/* Call callback for registry listener. */
|
||||
wl_display_roundtrip(d->display);
|
||||
/* Call callbacks for registered listeners. */
|
||||
wl_display_roundtrip(d->display);
|
||||
wl_registry_destroy(registry);
|
||||
|
||||
if (!d->xdg_shell) {
|
||||
display_destroy(d);
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
/* Register data device per seat for IPC between Wayland clients. */
|
||||
if (d->data_device_manager) {
|
||||
for (input_t *input : d->inputs) {
|
||||
input->data_device = wl_data_device_manager_get_data_device(d->data_device_manager,
|
||||
input->seat);
|
||||
wl_data_device_add_listener(input->data_device, &data_device_listener, input);
|
||||
}
|
||||
}
|
||||
|
||||
const char *theme = std::getenv("XCURSOR_THEME");
|
||||
const char *size = std::getenv("XCURSOR_SIZE");
|
||||
const int sizei = size ? std::stoi(size) : default_cursor_size;
|
||||
|
||||
d->cursor_theme = wl_cursor_theme_load(theme, sizei, d->shm);
|
||||
if (!d->cursor_theme) {
|
||||
display_destroy(d);
|
||||
throw std::exception();
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_SystemWayland::~GHOST_SystemWayland()
|
||||
{
|
||||
display_destroy(d);
|
||||
}
|
||||
|
||||
bool GHOST_SystemWayland::processEvents(bool /*waitForEvent*/)
|
||||
{
|
||||
wl_display_dispatch(d->display);
|
||||
return true;
|
||||
}
|
||||
|
||||
int GHOST_SystemWayland::toggleConsole(int /*action*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) const
|
||||
{
|
||||
if (!d->inputs.empty()) {
|
||||
static const xkb_state_component mods_all = xkb_state_component(
|
||||
XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_LOCKED |
|
||||
XKB_STATE_MODS_EFFECTIVE);
|
||||
|
||||
keys.set(GHOST_kModifierKeyLeftShift,
|
||||
xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_SHIFT, mods_all) ==
|
||||
1);
|
||||
keys.set(GHOST_kModifierKeyRightShift,
|
||||
xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_SHIFT, mods_all) ==
|
||||
1);
|
||||
keys.set(GHOST_kModifierKeyLeftAlt,
|
||||
xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "LAlt", mods_all) == 1);
|
||||
keys.set(GHOST_kModifierKeyRightAlt,
|
||||
xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "RAlt", mods_all) == 1);
|
||||
keys.set(GHOST_kModifierKeyLeftControl,
|
||||
xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "LControl", mods_all) == 1);
|
||||
keys.set(GHOST_kModifierKeyRightControl,
|
||||
xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "RControl", mods_all) == 1);
|
||||
keys.set(GHOST_kModifierKeyOS,
|
||||
xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "Super", mods_all) == 1);
|
||||
keys.set(GHOST_kModifierKeyNumMasks,
|
||||
xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "NumLock", mods_all) == 1);
|
||||
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const
|
||||
{
|
||||
if (!d->inputs.empty()) {
|
||||
buttons = d->inputs[0]->buttons;
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
GHOST_TUns8 *GHOST_SystemWayland::getClipboard(bool /*selection*/) const
|
||||
{
|
||||
GHOST_TUns8 *clipboard = static_cast<GHOST_TUns8 *>(malloc((selection.size() + 1)));
|
||||
memcpy(clipboard, selection.data(), selection.size() + 1);
|
||||
return clipboard;
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::putClipboard(GHOST_TInt8 *buffer, bool /*selection*/) const
|
||||
{
|
||||
if (!d->data_device_manager || d->inputs.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
data_source_t *data_source = d->inputs[0]->data_source;
|
||||
|
||||
/* Copy buffer. */
|
||||
data_source->buffer_out = static_cast<char *>(malloc(strlen(buffer) + 1));
|
||||
std::strcpy(data_source->buffer_out, buffer);
|
||||
|
||||
data_source->data_source = wl_data_device_manager_create_data_source(d->data_device_manager);
|
||||
|
||||
wl_data_source_add_listener(
|
||||
data_source->data_source, &data_source_listener, data_source->buffer_out);
|
||||
|
||||
for (const std::string &type : mime_send) {
|
||||
wl_data_source_offer(data_source->data_source, type.c_str());
|
||||
}
|
||||
|
||||
if (!d->inputs.empty() && d->inputs[0]->data_device) {
|
||||
wl_data_device_set_selection(
|
||||
d->inputs[0]->data_device, data_source->data_source, data_source->source_serial);
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_TUns8 GHOST_SystemWayland::getNumDisplays() const
|
||||
{
|
||||
return d ? GHOST_TUns8(d->outputs.size()) : 0;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(GHOST_TInt32 &x, GHOST_TInt32 &y) const
|
||||
{
|
||||
if (getWindowManager()->getActiveWindow() != nullptr && !d->inputs.empty()) {
|
||||
x = d->inputs[0]->x;
|
||||
y = d->inputs[0]->y;
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
else {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(GHOST_TInt32 /*x*/, GHOST_TInt32 /*y*/)
|
||||
{
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::getMainDisplayDimensions(GHOST_TUns32 &width, GHOST_TUns32 &height) const
|
||||
{
|
||||
if (getNumDisplays() > 0) {
|
||||
/* We assume first output as main. */
|
||||
width = uint32_t(d->outputs[0]->width);
|
||||
height = uint32_t(d->outputs[0]->height);
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::getAllDisplayDimensions(GHOST_TUns32 &width, GHOST_TUns32 &height) const
|
||||
{
|
||||
getMainDisplayDimensions(width, height);
|
||||
}
|
||||
|
||||
GHOST_IContext *GHOST_SystemWayland::createOffscreenContext()
|
||||
{
|
||||
/* Create new off-screen window. */
|
||||
wl_surface *os_surface = wl_compositor_create_surface(compositor());
|
||||
wl_egl_window *os_egl_window = wl_egl_window_create(os_surface, int(1), int(1));
|
||||
|
||||
d->os_surfaces.push_back(os_surface);
|
||||
d->os_egl_windows.push_back(os_egl_window);
|
||||
|
||||
GHOST_Context *context = new GHOST_ContextEGL(false,
|
||||
EGLNativeWindowType(os_egl_window),
|
||||
EGLNativeDisplayType(d->display),
|
||||
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
||||
3,
|
||||
3,
|
||||
GHOST_OPENGL_EGL_CONTEXT_FLAGS,
|
||||
GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY,
|
||||
EGL_OPENGL_API);
|
||||
|
||||
if (context->initializeDrawingContext()) {
|
||||
return context;
|
||||
}
|
||||
else {
|
||||
delete context;
|
||||
}
|
||||
|
||||
GHOST_PRINT("Cannot create off-screen EGL context" << std::endl);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::disposeContext(GHOST_IContext *context)
|
||||
{
|
||||
delete context;
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title,
|
||||
GHOST_TInt32 left,
|
||||
GHOST_TInt32 top,
|
||||
GHOST_TUns32 width,
|
||||
GHOST_TUns32 height,
|
||||
GHOST_TWindowState state,
|
||||
GHOST_TDrawingContextType type,
|
||||
GHOST_GLSettings glSettings,
|
||||
const bool exclusive,
|
||||
const bool /*is_dialog*/,
|
||||
const GHOST_IWindow *parentWindow)
|
||||
{
|
||||
GHOST_WindowWayland *window = new GHOST_WindowWayland(
|
||||
this,
|
||||
title,
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
state,
|
||||
parentWindow,
|
||||
type,
|
||||
((glSettings.flags & GHOST_glStereoVisual) != 0),
|
||||
exclusive);
|
||||
|
||||
if (window) {
|
||||
if (window->getValid()) {
|
||||
m_windowManager->addWindow(window);
|
||||
m_windowManager->setActiveWindow(window);
|
||||
pushEvent(new GHOST_Event(getMilliSeconds(), GHOST_kEventWindowSize, window));
|
||||
}
|
||||
else {
|
||||
delete window;
|
||||
window = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
wl_display *GHOST_SystemWayland::display()
|
||||
{
|
||||
return d->display;
|
||||
}
|
||||
|
||||
wl_compositor *GHOST_SystemWayland::compositor()
|
||||
{
|
||||
return d->compositor;
|
||||
}
|
||||
|
||||
xdg_wm_base *GHOST_SystemWayland::shell()
|
||||
{
|
||||
return d->xdg_shell;
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::setSelection(const std::string &selection)
|
||||
{
|
||||
this->selection = selection;
|
||||
}
|
||||
|
||||
static void set_cursor_buffer(input_t *input, wl_buffer *buffer)
|
||||
{
|
||||
input->cursor.visible = (buffer != nullptr);
|
||||
|
||||
wl_surface_attach(input->cursor.surface, buffer, 0, 0);
|
||||
wl_surface_commit(input->cursor.surface);
|
||||
|
||||
if (input->cursor.visible) {
|
||||
wl_surface_damage(input->cursor.surface,
|
||||
0,
|
||||
0,
|
||||
int32_t(input->cursor.image.width),
|
||||
int32_t(input->cursor.image.height));
|
||||
wl_pointer_set_cursor(input->pointer,
|
||||
input->pointer_serial,
|
||||
input->cursor.surface,
|
||||
int32_t(input->cursor.image.hotspot_x),
|
||||
int32_t(input->cursor.image.hotspot_y));
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape)
|
||||
{
|
||||
if (d->inputs.empty()) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
const std::string cursor_name = cursors.count(shape) ? cursors.at(shape) :
|
||||
cursors.at(GHOST_kStandardCursorDefault);
|
||||
|
||||
wl_cursor *cursor = wl_cursor_theme_get_cursor(d->cursor_theme, cursor_name.c_str());
|
||||
|
||||
if (!cursor) {
|
||||
GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl);
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
struct wl_cursor_image *image = cursor->images[0];
|
||||
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
||||
if (!buffer) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
cursor_t *c = &d->inputs[0]->cursor;
|
||||
c->buffer = buffer;
|
||||
c->image = *image;
|
||||
|
||||
set_cursor_buffer(d->inputs[0], buffer);
|
||||
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::hasCursorShape(GHOST_TStandardCursor cursorShape)
|
||||
{
|
||||
return GHOST_TSuccess(cursors.count(cursorShape) && !cursors.at(cursorShape).empty());
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(GHOST_TUns8 *bitmap,
|
||||
GHOST_TUns8 *mask,
|
||||
int sizex,
|
||||
int sizey,
|
||||
int hotX,
|
||||
int hotY,
|
||||
bool /*canInvertColor*/)
|
||||
{
|
||||
if (d->inputs.empty()) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
cursor_t *cursor = &d->inputs[0]->cursor;
|
||||
|
||||
static const int32_t stride = sizex * 4; /* ARGB */
|
||||
cursor->file_buffer->size = size_t(stride * sizey);
|
||||
|
||||
const int fd = memfd_create("blender-cursor-custom", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
|
||||
posix_fallocate(fd, 0, int32_t(cursor->file_buffer->size));
|
||||
|
||||
cursor->file_buffer->data = mmap(
|
||||
nullptr, cursor->file_buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(d->shm, fd, int32_t(cursor->file_buffer->size));
|
||||
|
||||
wl_buffer *buffer = wl_shm_pool_create_buffer(
|
||||
pool, 0, sizex, sizey, stride, WL_SHM_FORMAT_ARGB8888);
|
||||
|
||||
wl_shm_pool_destroy(pool);
|
||||
close(fd);
|
||||
|
||||
wl_buffer_add_listener(buffer, &buffer_listener, nullptr);
|
||||
|
||||
static constexpr uint32_t black = 0xFF000000;
|
||||
static constexpr uint32_t white = 0xFFFFFFFF;
|
||||
static constexpr uint32_t transparent = 0x00000000;
|
||||
|
||||
uint8_t datab = 0, maskb = 0;
|
||||
uint32_t *pixel;
|
||||
|
||||
for (int y = 0; y < sizey; ++y) {
|
||||
pixel = &static_cast<uint32_t *>(cursor->file_buffer->data)[y * sizex];
|
||||
for (int x = 0; x < sizex; ++x) {
|
||||
if ((x % 8) == 0) {
|
||||
datab = *bitmap++;
|
||||
maskb = *mask++;
|
||||
|
||||
/* Reverse bit order. */
|
||||
datab = uint8_t((datab * 0x0202020202ULL & 0x010884422010ULL) % 1023);
|
||||
maskb = uint8_t((maskb * 0x0202020202ULL & 0x010884422010ULL) % 1023);
|
||||
}
|
||||
|
||||
if (maskb & 0x80) {
|
||||
*pixel++ = (datab & 0x80) ? white : black;
|
||||
}
|
||||
else {
|
||||
*pixel++ = (datab & 0x80) ? white : transparent;
|
||||
}
|
||||
datab <<= 1;
|
||||
maskb <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
cursor->buffer = buffer;
|
||||
cursor->image.width = uint32_t(sizex);
|
||||
cursor->image.height = uint32_t(sizey);
|
||||
cursor->image.hotspot_x = uint32_t(hotX);
|
||||
cursor->image.hotspot_y = uint32_t(hotY);
|
||||
|
||||
set_cursor_buffer(d->inputs[0], buffer);
|
||||
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(bool visible)
|
||||
{
|
||||
if (d->inputs.empty()) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
cursor_t *cursor = &d->inputs[0]->cursor;
|
||||
if (visible) {
|
||||
if (!cursor->visible) {
|
||||
set_cursor_buffer(d->inputs[0], d->inputs[0]->cursor.buffer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (cursor->visible) {
|
||||
set_cursor_buffer(d->inputs[0], nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mode,
|
||||
wl_surface *surface)
|
||||
{
|
||||
if (d->inputs.empty()) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
input_t *input = d->inputs[0];
|
||||
|
||||
switch (mode) {
|
||||
case GHOST_kGrabDisable:
|
||||
if (input->relative_pointer) {
|
||||
zwp_relative_pointer_v1_destroy(input->relative_pointer);
|
||||
input->relative_pointer = nullptr;
|
||||
}
|
||||
if (input->locked_pointer) {
|
||||
zwp_locked_pointer_v1_destroy(input->locked_pointer);
|
||||
input->locked_pointer = nullptr;
|
||||
}
|
||||
break;
|
||||
|
||||
case GHOST_kGrabNormal:
|
||||
case GHOST_kGrabWrap:
|
||||
input->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
d->relative_pointer_manager, input->pointer);
|
||||
zwp_relative_pointer_v1_add_listener(
|
||||
input->relative_pointer, &relative_pointer_listener, input);
|
||||
input->locked_pointer = zwp_pointer_constraints_v1_lock_pointer(
|
||||
d->pointer_constraints,
|
||||
surface,
|
||||
input->pointer,
|
||||
nullptr,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
break;
|
||||
|
||||
case GHOST_kGrabHide:
|
||||
setCursorVisibility(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
111
intern/ghost/intern/GHOST_SystemWayland.h
Normal file
111
intern/ghost/intern/GHOST_SystemWayland.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
* Declaration of GHOST_SystemWayland class.
|
||||
*/
|
||||
|
||||
#ifndef __GHOST_SYSTEMWAYLAND_H__
|
||||
#define __GHOST_SYSTEMWAYLAND_H__
|
||||
|
||||
#include "../GHOST_Types.h"
|
||||
#include "GHOST_System.h"
|
||||
#include "GHOST_WindowWayland.h"
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <xdg-shell-client-protocol.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
class GHOST_WindowWayland;
|
||||
|
||||
struct display_t;
|
||||
|
||||
class GHOST_SystemWayland : public GHOST_System {
|
||||
public:
|
||||
GHOST_SystemWayland();
|
||||
|
||||
~GHOST_SystemWayland() override;
|
||||
|
||||
bool processEvents(bool waitForEvent) override;
|
||||
|
||||
int toggleConsole(int action) override;
|
||||
|
||||
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override;
|
||||
|
||||
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override;
|
||||
|
||||
GHOST_TUns8 *getClipboard(bool selection) const override;
|
||||
|
||||
void putClipboard(GHOST_TInt8 *buffer, bool selection) const override;
|
||||
|
||||
GHOST_TUns8 getNumDisplays() const override;
|
||||
|
||||
GHOST_TSuccess getCursorPosition(GHOST_TInt32 &x, GHOST_TInt32 &y) const override;
|
||||
|
||||
GHOST_TSuccess setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y) override;
|
||||
|
||||
void getMainDisplayDimensions(GHOST_TUns32 &width, GHOST_TUns32 &height) const override;
|
||||
|
||||
void getAllDisplayDimensions(GHOST_TUns32 &width, GHOST_TUns32 &height) const override;
|
||||
|
||||
GHOST_IContext *createOffscreenContext() override;
|
||||
|
||||
GHOST_TSuccess disposeContext(GHOST_IContext *context) override;
|
||||
|
||||
GHOST_IWindow *createWindow(const char *title,
|
||||
GHOST_TInt32 left,
|
||||
GHOST_TInt32 top,
|
||||
GHOST_TUns32 width,
|
||||
GHOST_TUns32 height,
|
||||
GHOST_TWindowState state,
|
||||
GHOST_TDrawingContextType type,
|
||||
GHOST_GLSettings glSettings,
|
||||
const bool exclusive,
|
||||
const bool is_dialog,
|
||||
const GHOST_IWindow *parentWindow) override;
|
||||
|
||||
wl_display *display();
|
||||
|
||||
wl_compositor *compositor();
|
||||
|
||||
xdg_wm_base *shell();
|
||||
|
||||
void setSelection(const std::string &selection);
|
||||
|
||||
GHOST_TSuccess setCursorShape(GHOST_TStandardCursor shape);
|
||||
|
||||
GHOST_TSuccess hasCursorShape(GHOST_TStandardCursor cursorShape);
|
||||
|
||||
GHOST_TSuccess setCustomCursorShape(GHOST_TUns8 *bitmap,
|
||||
GHOST_TUns8 *mask,
|
||||
int sizex,
|
||||
int sizey,
|
||||
int hotX,
|
||||
int hotY,
|
||||
bool canInvertColor);
|
||||
|
||||
GHOST_TSuccess setCursorVisibility(bool visible);
|
||||
|
||||
GHOST_TSuccess setCursorGrab(const GHOST_TGrabCursorMode mode, wl_surface *surface);
|
||||
|
||||
private:
|
||||
struct display_t *d;
|
||||
std::string selection;
|
||||
};
|
||||
|
||||
#endif /* __GHOST_SYSTEMWAYLAND_H__ */
|
||||
404
intern/ghost/intern/GHOST_WindowWayland.cpp
Normal file
404
intern/ghost/intern/GHOST_WindowWayland.cpp
Normal file
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
#include "GHOST_WindowWayland.h"
|
||||
#include "GHOST_SystemWayland.h"
|
||||
#include "GHOST_WindowManager.h"
|
||||
|
||||
#include "GHOST_Event.h"
|
||||
|
||||
#include "GHOST_ContextEGL.h"
|
||||
#include "GHOST_ContextNone.h"
|
||||
|
||||
#include <wayland-egl.h>
|
||||
|
||||
struct window_t {
|
||||
GHOST_WindowWayland *w;
|
||||
wl_surface *surface;
|
||||
struct xdg_surface *xdg_surface;
|
||||
struct xdg_toplevel *xdg_toplevel;
|
||||
wl_egl_window *egl_window;
|
||||
int32_t pending_width, pending_height;
|
||||
bool is_maximised;
|
||||
bool is_fullscreen;
|
||||
bool is_active;
|
||||
int32_t width, height;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Wayland Interface Callbacks
|
||||
*
|
||||
* These callbacks are registered for Wayland interfaces and called when
|
||||
* an event is received from the compositor.
|
||||
* \{ */
|
||||
|
||||
static void toplevel_configure(
|
||||
void *data, xdg_toplevel * /*xdg_toplevel*/, int32_t width, int32_t height, wl_array *states)
|
||||
{
|
||||
window_t *win = static_cast<window_t *>(data);
|
||||
win->pending_width = width;
|
||||
win->pending_height = height;
|
||||
|
||||
win->is_maximised = false;
|
||||
win->is_fullscreen = false;
|
||||
win->is_active = false;
|
||||
|
||||
/* Note that the macro 'wl_array_for_each' would typically be used to simplify this logic,
|
||||
* however it's not compatible with C++, so perform casts instead.
|
||||
* If this needs to be done more often we could define our own C++ compatible macro. */
|
||||
for (enum xdg_toplevel_state *state = static_cast<xdg_toplevel_state *>(states->data);
|
||||
reinterpret_cast<uint8_t *>(state) < (static_cast<uint8_t *>(states->data) + states->size);
|
||||
state++) {
|
||||
switch (*state) {
|
||||
case XDG_TOPLEVEL_STATE_MAXIMIZED:
|
||||
win->is_maximised = true;
|
||||
break;
|
||||
case XDG_TOPLEVEL_STATE_FULLSCREEN:
|
||||
win->is_fullscreen = true;
|
||||
break;
|
||||
case XDG_TOPLEVEL_STATE_ACTIVATED:
|
||||
win->is_active = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void toplevel_close(void *data, xdg_toplevel * /*xdg_toplevel*/)
|
||||
{
|
||||
static_cast<window_t *>(data)->w->close();
|
||||
}
|
||||
|
||||
static const xdg_toplevel_listener toplevel_listener = {
|
||||
toplevel_configure,
|
||||
toplevel_close,
|
||||
};
|
||||
|
||||
static void surface_configure(void *data, xdg_surface *xdg_surface, uint32_t serial)
|
||||
{
|
||||
window_t *win = static_cast<window_t *>(data);
|
||||
|
||||
int w, h;
|
||||
wl_egl_window_get_attached_size(win->egl_window, &w, &h);
|
||||
if (win->pending_width != 0 && win->pending_height != 0 && win->pending_width != w &&
|
||||
win->pending_height != h) {
|
||||
win->width = win->pending_width;
|
||||
win->height = win->pending_height;
|
||||
wl_egl_window_resize(win->egl_window, win->pending_width, win->pending_height, 0, 0);
|
||||
win->pending_width = 0;
|
||||
win->pending_height = 0;
|
||||
win->w->notify_size();
|
||||
}
|
||||
|
||||
if (win->is_active) {
|
||||
win->w->activate();
|
||||
}
|
||||
else {
|
||||
win->w->deactivate();
|
||||
}
|
||||
|
||||
xdg_surface_ack_configure(xdg_surface, serial);
|
||||
}
|
||||
|
||||
static const xdg_surface_listener surface_listener = {
|
||||
surface_configure,
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Ghost Implementation
|
||||
*
|
||||
* Wayland specific implementation of the GHOST_Window interface.
|
||||
* \{ */
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::hasCursorShape(GHOST_TStandardCursor cursorShape)
|
||||
{
|
||||
return m_system->hasCursorShape(cursorShape);
|
||||
}
|
||||
|
||||
GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
|
||||
const char *title,
|
||||
GHOST_TInt32 /*left*/,
|
||||
GHOST_TInt32 /*top*/,
|
||||
GHOST_TUns32 width,
|
||||
GHOST_TUns32 height,
|
||||
GHOST_TWindowState state,
|
||||
const GHOST_IWindow *parentWindow,
|
||||
GHOST_TDrawingContextType type,
|
||||
const bool stereoVisual,
|
||||
const bool exclusive)
|
||||
: GHOST_Window(width, height, state, stereoVisual, exclusive),
|
||||
m_system(system),
|
||||
w(new window_t)
|
||||
{
|
||||
w->w = this;
|
||||
|
||||
w->width = int32_t(width);
|
||||
w->height = int32_t(height);
|
||||
|
||||
/* Window surfaces. */
|
||||
w->surface = wl_compositor_create_surface(m_system->compositor());
|
||||
w->egl_window = wl_egl_window_create(w->surface, int(width), int(height));
|
||||
|
||||
w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->shell(), w->surface);
|
||||
w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface);
|
||||
|
||||
wl_surface_set_user_data(w->surface, this);
|
||||
|
||||
xdg_surface_add_listener(w->xdg_surface, &surface_listener, w);
|
||||
xdg_toplevel_add_listener(w->xdg_toplevel, &toplevel_listener, w);
|
||||
|
||||
if (parentWindow) {
|
||||
xdg_toplevel_set_parent(
|
||||
w->xdg_toplevel, dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->w->xdg_toplevel);
|
||||
}
|
||||
|
||||
/* Call top-level callbacks. */
|
||||
wl_surface_commit(w->surface);
|
||||
wl_display_roundtrip(m_system->display());
|
||||
|
||||
setTitle(title);
|
||||
|
||||
/* EGL context. */
|
||||
if (setDrawingContextType(type) == GHOST_kFailure) {
|
||||
GHOST_PRINT("Failed to create EGL context" << std::endl);
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::close()
|
||||
{
|
||||
return m_system->pushEvent(
|
||||
new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowClose, this));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::activate()
|
||||
{
|
||||
if (m_system->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
return m_system->pushEvent(
|
||||
new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowActivate, this));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::deactivate()
|
||||
{
|
||||
m_system->getWindowManager()->setWindowInactive(this);
|
||||
return m_system->pushEvent(
|
||||
new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowDeactivate, this));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::notify_size()
|
||||
{
|
||||
return m_system->pushEvent(
|
||||
new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowSize, this));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
|
||||
{
|
||||
return m_system->setCursorGrab(mode, w->surface);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape)
|
||||
{
|
||||
const GHOST_TSuccess ok = m_system->setCursorShape(shape);
|
||||
m_cursorShape = (ok == GHOST_kSuccess) ? shape : GHOST_kStandardCursorDefault;
|
||||
return ok;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape(GHOST_TUns8 *bitmap,
|
||||
GHOST_TUns8 *mask,
|
||||
int sizex,
|
||||
int sizey,
|
||||
int hotX,
|
||||
int hotY,
|
||||
bool canInvertColor)
|
||||
{
|
||||
return m_system->setCustomCursorShape(bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor);
|
||||
}
|
||||
|
||||
void GHOST_WindowWayland::setTitle(const char *title)
|
||||
{
|
||||
xdg_toplevel_set_title(w->xdg_toplevel, title);
|
||||
xdg_toplevel_set_app_id(w->xdg_toplevel, title);
|
||||
this->title = title;
|
||||
}
|
||||
|
||||
std::string GHOST_WindowWayland::getTitle() const
|
||||
{
|
||||
return this->title.empty() ? "untitled" : this->title;
|
||||
}
|
||||
|
||||
void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const
|
||||
{
|
||||
getClientBounds(bounds);
|
||||
}
|
||||
|
||||
void GHOST_WindowWayland::getClientBounds(GHOST_Rect &bounds) const
|
||||
{
|
||||
bounds.set(0, 0, w->width, w->height);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setClientWidth(GHOST_TUns32 width)
|
||||
{
|
||||
return setClientSize(width, GHOST_TUns32(w->height));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setClientHeight(GHOST_TUns32 height)
|
||||
{
|
||||
return setClientSize(GHOST_TUns32(w->width), height);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setClientSize(GHOST_TUns32 width, GHOST_TUns32 height)
|
||||
{
|
||||
wl_egl_window_resize(w->egl_window, int(width), int(height), 0, 0);
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
void GHOST_WindowWayland::screenToClient(GHOST_TInt32 inX,
|
||||
GHOST_TInt32 inY,
|
||||
GHOST_TInt32 &outX,
|
||||
GHOST_TInt32 &outY) const
|
||||
{
|
||||
outX = inX;
|
||||
outY = inY;
|
||||
}
|
||||
|
||||
void GHOST_WindowWayland::clientToScreen(GHOST_TInt32 inX,
|
||||
GHOST_TInt32 inY,
|
||||
GHOST_TInt32 &outX,
|
||||
GHOST_TInt32 &outY) const
|
||||
{
|
||||
outX = inX;
|
||||
outY = inY;
|
||||
}
|
||||
|
||||
GHOST_WindowWayland::~GHOST_WindowWayland()
|
||||
{
|
||||
releaseNativeHandles();
|
||||
|
||||
wl_egl_window_destroy(w->egl_window);
|
||||
xdg_toplevel_destroy(w->xdg_toplevel);
|
||||
xdg_surface_destroy(w->xdg_surface);
|
||||
wl_surface_destroy(w->surface);
|
||||
|
||||
delete w;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible)
|
||||
{
|
||||
return m_system->setCursorVisibility(visible);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state)
|
||||
{
|
||||
switch (state) {
|
||||
case GHOST_kWindowStateNormal:
|
||||
/* Unset states. */
|
||||
switch (getState()) {
|
||||
case GHOST_kWindowStateMaximized:
|
||||
xdg_toplevel_unset_maximized(w->xdg_toplevel);
|
||||
break;
|
||||
case GHOST_kWindowStateFullScreen:
|
||||
xdg_toplevel_unset_fullscreen(w->xdg_toplevel);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GHOST_kWindowStateMaximized:
|
||||
xdg_toplevel_set_maximized(w->xdg_toplevel);
|
||||
break;
|
||||
case GHOST_kWindowStateMinimized:
|
||||
xdg_toplevel_set_minimized(w->xdg_toplevel);
|
||||
break;
|
||||
case GHOST_kWindowStateFullScreen:
|
||||
xdg_toplevel_set_fullscreen(w->xdg_toplevel, nullptr);
|
||||
break;
|
||||
case GHOST_kWindowStateEmbedded:
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_TWindowState GHOST_WindowWayland::getState() const
|
||||
{
|
||||
if (w->is_fullscreen) {
|
||||
return GHOST_kWindowStateFullScreen;
|
||||
}
|
||||
else if (w->is_maximised) {
|
||||
return GHOST_kWindowStateMaximized;
|
||||
}
|
||||
else {
|
||||
return GHOST_kWindowStateNormal;
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::invalidate()
|
||||
{
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setOrder(GHOST_TWindowOrder /*order*/)
|
||||
{
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::beginFullScreen() const
|
||||
{
|
||||
xdg_toplevel_set_fullscreen(w->xdg_toplevel, nullptr);
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::endFullScreen() const
|
||||
{
|
||||
xdg_toplevel_unset_fullscreen(w->xdg_toplevel);
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* \param type The type of rendering context create.
|
||||
* \return Indication of success.
|
||||
*/
|
||||
GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType type)
|
||||
{
|
||||
GHOST_Context *context;
|
||||
switch (type) {
|
||||
case GHOST_kDrawingContextTypeNone:
|
||||
context = new GHOST_ContextNone(m_wantStereoVisual);
|
||||
break;
|
||||
case GHOST_kDrawingContextTypeOpenGL:
|
||||
context = new GHOST_ContextEGL(m_wantStereoVisual,
|
||||
EGLNativeWindowType(w->egl_window),
|
||||
EGLNativeDisplayType(m_system->display()),
|
||||
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
||||
3,
|
||||
3,
|
||||
GHOST_OPENGL_EGL_CONTEXT_FLAGS,
|
||||
GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY,
|
||||
EGL_OPENGL_API);
|
||||
break;
|
||||
}
|
||||
|
||||
return (context->initializeDrawingContext() == GHOST_kSuccess) ? context : nullptr;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
121
intern/ghost/intern/GHOST_WindowWayland.h
Normal file
121
intern/ghost/intern/GHOST_WindowWayland.h
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*
|
||||
* Declaration of GHOST_WindowWayland class.
|
||||
*/
|
||||
|
||||
#ifndef __GHOST_WINDOWWAYLAND_H__
|
||||
#define __GHOST_WINDOWWAYLAND_H__
|
||||
|
||||
#include "GHOST_Window.h"
|
||||
|
||||
class GHOST_SystemWayland;
|
||||
|
||||
struct window_t;
|
||||
|
||||
class GHOST_WindowWayland : public GHOST_Window {
|
||||
public:
|
||||
GHOST_TSuccess hasCursorShape(GHOST_TStandardCursor cursorShape) override;
|
||||
|
||||
GHOST_WindowWayland(GHOST_SystemWayland *system,
|
||||
const char *title,
|
||||
GHOST_TInt32 left,
|
||||
GHOST_TInt32 top,
|
||||
GHOST_TUns32 width,
|
||||
GHOST_TUns32 height,
|
||||
GHOST_TWindowState state,
|
||||
const GHOST_IWindow *parentWindow,
|
||||
GHOST_TDrawingContextType type,
|
||||
const bool stereoVisual,
|
||||
const bool exclusive);
|
||||
|
||||
~GHOST_WindowWayland() override;
|
||||
|
||||
GHOST_TSuccess close();
|
||||
|
||||
GHOST_TSuccess activate();
|
||||
|
||||
GHOST_TSuccess deactivate();
|
||||
|
||||
GHOST_TSuccess notify_size();
|
||||
|
||||
protected:
|
||||
GHOST_TSuccess setWindowCursorGrab(GHOST_TGrabCursorMode mode) override;
|
||||
|
||||
GHOST_TSuccess setWindowCursorShape(GHOST_TStandardCursor shape) override;
|
||||
|
||||
GHOST_TSuccess setWindowCustomCursorShape(GHOST_TUns8 *bitmap,
|
||||
GHOST_TUns8 *mask,
|
||||
int sizex,
|
||||
int sizey,
|
||||
int hotX,
|
||||
int hotY,
|
||||
bool canInvertColor) override;
|
||||
|
||||
void setTitle(const char *title) override;
|
||||
|
||||
std::string getTitle() const override;
|
||||
|
||||
void getWindowBounds(GHOST_Rect &bounds) const override;
|
||||
|
||||
void getClientBounds(GHOST_Rect &bounds) const override;
|
||||
|
||||
GHOST_TSuccess setClientWidth(GHOST_TUns32 width) override;
|
||||
|
||||
GHOST_TSuccess setClientHeight(GHOST_TUns32 height) override;
|
||||
|
||||
GHOST_TSuccess setClientSize(GHOST_TUns32 width, GHOST_TUns32 height) override;
|
||||
|
||||
void screenToClient(GHOST_TInt32 inX,
|
||||
GHOST_TInt32 inY,
|
||||
GHOST_TInt32 &outX,
|
||||
GHOST_TInt32 &outY) const override;
|
||||
|
||||
void clientToScreen(GHOST_TInt32 inX,
|
||||
GHOST_TInt32 inY,
|
||||
GHOST_TInt32 &outX,
|
||||
GHOST_TInt32 &outY) const override;
|
||||
|
||||
GHOST_TSuccess setWindowCursorVisibility(bool visible) override;
|
||||
|
||||
GHOST_TSuccess setState(GHOST_TWindowState state) override;
|
||||
|
||||
GHOST_TWindowState getState() const override;
|
||||
|
||||
GHOST_TSuccess invalidate() override;
|
||||
|
||||
GHOST_TSuccess setOrder(GHOST_TWindowOrder order) override;
|
||||
|
||||
GHOST_TSuccess beginFullScreen() const override;
|
||||
|
||||
GHOST_TSuccess endFullScreen() const override;
|
||||
|
||||
private:
|
||||
GHOST_SystemWayland *m_system;
|
||||
struct window_t *w;
|
||||
std::string title;
|
||||
|
||||
/**
|
||||
* \param type The type of rendering context create.
|
||||
* \return Indication of success.
|
||||
*/
|
||||
GHOST_Context *newDrawingContext(GHOST_TDrawingContextType type) override;
|
||||
};
|
||||
|
||||
#endif // __GHOST_WINDOWWAYLAND_H__
|
||||
Reference in New Issue
Block a user