diff options
Diffstat (limited to 'intern/ghost/intern')
25 files changed, 2360 insertions, 895 deletions
diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 69fc6b5f2d0..0c595b27148 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -24,7 +24,7 @@ GHOST_SystemHandle GHOST_CreateSystem(void) { - GHOST_ISystem::createSystem(true); + GHOST_ISystem::createSystem(true, false); GHOST_ISystem *system = GHOST_ISystem::getSystem(); return (GHOST_SystemHandle)system; @@ -52,6 +52,13 @@ GHOST_TSuccess GHOST_DisposeSystem(GHOST_SystemHandle systemhandle) return system->disposeSystem(); } +#if !(defined(WIN32) || defined(__APPLE__)) +const char *GHOST_SystemBackend() +{ + return GHOST_ISystem::getSystemBackend(); +} +#endif + void GHOST_ShowMessageBox(GHOST_SystemHandle systemhandle, const char *title, const char *message, @@ -154,7 +161,6 @@ GHOST_WindowHandle GHOST_CreateWindow(GHOST_SystemHandle systemhandle, uint32_t height, GHOST_TWindowState state, bool is_dialog, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings) { GHOST_ISystem *system = (GHOST_ISystem *)systemhandle; @@ -165,7 +171,6 @@ GHOST_WindowHandle GHOST_CreateWindow(GHOST_SystemHandle systemhandle, width, height, state, - type, glSettings, false, is_dialog, diff --git a/intern/ghost/intern/GHOST_ContextCGL.h b/intern/ghost/intern/GHOST_ContextCGL.h index 130b926f25c..d19fffffb43 100644 --- a/intern/ghost/intern/GHOST_ContextCGL.h +++ b/intern/ghost/intern/GHOST_ContextCGL.h @@ -30,7 +30,8 @@ class GHOST_ContextCGL : public GHOST_Context { GHOST_ContextCGL(bool stereoVisual, NSView *metalView, CAMetalLayer *metalLayer, - NSOpenGLView *openglView); + NSOpenGLView *openglView, + GHOST_TDrawingContextType type); /** * Destructor. diff --git a/intern/ghost/intern/GHOST_ContextCGL.mm b/intern/ghost/intern/GHOST_ContextCGL.mm index ff53ecdbbba..9dad337a5d6 100644 --- a/intern/ghost/intern/GHOST_ContextCGL.mm +++ b/intern/ghost/intern/GHOST_ContextCGL.mm @@ -46,8 +46,10 @@ int GHOST_ContextCGL::s_sharedCount = 0; GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual, NSView *metalView, CAMetalLayer *metalLayer, - NSOpenGLView *openGLView) + NSOpenGLView *openGLView, + GHOST_TDrawingContextType type) : GHOST_Context(stereoVisual), + m_useMetalForRendering(type == GHOST_kDrawingContextTypeMetal), m_metalView(metalView), m_metalLayer(metalLayer), m_metalCmdQueue(nil), diff --git a/intern/ghost/intern/GHOST_ContextGLX.cpp b/intern/ghost/intern/GHOST_ContextGLX.cpp index 4c1e2705b50..93708983f37 100644 --- a/intern/ghost/intern/GHOST_ContextGLX.cpp +++ b/intern/ghost/intern/GHOST_ContextGLX.cpp @@ -306,7 +306,7 @@ GHOST_TSuccess GHOST_ContextGLX::releaseNativeHandles() GHOST_TSuccess GHOST_ContextGLX::setSwapInterval(int interval) { - if (!epoxy_has_glx_extension(m_display, DefaultScreen(m_display), "GLX_EXT_swap_control")) { + if (epoxy_has_glx_extension(m_display, DefaultScreen(m_display), "GLX_EXT_swap_control")) { ::glXSwapIntervalEXT(m_display, m_window, interval); return GHOST_kSuccess; } diff --git a/intern/ghost/intern/GHOST_Debug.h b/intern/ghost/intern/GHOST_Debug.h index ec1a0b34be6..64eff7f9aed 100644 --- a/intern/ghost/intern/GHOST_Debug.h +++ b/intern/ghost/intern/GHOST_Debug.h @@ -15,29 +15,41 @@ # endif #endif -#if defined(WITH_GHOST_DEBUG) || (!defined(NDEBUG)) -# include <iostream> -# include <stdio.h> //for printf() -#endif // WITH_GHOST_DEBUG +#include <iostream> +#include <stdio.h> /* For `printf()`. */ #if defined(WITH_GHOST_DEBUG) # define GHOST_PRINT(x) \ { \ std::cout << x; \ } \ - (void)0 + ((void)0) # define GHOST_PRINTF(x, ...) \ { \ printf(x, __VA_ARGS__); \ } \ - (void)0 + ((void)0) #else -# define GHOST_PRINT(x) -# define GHOST_PRINTF(x, ...) +/* Expand even when `WITH_GHOST_DEBUG` is disabled to prevent expressions + * becoming invalid even when the option is disable. */ +# define GHOST_PRINT(x) \ + { \ + if (false) { \ + std::cout << x; \ + } \ + } \ + ((void)0) +# define GHOST_PRINTF(x, ...) \ + { \ + if (false) { \ + printf(x, __VA_ARGS__); \ + } \ + } \ + ((void)0) + #endif /* `!defined(WITH_GHOST_DEBUG)` */ #ifdef WITH_ASSERT_ABORT -# include <stdio.h> //for fprintf() # include <stdlib.h> //for abort() # define GHOST_ASSERT(x, info) \ { \ @@ -48,7 +60,7 @@ abort(); \ } \ } \ - (void)0 + ((void)0) /* Assert in non-release builds too. */ #elif defined(WITH_GHOST_DEBUG) || (!defined(NDEBUG)) # define GHOST_ASSERT(x, info) \ @@ -59,7 +71,7 @@ GHOST_PRINT("\n"); \ } \ } \ - (void)0 + ((void)0) #else /* `defined(WITH_GHOST_DEBUG) || (!defined(NDEBUG))` */ # define GHOST_ASSERT(x, info) ((void)0) #endif /* `defined(WITH_GHOST_DEBUG) || (!defined(NDEBUG))` */ diff --git a/intern/ghost/intern/GHOST_ISystem.cpp b/intern/ghost/intern/GHOST_ISystem.cpp index 304d7f0abe6..696848ce623 100644 --- a/intern/ghost/intern/GHOST_ISystem.cpp +++ b/intern/ghost/intern/GHOST_ISystem.cpp @@ -30,10 +30,11 @@ #endif GHOST_ISystem *GHOST_ISystem::m_system = nullptr; +const char *GHOST_ISystem::m_system_backend_id = nullptr; GHOST_TBacktraceFn GHOST_ISystem::m_backtrace_fn = nullptr; -GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose) +GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose, [[maybe_unused]] bool background) { /* When GHOST fails to start, report the back-ends that were attempted. * A Verbose argument could be supported in printing isn't always desired. */ @@ -47,7 +48,7 @@ GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose) /* Pass. */ #elif defined(WITH_GHOST_WAYLAND) # if defined(WITH_GHOST_WAYLAND_DYNLOAD) - const bool has_wayland_libraries = ghost_wl_dynload_libraries(); + const bool has_wayland_libraries = ghost_wl_dynload_libraries_init(); # else const bool has_wayland_libraries = true; # endif @@ -60,11 +61,14 @@ GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose) if (has_wayland_libraries) { backends_attempted[backends_attempted_num++] = "WAYLAND"; try { - m_system = new GHOST_SystemWayland(); + m_system = new GHOST_SystemWayland(background); } catch (const std::runtime_error &) { delete m_system; m_system = nullptr; +# ifdef WITH_GHOST_WAYLAND_DYNLOAD + ghost_wl_dynload_libraries_exit(); +# endif } } else { @@ -95,11 +99,14 @@ GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose) if (has_wayland_libraries) { backends_attempted[backends_attempted_num++] = "WAYLAND"; try { - m_system = new GHOST_SystemWayland(); + m_system = new GHOST_SystemWayland(background); } catch (const std::runtime_error &) { delete m_system; m_system = nullptr; +# ifdef WITH_GHOST_WAYLAND_DYNLOAD + ghost_wl_dynload_libraries_exit(); +# endif } } else { @@ -122,7 +129,10 @@ GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose) m_system = new GHOST_SystemCocoa(); #endif - if ((m_system == nullptr) && verbose) { + if (m_system) { + m_system_backend_id = backends_attempted[backends_attempted_num - 1]; + } + else if (verbose) { fprintf(stderr, "GHOST: failed to initialize display for back-end(s): ["); for (int i = 0; i < backends_attempted_num; i++) { if (i != 0) { @@ -150,7 +160,7 @@ GHOST_TSuccess GHOST_ISystem::createSystemBackground() if (!m_system) { #if !defined(WITH_HEADLESS) /* Try to create a off-screen render surface with the graphical systems. */ - success = createSystem(false); + success = createSystem(false, true); if (success) { return success; } @@ -186,6 +196,11 @@ GHOST_ISystem *GHOST_ISystem::getSystem() return m_system; } +const char *GHOST_ISystem::getSystemBackend() +{ + return m_system_backend_id; +} + GHOST_TBacktraceFn GHOST_ISystem::getBacktraceFn() { return GHOST_ISystem::m_backtrace_fn; diff --git a/intern/ghost/intern/GHOST_NDOFManager.cpp b/intern/ghost/intern/GHOST_NDOFManager.cpp index b1bd5287d24..9e9b9f14c43 100644 --- a/intern/ghost/intern/GHOST_NDOFManager.cpp +++ b/intern/ghost/intern/GHOST_NDOFManager.cpp @@ -7,53 +7,50 @@ #include "GHOST_WindowManager.h" #include "GHOST_utildefines.h" +/* Logging, use `ghost.ndof.*` prefix. */ +#include "CLG_log.h" + #include <climits> #include <cmath> -#include <cstdio> /* For error/info reporting. */ #include <cstring> /* For memory functions. */ -#ifdef DEBUG_NDOF_MOTION -/* Printable version of each GHOST_TProgress value. */ -static const char *progress_string[] = { - "not started", "starting", "in progress", "finishing", "finished"}; -#endif +/* -------------------------------------------------------------------- */ +/** \name NDOF Enum Strings + * \{ */ + +/* Printable values for #GHOST_TProgress enum (keep aligned). */ +static const char *ndof_progress_string[] = { + "not started", + "starting", + "in progress", + "finishing", + "finished", +}; -#ifdef DEBUG_NDOF_BUTTONS +/* Printable values for #NDOF_ButtonT enum (keep aligned) */ static const char *ndof_button_names[] = { - /* used internally, never sent */ - "NDOF_BUTTON_NONE", - /* these two are available from any 3Dconnexion device */ + /* Exclude `NDOF_BUTTON_NONE` (-1). */ "NDOF_BUTTON_MENU", "NDOF_BUTTON_FIT", - /* standard views */ "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM", "NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT", "NDOF_BUTTON_FRONT", "NDOF_BUTTON_BACK", - /* more views */ "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2", - /* 90 degree rotations */ "NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW", "NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CCW", - /* device control */ "NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT", "NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", - /* keyboard emulation */ - "NDOF_BUTTON_ESC", - "NDOF_BUTTON_ALT", - "NDOF_BUTTON_SHIFT", - "NDOF_BUTTON_CTRL", - /* general-purpose buttons */ "NDOF_BUTTON_1", "NDOF_BUTTON_2", "NDOF_BUTTON_3", @@ -64,18 +61,47 @@ static const char *ndof_button_names[] = { "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10", - /* more general-purpose buttons */ "NDOF_BUTTON_A", "NDOF_BUTTON_B", "NDOF_BUTTON_C", - /* the end */ - "NDOF_BUTTON_LAST"}; -#endif + "NDOF_BUTTON_V1", + "NDOF_BUTTON_V2", + "NDOF_BUTTON_V3", + /* Keyboard emulation. */ + "NDOF_BUTTON_ESC", + "NDOF_BUTTON_ENTER", + "NDOF_BUTTON_DELETE", + "NDOF_BUTTON_TAB", + "NDOF_BUTTON_SPACE", + "NDOF_BUTTON_ALT", + "NDOF_BUTTON_SHIFT", + "NDOF_BUTTON_CTRL", +}; + +static const char *ndof_device_names[] = { + "UnknownDevice", + "SpaceNavigator", + "SpaceExplorer", + "SpacePilotPro", + "SpaceMousePro", + "SpaceMouseWireless", + "SpaceMouseProWireless", + "SpaceMouseEnterprise", + "SpacePilot", + "Spaceball5000", + "SpaceTraveler", +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name NDOF Button Maps + * \{ */ /* Shared by the latest 3Dconnexion hardware * SpacePilotPro uses all of these * smaller devices use only some, based on button mask. */ -static const NDOF_ButtonT Modern3Dx_HID_map[] = { +static const NDOF_ButtonT ndof_HID_map_Modern3Dx[] = { NDOF_BUTTON_MENU, NDOF_BUTTON_FIT, NDOF_BUTTON_TOP, NDOF_BUTTON_LEFT, NDOF_BUTTON_RIGHT, NDOF_BUTTON_FRONT, NDOF_BUTTON_BOTTOM, NDOF_BUTTON_BACK, NDOF_BUTTON_ROLL_CW, NDOF_BUTTON_ROLL_CCW, NDOF_BUTTON_ISO1, NDOF_BUTTON_ISO2, @@ -85,7 +111,7 @@ static const NDOF_ButtonT Modern3Dx_HID_map[] = { NDOF_BUTTON_SHIFT, NDOF_BUTTON_CTRL, NDOF_BUTTON_ROTATE, NDOF_BUTTON_PANZOOM, NDOF_BUTTON_DOMINANT, NDOF_BUTTON_PLUS, NDOF_BUTTON_MINUS}; -static const NDOF_ButtonT SpaceExplorer_HID_map[] = { +static const NDOF_ButtonT ndof_HID_map_SpaceExplorer[] = { NDOF_BUTTON_1, NDOF_BUTTON_2, NDOF_BUTTON_TOP, @@ -103,9 +129,8 @@ static const NDOF_ButtonT SpaceExplorer_HID_map[] = { NDOF_BUTTON_ROTATE, }; -/* This is the older SpacePilot (sans Pro) - * thanks to polosson for info about this device. */ -static const NDOF_ButtonT SpacePilot_HID_map[] = { +/* This is the older SpacePilot (sans Pro). */ +static const NDOF_ButtonT ndof_HID_map_SpacePilot[] = { NDOF_BUTTON_1, NDOF_BUTTON_2, NDOF_BUTTON_3, NDOF_BUTTON_4, NDOF_BUTTON_5, NDOF_BUTTON_6, NDOF_BUTTON_TOP, NDOF_BUTTON_LEFT, NDOF_BUTTON_RIGHT, NDOF_BUTTON_FRONT, NDOF_BUTTON_ESC, NDOF_BUTTON_ALT, @@ -114,7 +139,7 @@ static const NDOF_ButtonT SpacePilot_HID_map[] = { NDOF_BUTTON_NONE /* the CONFIG button -- what does it do? */ }; -static const NDOF_ButtonT Generic_HID_map[] = { +static const NDOF_ButtonT ndof_HID_map_Generic[] = { NDOF_BUTTON_1, NDOF_BUTTON_2, NDOF_BUTTON_3, @@ -129,41 +154,91 @@ static const NDOF_ButtonT Generic_HID_map[] = { NDOF_BUTTON_C, }; -static const int genericButtonCount = ARRAY_SIZE(Generic_HID_map); +/* Values taken from: https://github.com/FreeSpacenav/spacenavd/wiki/Device-button-names */ +static const NDOF_ButtonT ndof_HID_map_SpaceMouseEnterprise[] = { + NDOF_BUTTON_1, /* (0) */ + NDOF_BUTTON_2, /* (1) */ + NDOF_BUTTON_3, /* (2) */ + NDOF_BUTTON_4, /* (3) */ + NDOF_BUTTON_5, /* (4) */ + NDOF_BUTTON_6, /* (5) */ + NDOF_BUTTON_7, /* (6) */ + NDOF_BUTTON_8, /* (7) */ + NDOF_BUTTON_9, /* (8) */ + NDOF_BUTTON_A, /* Labeled "10" (9). */ + NDOF_BUTTON_B, /* Labeled "11" (10). */ + NDOF_BUTTON_C, /* Labeled "12" (11). */ + NDOF_BUTTON_MENU, /* (12). */ + NDOF_BUTTON_FIT, /* (13). */ + NDOF_BUTTON_TOP, /* (14). */ + NDOF_BUTTON_RIGHT, /* (15). */ + NDOF_BUTTON_FRONT, /* (16). */ + NDOF_BUTTON_ROLL_CW, /* (17). */ + NDOF_BUTTON_ESC, /* (18). */ + NDOF_BUTTON_ALT, /* (19). */ + NDOF_BUTTON_SHIFT, /* (20). */ + NDOF_BUTTON_CTRL, /* (21). */ + NDOF_BUTTON_ROTATE, /* Labeled "Lock Rotate" (22). */ + NDOF_BUTTON_ENTER, /* Labeled "Enter" (23). */ + NDOF_BUTTON_DELETE, /* (24). */ + NDOF_BUTTON_TAB, /* (25). */ + NDOF_BUTTON_SPACE, /* (26). */ + NDOF_BUTTON_V1, /* Labeled "V1" (27). */ + NDOF_BUTTON_V2, /* Labeled "V2" (28). */ + NDOF_BUTTON_V3, /* Labeled "V3" (29). */ + NDOF_BUTTON_ISO1, /* Labeled "ISO1" (30). */ +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name NDOF Manager Class + * \{ */ + +static const int genericButtonCount = ARRAY_SIZE(ndof_HID_map_Generic); GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System &sys) - : m_system(sys), - m_deviceType(NDOF_UnknownDevice), /* Each platform has its own device detection code. */ - m_buttonCount(genericButtonCount), - m_buttonMask(0), - m_hidMap(Generic_HID_map), - m_buttons(0), - m_motionTime(0), - m_prevMotionTime(0), - m_motionState(GHOST_kNotStarted), - m_motionEventPending(false), - m_deadZone(0.0f) + : system_(sys), + device_type_(NDOF_UnknownDevice), /* Each platform has its own device detection code. */ + hid_map_button_num_(genericButtonCount), + hid_map_button_mask_(0), + hid_map_(ndof_HID_map_Generic), + button_depressed_(0), + motion_time_(0), + motion_time_prev_(0), + motion_state_(GHOST_kNotStarted), + motion_event_pending_(false), + motion_dead_zone_(0.0f) { /* To avoid the rare situation where one triple is updated and * the other is not, initialize them both here: */ - memset(m_translation, 0, sizeof(m_translation)); - memset(m_rotation, 0, sizeof(m_rotation)); + memset(translation_, 0, sizeof(translation_)); + memset(rotation_, 0, sizeof(rotation_)); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name NDOF Device Setup + * \{ */ + +static CLG_LogRef LOG_NDOF_DEVICE = {"ghost.ndof.device"}; +#define LOG (&LOG_NDOF_DEVICE) + bool GHOST_NDOFManager::setDevice(ushort vendor_id, ushort product_id) { /* Call this function until it returns true * it's a good idea to stop calling it after that, as it will "forget" - * whichever device it already found */ + * whichever device it already found. */ /* Default to safe generic behavior for "unknown" devices * unidentified devices will emit motion events like normal * rogue buttons do nothing by default, but can be customized by the user. */ - m_deviceType = NDOF_UnknownDevice; - m_hidMap = Generic_HID_map; - m_buttonCount = genericButtonCount; - m_buttonMask = 0; + device_type_ = NDOF_UnknownDevice; + hid_map_ = ndof_HID_map_Generic; + hid_map_button_num_ = genericButtonCount; + hid_map_button_mask_ = 0; /* "mystery device" owners can help build a HID_map for their hardware * A few users have already contributed information about several older devices @@ -173,112 +248,165 @@ bool GHOST_NDOFManager::setDevice(ushort vendor_id, ushort product_id) case 0x046D: /* Logitech (3Dconnexion was a subsidiary). */ switch (product_id) { /* -- current devices -- */ - case 0xC626: /* full-size SpaceNavigator */ - case 0xC628: /* the "for Notebooks" one */ - puts("ndof: using SpaceNavigator"); - m_deviceType = NDOF_SpaceNavigator; - m_buttonCount = 2; - m_hidMap = Modern3Dx_HID_map; + case 0xC626: /* Full-size SpaceNavigator. */ + case 0xC628: /* The "for Notebooks" one. */ + { + device_type_ = NDOF_SpaceNavigator; + hid_map_button_num_ = 2; + hid_map_ = ndof_HID_map_Modern3Dx; break; - case 0xC627: - puts("ndof: using SpaceExplorer"); - m_deviceType = NDOF_SpaceExplorer; - m_buttonCount = 15; - m_hidMap = SpaceExplorer_HID_map; + } + case 0xC627: { + device_type_ = NDOF_SpaceExplorer; + hid_map_button_num_ = 15; + hid_map_ = ndof_HID_map_SpaceExplorer; break; - case 0xC629: - puts("ndof: using SpacePilot Pro"); - m_deviceType = NDOF_SpacePilotPro; - m_buttonCount = 31; - m_hidMap = Modern3Dx_HID_map; + } + case 0xC629: { + device_type_ = NDOF_SpacePilotPro; + hid_map_button_num_ = 31; + hid_map_ = ndof_HID_map_Modern3Dx; break; - case 0xC62B: - puts("ndof: using SpaceMouse Pro"); - m_deviceType = NDOF_SpaceMousePro; - m_buttonCount = 27; - /* ^^ actually has 15 buttons, but their HID codes range from 0 to 26 */ - m_buttonMask = 0x07C0F137; - m_hidMap = Modern3Dx_HID_map; + } + case 0xC62B: { + device_type_ = NDOF_SpaceMousePro; + hid_map_button_num_ = 27; /* 15 physical buttons, but HID codes range from 0 to 26. */ + hid_map_button_mask_ = 0x07C0F137; + hid_map_ = ndof_HID_map_Modern3Dx; break; + } /* -- older devices -- */ - case 0xC625: - puts("ndof: using SpacePilot"); - m_deviceType = NDOF_SpacePilot; - m_buttonCount = 21; - m_hidMap = SpacePilot_HID_map; + case 0xC625: { + device_type_ = NDOF_SpacePilot; + hid_map_button_num_ = 21; + hid_map_ = ndof_HID_map_SpacePilot; break; - case 0xC621: - puts("ndof: using Spaceball 5000"); - m_deviceType = NDOF_Spaceball5000; - m_buttonCount = 12; + } + case 0xC621: { + device_type_ = NDOF_Spaceball5000; + hid_map_button_num_ = 12; break; - case 0xC623: - puts("ndof: using SpaceTraveler"); - m_deviceType = NDOF_SpaceTraveler; - m_buttonCount = 8; + } + case 0xC623: { + device_type_ = NDOF_SpaceTraveler; + hid_map_button_num_ = 8; break; - - default: - printf("ndof: unknown Logitech product %04hx\n", product_id); + } + default: { + CLOG_INFO(LOG, 2, "unknown Logitech product %04hx", product_id); + } } break; - case 0x256F: /* 3Dconnexion */ + case 0x256F: /* 3Dconnexion. */ switch (product_id) { case 0xC62E: /* Plugged in. */ case 0xC62F: /* Wireless. */ - puts("ndof: using SpaceMouse Wireless"); - m_deviceType = NDOF_SpaceMouseWireless; - m_buttonCount = 2; - m_hidMap = Modern3Dx_HID_map; + { + device_type_ = NDOF_SpaceMouseWireless; + hid_map_button_num_ = 2; + hid_map_ = ndof_HID_map_Modern3Dx; break; + } case 0xC631: /* Plugged in. */ case 0xC632: /* Wireless. */ - puts("ndof: using SpaceMouse Pro Wireless"); - m_deviceType = NDOF_SpaceMouseProWireless; - m_buttonCount = 27; - /* ^^ actually has 15 buttons, but their HID codes range from 0 to 26. */ - m_buttonMask = 0x07C0F137; - m_hidMap = Modern3Dx_HID_map; + { + device_type_ = NDOF_SpaceMouseProWireless; + hid_map_button_num_ = 27; /* 15 physical buttons, but HID codes range from 0 to 26. */ + hid_map_button_mask_ = 0x07C0F137; + hid_map_ = ndof_HID_map_Modern3Dx; break; - case 0xC633: - puts("ndof: using SpaceMouse Enterprise"); - m_deviceType = NDOF_SpaceMouseEnterprise; - m_buttonCount = 31; - m_hidMap = Modern3Dx_HID_map; + } + case 0xC633: { + device_type_ = NDOF_SpaceMouseEnterprise; + hid_map_button_num_ = 31; + hid_map_ = ndof_HID_map_SpaceMouseEnterprise; break; - - default: - printf("ndof: unknown 3Dconnexion product %04hx\n", product_id); + } + default: { + CLOG_INFO(LOG, 2, "unknown 3Dconnexion product %04hx", product_id); + } } break; default: - printf("ndof: unknown device %04hx:%04hx\n", vendor_id, product_id); + CLOG_INFO(LOG, 2, "unknown device %04hx:%04hx", vendor_id, product_id); } - if (m_buttonMask == 0) { - m_buttonMask = int(~(UINT_MAX << m_buttonCount)); + if (device_type_ != NDOF_UnknownDevice) { + CLOG_INFO(LOG, 2, "using %s", ndof_device_names[device_type_]); } -#ifdef DEBUG_NDOF_BUTTONS - printf("ndof: %d buttons -> hex:%X\n", m_buttonCount, m_buttonMask); -#endif + if (hid_map_button_mask_ == 0) { + hid_map_button_mask_ = int(~(UINT_MAX << hid_map_button_num_)); + } + + CLOG_INFO(LOG, 2, "%d buttons -> hex:%X", hid_map_button_num_, (uint)hid_map_button_mask_); - return m_deviceType != NDOF_UnknownDevice; + return device_type_ != NDOF_UnknownDevice; } +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name NDOF Update State + * \{ */ + void GHOST_NDOFManager::updateTranslation(const int t[3], uint64_t time) { - memcpy(m_translation, t, sizeof(m_translation)); - m_motionTime = time; - m_motionEventPending = true; + memcpy(translation_, t, sizeof(translation_)); + motion_time_ = time; + motion_event_pending_ = true; } void GHOST_NDOFManager::updateRotation(const int r[3], uint64_t time) { - memcpy(m_rotation, r, sizeof(m_rotation)); - m_motionTime = time; - m_motionEventPending = true; + memcpy(rotation_, r, sizeof(rotation_)); + motion_time_ = time; + motion_event_pending_ = true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name NDOF Buttons + * \{ */ + +static CLG_LogRef LOG_NDOF_BUTTONS = {"ghost.ndof.buttons"}; +#define LOG (&LOG_NDOF_BUTTONS) + +static GHOST_TKey ghost_map_keyboard_from_ndof_buttom(const NDOF_ButtonT button) +{ + switch (button) { + case NDOF_BUTTON_ESC: { + return GHOST_kKeyEsc; + } + case NDOF_BUTTON_ENTER: { + return GHOST_kKeyEnter; + } + case NDOF_BUTTON_DELETE: { + return GHOST_kKeyDelete; + } + case NDOF_BUTTON_TAB: { + return GHOST_kKeyTab; + } + case NDOF_BUTTON_SPACE: { + return GHOST_kKeySpace; + } + case NDOF_BUTTON_ALT: { + return GHOST_kKeyLeftAlt; + } + case NDOF_BUTTON_SHIFT: { + return GHOST_kKeyLeftShift; + } + case NDOF_BUTTON_CTRL: { + return GHOST_kKeyLeftControl; + } + default: { + return GHOST_kKeyUnknown; + } + } } void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, @@ -286,7 +414,7 @@ void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, uint64_t time, GHOST_IWindow *window) { - GHOST_ASSERT(button > NDOF_BUTTON_NONE && button < NDOF_BUTTON_LAST, + GHOST_ASSERT(button > NDOF_BUTTON_NONE && button < NDOF_BUTTON_NUM, "rogue button trying to escape NDOF manager"); GHOST_EventNDOFButton *event = new GHOST_EventNDOFButton(time, window); @@ -295,11 +423,7 @@ void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, data->action = press ? GHOST_kPress : GHOST_kRelease; data->button = button; -#ifdef DEBUG_NDOF_BUTTONS - printf("%s %s\n", ndof_button_names[button], press ? "pressed" : "released"); -#endif - - m_system.pushEvent(event); + system_.pushEvent(event); } void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key, @@ -310,62 +434,59 @@ void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key, GHOST_TEventType type = press ? GHOST_kEventKeyDown : GHOST_kEventKeyUp; GHOST_EventKey *event = new GHOST_EventKey(time, type, window, key, false); -#ifdef DEBUG_NDOF_BUTTONS - printf("keyboard %s\n", press ? "down" : "up"); -#endif - - m_system.pushEvent(event); + system_.pushEvent(event); } void GHOST_NDOFManager::updateButton(int button_number, bool press, uint64_t time) { - GHOST_IWindow *window = m_system.getWindowManager()->getActiveWindow(); - -#ifdef DEBUG_NDOF_BUTTONS - printf("ndof: button %d -> ", button_number); -#endif - - NDOF_ButtonT button = (button_number < m_buttonCount) ? m_hidMap[button_number] : - NDOF_BUTTON_NONE; + if (button_number >= hid_map_button_num_) { + CLOG_INFO(LOG, + 2, + "button=%d, press=%d (out of range %d, ignoring!)", + button_number, + (int)press, + hid_map_button_num_); + return; + } + const NDOF_ButtonT button = hid_map_[button_number]; + if (button == NDOF_BUTTON_NONE) { + CLOG_INFO( + LOG, 2, "button=%d, press=%d (mapped to none, ignoring!)", button_number, (int)press); + return; + } - switch (button) { - case NDOF_BUTTON_NONE: -#ifdef DEBUG_NDOF_BUTTONS - printf("discarded\n"); -#endif - break; - case NDOF_BUTTON_ESC: - sendKeyEvent(GHOST_kKeyEsc, press, time, window); - break; - case NDOF_BUTTON_ALT: - sendKeyEvent(GHOST_kKeyLeftAlt, press, time, window); - break; - case NDOF_BUTTON_SHIFT: - sendKeyEvent(GHOST_kKeyLeftShift, press, time, window); - break; - case NDOF_BUTTON_CTRL: - sendKeyEvent(GHOST_kKeyLeftControl, press, time, window); - break; - default: - sendButtonEvent(button, press, time, window); + CLOG_INFO(LOG, + 2, + "button=%d, press=%d, name=%s", + button_number, + (int)press, + ndof_button_names[button]); + + GHOST_IWindow *window = system_.getWindowManager()->getActiveWindow(); + const GHOST_TKey key = ghost_map_keyboard_from_ndof_buttom(button); + if (key != GHOST_kKeyUnknown) { + sendKeyEvent(key, press, time, window); + } + else { + sendButtonEvent(button, press, time, window); } int mask = 1 << button_number; if (press) { - m_buttons |= mask; /* Set this button's bit. */ + button_depressed_ |= mask; /* Set this button's bit. */ } else { - m_buttons &= ~mask; /* Clear this button's bit. */ + button_depressed_ &= ~mask; /* Clear this button's bit. */ } } void GHOST_NDOFManager::updateButtons(int button_bits, uint64_t time) { - button_bits &= m_buttonMask; /* Discard any "garbage" bits. */ + button_bits &= hid_map_button_mask_; /* Discard any "garbage" bits. */ - int diff = m_buttons ^ button_bits; + int diff = button_depressed_ ^ button_bits; - for (int button_number = 0; button_number < m_buttonCount; ++button_number) { + for (int button_number = 0; button_number < hid_map_button_num_; ++button_number) { int mask = 1 << button_number; if (diff & mask) { @@ -375,19 +496,27 @@ void GHOST_NDOFManager::updateButtons(int button_bits, uint64_t time) } } +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name NDOF Motion + * \{ */ + +static CLG_LogRef LOG_NDOF_MOTION = {"ghost.ndof.motion"}; +#define LOG (&LOG_NDOF_MOTION) + void GHOST_NDOFManager::setDeadZone(float dz) { if (dz < 0.0f) { /* Negative values don't make sense, so clamp at zero. */ dz = 0.0f; } - else if (dz > 0.5f) { - /* Warn the rogue user/developer, but allow it. */ - GHOST_PRINTF("ndof: dead zone of %.2f is rather high...\n", dz); - } - m_deadZone = dz; + motion_dead_zone_ = dz; - GHOST_PRINTF("ndof: dead zone set to %.2f\n", dz); + /* Warn the rogue user/developer about high dead-zone, but allow it. */ + CLOG_INFO(LOG, 2, "dead zone set to %.2f%s", dz, (dz > 0.5f) ? " (unexpectedly high)" : ""); } static bool atHomePosition(GHOST_TEventNDOFMotionData *ndof) @@ -402,29 +531,27 @@ static bool nearHomePosition(GHOST_TEventNDOFMotionData *ndof, float threshold) if (threshold == 0.0f) { return atHomePosition(ndof); } - else { #define HOME(foo) (fabsf(ndof->foo) < threshold) - return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz); + return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz); #undef HOME - } } bool GHOST_NDOFManager::sendMotionEvent() { - if (!m_motionEventPending) { + if (!motion_event_pending_) { return false; } - m_motionEventPending = false; /* Any pending motion is handled right now. */ + motion_event_pending_ = false; /* Any pending motion is handled right now. */ - GHOST_IWindow *window = m_system.getWindowManager()->getActiveWindow(); + GHOST_IWindow *window = system_.getWindowManager()->getActiveWindow(); - if (window == NULL) { - m_motionState = GHOST_kNotStarted; /* Avoid large `dt` times when changing windows. */ + if (window == nullptr) { + motion_state_ = GHOST_kNotStarted; /* Avoid large `dt` times when changing windows. */ return false; /* Delivery will fail, so don't bother sending. */ } - GHOST_EventNDOFMotion *event = new GHOST_EventNDOFMotion(m_motionTime, window); + GHOST_EventNDOFMotion *event = new GHOST_EventNDOFMotion(motion_time_, window); GHOST_TEventNDOFMotionData *data = (GHOST_TEventNDOFMotionData *)event->getData(); /* Scale axis values here to normalize them to around +/- 1 @@ -432,76 +559,82 @@ bool GHOST_NDOFManager::sendMotionEvent() const float scale = 1.0f / 350.0f; /* 3Dconnexion devices send +/- 350 usually */ - data->tx = scale * m_translation[0]; - data->ty = scale * m_translation[1]; - data->tz = scale * m_translation[2]; - - data->rx = scale * m_rotation[0]; - data->ry = scale * m_rotation[1]; - data->rz = scale * m_rotation[2]; + data->tx = scale * translation_[0]; + data->ty = scale * translation_[1]; + data->tz = scale * translation_[2]; - data->dt = 0.001f * (m_motionTime - m_prevMotionTime); /* In seconds. */ - m_prevMotionTime = m_motionTime; + data->rx = scale * rotation_[0]; + data->ry = scale * rotation_[1]; + data->rz = scale * rotation_[2]; + data->dt = 0.001f * (motion_time_ - motion_time_prev_); /* In seconds. */ + motion_time_prev_ = motion_time_; - bool weHaveMotion = !nearHomePosition(data, m_deadZone); + bool weHaveMotion = !nearHomePosition(data, motion_dead_zone_); /* Determine what kind of motion event to send `(Starting, InProgress, Finishing)` * and where that leaves this NDOF manager `(NotStarted, InProgress, Finished)`. */ - switch (m_motionState) { + switch (motion_state_) { case GHOST_kNotStarted: - case GHOST_kFinished: + case GHOST_kFinished: { if (weHaveMotion) { data->progress = GHOST_kStarting; - m_motionState = GHOST_kInProgress; + motion_state_ = GHOST_kInProgress; /* Previous motion time will be ancient, so just make up a reasonable time delta. */ data->dt = 0.0125f; } else { /* Send no event and keep current state. */ -#ifdef DEBUG_NDOF_MOTION - printf("ndof motion ignored -- %s\n", progress_string[data->progress]); -#endif + CLOG_INFO(LOG, 2, "motion ignored"); delete event; return false; } break; - case GHOST_kInProgress: + } + case GHOST_kInProgress: { if (weHaveMotion) { data->progress = GHOST_kInProgress; /* Remain 'InProgress'. */ } else { data->progress = GHOST_kFinishing; - m_motionState = GHOST_kFinished; + motion_state_ = GHOST_kFinished; } break; - default: + } + default: { /* Will always be one of the above. */ break; + } } -#ifdef DEBUG_NDOF_MOTION - printf("ndof motion sent -- %s\n", progress_string[data->progress]); - - /* Show details about this motion event. */ - printf(" T=(%d,%d,%d) R=(%d,%d,%d) raw\n", - m_translation[0], - m_translation[1], - m_translation[2], - m_rotation[0], - m_rotation[1], - m_rotation[2]); - printf(" T=(%.2f,%.2f,%.2f) R=(%.2f,%.2f,%.2f) dt=%.3f\n", - data->tx, - data->ty, - data->tz, - data->rx, - data->ry, - data->rz, - data->dt); +#if 1 + CLOG_INFO(LOG, + 2, + "motion sent, T=(%.2f,%.2f,%.2f), R=(%.2f,%.2f,%.2f) dt=%.3f, status=%s", + data->tx, + data->ty, + data->tz, + data->rx, + data->ry, + data->rz, + data->dt, + ndof_progress_string[data->progress]); +#else + /* Raw values, may be useful for debugging. */ + CLOG_INFO(LOG, + 2, + "motion sent, T=(%d,%d,%d) R=(%d,%d,%d) status=%s", + translation_[0], + translation_[1], + translation_[2], + rotation_[0], + rotation_[1], + rotation_[2], + ndof_progress_string[data->progress]); #endif - - m_system.pushEvent(event); + system_.pushEvent(event); return true; } + +/** \} */ diff --git a/intern/ghost/intern/GHOST_NDOFManager.h b/intern/ghost/intern/GHOST_NDOFManager.h index d49e6326722..2d5bba14aa4 100644 --- a/intern/ghost/intern/GHOST_NDOFManager.h +++ b/intern/ghost/intern/GHOST_NDOFManager.h @@ -8,11 +8,8 @@ #include "GHOST_System.h" -// #define DEBUG_NDOF_MOTION -// #define DEBUG_NDOF_BUTTONS - typedef enum { - NDOF_UnknownDevice, + NDOF_UnknownDevice = 0, /* Current devices. */ NDOF_SpaceNavigator, @@ -32,8 +29,8 @@ typedef enum { /* NDOF device button event types */ typedef enum { - /* Used internally, never sent. */ - NDOF_BUTTON_NONE, + /* Used internally, never sent or used as an index. */ + NDOF_BUTTON_NONE = -1, /* These two are available from any 3Dconnexion device. */ NDOF_BUTTON_MENU, NDOF_BUTTON_FIT, @@ -61,11 +58,6 @@ typedef enum { NDOF_BUTTON_DOMINANT, NDOF_BUTTON_PLUS, NDOF_BUTTON_MINUS, - /* Keyboard emulation. */ - NDOF_BUTTON_ESC, - NDOF_BUTTON_ALT, - NDOF_BUTTON_SHIFT, - NDOF_BUTTON_CTRL, /* General-purpose buttons. * Users can assign functions via keymap editor. */ NDOF_BUTTON_1, @@ -82,8 +74,20 @@ typedef enum { NDOF_BUTTON_A, NDOF_BUTTON_B, NDOF_BUTTON_C, - /* The end. */ - NDOF_BUTTON_LAST + /* Store Views. */ + NDOF_BUTTON_V1, + NDOF_BUTTON_V2, + NDOF_BUTTON_V3, + /* Keyboard emulation. */ + NDOF_BUTTON_ESC, + NDOF_BUTTON_ENTER, + NDOF_BUTTON_DELETE, + NDOF_BUTTON_TAB, + NDOF_BUTTON_SPACE, + NDOF_BUTTON_ALT, + NDOF_BUTTON_SHIFT, + NDOF_BUTTON_CTRL, +#define NDOF_BUTTON_NUM (NDOF_BUTTON_CTRL + 1) } NDOF_ButtonT; class GHOST_NDOFManager { @@ -143,25 +147,25 @@ class GHOST_NDOFManager { bool sendMotionEvent(); protected: - GHOST_System &m_system; + GHOST_System &system_; private: void sendButtonEvent(NDOF_ButtonT, bool press, uint64_t time, GHOST_IWindow *); void sendKeyEvent(GHOST_TKey, bool press, uint64_t time, GHOST_IWindow *); - NDOF_DeviceT m_deviceType; - int m_buttonCount; - int m_buttonMask; - const NDOF_ButtonT *m_hidMap; + NDOF_DeviceT device_type_; + int hid_map_button_num_; + int hid_map_button_mask_; + const NDOF_ButtonT *hid_map_; - int m_translation[3]; - int m_rotation[3]; - int m_buttons; /* Bit field. */ + int translation_[3]; + int rotation_[3]; + int button_depressed_; /* Bit field. */ - uint64_t m_motionTime; /* In milliseconds. */ - uint64_t m_prevMotionTime; /* Time of most recent motion event sent. */ + uint64_t motion_time_; /* In milliseconds. */ + uint64_t motion_time_prev_; /* Time of most recent motion event sent. */ - GHOST_TProgress m_motionState; - bool m_motionEventPending; - float m_deadZone; /* Discard motion with each component < this. */ + GHOST_TProgress motion_state_; + bool motion_event_pending_; + float motion_dead_zone_; /* Discard motion with each component < this. */ }; diff --git a/intern/ghost/intern/GHOST_NDOFManagerUnix.cpp b/intern/ghost/intern/GHOST_NDOFManagerUnix.cpp index 94bf0337371..0ccf4dc9bfb 100644 --- a/intern/ghost/intern/GHOST_NDOFManagerUnix.cpp +++ b/intern/ghost/intern/GHOST_NDOFManagerUnix.cpp @@ -10,7 +10,7 @@ #define SPNAV_SOCK_PATH "/var/run/spnav.sock" GHOST_NDOFManagerUnix::GHOST_NDOFManagerUnix(GHOST_System &sys) - : GHOST_NDOFManager(sys), m_available(false) + : GHOST_NDOFManager(sys), available_(false) { if (access(SPNAV_SOCK_PATH, F_OK) != 0) { #ifdef DEBUG @@ -20,7 +20,7 @@ GHOST_NDOFManagerUnix::GHOST_NDOFManagerUnix(GHOST_System &sys) #endif } else if (spnav_open() != -1) { - m_available = true; + available_ = true; /* determine exactly which device (if any) is plugged in */ @@ -45,14 +45,14 @@ GHOST_NDOFManagerUnix::GHOST_NDOFManagerUnix(GHOST_System &sys) GHOST_NDOFManagerUnix::~GHOST_NDOFManagerUnix() { - if (m_available) { + if (available_) { spnav_close(); } } bool GHOST_NDOFManagerUnix::available() { - return m_available; + return available_; } /* @@ -74,7 +74,7 @@ bool GHOST_NDOFManagerUnix::processEvents() { bool anyProcessed = false; - if (m_available) { + if (available_) { spnav_event e; #ifdef USE_FINISH_GLITCH_WORKAROUND @@ -85,7 +85,7 @@ bool GHOST_NDOFManagerUnix::processEvents() switch (e.type) { case SPNAV_EVENT_MOTION: { /* convert to blender view coords */ - uint64_t now = m_system.getMilliSeconds(); + uint64_t now = system_.getMilliSeconds(); const int t[3] = {int(e.motion.x), int(e.motion.y), int(-e.motion.z)}; const int r[3] = {int(-e.motion.rx), int(-e.motion.ry), int(e.motion.rz)}; @@ -97,7 +97,7 @@ bool GHOST_NDOFManagerUnix::processEvents() break; } case SPNAV_EVENT_BUTTON: - uint64_t now = m_system.getMilliSeconds(); + uint64_t now = system_.getMilliSeconds(); updateButton(e.button.bnum, e.button.press, now); break; } @@ -106,7 +106,7 @@ bool GHOST_NDOFManagerUnix::processEvents() #ifdef USE_FINISH_GLITCH_WORKAROUND if (motion_test_prev == true && motion_test == false) { - uint64_t now = m_system.getMilliSeconds(); + uint64_t now = system_.getMilliSeconds(); const int v[3] = {0, 0, 0}; updateTranslation(v, now); diff --git a/intern/ghost/intern/GHOST_NDOFManagerUnix.h b/intern/ghost/intern/GHOST_NDOFManagerUnix.h index fd603e3cb54..2b98fad974f 100644 --- a/intern/ghost/intern/GHOST_NDOFManagerUnix.h +++ b/intern/ghost/intern/GHOST_NDOFManagerUnix.h @@ -15,5 +15,5 @@ class GHOST_NDOFManagerUnix : public GHOST_NDOFManager { bool processEvents(); private: - bool m_available; + bool available_; }; diff --git a/intern/ghost/intern/GHOST_System.cpp b/intern/ghost/intern/GHOST_System.cpp index 94d021fd822..670ede35989 100644 --- a/intern/ghost/intern/GHOST_System.cpp +++ b/intern/ghost/intern/GHOST_System.cpp @@ -384,6 +384,7 @@ GHOST_TSuccess GHOST_System::createFullScreenWindow(GHOST_Window **window, if (stereoVisual) { glSettings.flags |= GHOST_glStereoVisual; } + glSettings.context_type = GHOST_kDrawingContextTypeOpenGL; /* NOTE: don't use #getCurrentDisplaySetting() because on X11 we may * be zoomed in and the desktop may be bigger than the viewport. */ GHOST_ASSERT(m_displayManager, @@ -395,7 +396,6 @@ GHOST_TSuccess GHOST_System::createFullScreenWindow(GHOST_Window **window, settings.xPixels, settings.yPixels, GHOST_kWindowStateNormal, - GHOST_kDrawingContextTypeOpenGL, glSettings, true /* exclusive */); return (*window == nullptr) ? GHOST_kFailure : GHOST_kSuccess; diff --git a/intern/ghost/intern/GHOST_SystemCocoa.h b/intern/ghost/intern/GHOST_SystemCocoa.h index dbb41c7fddf..0211694aad4 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.h +++ b/intern/ghost/intern/GHOST_SystemCocoa.h @@ -77,7 +77,6 @@ class GHOST_SystemCocoa : public GHOST_System { * \param width: The width the window. * \param height: The height the window. * \param state: The state of the window when opened. - * \param type: The type of drawing context installed in this window. * \param glSettings: Misc OpenGL settings. * \param exclusive: Use to show the window on top and ignore others (used full-screen). * \param parentWindow: Parent (embedder) window. @@ -89,7 +88,6 @@ class GHOST_SystemCocoa : public GHOST_System { uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive = false, const bool is_dialog = false, diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index bfa90114e4c..a1016dd4843 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -689,7 +689,6 @@ GHOST_IWindow *GHOST_SystemCocoa::createWindow(const char *title, uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive, const bool is_dialog, @@ -719,7 +718,7 @@ GHOST_IWindow *GHOST_SystemCocoa::createWindow(const char *title, width, height, state, - type, + glSettings.context_type, glSettings.flags & GHOST_glStereoVisual, glSettings.flags & GHOST_glDebugContext, is_dialog, @@ -751,7 +750,7 @@ GHOST_IWindow *GHOST_SystemCocoa::createWindow(const char *title, */ GHOST_IContext *GHOST_SystemCocoa::createOffscreenContext(GHOST_GLSettings glSettings) { - GHOST_Context *context = new GHOST_ContextCGL(false, NULL, NULL, NULL); + GHOST_Context *context = new GHOST_ContextCGL(false, NULL, NULL, NULL, glSettings.context_type); if (context->initializeDrawingContext()) return context; else diff --git a/intern/ghost/intern/GHOST_SystemHeadless.h b/intern/ghost/intern/GHOST_SystemHeadless.h index b02a82fc9eb..66af65f763d 100644 --- a/intern/ghost/intern/GHOST_SystemHeadless.h +++ b/intern/ghost/intern/GHOST_SystemHeadless.h @@ -142,7 +142,6 @@ class GHOST_SystemHeadless : public GHOST_System { uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool /*exclusive*/, const bool /*is_dialog*/, @@ -155,7 +154,7 @@ class GHOST_SystemHeadless : public GHOST_System { height, state, parentWindow, - type, + glSettings.context_type, ((glSettings.flags & GHOST_glStereoVisual) != 0)); } diff --git a/intern/ghost/intern/GHOST_SystemSDL.cpp b/intern/ghost/intern/GHOST_SystemSDL.cpp index ad5c4dc85fb..9174664adc4 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cpp +++ b/intern/ghost/intern/GHOST_SystemSDL.cpp @@ -42,7 +42,6 @@ GHOST_IWindow *GHOST_SystemSDL::createWindow(const char *title, uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive, const bool /* is_dialog */, @@ -57,7 +56,7 @@ GHOST_IWindow *GHOST_SystemSDL::createWindow(const char *title, width, height, state, - type, + glSettings.context_type, ((glSettings.flags & GHOST_glStereoVisual) != 0), exclusive, parentWindow); diff --git a/intern/ghost/intern/GHOST_SystemSDL.h b/intern/ghost/intern/GHOST_SystemSDL.h index bee277ba674..385bfb841e3 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.h +++ b/intern/ghost/intern/GHOST_SystemSDL.h @@ -71,7 +71,6 @@ class GHOST_SystemSDL : public GHOST_System { uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive = false, const bool is_dialog = false, diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 0721ca78603..67f475a2963 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -10,6 +10,7 @@ #include "GHOST_EventCursor.h" #include "GHOST_EventDragnDrop.h" #include "GHOST_EventKey.h" +#include "GHOST_EventTrackpad.h" #include "GHOST_EventWheel.h" #include "GHOST_PathUtils.h" #include "GHOST_TimerManager.h" @@ -50,10 +51,17 @@ /* Generated by `wayland-scanner`. */ #include <pointer-constraints-unstable-v1-client-protocol.h> +#include <pointer-gestures-unstable-v1-client-protocol.h> +#include <primary-selection-unstable-v1-client-protocol.h> #include <relative-pointer-unstable-v1-client-protocol.h> #include <tablet-unstable-v2-client-protocol.h> #include <xdg-output-unstable-v1-client-protocol.h> +/* Decorations `xdg_decor`. */ +#include <xdg-decoration-unstable-v1-client-protocol.h> +#include <xdg-shell-client-protocol.h> +/* End `xdg_decor`. */ + #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> @@ -64,10 +72,23 @@ /* Logging, use `ghost.wl.*` prefix. */ #include "CLG_log.h" +#ifdef WITH_GHOST_WAYLAND_LIBDECOR +static bool use_libdecor = true; +# ifdef WITH_GHOST_WAYLAND_DYNLOAD +static bool has_libdecor = false; +# else +static bool has_libdecor = true; +# endif +#endif + static void keyboard_handle_key_repeat_cancel(struct GWL_Seat *seat); static void output_handle_done(void *data, struct wl_output *wl_output); +static void gwl_seat_capability_pointer_disable(GWL_Seat *seat); +static void gwl_seat_capability_keyboard_disable(GWL_Seat *seat); +static void gwl_seat_capability_touch_disable(GWL_Seat *seat); + /* -------------------------------------------------------------------- */ /** \name Local Defines * @@ -102,10 +123,21 @@ static bool use_gnome_confine_hack = false; * This define could be removed without changing any functionality, * it just means GNOME users will see verbose warning messages that alert them about * a known problem that needs to be fixed up-stream. + * + * This has been fixed for GNOME 43. Keep the workaround until support for gnome 42 is dropped. * See: https://gitlab.gnome.org/GNOME/mutter/-/issues/2457 */ #define USE_GNOME_KEYBOARD_SUPPRESS_WARNING +/** + * When GNOME is found, require `libdecor`. + * This is a hack because it seems there is no way to check if the compositor supports + * server side decorations when initializing WAYLAND. + */ +#if defined(WITH_GHOST_WAYLAND_LIBDECOR) && defined(WITH_GHOST_X11) +# define USE_GNOME_NEEDS_LIBDECOR_HACK +#endif + /** \} */ /* -------------------------------------------------------------------- */ @@ -117,8 +149,7 @@ static bool use_gnome_confine_hack = false; * \{ */ /** - * The event codes are used to - * to differentiate from which mouse button an event comes from. + * The event codes are used to differentiate from which mouse button an event comes from. */ #define BTN_LEFT 0x110 #define BTN_RIGHT 0x111 @@ -167,48 +198,93 @@ struct GWL_ModifierInfo { }; static const GWL_ModifierInfo g_modifier_info_table[MOD_INDEX_NUM] = { - [MOD_INDEX_SHIFT] = - { - .display_name = "Shift", - .xkb_id = XKB_MOD_NAME_SHIFT, - .key_l = GHOST_kKeyLeftShift, - .key_r = GHOST_kKeyRightShift, - .mod_l = GHOST_kModifierKeyLeftShift, - .mod_r = GHOST_kModifierKeyRightShift, - }, - [MOD_INDEX_ALT] = - { - .display_name = "Alt", - .xkb_id = XKB_MOD_NAME_ALT, - .key_l = GHOST_kKeyLeftAlt, - .key_r = GHOST_kKeyRightAlt, - .mod_l = GHOST_kModifierKeyLeftAlt, - .mod_r = GHOST_kModifierKeyRightAlt, - }, - [MOD_INDEX_CTRL] = - { - .display_name = "Control", - .xkb_id = XKB_MOD_NAME_CTRL, - .key_l = GHOST_kKeyLeftControl, - .key_r = GHOST_kKeyRightControl, - .mod_l = GHOST_kModifierKeyLeftControl, - .mod_r = GHOST_kModifierKeyRightControl, - }, - [MOD_INDEX_OS] = - { - .display_name = "OS", - .xkb_id = XKB_MOD_NAME_LOGO, - .key_l = GHOST_kKeyLeftOS, - .key_r = GHOST_kKeyRightOS, - .mod_l = GHOST_kModifierKeyLeftOS, - .mod_r = GHOST_kModifierKeyRightOS, - }, + /* MOD_INDEX_SHIFT */ + { + /* display_name */ "Shift", + /* xkb_id */ XKB_MOD_NAME_SHIFT, + /* key_l */ GHOST_kKeyLeftShift, + /* key_r */ GHOST_kKeyRightShift, + /* mod_l */ GHOST_kModifierKeyLeftShift, + /* mod_r */ GHOST_kModifierKeyRightShift, + }, + /* MOD_INDEX_ALT */ + { + /* display_name */ "Alt", + /* xkb_id */ XKB_MOD_NAME_ALT, + /* key_l */ GHOST_kKeyLeftAlt, + /* key_r */ GHOST_kKeyRightAlt, + /* mod_l */ GHOST_kModifierKeyLeftAlt, + /* mod_r */ GHOST_kModifierKeyRightAlt, + }, + /* MOD_INDEX_CTRL */ + { + /* display_name */ "Control", + /* xkb_id */ XKB_MOD_NAME_CTRL, + /* key_l */ GHOST_kKeyLeftControl, + /* key_r */ GHOST_kKeyRightControl, + /* mod_l */ GHOST_kModifierKeyLeftControl, + /* mod_r */ GHOST_kModifierKeyRightControl, + }, + /* MOD_INDEX_OS */ + { + /* display_name */ "OS", + /* xkb_id */ XKB_MOD_NAME_LOGO, + /* key_l */ GHOST_kKeyLeftOS, + /* key_r */ GHOST_kKeyRightOS, + /* mod_l */ GHOST_kModifierKeyLeftOS, + /* mod_r */ GHOST_kModifierKeyRightOS, + }, }; /** \} */ /* -------------------------------------------------------------------- */ -/** \name Private Types & Defines +/** \name Internal #GWL_SimpleBuffer Type + * \{ */ + +struct GWL_SimpleBuffer { + /** Constant data, but may be freed. */ + const char *data = nullptr; + size_t data_size = 0; +}; + +static void gwl_simple_buffer_free_data(GWL_SimpleBuffer *buffer) +{ + free(const_cast<char *>(buffer->data)); + buffer->data = nullptr; + buffer->data_size = 0; +} + +static void gwl_simple_buffer_set_and_take_ownership(GWL_SimpleBuffer *buffer, + const char *data, + size_t data_size) +{ + free(const_cast<char *>(buffer->data)); + buffer->data = data; + buffer->data_size = data_size; +} + +static void gwl_simple_buffer_set_from_string(GWL_SimpleBuffer *buffer, const char *str) +{ + free(const_cast<char *>(buffer->data)); + buffer->data_size = strlen(str); + char *data = static_cast<char *>(malloc(buffer->data_size)); + std::memcpy(data, str, buffer->data_size); + buffer->data = data; +} + +static char *gwl_simple_buffer_as_string(const GWL_SimpleBuffer *buffer) +{ + char *buffer_str = static_cast<char *>(malloc(buffer->data_size + 1)); + memcpy(buffer_str, buffer->data, buffer->data_size); + buffer_str[buffer->data_size] = '\0'; + return buffer_str; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_Cursor Type * \{ */ /** @@ -226,16 +302,26 @@ struct GWL_Cursor { * the hardware cursor is used. */ bool is_hardware = true; + /** When true, a custom image is used to display the cursor (stored in `wl_image`). */ bool is_custom = false; struct wl_surface *wl_surface = nullptr; struct wl_buffer *wl_buffer = nullptr; struct wl_cursor_image wl_image = {0}; struct wl_cursor_theme *wl_theme = nullptr; void *custom_data = nullptr; + /** The size of `custom_data` in bytes. */ size_t custom_data_size = 0; - int size = 0; + /** + * The name of the theme (loaded by DBUS, depends on #WITH_GHOST_WAYLAND_DBUS). + * When disabled, leave as an empty string and the default theme will be used. + */ std::string theme_name; - + /** + * The size of the cursor (when looking up a cursor theme). + * This must be scaled by the maximum output scale when passing to wl_cursor_theme_load. + * See #update_cursor_scale. + * */ + int theme_size = 0; int custom_scale = 1; }; @@ -246,6 +332,7 @@ struct GWL_Cursor { */ struct GWL_TabletTool { struct GWL_Seat *seat = nullptr; + /** Tablets have a separate cursor to the 'pointer', this surface is used for cursor drawing. */ struct wl_surface *wl_surface_cursor = nullptr; /** Used to delay clearing tablet focused wl_surface until the frame is handled. */ bool proximity = false; @@ -253,23 +340,51 @@ struct GWL_TabletTool { GHOST_TabletData data = GHOST_TABLET_DATA_NONE; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_DataOffer Type + * \{ */ + +/** + * Data storage used for clipboard paste & drag-and-drop. + */ struct GWL_DataOffer { - std::unordered_set<std::string> types; - uint32_t source_actions = 0; - uint32_t dnd_action = 0; struct wl_data_offer *id = nullptr; + std::unordered_set<std::string> types; std::atomic<bool> in_use = false; + struct { + /** + * Bit-mask with available drop options. + * #WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, #WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE.. etc. + * The application that initializes the drag may set these depending on modifiers held + * \note when dragging begins. Currently ghost doesn't make use of these. + */ + enum wl_data_device_manager_dnd_action source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + enum wl_data_device_manager_dnd_action action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; /** Compatible with #GWL_Seat.xy coordinates. */ wl_fixed_t xy[2] = {0, 0}; } dnd; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_DataSource Type + * \{ */ + struct GWL_DataSource { - struct wl_data_source *data_source = nullptr; - char *buffer_out = nullptr; + struct wl_data_source *wl_source = nullptr; + GWL_SimpleBuffer buffer_out; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_Seat Type (#wl_seat wrapper & associated types) + * \{ */ + /** * Data used to implement client-side key-repeat. * @@ -335,6 +450,52 @@ struct GWL_SeatStatePointer { }; /** + * Scroll state, applying to pointer (not tablet) events. + * Otherwise this would be part of #GWL_SeatStatePointer. + */ +struct GWL_SeatStatePointerScroll { + /** Smooth scrolling (handled & reset with pointer "frame" callback). */ + wl_fixed_t smooth_xy[2] = {0, 0}; + /** Discrete scrolling (handled & reset with pointer "frame" callback). */ + int32_t discrete_xy[2] = {0, 0}; + /** The source of scroll event. */ + enum wl_pointer_axis_source axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; +}; + +/** + * Utility struct to access rounded values from a scaled `wl_fixed_t`, + * without loosing information. + * + * As the rounded result is rounded to a lower precision integer, + * the high precision value is accumulated and converted to an integer to + * prevent the accumulation of rounded values giving an inaccurate result. + * + * \note This is simple but doesn't read well when expanded multiple times inline. + */ +struct GWL_ScaledFixedT { + wl_fixed_t value = 0; + wl_fixed_t factor = 1; +}; + +static int gwl_scaled_fixed_t_add_and_calc_rounded_delta(GWL_ScaledFixedT *sf, + const wl_fixed_t add) +{ + const int result_prev = wl_fixed_to_int(sf->value * sf->factor); + sf->value += add; + const int result_curr = wl_fixed_to_int(sf->value * sf->factor); + return result_curr - result_prev; +} + +/** + * Gesture state. + * This is needed so the gesture values can be converted to deltas. + */ +struct GWL_SeatStatePointerGesture_Pinch { + GWL_ScaledFixedT scale; + GWL_ScaledFixedT rotation; +}; + +/** * State of the keyboard (in #GWL_Seat). */ struct GWL_SeatStateKeyboard { @@ -353,18 +514,105 @@ struct GWL_SeatStateKeyboard { * * Needed as #GWL_Seat.xkb_state doesn't store which modifier keys are held. */ -struct WGL_KeyboardDepressedState { +struct GWL_KeyboardDepressedState { int16_t mods[GHOST_KEY_MODIFIER_NUM] = {0}; }; +#ifdef WITH_GHOST_WAYLAND_LIBDECOR +struct GWL_LibDecor_System { + struct libdecor *context = nullptr; +}; + +static void gwl_libdecor_system_destroy(GWL_LibDecor_System *decor) +{ + if (decor->context) { + libdecor_unref(decor->context); + } + delete decor; +} +#endif + +struct GWL_XDG_Decor_System { + struct xdg_wm_base *shell = nullptr; + struct zxdg_decoration_manager_v1 *manager = nullptr; +}; + +static void gwl_xdg_decor_system_destroy(GWL_XDG_Decor_System *decor) +{ + if (decor->manager) { + zxdg_decoration_manager_v1_destroy(decor->manager); + } + if (decor->shell) { + xdg_wm_base_destroy(decor->shell); + } + delete decor; +} + +struct GWL_PrimarySelection_DataOffer { + struct zwp_primary_selection_offer_v1 *id = nullptr; + std::atomic<bool> in_use = false; + + std::unordered_set<std::string> types; +}; + +struct GWL_PrimarySelection_DataSource { + struct zwp_primary_selection_source_v1 *wp_source = nullptr; + GWL_SimpleBuffer buffer_out; +}; + +/** Primary selection support. */ +struct GWL_PrimarySelection { + + GWL_PrimarySelection_DataSource *data_source = nullptr; + std::mutex data_source_mutex; + + GWL_PrimarySelection_DataOffer *data_offer = nullptr; + std::mutex data_offer_mutex; +}; + +static void gwl_primary_selection_discard_offer(GWL_PrimarySelection *primary) +{ + if (primary->data_offer == nullptr) { + return; + } + zwp_primary_selection_offer_v1_destroy(primary->data_offer->id); + delete primary->data_offer; + primary->data_offer = nullptr; +} + +static void gwl_primary_selection_discard_source(GWL_PrimarySelection *primary) +{ + GWL_PrimarySelection_DataSource *data_source = primary->data_source; + if (data_source == nullptr) { + return; + } + gwl_simple_buffer_free_data(&data_source->buffer_out); + if (data_source->wp_source) { + zwp_primary_selection_source_v1_destroy(data_source->wp_source); + } + delete primary->data_source; + primary->data_source = nullptr; +} + struct GWL_Seat { GHOST_SystemWayland *system = nullptr; std::string name; struct wl_seat *wl_seat = nullptr; struct wl_pointer *wl_pointer = nullptr; + struct wl_touch *wl_touch = nullptr; struct wl_keyboard *wl_keyboard = nullptr; - struct zwp_tablet_seat_v2 *tablet_seat = nullptr; + struct zwp_tablet_seat_v2 *wp_tablet_seat = nullptr; + +#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE + struct zwp_pointer_gesture_hold_v1 *wp_pointer_gesture_hold = nullptr; +#endif +#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE + struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch = nullptr; +#endif +#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE + struct zwp_pointer_gesture_swipe_v1 *wp_pointer_gesture_swipe = nullptr; +#endif /** All currently active tablet tools (needed for changing the cursor). */ std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools; @@ -373,6 +621,8 @@ struct GWL_Seat { uint32_t cursor_source_serial = 0; GWL_SeatStatePointer pointer; + GWL_SeatStatePointerScroll pointer_scroll; + GWL_SeatStatePointerGesture_Pinch pointer_gesture_pinch; /** Mostly this can be interchanged with `pointer` however it can't be locked/confined. */ GWL_SeatStatePointer tablet; @@ -387,9 +637,9 @@ struct GWL_Seat { struct GWL_Cursor cursor; - struct zwp_relative_pointer_v1 *relative_pointer = nullptr; - struct zwp_locked_pointer_v1 *locked_pointer = nullptr; - struct zwp_confined_pointer_v1 *confined_pointer = nullptr; + struct zwp_relative_pointer_v1 *wp_relative_pointer = nullptr; + struct zwp_locked_pointer_v1 *wp_locked_pointer = nullptr; + struct zwp_confined_pointer_v1 *wp_confined_pointer = nullptr; struct xkb_context *xkb_context = nullptr; @@ -405,7 +655,7 @@ struct GWL_Seat { struct xkb_state *xkb_state_empty_with_numlock = nullptr; /** Keys held matching `xkb_state`. */ - struct WGL_KeyboardDepressedState key_depressed; + struct GWL_KeyboardDepressedState key_depressed; #ifdef USE_GNOME_KEYBOARD_SUPPRESS_WARNING struct { @@ -432,7 +682,7 @@ struct GWL_Seat { struct wl_surface *wl_surface_focus_dnd = nullptr; - struct wl_data_device *data_device = nullptr; + struct wl_data_device *wl_data_device = nullptr; /** Drag & Drop. */ struct GWL_DataOffer *data_offer_dnd = nullptr; std::mutex data_offer_dnd_mutex; @@ -444,35 +694,47 @@ struct GWL_Seat { struct GWL_DataSource *data_source = nullptr; std::mutex data_source_mutex; + struct zwp_primary_selection_device_v1 *wp_primary_selection_device = nullptr; + struct GWL_PrimarySelection primary_selection; + /** Last device that was active. */ uint32_t data_source_serial = 0; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_Display Type (#wl_display & #wl_compositor wrapper) + * \{ */ + struct GWL_Display { GHOST_SystemWayland *system = nullptr; - struct wl_display *display = nullptr; - struct wl_compositor *compositor = nullptr; + struct wl_display *wl_display = nullptr; + struct wl_compositor *wl_compositor = nullptr; #ifdef WITH_GHOST_WAYLAND_LIBDECOR - struct libdecor *decor_context = nullptr; -#else - struct xdg_wm_base *xdg_shell = nullptr; - struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr; + GWL_LibDecor_System *libdecor = nullptr; + bool libdecor_required = false; #endif + GWL_XDG_Decor_System *xdg_decor = nullptr; struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; - struct wl_shm *shm = nullptr; + struct wl_shm *wl_shm = nullptr; std::vector<GWL_Output *> outputs; std::vector<GWL_Seat *> seats; - struct wl_data_device_manager *data_device_manager = nullptr; - struct zwp_tablet_manager_v2 *tablet_manager = nullptr; - struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr; - struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr; -}; + struct wl_data_device_manager *wl_data_device_manager = nullptr; + struct zwp_tablet_manager_v2 *wp_tablet_manager = nullptr; + struct zwp_relative_pointer_manager_v1 *wp_relative_pointer_manager = nullptr; + struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr; + struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr; -#undef LOG + struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr; + + GWL_SimpleBuffer clipboard; + GWL_SimpleBuffer clipboard_primary; +}; /** \} */ @@ -482,8 +744,8 @@ struct GWL_Display { static GHOST_WindowManager *window_manager = nullptr; -/** Check this lock before accessing `GHOST_SystemWayland::selection` from a thread. */ -static std::mutex system_selection_mutex; +/** Check this lock before accessing #GHOST_SystemWayland::clipboard_ from a thread. */ +static std::mutex system_clipboard_mutex; /** * Callback for WAYLAND to run when there is an error. @@ -526,31 +788,31 @@ static GWL_SeatStatePointer *seat_state_pointer_from_cursor_surface(GWL_Seat *se return nullptr; } -static void display_destroy(GWL_Display *d) +static void display_destroy(GWL_Display *display) { - if (d->data_device_manager) { - wl_data_device_manager_destroy(d->data_device_manager); + if (display->wl_data_device_manager) { + wl_data_device_manager_destroy(display->wl_data_device_manager); } - if (d->tablet_manager) { - zwp_tablet_manager_v2_destroy(d->tablet_manager); + if (display->wp_tablet_manager) { + zwp_tablet_manager_v2_destroy(display->wp_tablet_manager); } - for (GWL_Output *output : d->outputs) { + for (GWL_Output *output : display->outputs) { wl_output_destroy(output->wl_output); delete output; } - for (GWL_Seat *seat : d->seats) { + for (GWL_Seat *seat : display->seats) { /* First handle members that require locking. * While highly unlikely, it's possible they are being used while this function runs. */ { std::lock_guard lock{seat->data_source_mutex}; if (seat->data_source) { - free(seat->data_source->buffer_out); - if (seat->data_source->data_source) { - wl_data_source_destroy(seat->data_source->data_source); + gwl_simple_buffer_free_data(&seat->data_source->buffer_out); + if (seat->data_source->wl_source) { + wl_data_source_destroy(seat->data_source->wl_source); } delete seat->data_source; } @@ -572,32 +834,39 @@ static void display_destroy(GWL_Display *d) } } - if (seat->data_device) { - wl_data_device_release(seat->data_device); + { + GWL_PrimarySelection *primary = &seat->primary_selection; + std::lock_guard lock{primary->data_offer_mutex}; + gwl_primary_selection_discard_offer(primary); } - if (seat->cursor.custom_data) { - munmap(seat->cursor.custom_data, seat->cursor.custom_data_size); + { + GWL_PrimarySelection *primary = &seat->primary_selection; + std::lock_guard lock{primary->data_source_mutex}; + gwl_primary_selection_discard_source(primary); } - if (seat->wl_pointer) { - if (seat->cursor.wl_surface) { - wl_surface_destroy(seat->cursor.wl_surface); - } - if (seat->cursor.wl_theme) { - wl_cursor_theme_destroy(seat->cursor.wl_theme); - } - if (seat->wl_pointer) { - wl_pointer_destroy(seat->wl_pointer); - } + if (seat->wp_primary_selection_device) { + zwp_primary_selection_device_v1_destroy(seat->wp_primary_selection_device); } - if (seat->wl_keyboard) { - if (seat->key_repeat.timer) { - keyboard_handle_key_repeat_cancel(seat); - } - wl_keyboard_destroy(seat->wl_keyboard); + + if (seat->wl_data_device) { + wl_data_device_release(seat->wl_data_device); + } + + if (seat->cursor.custom_data) { + munmap(seat->cursor.custom_data, seat->cursor.custom_data_size); } + /* Disable all capabilities as a way to free: + * - `seat.wl_pointer` (and related cursor variables). + * - `seat.wl_touch`. + * - `seat.wl_keyboard`. + */ + gwl_seat_capability_pointer_disable(seat); + gwl_seat_capability_keyboard_disable(seat); + gwl_seat_capability_touch_disable(seat); + /* Un-referencing checks for NULL case. */ xkb_state_unref(seat->xkb_state); xkb_state_unref(seat->xkb_state_empty); @@ -609,45 +878,55 @@ static void display_destroy(GWL_Display *d) delete seat; } - if (d->shm) { - wl_shm_destroy(d->shm); + if (display->wl_shm) { + wl_shm_destroy(display->wl_shm); } - if (d->relative_pointer_manager) { - zwp_relative_pointer_manager_v1_destroy(d->relative_pointer_manager); + if (display->wp_relative_pointer_manager) { + zwp_relative_pointer_manager_v1_destroy(display->wp_relative_pointer_manager); } - if (d->pointer_constraints) { - zwp_pointer_constraints_v1_destroy(d->pointer_constraints); + if (display->wp_pointer_constraints) { + zwp_pointer_constraints_v1_destroy(display->wp_pointer_constraints); } - if (d->compositor) { - wl_compositor_destroy(d->compositor); + if (display->wp_pointer_gestures) { + zwp_pointer_gestures_v1_destroy(display->wp_pointer_gestures); + } + + if (display->wl_compositor) { + wl_compositor_destroy(display->wl_compositor); } #ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (d->decor_context) { - libdecor_unref(d->decor_context); + if (use_libdecor) { + if (display->libdecor) { + gwl_libdecor_system_destroy(display->libdecor); + } } -#else - if (d->xdg_decoration_manager) { - zxdg_decoration_manager_v1_destroy(d->xdg_decoration_manager); + else +#endif + { + if (display->xdg_decor) { + gwl_xdg_decor_system_destroy(display->xdg_decor); + } } - if (d->xdg_shell) { - xdg_wm_base_destroy(d->xdg_shell); + if (eglGetDisplay) { + ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl_display))); } -#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */ - if (eglGetDisplay) { - ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(d->display))); + if (display->wl_display) { + wl_display_disconnect(display->wl_display); } - if (d->display) { - wl_display_disconnect(d->display); + { + std::lock_guard lock{system_clipboard_mutex}; + gwl_simple_buffer_free_data(&display->clipboard); + gwl_simple_buffer_free_data(&display->clipboard_primary); } - delete d; + delete display; } static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym) @@ -785,9 +1064,24 @@ static GHOST_TKey xkb_map_gkey_or_scan_code(const xkb_keysym_t sym, const uint32 return gkey; } -static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wl_tablet_tool_type) +static int pointer_axis_as_index(const uint32_t axis) +{ + switch (axis) { + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: { + return 0; + } + case WL_POINTER_AXIS_VERTICAL_SCROLL: { + return 1; + } + default: { + return -1; + } + } +} + +static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_tablet_tool_type) { - switch (wl_tablet_tool_type) { + switch (wp_tablet_tool_type) { case ZWP_TABLET_TOOL_V2_TYPE_ERASER: { return GHOST_kTabletModeEraser; } @@ -802,7 +1096,7 @@ static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wl_ta } } - GHOST_PRINT("unknown tablet tool: " << wl_tablet_tool_type << std::endl); + GHOST_PRINT("unknown tablet tool: " << wp_tablet_tool_type << std::endl); return GHOST_kTabletModeStylus; } @@ -993,7 +1287,7 @@ static void keyboard_depressed_state_key_event(GWL_Seat *seat, } static void keyboard_depressed_state_push_events_from_change( - GWL_Seat *seat, const WGL_KeyboardDepressedState &key_depressed_prev) + GWL_Seat *seat, const GWL_KeyboardDepressedState &key_depressed_prev) { GHOST_IWindow *win = ghost_wl_surface_user_data(seat->keyboard.wl_surface); GHOST_SystemWayland *system = seat->system; @@ -1131,15 +1425,81 @@ static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event) } } -static std::string read_pipe(GWL_DataOffer *data_offer, - const std::string mime_receive, - std::mutex *mutex) +/** + * Read from `fd` into a buffer which is returned. + * \return the buffer or null on failure. + */ +static const char *read_file_as_buffer(const int fd, size_t *r_len) +{ + struct ByteChunk { + ByteChunk *next; + char data[4096 - sizeof(ByteChunk *)]; + }; + ByteChunk *chunk_first = nullptr, **chunk_link_p = &chunk_first; + bool ok = true; + size_t len = 0; + while (true) { + ByteChunk *chunk = static_cast<typeof(chunk)>(malloc(sizeof(*chunk))); + if (UNLIKELY(chunk == nullptr)) { + CLOG_WARN(LOG, "unable to allocate chunk for file buffer"); + ok = false; + break; + } + chunk->next = nullptr; + const ssize_t len_chunk = read(fd, chunk->data, sizeof(chunk->data)); + if (len_chunk <= 0) { + if (UNLIKELY(len_chunk < 0)) { + CLOG_WARN(LOG, "error reading from pipe: %s", std::strerror(errno)); + ok = false; + } + free(chunk); + break; + } + if (chunk_first == nullptr) { + chunk_first = chunk; + } + *chunk_link_p = chunk; + chunk_link_p = &chunk->next; + len += len_chunk; + } + + char *buf = nullptr; + if (ok) { + buf = static_cast<char *>(malloc(len)); + if (UNLIKELY(buf == nullptr)) { + CLOG_WARN(LOG, "unable to allocate file buffer: %zu bytes", len); + ok = false; + } + } + + *r_len = ok ? len : 0; + char *buf_stride = buf; + while (chunk_first) { + if (ok) { + const size_t len_chunk = std::min(len, sizeof(chunk_first->data)); + memcpy(buf_stride, chunk_first->data, len_chunk); + buf_stride += len_chunk; + len -= len_chunk; + } + ByteChunk *chunk = chunk_first->next; + free(chunk_first); + chunk_first = chunk; + } + + return buf; +} + +static const char *read_buffer_from_data_offer(GWL_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + size_t *r_len) { int pipefd[2]; if (UNLIKELY(pipe(pipefd) != 0)) { - return {}; + CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno)); + return nullptr; } - wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]); + wl_data_offer_receive(data_offer->id, mime_receive, pipefd[1]); close(pipefd[1]); data_offer->in_use.store(false); @@ -1149,15 +1509,34 @@ static std::string read_pipe(GWL_DataOffer *data_offer, } /* WARNING: `data_offer` may be freed from now on. */ - 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); - } + const char *buf = read_file_as_buffer(pipefd[0], r_len); close(pipefd[0]); + return buf; +} + +static const char *read_buffer_from_primary_selection_offer( + GWL_PrimarySelection_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + size_t *r_len) +{ + int pipefd[2]; + if (UNLIKELY(pipe(pipefd) != 0)) { + CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno)); + return nullptr; + } + zwp_primary_selection_offer_v1_receive(data_offer->id, mime_receive, pipefd[1]); + close(pipefd[1]); - return data; + data_offer->in_use.store(false); + + if (mutex) { + mutex->unlock(); + } + /* WARNING: `data_offer` may be freed from now on. */ + const char *buf = read_file_as_buffer(pipefd[0], r_len); + close(pipefd[0]); + return buf; } /** @@ -1183,16 +1562,22 @@ static void data_source_handle_send(void *data, CLOG_INFO(LOG, 2, "send"); - const char *const buffer = seat->data_source->buffer_out; - if (write(fd, buffer, strlen(buffer)) < 0) { - GHOST_PRINT("error writing to clipboard: " << std::strerror(errno) << std::endl); + const char *const buffer = seat->data_source->buffer_out.data; + if (UNLIKELY(write(fd, buffer, seat->data_source->buffer_out.data_size) < 0)) { + CLOG_WARN(LOG, "error writing to clipboard: %s", std::strerror(errno)); } close(fd); } -static void data_source_handle_cancelled(void * /*data*/, struct wl_data_source *wl_data_source) +static void data_source_handle_cancelled(void *data, struct wl_data_source *wl_data_source) { CLOG_INFO(LOG, 2, "cancelled"); + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + GWL_DataSource *data_source = seat->data_source; + if (seat->data_source->wl_source == wl_data_source) { + data_source->wl_source = nullptr; + } + wl_data_source_destroy(wl_data_source); } @@ -1261,7 +1646,8 @@ static void data_offer_handle_offer(void *data, const char *mime_type) { CLOG_INFO(LOG, 2, "offer (mime_type=%s)", mime_type); - static_cast<GWL_DataOffer *>(data)->types.insert(mime_type); + GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data); + data_offer->types.insert(mime_type); } static void data_offer_handle_source_actions(void *data, @@ -1269,7 +1655,8 @@ static void data_offer_handle_source_actions(void *data, const uint32_t source_actions) { CLOG_INFO(LOG, 2, "source_actions (%u)", source_actions); - static_cast<GWL_DataOffer *>(data)->source_actions = source_actions; + GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data); + data_offer->dnd.source_actions = (enum wl_data_device_manager_dnd_action)source_actions; } static void data_offer_handle_action(void *data, @@ -1277,7 +1664,8 @@ static void data_offer_handle_action(void *data, const uint32_t dnd_action) { CLOG_INFO(LOG, 2, "actions (%u)", dnd_action); - static_cast<GWL_DataOffer *>(data)->dnd_action = dnd_action; + GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data); + data_offer->dnd.action = (enum wl_data_device_manager_dnd_action)dnd_action; } static const struct wl_data_offer_listener data_offer_listener = { @@ -1400,7 +1788,11 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat const std::string mime_receive) { const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)}; - const std::string data = read_pipe(data_offer, mime_receive, nullptr); + size_t data_buf_len = 0; + const char *data_buf = read_buffer_from_data_offer( + data_offer, mime_receive.c_str(), nullptr, &data_buf_len); + std::string data = data_buf ? std::string(data_buf, data_buf_len) : ""; + free(const_cast<char *>(data_buf)); CLOG_INFO( LOG, 2, "drop_read_uris mime_receive=%s, data=%s", mime_receive.c_str(), data.c_str()); @@ -1466,7 +1858,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat * 'text_update_edited' to behave like dropped text was pasted. */ CLOG_INFO(LOG, 2, "drop_read_uris_fn (text_plain, text_utf8), unhandled!"); } - wl_display_roundtrip(system->display()); + wl_display_roundtrip(system->wl_display()); }; /* Pass in `seat->wl_surface_focus_dnd` instead of accessing it from `seat` since the leave @@ -1499,7 +1891,6 @@ static void data_device_handle_selection(void *data, return; } CLOG_INFO(LOG, 2, "selection"); - /* Get new data offer. */ data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id)); seat->data_offer_copy_paste = data_offer; @@ -1516,12 +1907,15 @@ static void data_device_handle_selection(void *data, break; } } - const std::string data = read_pipe( - data_offer, mime_receive, &seat->data_offer_copy_paste_mutex); + + size_t data_len = 0; + const char *data = read_buffer_from_data_offer( + data_offer, mime_receive.c_str(), &seat->data_offer_copy_paste_mutex, &data_len); { - std::lock_guard lock{system_selection_mutex}; - system->selection_set(data); + std::lock_guard lock{system_clipboard_mutex}; + GWL_SimpleBuffer *buf = system->clipboard_data(false); + gwl_simple_buffer_set_and_take_ownership(buf, data, data_len); } }; @@ -1595,7 +1989,8 @@ static bool update_cursor_scale(GWL_Cursor &cursor, wl_surface_set_buffer_scale(wl_cursor_surface, scale); } wl_cursor_theme_destroy(cursor.wl_theme); - cursor.wl_theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm); + cursor.wl_theme = wl_cursor_theme_load( + cursor.theme_name.c_str(), scale * cursor.theme_size, shm); return true; } return false; @@ -1616,7 +2011,7 @@ static void cursor_surface_handle_enter(void *data, wl_surface); const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output); seat_state_pointer->outputs.insert(reg_output); - update_cursor_scale(seat->cursor, seat->system->shm(), seat_state_pointer, wl_surface); + update_cursor_scale(seat->cursor, seat->system->wl_shm(), seat_state_pointer, wl_surface); } static void cursor_surface_handle_leave(void *data, @@ -1634,7 +2029,7 @@ static void cursor_surface_handle_leave(void *data, wl_surface); const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output); seat_state_pointer->outputs.erase(reg_output); - update_cursor_scale(seat->cursor, seat->system->shm(), seat_state_pointer, wl_surface); + update_cursor_scale(seat->cursor, seat->system->wl_shm(), seat_state_pointer, wl_surface); } static const struct wl_surface_listener cursor_surface_listener = { @@ -1675,6 +2070,15 @@ static void pointer_handle_enter(void *data, seat->pointer.serial = serial; seat->pointer.xy[0] = surface_x; seat->pointer.xy[1] = surface_y; + + /* Resetting scroll events is likely unnecessary, + * do this to avoid any possible problems as it's harmless. */ + seat->pointer_scroll.smooth_xy[0] = 0; + seat->pointer_scroll.smooth_xy[1] = 0; + seat->pointer_scroll.discrete_xy[0] = 0; + seat->pointer_scroll.discrete_xy[1] = 0; + seat->pointer_scroll.axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; + seat->pointer.wl_surface = wl_surface; win->setCursorShape(win->getCursorShape()); @@ -1786,24 +2190,91 @@ static void pointer_handle_button(void *data, } } -static void pointer_handle_axis(void * /*data*/, +static void pointer_handle_axis(void *data, struct wl_pointer * /*wl_pointer*/, const uint32_t /*time*/, const uint32_t axis, const wl_fixed_t value) { + /* NOTE: this is used for touch based scrolling - or other input that doesn't scroll with + * discrete "steps". This allows supporting smooth-scrolling without "touch" gesture support. */ CLOG_INFO(LOG, 2, "axis (axis=%u, value=%d)", axis, value); + const int index = pointer_axis_as_index(axis); + if (UNLIKELY(index == -1)) { + return; + } + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + seat->pointer_scroll.smooth_xy[index] = value; } -static void pointer_handle_frame(void * /*data*/, struct wl_pointer * /*wl_pointer*/) +static void pointer_handle_frame(void *data, struct wl_pointer * /*wl_pointer*/) { CLOG_INFO(LOG, 2, "frame"); + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + + /* Both discrete and smooth events may be set at once, never generate events for both + * as this will be handling the same event in to different ways. + * Prioritize discrete axis events for the mouse wheel, otherwise smooth scroll. */ + if (seat->pointer_scroll.axis_source == WL_POINTER_AXIS_SOURCE_WHEEL) { + if (seat->pointer_scroll.discrete_xy[0]) { + seat->pointer_scroll.smooth_xy[0] = 0; + } + if (seat->pointer_scroll.discrete_xy[1]) { + seat->pointer_scroll.smooth_xy[1] = 0; + } + } + else { + if (seat->pointer_scroll.smooth_xy[0]) { + seat->pointer_scroll.discrete_xy[0] = 0; + } + if (seat->pointer_scroll.smooth_xy[1]) { + seat->pointer_scroll.discrete_xy[1] = 0; + } + } + + /* Discrete X axis currently unsupported. */ + if (seat->pointer_scroll.discrete_xy[1]) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); + const int32_t discrete = seat->pointer_scroll.discrete_xy[1]; + seat->system->pushEvent(new GHOST_EventWheel( + seat->system->getMilliSeconds(), win, std::signbit(discrete) ? +1 : -1)); + } + seat->pointer_scroll.discrete_xy[0] = 0; + seat->pointer_scroll.discrete_xy[1] = 0; + } + + if (seat->pointer_scroll.smooth_xy[0] || seat->pointer_scroll.smooth_xy[1]) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); + const wl_fixed_t scale = win->scale(); + seat->system->pushEvent(new GHOST_EventTrackpad( + seat->system->getMilliSeconds(), + win, + GHOST_kTrackpadEventScroll, + wl_fixed_to_int(scale * seat->pointer.xy[0]), + wl_fixed_to_int(scale * seat->pointer.xy[1]), + /* NOTE: scaling the delta doesn't seem necessary. + * NOTE: inverting delta gives correct results, see: QTBUG-85767. */ + -wl_fixed_to_int(seat->pointer_scroll.smooth_xy[0]), + -wl_fixed_to_int(seat->pointer_scroll.smooth_xy[1]), + /* TODO: investigate a way to request this configuration from the system. */ + false)); + } + + seat->pointer_scroll.smooth_xy[0] = 0; + seat->pointer_scroll.smooth_xy[1] = 0; + } + + seat->pointer_scroll.axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; } -static void pointer_handle_axis_source(void * /*data*/, +static void pointer_handle_axis_source(void *data, struct wl_pointer * /*wl_pointer*/, uint32_t axis_source) { CLOG_INFO(LOG, 2, "axis_source (axis_source=%u)", axis_source); + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + seat->pointer_scroll.axis_source = (enum wl_pointer_axis_source)axis_source; } static void pointer_handle_axis_stop(void * /*data*/, struct wl_pointer * /*wl_pointer*/, @@ -1817,18 +2288,15 @@ static void pointer_handle_axis_discrete(void *data, uint32_t axis, int32_t discrete) { + /* NOTE: a discrete axis are typically mouse wheel events. + * The non-discrete version of this function is used for touch-pad. */ CLOG_INFO(LOG, 2, "axis_discrete (axis=%u, discrete=%d)", axis, discrete); - - GWL_Seat *seat = static_cast<GWL_Seat *>(data); - if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) { + const int index = pointer_axis_as_index(axis); + if (UNLIKELY(index == -1)) { return; } - - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { - GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); - seat->system->pushEvent(new GHOST_EventWheel( - seat->system->getMilliSeconds(), win, std::signbit(discrete) ? +1 : -1)); - } + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + seat->pointer_scroll.discrete_xy[index] = discrete; } static const struct wl_pointer_listener pointer_listener = { @@ -1848,6 +2316,312 @@ static const struct wl_pointer_listener pointer_listener = { /** \} */ /* -------------------------------------------------------------------- */ +/** \name Listener (Pointer Gesture: Hold), #zwp_pointer_gesture_hold_v1_listener + * \{ */ + +#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE +static CLG_LogRef LOG_WL_POINTER_GESTURE_HOLD = {"ghost.wl.handle.pointer_gesture.hold"}; +# define LOG (&LOG_WL_POINTER_GESTURE_HOLD) + +static void gesture_hold_handle_begin( + void * /*data*/, + struct zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/, + uint32_t /*serial*/, + uint32_t /*time*/, + struct wl_surface * /*surface*/, + uint32_t fingers) +{ + CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers); +} + +static void gesture_hold_handle_end( + void * /*data*/, + struct zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/, + uint32_t /*serial*/, + uint32_t /*time*/, + int32_t cancelled) +{ + CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled); +} + +static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = { + gesture_hold_handle_begin, + gesture_hold_handle_end, +}; + +# undef LOG +#endif /* ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Pointer Gesture: Pinch), #zwp_pointer_gesture_pinch_v1_listener + * \{ */ + +#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE +static CLG_LogRef LOG_WL_POINTER_GESTURE_PINCH = {"ghost.wl.handle.pointer_gesture.pinch"}; +# define LOG (&LOG_WL_POINTER_GESTURE_PINCH) + +static void gesture_pinch_handle_begin(void *data, + struct zwp_pointer_gesture_pinch_v1 * /*pinch*/, + uint32_t /*serial*/, + uint32_t /*time*/, + struct wl_surface * /*surface*/, + uint32_t fingers) +{ + CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers); + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + /* Reset defaults. */ + seat->pointer_gesture_pinch = GWL_SeatStatePointerGesture_Pinch{}; + + GHOST_WindowWayland *win = nullptr; + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + win = ghost_wl_surface_user_data(wl_surface_focus); + } + const wl_fixed_t win_scale = win ? win->scale() : 1; + + /* NOTE(@campbellbarton): Scale factors match Blender's operators & default preferences. + * For these values to work correctly, operator logic will need to be changed not to scale input + * by the region size (as with 3D view zoom) or preference for 3D view orbit sensitivity. + * + * By working "correctly" I mean that a rotation action where the users fingers rotate to + * opposite locations should always rotate the viewport 180d, since users will expect the + * physical location of their fingers to match the viewport. + * Similarly with zoom, the scale value from the pinch action can be mapped to a zoom level + * although unlike rotation, an inexact mapping is less noticeable. + * Users may even prefer the zoom level to be scaled - which could be a preference. */ + seat->pointer_gesture_pinch.scale.value = wl_fixed_from_int(1); + /* The value 300 matches a value used in clip & image zoom operators. + * It seems OK for the 3D view too. */ + seat->pointer_gesture_pinch.scale.factor = 300 * win_scale; + /* The value 5 is used on macOS and roughly maps 1:1 with turntable rotation, + * although preferences can scale the sensitivity (which would be skipped ideally). */ + seat->pointer_gesture_pinch.rotation.factor = 5 * win_scale; +} + +static void gesture_pinch_handle_update(void *data, + struct zwp_pointer_gesture_pinch_v1 * /*pinch*/, + uint32_t /*time*/, + wl_fixed_t dx, + wl_fixed_t dy, + wl_fixed_t scale, + wl_fixed_t rotation) +{ + CLOG_INFO(LOG, + 2, + "update (dx=%.3f, dy=%.3f, scale=%.3f, rotation=%.3f)", + wl_fixed_to_double(dx), + wl_fixed_to_double(dy), + wl_fixed_to_double(scale), + wl_fixed_to_double(rotation)); + + GWL_Seat *seat = static_cast<GWL_Seat *>(data); + + GHOST_WindowWayland *win = nullptr; + + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + win = ghost_wl_surface_user_data(wl_surface_focus); + } + + /* Scale defaults to `wl_fixed_from_int(1)` which may change while pinching. + * This needs to be converted to a delta. */ + const wl_fixed_t scale_delta = scale - seat->pointer_gesture_pinch.scale.value; + const int scale_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta( + &seat->pointer_gesture_pinch.scale, scale_delta); + + /* Rotation in degrees, unlike scale this is a delta. */ + const int rotation_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta( + &seat->pointer_gesture_pinch.rotation, rotation); + + if (win) { + const wl_fixed_t win_scale = win->scale(); + const int32_t event_xy[2] = { + wl_fixed_to_int(win_scale * seat->pointer.xy[0]), + wl_fixed_to_int(win_scale * seat->pointer.xy[1]), + }; + if (scale_as_delta_px) { + seat->system->pushEvent(new GHOST_EventTrackpad(seat->system->getMilliSeconds(), + win, + GHOST_kTrackpadEventMagnify, + event_xy[0], + event_xy[1], + scale_as_delta_px, + 0, + false)); + } + + if (rotation_as_delta_px) { + seat->system->pushEvent(new GHOST_EventTrackpad(seat->system->getMilliSeconds(), + win, + GHOST_kTrackpadEventRotate, + event_xy[0], + event_xy[1], + rotation_as_delta_px, + 0, + false)); + } + } +} + +static void gesture_pinch_handle_end(void * /*data*/, + struct zwp_pointer_gesture_pinch_v1 * /*pinch*/, + uint32_t /*serial*/, + uint32_t /*time*/, + int32_t cancelled) +{ + CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled); +} + +static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener = { + gesture_pinch_handle_begin, + gesture_pinch_handle_update, + gesture_pinch_handle_end, +}; + +# undef LOG +#endif /* ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Pointer Gesture: Swipe), #zwp_pointer_gesture_swipe_v1 + * + * \note In both Gnome-Shell & KDE this gesture isn't emitted at time of writing, + * instead, high resolution 2D #wl_pointer_listener.axis data is generated which works well. + * There may be some situations where WAYLAND compositors generate this gesture + * (swiping with 3+ fingers, for e.g.). So keep this to allow logging & testing gestures. + * \{ */ + +#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE +static CLG_LogRef LOG_WL_POINTER_GESTURE_SWIPE = {"ghost.wl.handle.pointer_gesture.swipe"}; +# define LOG (&LOG_WL_POINTER_GESTURE_SWIPE) + +static void gesture_swipe_handle_begin( + void * /*data*/, + struct zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/, + uint32_t /*serial*/, + uint32_t /*time*/, + struct wl_surface * /*surface*/, + uint32_t fingers) +{ + CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers); +} + +static void gesture_swipe_handle_update( + void * /*data*/, + struct zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/, + uint32_t /*time*/, + wl_fixed_t dx, + wl_fixed_t dy) +{ + CLOG_INFO(LOG, 2, "update (dx=%.3f, dy=%.3f)", wl_fixed_to_double(dx), wl_fixed_to_double(dy)); +} + +static void gesture_swipe_handle_end( + void * /*data*/, + struct zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/, + uint32_t /*serial*/, + uint32_t /*time*/, + int32_t cancelled) +{ + CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled); +} + +static const struct zwp_pointer_gesture_swipe_v1_listener gesture_swipe_listener = { + gesture_swipe_handle_begin, + gesture_swipe_handle_update, + gesture_swipe_handle_end, +}; + +# undef LOG +#endif /* ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Touch Seat), #wl_touch_listener + * + * TODO(@campbellbarton): Only setup the callbacks for now as I don't have + * hardware that generates touch events. + * \{ */ + +static CLG_LogRef LOG_WL_TOUCH = {"ghost.wl.handle.touch"}; +#define LOG (&LOG_WL_TOUCH) + +static void touch_seat_handle_down(void * /*data*/, + struct wl_touch * /*wl_touch*/, + uint32_t /*serial*/, + uint32_t /*time*/, + struct wl_surface * /*wl_surface*/, + int32_t /*id*/, + wl_fixed_t /*x*/, + wl_fixed_t /*y*/) +{ + CLOG_INFO(LOG, 2, "down"); +} + +static void touch_seat_handle_up(void * /*data*/, + struct wl_touch * /*wl_touch*/, + uint32_t /*serial*/, + uint32_t /*time*/, + int32_t /*id*/) +{ + CLOG_INFO(LOG, 2, "up"); +} + +static void touch_seat_handle_motion(void * /*data*/, + struct wl_touch * /*wl_touch*/, + uint32_t /*time*/, + int32_t /*id*/, + wl_fixed_t /*x*/, + wl_fixed_t /*y*/) +{ + CLOG_INFO(LOG, 2, "motion"); +} + +static void touch_seat_handle_frame(void * /*data*/, struct wl_touch * /*wl_touch*/) +{ + CLOG_INFO(LOG, 2, "frame"); +} + +static void touch_seat_handle_cancel(void * /*data*/, struct wl_touch * /*wl_touch*/) +{ + + CLOG_INFO(LOG, 2, "cancel"); +} + +static void touch_seat_handle_shape(void * /*data*/, + struct wl_touch * /*touch*/, + int32_t /*id*/, + wl_fixed_t /*major*/, + wl_fixed_t /*minor*/) +{ + CLOG_INFO(LOG, 2, "shape"); +} + +static void touch_seat_handle_orientation(void * /*data*/, + struct wl_touch * /*touch*/, + int32_t /*id*/, + wl_fixed_t /*orientation*/) +{ + CLOG_INFO(LOG, 2, "orientation"); +} + +static const struct wl_touch_listener touch_seat_listener = { + touch_seat_handle_down, + touch_seat_handle_up, + touch_seat_handle_motion, + touch_seat_handle_frame, + touch_seat_handle_cancel, + touch_seat_handle_shape, + touch_seat_handle_orientation, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Listener (Tablet Tool), #zwp_tablet_tool_v2_listener * \{ */ @@ -2206,7 +2980,7 @@ static void tablet_seat_handle_tool_added(void *data, tablet_tool->seat = seat; /* Every tool has it's own cursor wl_surface. */ - tablet_tool->wl_surface_cursor = wl_compositor_create_surface(seat->system->compositor()); + tablet_tool->wl_surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor()); ghost_wl_surface_tag_cursor_tablet(tablet_tool->wl_surface_cursor); wl_surface_add_listener(tablet_tool->wl_surface_cursor, &cursor_surface_listener, (void *)seat); @@ -2326,7 +3100,7 @@ static void keyboard_handle_enter(void *data, /* If there are any keys held when activating the window, * modifiers will be compared against the seat state, * only enabling modifiers that were previously disabled. */ - WGL_KeyboardDepressedState key_depressed_prev = seat->key_depressed; + GWL_KeyboardDepressedState key_depressed_prev = seat->key_depressed; keyboard_depressed_state_reset(seat); uint32_t *key; @@ -2536,11 +3310,10 @@ static void keyboard_handle_key(void *data, /* Start timer for repeating key, if applicable. */ if ((seat->key_repeat.rate > 0) && (etype == GHOST_kEventKeyDown) && xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb_state), key_code)) { - key_repeat_payload = new GWL_KeyRepeatPlayload({ - .seat = seat, - .key_code = key_code, - .key_data = {.gkey = gkey}, - }); + key_repeat_payload = new GWL_KeyRepeatPlayload(); + key_repeat_payload->seat = seat; + key_repeat_payload->key_code = key_code; + key_repeat_payload->key_data.gkey = gkey; } } @@ -2630,12 +3403,306 @@ static const struct wl_keyboard_listener keyboard_listener = { /** \} */ /* -------------------------------------------------------------------- */ +/** \name Listener (Primary Selection Offer), #zwp_primary_selection_offer_v1_listener + * \{ */ + +static CLG_LogRef LOG_WL_PRIMARY_SELECTION_OFFER = {"ghost.wl.handle.primary_selection_offer"}; +#define LOG (&LOG_WL_PRIMARY_SELECTION_OFFER) + +static void primary_selection_offer_offer(void *data, + struct zwp_primary_selection_offer_v1 *id, + const char *type) +{ + GWL_PrimarySelection_DataOffer *data_offer = static_cast<GWL_PrimarySelection_DataOffer *>(data); + if (data_offer->id != id) { + CLOG_INFO(LOG, 2, "offer: %p: offer for unknown selection %p of %s (skipped)", data, id, type); + return; + } + + data_offer->types.insert(std::string(type)); +} + +static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = { + primary_selection_offer_offer, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Primary Selection Device), #zwp_primary_selection_device_v1_listener + * \{ */ + +static CLG_LogRef LOG_WL_PRIMARY_SELECTION_DEVICE = {"ghost.wl.handle.primary_selection_device"}; +#define LOG (&LOG_WL_PRIMARY_SELECTION_DEVICE) + +static void primary_selection_device_handle_data_offer( + void * /*data*/, + struct zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/, + struct zwp_primary_selection_offer_v1 *id) +{ + CLOG_INFO(LOG, 2, "data_offer"); + + GWL_PrimarySelection_DataOffer *data_offer = new GWL_PrimarySelection_DataOffer; + data_offer->id = id; + zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, data_offer); +} + +static void primary_selection_device_handle_selection( + void *data, + struct zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/, + struct zwp_primary_selection_offer_v1 *id) +{ + GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data); + + std::lock_guard lock{primary->data_offer_mutex}; + + /* Delete old data offer. */ + if (primary->data_offer != nullptr) { + gwl_primary_selection_discard_offer(primary); + } + + if (id == nullptr) { + CLOG_INFO(LOG, 2, "selection: (skipped)"); + return; + } + CLOG_INFO(LOG, 2, "selection"); + /* Get new data offer. */ + GWL_PrimarySelection_DataOffer *data_offer = static_cast<GWL_PrimarySelection_DataOffer *>( + zwp_primary_selection_offer_v1_get_user_data(id)); + primary->data_offer = data_offer; + + auto read_selection_fn = [](GWL_PrimarySelection *primary) { + GHOST_SystemWayland *system = static_cast<GHOST_SystemWayland *>(GHOST_ISystem::getSystem()); + primary->data_offer_mutex.lock(); + + GWL_PrimarySelection_DataOffer *data_offer = primary->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; + } + } + size_t data_len = 0; + const char *data = read_buffer_from_primary_selection_offer( + data_offer, mime_receive.c_str(), &primary->data_offer_mutex, &data_len); + + { + std::lock_guard lock{system_clipboard_mutex}; + GWL_SimpleBuffer *buf = system->clipboard_data(true); + gwl_simple_buffer_set_and_take_ownership(buf, data, data_len); + } + }; + + std::thread read_thread(read_selection_fn, primary); + read_thread.detach(); +} + +static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = { + primary_selection_device_handle_data_offer, + primary_selection_device_handle_selection, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Primary Selection Source), #zwp_primary_selection_source_v1_listener + * \{ */ + +static CLG_LogRef LOG_WL_PRIMARY_SELECTION_SOURCE = {"ghost.wl.handle.primary_selection_source"}; +#define LOG (&LOG_WL_PRIMARY_SELECTION_SOURCE) + +static void primary_selection_source_send(void *data, + struct zwp_primary_selection_source_v1 * /*source*/, + const char * /*mime_type*/, + int32_t fd) +{ + CLOG_INFO(LOG, 2, "send"); + + GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data); + + std::lock_guard lock{primary->data_source_mutex}; + GWL_PrimarySelection_DataSource *data_source = primary->data_source; + + const char *const buffer = data_source->buffer_out.data; + if (UNLIKELY(write(fd, buffer, data_source->buffer_out.data_size) < 0)) { + CLOG_WARN(LOG, "error writing to primary clipboard: %s", std::strerror(errno)); + } + close(fd); +} + +static void primary_selection_source_cancelled(void *data, + struct zwp_primary_selection_source_v1 *source) +{ + CLOG_INFO(LOG, 2, "cancelled"); + + GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data); + + if (source == primary->data_source->wp_source) { + gwl_primary_selection_discard_source(primary); + } +} + +static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = { + primary_selection_source_send, + primary_selection_source_cancelled, +}; + +#undef LOG + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Listener (Seat), #wl_seat_listener * \{ */ static CLG_LogRef LOG_WL_SEAT = {"ghost.wl.handle.seat"}; #define LOG (&LOG_WL_SEAT) +static void gwl_seat_capability_pointer_enable(GWL_Seat *seat) +{ + if (seat->wl_pointer) { + return; + } + seat->wl_pointer = wl_seat_get_pointer(seat->wl_seat); + seat->cursor.wl_surface = wl_compositor_create_surface(seat->system->wl_compositor()); + seat->cursor.visible = true; + seat->cursor.wl_buffer = nullptr; + if (!get_cursor_settings(seat->cursor.theme_name, seat->cursor.theme_size)) { + seat->cursor.theme_name = std::string(); + seat->cursor.theme_size = default_cursor_size; + } + wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); + + wl_surface_add_listener(seat->cursor.wl_surface, &cursor_surface_listener, seat); + ghost_wl_surface_tag_cursor_pointer(seat->cursor.wl_surface); + + zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures(); + if (pointer_gestures) { +#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE + { /* Hold gesture. */ + struct zwp_pointer_gesture_hold_v1 *gesture = zwp_pointer_gestures_v1_get_hold_gesture( + pointer_gestures, seat->wl_pointer); + zwp_pointer_gesture_hold_v1_set_user_data(gesture, seat); + zwp_pointer_gesture_hold_v1_add_listener(gesture, &gesture_hold_listener, seat); + seat->wp_pointer_gesture_hold = gesture; + } +#endif +#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE + { /* Pinch gesture. */ + struct zwp_pointer_gesture_pinch_v1 *gesture = zwp_pointer_gestures_v1_get_pinch_gesture( + pointer_gestures, seat->wl_pointer); + zwp_pointer_gesture_pinch_v1_set_user_data(gesture, seat); + zwp_pointer_gesture_pinch_v1_add_listener(gesture, &gesture_pinch_listener, seat); + seat->wp_pointer_gesture_pinch = gesture; + } +#endif +#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE + { /* Swipe gesture. */ + struct zwp_pointer_gesture_swipe_v1 *gesture = zwp_pointer_gestures_v1_get_swipe_gesture( + pointer_gestures, seat->wl_pointer); + zwp_pointer_gesture_swipe_v1_set_user_data(gesture, seat); + zwp_pointer_gesture_swipe_v1_add_listener(gesture, &gesture_swipe_listener, seat); + seat->wp_pointer_gesture_swipe = gesture; + } +#endif + } +} + +static void gwl_seat_capability_pointer_disable(GWL_Seat *seat) +{ + if (!seat->wl_pointer) { + return; + } + + zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures(); + if (pointer_gestures) { +#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE + { /* Hold gesture. */ + struct zwp_pointer_gesture_hold_v1 **gesture_p = &seat->wp_pointer_gesture_hold; + if (*gesture_p) { + zwp_pointer_gesture_hold_v1_destroy(*gesture_p); + *gesture_p = nullptr; + } + } +#endif +#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE + { /* Pinch gesture. */ + struct zwp_pointer_gesture_pinch_v1 **gesture_p = &seat->wp_pointer_gesture_pinch; + if (*gesture_p) { + zwp_pointer_gesture_pinch_v1_destroy(*gesture_p); + *gesture_p = nullptr; + } + } +#endif +#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE + { /* Swipe gesture. */ + struct zwp_pointer_gesture_swipe_v1 **gesture_p = &seat->wp_pointer_gesture_swipe; + if (*gesture_p) { + zwp_pointer_gesture_swipe_v1_destroy(*gesture_p); + *gesture_p = nullptr; + } + } +#endif + } + + if (seat->cursor.wl_surface) { + wl_surface_destroy(seat->cursor.wl_surface); + seat->cursor.wl_surface = nullptr; + } + if (seat->cursor.wl_theme) { + wl_cursor_theme_destroy(seat->cursor.wl_theme); + seat->cursor.wl_theme = nullptr; + } + + wl_pointer_destroy(seat->wl_pointer); + seat->wl_pointer = nullptr; +} + +static void gwl_seat_capability_keyboard_enable(GWL_Seat *seat) +{ + if (seat->wl_keyboard) { + return; + } + seat->wl_keyboard = wl_seat_get_keyboard(seat->wl_seat); + wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, seat); +} + +static void gwl_seat_capability_keyboard_disable(GWL_Seat *seat) +{ + if (!seat->wl_keyboard) { + return; + } + if (seat->key_repeat.timer) { + keyboard_handle_key_repeat_cancel(seat); + } + wl_keyboard_destroy(seat->wl_keyboard); + seat->wl_keyboard = nullptr; +} + +static void gwl_seat_capability_touch_enable(GWL_Seat *seat) +{ + if (seat->wl_touch) { + return; + } + seat->wl_touch = wl_seat_get_touch(seat->wl_seat); + wl_touch_set_user_data(seat->wl_touch, seat); + wl_touch_add_listener(seat->wl_touch, &touch_seat_listener, seat); +} + +static void gwl_seat_capability_touch_disable(GWL_Seat *seat) +{ + if (!seat->wl_touch) { + return; + } + wl_touch_destroy(seat->wl_touch); + seat->wl_touch = nullptr; +} + static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, const uint32_t capabilities) @@ -2648,27 +3715,43 @@ static void seat_handle_capabilities(void *data, (capabilities & WL_SEAT_CAPABILITY_TOUCH) != 0); GWL_Seat *seat = static_cast<GWL_Seat *>(data); - seat->wl_pointer = nullptr; - seat->wl_keyboard = nullptr; + GHOST_ASSERT(seat->wl_seat == wl_seat, "Seat mismatch"); if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - seat->wl_pointer = wl_seat_get_pointer(wl_seat); - seat->cursor.wl_surface = wl_compositor_create_surface(seat->system->compositor()); - seat->cursor.visible = true; - seat->cursor.wl_buffer = nullptr; - if (!get_cursor_settings(seat->cursor.theme_name, seat->cursor.size)) { - seat->cursor.theme_name = std::string(); - seat->cursor.size = default_cursor_size; - } - wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, data); - - wl_surface_add_listener(seat->cursor.wl_surface, &cursor_surface_listener, data); - ghost_wl_surface_tag_cursor_pointer(seat->cursor.wl_surface); + gwl_seat_capability_pointer_enable(seat); + } + else { + gwl_seat_capability_pointer_disable(seat); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - seat->wl_keyboard = wl_seat_get_keyboard(wl_seat); - wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, data); + gwl_seat_capability_keyboard_enable(seat); + } + else { + gwl_seat_capability_keyboard_disable(seat); + } + + if (capabilities & WL_SEAT_CAPABILITY_TOUCH) { + gwl_seat_capability_touch_enable(seat); + } + else { + gwl_seat_capability_touch_disable(seat); + } + + /* TODO(@campbellbarton): this could be moved out elsewhere. */ + if (seat->system) { + zwp_primary_selection_device_manager_v1 *primary_selection_device_manager = + seat->system->wp_primary_selection_manager(); + if (primary_selection_device_manager) { + if (seat->wp_primary_selection_device == nullptr) { + seat->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device( + primary_selection_device_manager, seat->wl_seat); + + zwp_primary_selection_device_v1_add_listener(seat->wp_primary_selection_device, + &primary_selection_device_listener, + &seat->primary_selection); + } + } } } @@ -2903,10 +3986,8 @@ static const struct wl_output_listener output_listener = { /** \name Listener (XDG WM Base), #xdg_wm_base_listener * \{ */ -#ifndef WITH_GHOST_WAYLAND_LIBDECOR - static CLG_LogRef LOG_WL_XDG_WM_BASE = {"ghost.wl.handle.xdg_wm_base"}; -# define LOG (&LOG_WL_XDG_WM_BASE) +#define LOG (&LOG_WL_XDG_WM_BASE) static void shell_handle_ping(void * /*data*/, struct xdg_wm_base *xdg_wm_base, @@ -2920,9 +4001,7 @@ static const struct xdg_wm_base_listener shell_listener = { shell_handle_ping, }; -# undef LOG - -#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ +#undef LOG /** \} */ @@ -2975,22 +4054,20 @@ static void global_handle_add(void *data, struct GWL_Display *display = static_cast<struct GWL_Display *>(data); if (STREQ(interface, wl_compositor_interface.name)) { - display->compositor = static_cast<wl_compositor *>( + display->wl_compositor = static_cast<wl_compositor *>( wl_registry_bind(wl_registry, name, &wl_compositor_interface, 3)); } -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - /* Pass. */ -#else else if (STREQ(interface, xdg_wm_base_interface.name)) { - display->xdg_shell = static_cast<xdg_wm_base *>( + GWL_XDG_Decor_System &decor = *display->xdg_decor; + decor.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); + xdg_wm_base_add_listener(decor.shell, &shell_listener, nullptr); } else if (STREQ(interface, zxdg_decoration_manager_v1_interface.name)) { - display->xdg_decoration_manager = static_cast<zxdg_decoration_manager_v1 *>( + GWL_XDG_Decor_System &decor = *display->xdg_decor; + decor.manager = static_cast<zxdg_decoration_manager_v1 *>( wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1)); } -#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ else if (STREQ(interface, zxdg_output_manager_v1_interface.name)) { display->xdg_output_manager = static_cast<zxdg_output_manager_v1 *>( wl_registry_bind(wl_registry, name, &zxdg_output_manager_v1_interface, 2)); @@ -3027,27 +4104,46 @@ static void global_handle_add(void *data, wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); } else if (STREQ(interface, wl_shm_interface.name)) { - display->shm = static_cast<wl_shm *>( + display->wl_shm = static_cast<wl_shm *>( wl_registry_bind(wl_registry, name, &wl_shm_interface, 1)); } else if (STREQ(interface, wl_data_device_manager_interface.name)) { - display->data_device_manager = static_cast<wl_data_device_manager *>( + display->wl_data_device_manager = static_cast<wl_data_device_manager *>( wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3)); } else if (STREQ(interface, zwp_tablet_manager_v2_interface.name)) { - display->tablet_manager = static_cast<zwp_tablet_manager_v2 *>( + display->wp_tablet_manager = static_cast<zwp_tablet_manager_v2 *>( wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1)); } else if (STREQ(interface, zwp_relative_pointer_manager_v1_interface.name)) { - display->relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>( + display->wp_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 (STREQ(interface, zwp_pointer_constraints_v1_interface.name)) { - display->pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>( + display->wp_pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>( wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1)); } + else if (STREQ(interface, zwp_pointer_gestures_v1_interface.name)) { + display->wp_pointer_gestures = static_cast<zwp_pointer_gestures_v1 *>( + wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 3)); + } + + else if (!strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name)) { + display->wp_primary_selection_device_manager = + static_cast<zwp_primary_selection_device_manager_v1 *>(wl_registry_bind( + wl_registry, name, &zwp_primary_selection_device_manager_v1_interface, 1)); + } + else { found = false; + +#ifdef USE_GNOME_NEEDS_LIBDECOR_HACK + if (STRPREFIX(interface, "gtk_shell")) { /* `gtk_shell1` at time of writing. */ + /* Only require `libdecor` when built with X11 support, + * otherwise there is nothing to fall back on. */ + display->libdecor_required = true; + } +#endif } CLOG_INFO(LOG, @@ -3090,60 +4186,103 @@ static const struct wl_registry_listener registry_listener = { * WAYLAND specific implementation of the #GHOST_System interface. * \{ */ -GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new GWL_Display) +GHOST_SystemWayland::GHOST_SystemWayland(bool background) + : GHOST_System(), display_(new GWL_Display) { wl_log_set_handler_client(ghost_wayland_log_handler); - d->system = this; + display_->system = this; /* Connect to the Wayland server. */ - d->display = wl_display_connect(nullptr); - if (!d->display) { - display_destroy(d); + display_->wl_display = wl_display_connect(nullptr); + if (!display_->wl_display) { + display_destroy(display_); throw std::runtime_error("Wayland: unable to connect to display!"); } + /* This may be removed later if decorations are required, needed as part of registration. */ + display_->xdg_decor = new GWL_XDG_Decor_System; + /* Register interfaces. */ - struct wl_registry *registry = wl_display_get_registry(d->display); - wl_registry_add_listener(registry, ®istry_listener, d); + struct wl_registry *registry = wl_display_get_registry(display_->wl_display); + wl_registry_add_listener(registry, ®istry_listener, display_); /* Call callback for registry listener. */ - wl_display_roundtrip(d->display); + wl_display_roundtrip(display_->wl_display); /* Call callbacks for registered listeners. */ - wl_display_roundtrip(d->display); + wl_display_roundtrip(display_->wl_display); wl_registry_destroy(registry); #ifdef WITH_GHOST_WAYLAND_LIBDECOR - d->decor_context = libdecor_new(d->display, &libdecor_interface); - if (!d->decor_context) { - display_destroy(d); - throw std::runtime_error("Wayland: unable to create window decorations!"); + /* Ignore windowing requirements when running in background mode, + * as it doesn't make sense to fall back to X11 because of windowing functionality + * in background mode, also LIBDECOR is crashing in background mode `blender -b -f 1` + * for e.g. while it could be fixed, requiring the library at all makes no sense . */ + if (background) { + display_->libdecor_required = false; } -#else - if (!d->xdg_shell) { - display_destroy(d); - throw std::runtime_error("Wayland: unable to access xdg_shell!"); + + if (display_->libdecor_required) { + gwl_xdg_decor_system_destroy(display_->xdg_decor); + display_->xdg_decor = nullptr; + + if (!has_libdecor) { +# ifdef WITH_GHOST_X11 + /* LIBDECOR was the only reason X11 was used, let the user know they need it installed. */ + fprintf(stderr, + "WAYLAND found but libdecor was not, install libdecor for Wayland support, " + "falling back to X11\n"); +# endif + display_destroy(display_); + throw std::runtime_error("Wayland: unable to find libdecor!"); + + use_libdecor = true; + } + } + else { + use_libdecor = false; + } +#endif /* WITH_GHOST_WAYLAND_LIBDECOR */ + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + display_->libdecor = new GWL_LibDecor_System; + GWL_LibDecor_System &decor = *display_->libdecor; + decor.context = libdecor_new(display_->wl_display, &libdecor_interface); + if (!decor.context) { + display_destroy(display_); + throw std::runtime_error("Wayland: unable to create window decorations!"); + } } + else #endif + { + GWL_XDG_Decor_System &decor = *display_->xdg_decor; + if (!decor.shell) { + display_destroy(display_); + throw std::runtime_error("Wayland: unable to access xdg_shell!"); + } + } /* Register data device per seat for IPC between Wayland clients. */ - if (d->data_device_manager) { - for (GWL_Seat *seat : d->seats) { - seat->data_device = wl_data_device_manager_get_data_device(d->data_device_manager, - seat->wl_seat); - wl_data_device_add_listener(seat->data_device, &data_device_listener, seat); + if (display_->wl_data_device_manager) { + for (GWL_Seat *seat : display_->seats) { + seat->wl_data_device = wl_data_device_manager_get_data_device( + display_->wl_data_device_manager, seat->wl_seat); + wl_data_device_add_listener(seat->wl_data_device, &data_device_listener, seat); } } - if (d->tablet_manager) { - for (GWL_Seat *seat : d->seats) { - seat->tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(d->tablet_manager, seat->wl_seat); - zwp_tablet_seat_v2_add_listener(seat->tablet_seat, &tablet_seat_listener, seat); + if (display_->wp_tablet_manager) { + for (GWL_Seat *seat : display_->seats) { + seat->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(display_->wp_tablet_manager, + seat->wl_seat); + zwp_tablet_seat_v2_add_listener(seat->wp_tablet_seat, &tablet_seat_listener, seat); } } } GHOST_SystemWayland::~GHOST_SystemWayland() { - display_destroy(d); + display_destroy(display_); } GHOST_TSuccess GHOST_SystemWayland::init() @@ -3178,10 +4317,10 @@ bool GHOST_SystemWayland::processEvents(bool waitForEvent) #endif /* WITH_INPUT_NDOF */ if (waitForEvent) { - wl_display_dispatch(d->display); + wl_display_dispatch(display_->wl_display); } else { - wl_display_roundtrip(d->display); + wl_display_roundtrip(display_->wl_display); } if (getEventManager()->getNumEvents() > 0) { @@ -3198,11 +4337,11 @@ bool GHOST_SystemWayland::setConsoleWindowState(GHOST_TConsoleWindowState /*acti GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) const { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; const xkb_mod_mask_t state = xkb_state_serialize_mods(seat->xkb_state, XKB_STATE_MODS_DEPRESSED); @@ -3215,7 +4354,7 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co } #endif - /* Use local #WGL_KeyboardDepressedState to check which key is pressed. + /* Use local #GWL_KeyboardDepressedState to check which key is pressed. * Use XKB as the source of truth, if there is any discrepancy. */ for (int i = 0; i < MOD_INDEX_NUM; i++) { if (UNLIKELY(seat->xkb_keymap_mod_index[i] == XKB_MOD_INVALID)) { @@ -3260,10 +4399,10 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_active(seat); if (!seat_state_pointer) { return GHOST_kFailure; @@ -3273,48 +4412,92 @@ GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const return GHOST_kSuccess; } -char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const +char *GHOST_SystemWayland::getClipboard(bool selection) const { - char *clipboard = static_cast<char *>(malloc(selection.size() + 1)); - memcpy(clipboard, selection.data(), selection.size() + 1); - return clipboard; + const GWL_SimpleBuffer *buf = clipboard_data(selection); + if (buf->data == nullptr) { + return nullptr; + } + return gwl_simple_buffer_as_string(buf); } -void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) const +static void system_clipboard_put_primary_selection(GWL_Display *display, const char *buffer) { - if (UNLIKELY(!d->data_device_manager || d->seats.empty())) { + if (!display->wp_primary_selection_device_manager) { return; } + GWL_Seat *seat = display->seats[0]; + GWL_PrimarySelection *primary = &seat->primary_selection; + + std::lock_guard lock{primary->data_source_mutex}; + + gwl_primary_selection_discard_source(primary); + + GWL_PrimarySelection_DataSource *data_source = new GWL_PrimarySelection_DataSource; + primary->data_source = data_source; + + /* Copy buffer. */ + gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer); + + data_source->wp_source = zwp_primary_selection_device_manager_v1_create_source( + display->wp_primary_selection_device_manager); + + zwp_primary_selection_source_v1_add_listener( + data_source->wp_source, &primary_selection_source_listener, primary); + + for (const std::string &type : mime_send) { + zwp_primary_selection_source_v1_offer(data_source->wp_source, type.c_str()); + } - GWL_Seat *seat = d->seats[0]; + if (seat->wp_primary_selection_device) { + zwp_primary_selection_device_v1_set_selection( + seat->wp_primary_selection_device, data_source->wp_source, seat->data_source_serial); + } +} + +static void system_clipboard_put(GWL_Display *display, const char *buffer) +{ + GWL_Seat *seat = display->seats[0]; std::lock_guard lock{seat->data_source_mutex}; GWL_DataSource *data_source = seat->data_source; /* Copy buffer. */ - free(data_source->buffer_out); - const size_t buffer_size = strlen(buffer) + 1; - data_source->buffer_out = static_cast<char *>(malloc(buffer_size)); - std::memcpy(data_source->buffer_out, buffer, buffer_size); + gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer); - data_source->data_source = wl_data_device_manager_create_data_source(d->data_device_manager); + data_source->wl_source = wl_data_device_manager_create_data_source( + display->wl_data_device_manager); - wl_data_source_add_listener(data_source->data_source, &data_source_listener, seat); + wl_data_source_add_listener(data_source->wl_source, &data_source_listener, seat); for (const std::string &type : mime_send) { - wl_data_source_offer(data_source->data_source, type.c_str()); + wl_data_source_offer(data_source->wl_source, type.c_str()); } - if (seat->data_device) { + if (seat->wl_data_device) { wl_data_device_set_selection( - seat->data_device, data_source->data_source, seat->data_source_serial); + seat->wl_data_device, data_source->wl_source, seat->data_source_serial); + } +} + +void GHOST_SystemWayland::putClipboard(const char *buffer, bool selection) const +{ + if (UNLIKELY(!display_->wl_data_device_manager || display_->seats.empty())) { + return; + } + + if (selection) { + system_clipboard_put_primary_selection(display_, buffer); + } + else { + system_clipboard_put(display_, buffer); } } uint8_t GHOST_SystemWayland::getNumDisplays() const { - return d ? uint8_t(d->outputs.size()) : 0; + return display_ ? uint8_t(display_->outputs.size()) : 0; } static GHOST_TSuccess getCursorPositionClientRelative_impl( @@ -3337,7 +4520,7 @@ static GHOST_TSuccess setCursorPositionClientRelative_impl(GWL_Seat *seat, /* NOTE: WAYLAND doesn't support warping the cursor. * However when grab is enabled, we already simulate a cursor location * so that can be set to a new location. */ - if (!seat->relative_pointer) { + if (!seat->wp_relative_pointer) { return GHOST_kFailure; } const wl_fixed_t scale = win->scale(); @@ -3356,10 +4539,10 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPositionClientRelative(const GHOST_ int32_t &x, int32_t &y) const { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_active(seat); if (!seat_state_pointer || !seat_state_pointer->wl_surface) { return GHOST_kFailure; @@ -3372,20 +4555,20 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindo const int32_t x, const int32_t y) { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(window); return setCursorPositionClientRelative_impl(seat, win, x, y); } GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) const { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_active(seat); if (!seat_state_pointer) { return GHOST_kFailure; @@ -3400,10 +4583,10 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) co GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int32_t y) { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; /* Intentionally different from `getCursorPosition` which supports both tablet & pointer. * In the case of setting the cursor location, tablets don't support this. */ @@ -3420,8 +4603,8 @@ void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &he return; } /* We assume first output as main. */ - width = uint32_t(d->outputs[0]->size_native[0]); - height = uint32_t(d->outputs[0]->size_native[1]); + width = uint32_t(display_->outputs[0]->size_native[0]); + height = uint32_t(display_->outputs[0]->size_native[1]); } void GHOST_SystemWayland::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const @@ -3429,7 +4612,7 @@ void GHOST_SystemWayland::getAllDisplayDimensions(uint32_t &width, uint32_t &hei int32_t xy_min[2] = {INT32_MAX, INT32_MAX}; int32_t xy_max[2] = {INT32_MIN, INT32_MIN}; - for (const GWL_Output *output : d->outputs) { + for (const GWL_Output *output : display_->outputs) { int32_t xy[2] = {0, 0}; if (output->has_position_logical) { xy[0] = output->position_logical[0]; @@ -3490,10 +4673,10 @@ static GHOST_Context *createOffscreenContext_impl(GHOST_SystemWayland *system, GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*glSettings*/) { /* Create new off-screen window. */ - wl_surface *wl_surface = wl_compositor_create_surface(compositor()); + wl_surface *wl_surface = wl_compositor_create_surface(wl_compositor()); wl_egl_window *egl_window = wl_surface ? wl_egl_window_create(wl_surface, 1, 1) : nullptr; - GHOST_Context *context = createOffscreenContext_impl(this, d->display, egl_window); + GHOST_Context *context = createOffscreenContext_impl(this, display_->wl_display, egl_window); if (!context) { GHOST_PRINT("Cannot create off-screen EGL context" << std::endl); @@ -3530,7 +4713,6 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, const uint32_t width, const uint32_t height, const GHOST_TWindowState state, - const GHOST_TDrawingContextType type, const GHOST_GLSettings glSettings, const bool exclusive, const bool is_dialog, @@ -3550,7 +4732,7 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, height, state, parentWindow, - type, + glSettings.context_type, is_dialog, ((glSettings.flags & GHOST_glStereoVisual) != 0), exclusive); @@ -3578,22 +4760,22 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, */ static void cursor_buffer_show(const GWL_Seat *seat) { - const GWL_Cursor *c = &seat->cursor; + const GWL_Cursor *cursor = &seat->cursor; if (seat->wl_pointer) { - const int scale = c->is_custom ? c->custom_scale : seat->pointer.theme_scale; - const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale; - const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale; + const int scale = cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale; + const int32_t hotspot_x = int32_t(cursor->wl_image.hotspot_x) / scale; + const int32_t hotspot_y = int32_t(cursor->wl_image.hotspot_y) / scale; if (seat->wl_pointer) { wl_pointer_set_cursor( - seat->wl_pointer, seat->pointer.serial, c->wl_surface, hotspot_x, hotspot_y); + seat->wl_pointer, seat->pointer.serial, cursor->wl_surface, hotspot_x, hotspot_y); } } if (!seat->tablet_tools.empty()) { - const int scale = c->is_custom ? c->custom_scale : seat->tablet.theme_scale; - const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale; - const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale; + const int scale = cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale; + const int32_t hotspot_x = int32_t(cursor->wl_image.hotspot_x) / scale; + const int32_t hotspot_y = int32_t(cursor->wl_image.hotspot_y) / scale; for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->tablet_tools) { GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>( zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2)); @@ -3656,21 +4838,21 @@ static void cursor_buffer_set_surface_impl(const GWL_Seat *seat, static void cursor_buffer_set(const GWL_Seat *seat, wl_buffer *buffer) { - const GWL_Cursor *c = &seat->cursor; + const GWL_Cursor *cursor = &seat->cursor; const wl_cursor_image *wl_image = &seat->cursor.wl_image; - const bool visible = (c->visible && c->is_hardware); + const bool visible = (cursor->visible && cursor->is_hardware); /* This is a requirement of WAYLAND, when this isn't the case, * it causes Blender's window to close intermittently. */ if (seat->wl_pointer) { const int scale = cursor_buffer_compatible_scale_from_image( - wl_image, c->is_custom ? c->custom_scale : seat->pointer.theme_scale); + wl_image, cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale); const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale; const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale; - cursor_buffer_set_surface_impl(seat, buffer, c->wl_surface, scale); + cursor_buffer_set_surface_impl(seat, buffer, cursor->wl_surface, scale); wl_pointer_set_cursor(seat->wl_pointer, seat->pointer.serial, - visible ? c->wl_surface : nullptr, + visible ? cursor->wl_surface : nullptr, hotspot_x, hotspot_y); } @@ -3678,7 +4860,7 @@ static void cursor_buffer_set(const GWL_Seat *seat, wl_buffer *buffer) /* Set the cursor for all tablet tools as well. */ if (!seat->tablet_tools.empty()) { const int scale = cursor_buffer_compatible_scale_from_image( - wl_image, c->is_custom ? c->custom_scale : seat->tablet.theme_scale); + wl_image, cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale); const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale; const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale; for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->tablet_tools) { @@ -3756,7 +4938,7 @@ static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor shape) { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } auto cursor_find = cursors.find(shape); @@ -3764,31 +4946,32 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor s cursors.at(GHOST_kStandardCursorDefault) : (*cursor_find).second; - GWL_Seat *seat = d->seats[0]; - GWL_Cursor *c = &seat->cursor; + GWL_Seat *seat = display_->seats[0]; + GWL_Cursor *cursor = &seat->cursor; - if (!c->wl_theme) { + if (!cursor->wl_theme) { /* The cursor wl_surface hasn't entered an output yet. Initialize theme with scale 1. */ - c->wl_theme = wl_cursor_theme_load(c->theme_name.c_str(), c->size, d->seats[0]->system->shm()); + cursor->wl_theme = wl_cursor_theme_load( + cursor->theme_name.c_str(), cursor->theme_size, wl_shm()); } - wl_cursor *cursor = wl_cursor_theme_get_cursor(c->wl_theme, cursor_name); + wl_cursor *wl_cursor = wl_cursor_theme_get_cursor(cursor->wl_theme, cursor_name); - if (!cursor) { + if (!wl_cursor) { GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl); return GHOST_kFailure; } - struct wl_cursor_image *image = cursor->images[0]; + struct wl_cursor_image *image = wl_cursor->images[0]; struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); if (!buffer) { return GHOST_kFailure; } - c->visible = true; - c->is_custom = false; - c->wl_buffer = buffer; - c->wl_image = *image; + cursor->visible = true; + cursor->is_custom = false; + cursor->wl_buffer = buffer; + cursor->wl_image = *image; cursor_buffer_set(seat, buffer); @@ -3816,11 +4999,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, const int hotY, const bool /*canInvertColor*/) { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } - GWL_Cursor *cursor = &d->seats[0]->cursor; + GWL_Cursor *cursor = &display_->seats[0]->cursor; if (cursor->custom_data) { munmap(cursor->custom_data, cursor->custom_data_size); @@ -3829,8 +5012,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, } const int32_t size_xy[2] = {sizex, sizey}; - wl_buffer *buffer = ghost_wl_buffer_create_for_image( - d->shm, size_xy, WL_SHM_FORMAT_ARGB8888, &cursor->custom_data, &cursor->custom_data_size); + wl_buffer *buffer = ghost_wl_buffer_create_for_image(display_->wl_shm, + size_xy, + WL_SHM_FORMAT_ARGB8888, + &cursor->custom_data, + &cursor->custom_data_size); if (buffer == nullptr) { return GHOST_kFailure; } @@ -3876,14 +5062,14 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, cursor->wl_image.hotspot_x = uint32_t(hotX); cursor->wl_image.hotspot_y = uint32_t(hotY); - cursor_buffer_set(d->seats[0], buffer); + cursor_buffer_set(display_->seats[0], buffer); return GHOST_kSuccess; } GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) { - GWL_Cursor *cursor = &d->seats[0]->cursor; + GWL_Cursor *cursor = &display_->seats[0]->cursor; if (cursor->custom_data == nullptr) { return GHOST_kFailure; } @@ -3904,11 +5090,11 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(const bool visible) { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; cursor_visible_set(seat, visible, seat->cursor.is_hardware, CURSOR_VISIBLE_ALWAYS_SET); return GHOST_kSuccess; } @@ -3928,12 +5114,12 @@ bool GHOST_SystemWayland::supportsWindowPosition() bool GHOST_SystemWayland::getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode) { - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return false; } #ifdef USE_GNOME_CONFINE_HACK - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; const bool use_software_confine = seat->use_pointer_software_confine; #else const bool use_software_confine = false; @@ -3972,11 +5158,10 @@ static GWL_SeatStateGrab seat_grab_state_from_mode(const GHOST_TGrabCursorMode m const bool use_software_confine) { /* Initialize all members. */ - const struct GWL_SeatStateGrab grab_state = { - /* Warping happens to require software cursor which also hides. */ - .use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine, - .use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false), - }; + GWL_SeatStateGrab grab_state; + /* Warping happens to require software cursor which also hides. */ + grab_state.use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine; + grab_state.use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false); return grab_state; } @@ -4040,45 +5225,55 @@ void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *wl_surface) * Expose some members via methods. * \{ */ -wl_display *GHOST_SystemWayland::display() +wl_display *GHOST_SystemWayland::wl_display() +{ + return display_->wl_display; +} + +wl_compositor *GHOST_SystemWayland::wl_compositor() +{ + return display_->wl_compositor; +} + +struct zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wp_primary_selection_manager() { - return d->display; + return display_->wp_primary_selection_device_manager; } -wl_compositor *GHOST_SystemWayland::compositor() +struct zwp_pointer_gestures_v1 *GHOST_SystemWayland::wp_pointer_gestures() { - return d->compositor; + return display_->wp_pointer_gestures; } #ifdef WITH_GHOST_WAYLAND_LIBDECOR -libdecor *GHOST_SystemWayland::decor_context() +libdecor *GHOST_SystemWayland::libdecor_context() { - return d->decor_context; + return display_->libdecor->context; } -#else /* WITH_GHOST_WAYLAND_LIBDECOR */ +#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */ -xdg_wm_base *GHOST_SystemWayland::xdg_shell() +xdg_wm_base *GHOST_SystemWayland::xdg_decor_shell() { - return d->xdg_shell; + return display_->xdg_decor->shell; } -zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager() +zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decor_manager() { - return d->xdg_decoration_manager; + return display_->xdg_decor->manager; } -#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */ +/* End `xdg_decor`. */ const std::vector<GWL_Output *> &GHOST_SystemWayland::outputs() const { - return d->outputs; + return display_->outputs; } -wl_shm *GHOST_SystemWayland::shm() const +struct wl_shm *GHOST_SystemWayland::wl_shm() const { - return d->shm; + return display_->wl_shm; } /** \} */ @@ -4112,11 +5307,6 @@ GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *wl_surface) * Functionality only used for the WAYLAND implementation. * \{ */ -void GHOST_SystemWayland::selection_set(const std::string &selection) -{ - this->selection = selection; -} - void GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface) { #define SURFACE_CLEAR_PTR(surface_test) \ @@ -4126,7 +5316,7 @@ void GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface) ((void)0); /* Only clear window surfaces (not cursors, off-screen surfaces etc). */ - for (GWL_Seat *seat : d->seats) { + for (GWL_Seat *seat : display_->seats) { SURFACE_CLEAR_PTR(seat->pointer.wl_surface); SURFACE_CLEAR_PTR(seat->tablet.wl_surface); SURFACE_CLEAR_PTR(seat->keyboard.wl_surface); @@ -4144,11 +5334,11 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod const int scale) { /* Ignore, if the required protocols are not supported. */ - if (UNLIKELY(!d->relative_pointer_manager || !d->pointer_constraints)) { + if (UNLIKELY(!display_->wp_relative_pointer_manager || !display_->wp_pointer_constraints)) { return GHOST_kFailure; } - if (UNLIKELY(d->seats.empty())) { + if (UNLIKELY(display_->seats.empty())) { return GHOST_kFailure; } /* No change, success. */ @@ -4156,7 +5346,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod return GHOST_kSuccess; } - GWL_Seat *seat = d->seats[0]; + GWL_Seat *seat = display_->seats[0]; #ifdef USE_GNOME_CONFINE_HACK const bool was_software_confine = seat->use_pointer_software_confine; @@ -4183,11 +5373,11 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod * in this case disable the current locks as it makes logic confusing, * postpone changing the cursor to avoid flickering. */ if (!grab_state_next.use_lock) { - if (seat->relative_pointer) { - zwp_relative_pointer_v1_destroy(seat->relative_pointer); - seat->relative_pointer = nullptr; + if (seat->wp_relative_pointer) { + zwp_relative_pointer_v1_destroy(seat->wp_relative_pointer); + seat->wp_relative_pointer = nullptr; } - if (seat->locked_pointer) { + if (seat->wp_locked_pointer) { /* Potentially add a motion event so the application has updated X/Y coordinates. */ int32_t xy_motion[2] = {0, 0}; bool xy_motion_create_event = false; @@ -4216,7 +5406,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod seat->pointer.xy[0] = xy_next[0]; seat->pointer.xy[1] = xy_next[1]; - zwp_locked_pointer_v1_set_cursor_position_hint(seat->locked_pointer, UNPACK2(xy_next)); + zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp_locked_pointer, UNPACK2(xy_next)); wl_surface_commit(wl_surface); } else if (mode_current == GHOST_kGrabHide) { @@ -4226,7 +5416,8 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod wl_fixed_from_int(init_grab_xy[0]) / scale, wl_fixed_from_int(init_grab_xy[1]) / scale, }; - zwp_locked_pointer_v1_set_cursor_position_hint(seat->locked_pointer, UNPACK2(xy_next)); + zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp_locked_pointer, + UNPACK2(xy_next)); wl_surface_commit(wl_surface); /* NOTE(@campbellbarton): The new cursor position is a hint, @@ -4240,7 +5431,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod #ifdef USE_GNOME_CONFINE_HACK else if (mode_current == GHOST_kGrabNormal) { if (was_software_confine) { - zwp_locked_pointer_v1_set_cursor_position_hint(seat->locked_pointer, + zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp_locked_pointer, UNPACK2(seat->pointer.xy)); wl_surface_commit(wl_surface); } @@ -4256,15 +5447,15 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod GHOST_TABLET_DATA_NONE)); } - zwp_locked_pointer_v1_destroy(seat->locked_pointer); - seat->locked_pointer = nullptr; + zwp_locked_pointer_v1_destroy(seat->wp_locked_pointer); + seat->wp_locked_pointer = nullptr; } } if (!grab_state_next.use_confine) { - if (seat->confined_pointer) { - zwp_confined_pointer_v1_destroy(seat->confined_pointer); - seat->confined_pointer = nullptr; + if (seat->wp_confined_pointer) { + zwp_confined_pointer_v1_destroy(seat->wp_confined_pointer); + seat->wp_confined_pointer = nullptr; } } @@ -4275,12 +5466,12 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod * possible to support #GHOST_kGrabWrap by pragmatically settings it's coordinates. * An alternative could be to draw the cursor in software (and hide the real cursor), * or just accept a locked cursor on WAYLAND. */ - seat->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( - d->relative_pointer_manager, seat->wl_pointer); + seat->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( + display_->wp_relative_pointer_manager, seat->wl_pointer); zwp_relative_pointer_v1_add_listener( - seat->relative_pointer, &relative_pointer_listener, seat); - seat->locked_pointer = zwp_pointer_constraints_v1_lock_pointer( - d->pointer_constraints, + seat->wp_relative_pointer, &relative_pointer_listener, seat); + seat->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer( + display_->wp_pointer_constraints, wl_surface, seat->wl_pointer, nullptr, @@ -4297,8 +5488,8 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod } else if (grab_state_next.use_confine) { if (!grab_state_prev.use_confine) { - seat->confined_pointer = zwp_pointer_constraints_v1_confine_pointer( - d->pointer_constraints, + seat->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer( + display_->wp_pointer_constraints, wl_surface, seat->wl_pointer, nullptr, @@ -4317,35 +5508,57 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod return GHOST_kSuccess; } +struct GWL_SimpleBuffer *GHOST_SystemWayland::clipboard_data(bool selection) const +{ + return selection ? &display_->clipboard_primary : &display_->clipboard; +} + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR +bool GHOST_SystemWayland::use_libdecor_runtime() +{ + return use_libdecor; +} +#endif + #ifdef WITH_GHOST_WAYLAND_DYNLOAD -bool ghost_wl_dynload_libraries() +bool ghost_wl_dynload_libraries_init() { - /* Only report when `libwayland-client` is not found when building without X11, - * which will be used as a fallback. */ # ifdef WITH_GHOST_X11 - bool verbose = false; + /* When running in WAYLAND, let the user know when a missing library is the only reason + * WAYLAND could not be used. Otherwise it's not obvious why X11 is used as a fallback. + * Otherwise when X11 is used, reporting WAYLAND library warnings is unwelcome noise. */ + bool verbose = getenv("WAYLAND_DISPLAY") != nullptr; # else bool verbose = true; -# endif +# endif /* !WITH_GHOST_X11 */ if (wayland_dynload_client_init(verbose) && /* `libwayland-client`. */ wayland_dynload_cursor_init(verbose) && /* `libwayland-cursor`. */ - wayland_dynload_egl_init(verbose) && /* `libwayland-egl`. */ + wayland_dynload_egl_init(verbose) /* `libwayland-egl`. */ + ) { # ifdef WITH_GHOST_WAYLAND_LIBDECOR - wayland_dynload_libdecor_init(verbose) && /* `libdecor-0`. */ + has_libdecor = wayland_dynload_libdecor_init(verbose); /* `libdecor-0`. */ # endif - true) { return true; } -# ifdef WITH_GHOST_WAYLAND_LIBDECOR - wayland_dynload_libdecor_exit(); -# endif + wayland_dynload_client_exit(); wayland_dynload_cursor_exit(); wayland_dynload_egl_exit(); return false; } + +void ghost_wl_dynload_libraries_exit() +{ + wayland_dynload_client_exit(); + wayland_dynload_cursor_exit(); + wayland_dynload_egl_exit(); +# ifdef WITH_GHOST_WAYLAND_LIBDECOR + wayland_dynload_libdecor_exit(); +# endif +} + #endif /* WITH_GHOST_WAYLAND_DYNLOAD */ /** \} */ diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index caea7b0d748..c27f175002e 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -21,18 +21,12 @@ # include <wayland_dynload_libdecor.h> # endif # include <libdecor.h> -#else -/* Generated by `wayland-scanner`. */ -# include <xdg-decoration-unstable-v1-client-protocol.h> -# include <xdg-shell-client-protocol.h> #endif #include <string> class GHOST_WindowWayland; -struct GWL_Display; - bool ghost_wl_output_own(const struct wl_output *wl_output); void ghost_wl_output_tag(struct wl_output *wl_output); struct GWL_Output *ghost_wl_output_user_data(struct wl_output *wl_output); @@ -52,7 +46,8 @@ void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *surface); * Return true when all required WAYLAND libraries are present, * Performs dynamic loading when `WITH_GHOST_WAYLAND_DYNLOAD` is in use. */ -bool ghost_wl_dynload_libraries(); +bool ghost_wl_dynload_libraries_init(); +void ghost_wl_dynload_libraries_exit(); #endif struct GWL_Output { @@ -89,11 +84,12 @@ struct GWL_Output { class GHOST_SystemWayland : public GHOST_System { public: - GHOST_SystemWayland(); + GHOST_SystemWayland(bool background); + GHOST_SystemWayland() : GHOST_SystemWayland(true){}; ~GHOST_SystemWayland() override; - GHOST_TSuccess init(); + GHOST_TSuccess init() override; bool processEvents(bool waitForEvent) override; @@ -133,7 +129,6 @@ class GHOST_SystemWayland : public GHOST_System { uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive, const bool is_dialog, @@ -155,32 +150,31 @@ class GHOST_SystemWayland : public GHOST_System { GHOST_TSuccess setCursorVisibility(bool visible); - bool supportsCursorWarp(); - bool supportsWindowPosition(); + bool supportsCursorWarp() override; + bool supportsWindowPosition() override; bool getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode); /* WAYLAND direct-data access. */ - wl_display *display(); - - wl_compositor *compositor(); + struct wl_display *wl_display(); + struct wl_compositor *wl_compositor(); + struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_manager(); + struct zwp_pointer_gestures_v1 *wp_pointer_gestures(); #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor *decor_context(); -#else - xdg_wm_base *xdg_shell(); - zxdg_decoration_manager_v1 *xdg_decoration_manager(); + libdecor *libdecor_context(); #endif + struct xdg_wm_base *xdg_decor_shell(); + struct zxdg_decoration_manager_v1 *xdg_decor_manager(); + /* End `xdg_decor`. */ const std::vector<GWL_Output *> &outputs() const; - wl_shm *shm() const; + struct wl_shm *wl_shm() const; /* WAYLAND utility functions. */ - void selection_set(const std::string &selection); - /** Clear all references to this surface to prevent accessing NULL pointers. */ void window_surface_unref(const wl_surface *wl_surface); @@ -192,7 +186,12 @@ class GHOST_SystemWayland : public GHOST_System { wl_surface *wl_surface, int scale); + struct GWL_SimpleBuffer *clipboard_data(bool selection) const; + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + static bool use_libdecor_runtime(); +#endif + private: - struct GWL_Display *d; - std::string selection; + struct GWL_Display *display_; }; diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp index 667198241f0..54c892d296e 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cpp +++ b/intern/ghost/intern/GHOST_SystemWin32.cpp @@ -213,7 +213,6 @@ GHOST_IWindow *GHOST_SystemWin32::createWindow(const char *title, uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive, const bool is_dialog, @@ -227,7 +226,7 @@ GHOST_IWindow *GHOST_SystemWin32::createWindow(const char *title, width, height, state, - type, + glSettings.context_type, ((glSettings.flags & GHOST_glStereoVisual) != 0), false, (GHOST_WindowWin32 *)parentWindow, diff --git a/intern/ghost/intern/GHOST_SystemWin32.h b/intern/ghost/intern/GHOST_SystemWin32.h index f5cd0055b34..98a7e5dfb35 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.h +++ b/intern/ghost/intern/GHOST_SystemWin32.h @@ -103,7 +103,6 @@ class GHOST_SystemWin32 : public GHOST_System { * \param width: The width the window. * \param height: The height the window. * \param state: The state of the window when opened. - * \param type: The type of drawing context installed in this window. * \param glSettings: Misc OpenGL settings. * \param exclusive: Use to show the window on top and ignore others (used full-screen). * \param parentWindow: Parent window. @@ -115,7 +114,6 @@ class GHOST_SystemWin32 : public GHOST_System { uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive = false, const bool is_dialog = false, diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 6b468f041c1..5c89febe97c 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -308,7 +308,6 @@ void GHOST_SystemX11::getAllDisplayDimensions(uint32_t &width, uint32_t &height) * \param width: The width the window. * \param height: The height the window. * \param state: The state of the window when opened. - * \param type: The type of drawing context installed in this window. * \param glSettings: Misc OpenGL settings. * \param exclusive: Use to show the window on top and ignore others (used full-screen). * \param parentWindow: Parent window. @@ -320,7 +319,6 @@ GHOST_IWindow *GHOST_SystemX11::createWindow(const char *title, uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive, const bool is_dialog, @@ -341,7 +339,7 @@ GHOST_IWindow *GHOST_SystemX11::createWindow(const char *title, height, state, (GHOST_WindowX11 *)parentWindow, - type, + glSettings.context_type, is_dialog, ((glSettings.flags & GHOST_glStereoVisual) != 0), exclusive, @@ -2435,11 +2433,11 @@ GHOST_TSuccess GHOST_SystemX11::showMessageBox(const char *title, utf8Str, 8, PropModeReplace, - (const unsigned char *)title, + (const uchar *)title, int(strlen(title))); XChangeProperty( - m_display, window, winType, XA_ATOM, 32, PropModeReplace, (unsigned char *)&typeDialog, 1); + m_display, window, winType, XA_ATOM, 32, PropModeReplace, (uchar *)&typeDialog, 1); } /* Create buttons GC */ diff --git a/intern/ghost/intern/GHOST_SystemX11.h b/intern/ghost/intern/GHOST_SystemX11.h index 572be30174d..1f071da6da7 100644 --- a/intern/ghost/intern/GHOST_SystemX11.h +++ b/intern/ghost/intern/GHOST_SystemX11.h @@ -113,7 +113,6 @@ class GHOST_SystemX11 : public GHOST_System { * \param width: The width the window. * \param height: The height the window. * \param state: The state of the window when opened. - * \param type: The type of drawing context installed in this window. * \param stereoVisual: Create a stereo visual for quad buffered stereo. * \param exclusive: Use to show the window on top and ignore others (used full-screen). * \param parentWindow: Parent (embedder) window. @@ -125,7 +124,6 @@ class GHOST_SystemX11 : public GHOST_System { uint32_t width, uint32_t height, GHOST_TWindowState state, - GHOST_TDrawingContextType type, GHOST_GLSettings glSettings, const bool exclusive = false, const bool is_dialog = false, diff --git a/intern/ghost/intern/GHOST_WindowCocoa.mm b/intern/ghost/intern/GHOST_WindowCocoa.mm index 737fd64bdf0..bc1f1e99a3a 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.mm +++ b/intern/ghost/intern/GHOST_WindowCocoa.mm @@ -803,10 +803,10 @@ GHOST_TSuccess GHOST_WindowCocoa::setOrder(GHOST_TWindowOrder order) GHOST_Context *GHOST_WindowCocoa::newDrawingContext(GHOST_TDrawingContextType type) { - if (type == GHOST_kDrawingContextTypeOpenGL) { + if (type == GHOST_kDrawingContextTypeOpenGL || type == GHOST_kDrawingContextTypeMetal) { GHOST_Context *context = new GHOST_ContextCGL( - m_wantStereoVisual, m_metalView, m_metalLayer, m_openGLView); + m_wantStereoVisual, m_metalView, m_metalLayer, m_openGLView, type); if (context->initializeDrawingContext()) return context; diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 5f3cb3e3f3a..986e18d7a87 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -31,15 +31,54 @@ # include <libdecor.h> #endif +/* Generated by `wayland-scanner`. */ +#include <xdg-decoration-unstable-v1-client-protocol.h> +#include <xdg-shell-client-protocol.h> + /* Logging, use `ghost.wl.*` prefix. */ #include "CLG_log.h" static constexpr size_t base_dpi = 96; +#ifdef WITH_GHOST_WAYLAND_LIBDECOR +/* Access `use_libdecor` in #GHOST_SystemWayland. */ +# define use_libdecor GHOST_SystemWayland::use_libdecor_runtime() +#endif + static GHOST_WindowManager *window_manager = nullptr; +#ifdef WITH_GHOST_WAYLAND_LIBDECOR +struct WGL_LibDecor_Window { + struct libdecor_frame *frame = nullptr; + bool configured = false; +}; + +static void wgl_libdecor_window_destroy(WGL_LibDecor_Window *decor) +{ + libdecor_frame_unref(decor->frame); + delete decor; +} +#endif /* WITH_GHOST_WAYLAND_LIBDECOR */ + +struct WGL_XDG_Decor_Window { + struct xdg_surface *surface = nullptr; + struct zxdg_toplevel_decoration_v1 *toplevel_decor = nullptr; + struct xdg_toplevel *toplevel = nullptr; + enum zxdg_toplevel_decoration_v1_mode mode = (enum zxdg_toplevel_decoration_v1_mode)0; +}; + +static void wgl_xdg_decor_window_destroy(WGL_XDG_Decor_Window *decor) +{ + if (decor->toplevel_decor) { + zxdg_toplevel_decoration_v1_destroy(decor->toplevel_decor); + } + xdg_toplevel_destroy(decor->toplevel); + xdg_surface_destroy(decor->surface); + delete decor; +} + struct GWL_Window { - GHOST_WindowWayland *w = nullptr; + GHOST_WindowWayland *ghost_window = nullptr; struct wl_surface *wl_surface = nullptr; /** * Outputs on which the window is currently shown on. @@ -60,15 +99,9 @@ struct GWL_Window { uint32_t dpi = 0; #ifdef WITH_GHOST_WAYLAND_LIBDECOR - struct libdecor_frame *decor_frame = nullptr; - bool decor_configured = false; -#else - struct xdg_surface *xdg_surface = nullptr; - struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration = nullptr; - struct xdg_toplevel *xdg_toplevel = nullptr; - - enum zxdg_toplevel_decoration_v1_mode decoration_mode = (enum zxdg_toplevel_decoration_v1_mode)0; + WGL_LibDecor_Window *libdecor = nullptr; #endif + WGL_XDG_Decor_Window *xdg_decor = nullptr; wl_egl_window *egl_window = nullptr; bool is_maximised = false; @@ -146,10 +179,8 @@ static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs /** \name Listener (XDG Top Level), #xdg_toplevel_listener * \{ */ -#ifndef WITH_GHOST_WAYLAND_LIBDECOR - static CLG_LogRef LOG_WL_XDG_TOPLEVEL = {"ghost.wl.handle.xdg_toplevel"}; -# define LOG (&LOG_WL_XDG_TOPLEVEL) +#define LOG (&LOG_WL_XDG_TOPLEVEL) static void xdg_toplevel_handle_configure(void *data, xdg_toplevel * /*xdg_toplevel*/, @@ -189,7 +220,7 @@ static void xdg_toplevel_handle_configure(void *data, static void xdg_toplevel_handle_close(void *data, xdg_toplevel * /*xdg_toplevel*/) { CLOG_INFO(LOG, 2, "close"); - static_cast<GWL_Window *>(data)->w->close(); + static_cast<GWL_Window *>(data)->ghost_window->close(); } static const xdg_toplevel_listener toplevel_listener = { @@ -197,9 +228,7 @@ static const xdg_toplevel_listener toplevel_listener = { xdg_toplevel_handle_close, }; -# undef LOG - -#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ +#undef LOG /** \} */ @@ -234,7 +263,7 @@ static void frame_handle_configure(struct libdecor_frame *frame, win->size[1] = win->scale * size_next[1]; wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); - win->w->notify_size(); + win->ghost_window->notify_size(); if (!libdecor_configuration_get_window_state(configuration, &window_state)) { window_state = LIBDECOR_WINDOW_STATE_NONE; @@ -244,20 +273,20 @@ static void frame_handle_configure(struct libdecor_frame *frame, win->is_fullscreen = window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN; win->is_active = window_state & LIBDECOR_WINDOW_STATE_ACTIVE; - win->is_active ? win->w->activate() : win->w->deactivate(); + win->is_active ? win->ghost_window->activate() : win->ghost_window->deactivate(); state = libdecor_state_new(UNPACK2(size_next)); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); - win->decor_configured = true; + win->libdecor->configured = true; } static void frame_handle_close(struct libdecor_frame * /*frame*/, void *data) { CLOG_INFO(LOG, 2, "close"); - static_cast<GWL_Window *>(data)->w->close(); + static_cast<GWL_Window *>(data)->ghost_window->close(); } static void frame_handle_commit(struct libdecor_frame * /*frame*/, void *data) @@ -265,8 +294,8 @@ static void frame_handle_commit(struct libdecor_frame * /*frame*/, void *data) CLOG_INFO(LOG, 2, "commit"); /* We have to swap twice to keep any pop-up menus alive. */ - static_cast<GWL_Window *>(data)->w->swapBuffers(); - static_cast<GWL_Window *>(data)->w->swapBuffers(); + static_cast<GWL_Window *>(data)->ghost_window->swapBuffers(); + static_cast<GWL_Window *>(data)->ghost_window->swapBuffers(); } static struct libdecor_frame_interface libdecor_frame_iface = { @@ -285,10 +314,8 @@ static struct libdecor_frame_interface libdecor_frame_iface = { /** \name Listener (XDG Decoration Listener), #zxdg_toplevel_decoration_v1_listener * \{ */ -#ifndef WITH_GHOST_WAYLAND_LIBDECOR - static CLG_LogRef LOG_WL_XDG_TOPLEVEL_DECORATION = {"ghost.wl.handle.xdg_toplevel_decoration"}; -# define LOG (&LOG_WL_XDG_TOPLEVEL_DECORATION) +#define LOG (&LOG_WL_XDG_TOPLEVEL_DECORATION) static void xdg_toplevel_decoration_handle_configure( void *data, @@ -296,16 +323,14 @@ static void xdg_toplevel_decoration_handle_configure( const uint32_t mode) { CLOG_INFO(LOG, 2, "configure (mode=%u)", mode); - static_cast<GWL_Window *>(data)->decoration_mode = (zxdg_toplevel_decoration_v1_mode)mode; + static_cast<GWL_Window *>(data)->xdg_decor->mode = (zxdg_toplevel_decoration_v1_mode)mode; } static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listener = { xdg_toplevel_decoration_handle_configure, }; -# undef LOG - -#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ +#undef LOG /** \} */ @@ -313,10 +338,8 @@ static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listene /** \name Listener (XDG Surface Handle Configure), #xdg_surface_listener * \{ */ -#ifndef WITH_GHOST_WAYLAND_LIBDECOR - static CLG_LogRef LOG_WL_XDG_SURFACE = {"ghost.wl.handle.xdg_surface"}; -# define LOG (&LOG_WL_XDG_SURFACE) +#define LOG (&LOG_WL_XDG_SURFACE) static void xdg_surface_handle_configure(void *data, xdg_surface *xdg_surface, @@ -324,7 +347,7 @@ static void xdg_surface_handle_configure(void *data, { GWL_Window *win = static_cast<GWL_Window *>(data); - if (win->xdg_surface != xdg_surface) { + if (win->xdg_decor->surface != xdg_surface) { CLOG_INFO(LOG, 2, "configure (skipped)"); return; } @@ -337,14 +360,14 @@ static void xdg_surface_handle_configure(void *data, wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); win->size_pending[0] = 0; win->size_pending[1] = 0; - win->w->notify_size(); + win->ghost_window->notify_size(); } if (win->is_active) { - win->w->activate(); + win->ghost_window->activate(); } else { - win->w->deactivate(); + win->ghost_window->deactivate(); } xdg_surface_ack_configure(xdg_surface, serial); @@ -354,9 +377,7 @@ static const xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; -# undef LOG - -#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ +#undef LOG /** \} */ @@ -418,7 +439,7 @@ static struct wl_surface_listener wl_surface_listener = { GHOST_TSuccess GHOST_WindowWayland::hasCursorShape(GHOST_TStandardCursor cursorShape) { - return m_system->hasCursorShape(cursorShape); + return system_->hasCursorShape(cursorShape); } GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, @@ -434,20 +455,20 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, const bool stereoVisual, const bool exclusive) : GHOST_Window(width, height, state, stereoVisual, exclusive), - m_system(system), - w(new GWL_Window) + system_(system), + window_(new GWL_Window) { /* Globally store pointer to window manager. */ if (!window_manager) { - window_manager = m_system->getWindowManager(); + window_manager = system_->getWindowManager(); } - w->w = this; + window_->ghost_window = this; - w->size[0] = int32_t(width); - w->size[1] = int32_t(height); + window_->size[0] = int32_t(width); + window_->size[1] = int32_t(height); - w->is_dialog = is_dialog; + window_->is_dialog = is_dialog; /* NOTE(@campbellbarton): The scale set here to avoid flickering on startup. * When all monitors use the same scale (which is quite common) there aren't any problems. @@ -458,75 +479,100 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, * * Using the maximum scale is best as it results in the window first being smaller, * avoiding a large window flashing before it's made smaller. */ - w->scale = outputs_max_scale_or_default(this->m_system->outputs(), 1, &w->dpi); + window_->scale = outputs_max_scale_or_default(system_->outputs(), 1, &window_->dpi); /* Window surfaces. */ - w->wl_surface = wl_compositor_create_surface(m_system->compositor()); - ghost_wl_surface_tag(w->wl_surface); + window_->wl_surface = wl_compositor_create_surface(system_->wl_compositor()); + ghost_wl_surface_tag(window_->wl_surface); - wl_surface_set_buffer_scale(w->wl_surface, w->scale); + wl_surface_set_buffer_scale(window_->wl_surface, window_->scale); - wl_surface_add_listener(w->wl_surface, &wl_surface_listener, this); + wl_surface_add_listener(window_->wl_surface, &wl_surface_listener, this); - w->egl_window = wl_egl_window_create(w->wl_surface, int(w->size[0]), int(w->size[1])); + window_->egl_window = wl_egl_window_create( + window_->wl_surface, int(window_->size[0]), int(window_->size[1])); /* NOTE: The limit is in points (not pixels) so Hi-DPI will limit to larger number of pixels. * This has the advantage that the size limit is the same when moving the window between monitors * with different scales set. If it was important to limit in pixels it could be re-calculated - * when the `w->scale` changed. */ + * when the `window_->scale` changed. */ const int32_t size_min[2] = {320, 240}; -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - /* create window decorations */ - w->decor_frame = libdecor_decorate( - m_system->decor_context(), w->wl_surface, &libdecor_frame_iface, w); - libdecor_frame_map(w->decor_frame); - - libdecor_frame_set_min_content_size(w->decor_frame, UNPACK2(size_min)); - - if (parentWindow) { - libdecor_frame_set_parent( - w->decor_frame, dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->w->decor_frame); - } -#else - w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->xdg_shell(), w->wl_surface); - w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface); - - xdg_toplevel_set_min_size(w->xdg_toplevel, UNPACK2(size_min)); + /* This value is expected to match the base name of the `.desktop` file. see T101805. + * + * NOTE: the XDG desktop-entry-spec defines that this should follow the "reverse DNS" convention. + * For e.g. `org.blender.Blender` - however the `.desktop` file distributed with Blender is + * simply called `blender.desktop`, so the it's important to follow that name. + * Other distributions such as SNAP & FLATPAK may need to change this value T101779. + * Currently there isn't a way to configure this, we may want to support that. */ + const char *xdg_app_id = "blender"; - if (m_system->xdg_decoration_manager()) { - w->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( - m_system->xdg_decoration_manager(), w->xdg_toplevel); - zxdg_toplevel_decoration_v1_add_listener( - w->xdg_toplevel_decoration, &toplevel_decoration_v1_listener, w); - zxdg_toplevel_decoration_v1_set_mode(w->xdg_toplevel_decoration, - ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + window_->libdecor = new WGL_LibDecor_Window; + WGL_LibDecor_Window &decor = *window_->libdecor; + + /* create window decorations */ + decor.frame = libdecor_decorate( + system_->libdecor_context(), window_->wl_surface, &libdecor_frame_iface, window_); + libdecor_frame_map(window_->libdecor->frame); + + libdecor_frame_set_min_content_size(decor.frame, UNPACK2(size_min)); + libdecor_frame_set_app_id(decor.frame, xdg_app_id); + + if (parentWindow) { + WGL_LibDecor_Window &decor_parent = + *dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->window_->libdecor; + libdecor_frame_set_parent(decor.frame, decor_parent.frame); + } } + else +#endif + { + window_->xdg_decor = new WGL_XDG_Decor_Window; + WGL_XDG_Decor_Window &decor = *window_->xdg_decor; + decor.surface = xdg_wm_base_get_xdg_surface(system_->xdg_decor_shell(), window_->wl_surface); + decor.toplevel = xdg_surface_get_toplevel(decor.surface); + + xdg_toplevel_set_min_size(decor.toplevel, UNPACK2(size_min)); + xdg_toplevel_set_app_id(decor.toplevel, xdg_app_id); + + if (system_->xdg_decor_manager()) { + decor.toplevel_decor = zxdg_decoration_manager_v1_get_toplevel_decoration( + system_->xdg_decor_manager(), decor.toplevel); + zxdg_toplevel_decoration_v1_add_listener( + decor.toplevel_decor, &toplevel_decoration_v1_listener, window_); + zxdg_toplevel_decoration_v1_set_mode(decor.toplevel_decor, + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } - xdg_surface_add_listener(w->xdg_surface, &xdg_surface_listener, w); - xdg_toplevel_add_listener(w->xdg_toplevel, &toplevel_listener, w); + xdg_surface_add_listener(decor.surface, &xdg_surface_listener, window_); + xdg_toplevel_add_listener(decor.toplevel, &toplevel_listener, window_); - if (parentWindow && is_dialog) { - xdg_toplevel_set_parent( - w->xdg_toplevel, dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->w->xdg_toplevel); + if (parentWindow && is_dialog) { + WGL_XDG_Decor_Window &decor_parent = + *dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->window_->xdg_decor; + xdg_toplevel_set_parent(decor.toplevel, decor_parent.toplevel); + } } -#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */ - setTitle(title); - wl_surface_set_user_data(w->wl_surface, this); + wl_surface_set_user_data(window_->wl_surface, this); /* Call top-level callbacks. */ - wl_surface_commit(w->wl_surface); - wl_display_roundtrip(m_system->display()); + wl_surface_commit(window_->wl_surface); + wl_display_roundtrip(system_->wl_display()); #ifdef WITH_GHOST_WAYLAND_LIBDECOR - /* It's important not to return until the window is configured or - * calls to `setState` from Blender will crash `libdecor`. */ - while (!w->decor_configured) { - if (libdecor_dispatch(m_system->decor_context(), 0) < 0) { - break; + if (use_libdecor) { + WGL_LibDecor_Window &decor = *window_->libdecor; + /* It's important not to return until the window is configured or + * calls to `setState` from Blender will crash `libdecor`. */ + while (!decor.configured) { + if (libdecor_dispatch(system_->libdecor_context(), 0) < 0) { + break; + } } } #endif @@ -535,9 +581,13 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, setOpaque(); #endif -#ifndef WITH_GHOST_WAYLAND_LIBDECOR /* Causes a glitch with `libdecor` for some reason. */ - setState(state); + /* Causes a glitch with `libdecor` for some reason. */ +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor == false) #endif + { + setState(state); + } /* EGL context. */ if (setDrawingContextType(type) == GHOST_kFailure) { @@ -558,13 +608,13 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mo } bounds = &bounds_buf; } - if (m_system->window_cursor_grab_set(mode, - m_cursorGrab, - m_cursorGrabInitPos, - bounds, - m_cursorGrabAxis, - w->wl_surface, - w->scale)) { + if (system_->window_cursor_grab_set(mode, + m_cursorGrab, + m_cursorGrabInitPos, + bounds, + m_cursorGrabAxis, + window_->wl_surface, + window_->scale)) { return GHOST_kSuccess; } return GHOST_kFailure; @@ -572,43 +622,47 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mo GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape) { - const GHOST_TSuccess ok = m_system->setCursorShape(shape); + const GHOST_TSuccess ok = system_->setCursorShape(shape); m_cursorShape = (ok == GHOST_kSuccess) ? shape : GHOST_kStandardCursorDefault; return ok; } bool GHOST_WindowWayland::getCursorGrabUseSoftwareDisplay() { - return m_system->getCursorGrabUseSoftwareDisplay(m_cursorGrab); + return system_->getCursorGrabUseSoftwareDisplay(m_cursorGrab); } GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape( uint8_t *bitmap, uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor) { - return m_system->setCustomCursorShape(bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor); + return system_->setCustomCursorShape(bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor); } GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) { - return m_system->getCursorBitmap(bitmap); + return system_->getCursorBitmap(bitmap); } void GHOST_WindowWayland::setTitle(const char *title) { #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_set_app_id(w->decor_frame, title); - libdecor_frame_set_title(w->decor_frame, title); -#else - xdg_toplevel_set_title(w->xdg_toplevel, title); - xdg_toplevel_set_app_id(w->xdg_toplevel, title); + if (use_libdecor) { + WGL_LibDecor_Window &decor = *window_->libdecor; + libdecor_frame_set_title(decor.frame, title); + } + else #endif + { + WGL_XDG_Decor_Window &decor = *window_->xdg_decor; + xdg_toplevel_set_title(decor.toplevel, title); + } - this->title = title; + title_ = title; } std::string GHOST_WindowWayland::getTitle() const { - return this->title.empty() ? "untitled" : this->title; + return title_.empty() ? "untitled" : title_; } void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const @@ -618,29 +672,29 @@ void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const void GHOST_WindowWayland::getClientBounds(GHOST_Rect &bounds) const { - bounds.set(0, 0, UNPACK2(w->size)); + bounds.set(0, 0, UNPACK2(window_->size)); } GHOST_TSuccess GHOST_WindowWayland::setClientWidth(const uint32_t width) { - return setClientSize(width, uint32_t(w->size[1])); + return setClientSize(width, uint32_t(window_->size[1])); } GHOST_TSuccess GHOST_WindowWayland::setClientHeight(const uint32_t height) { - return setClientSize(uint32_t(w->size[0]), height); + return setClientSize(uint32_t(window_->size[0]), height); } GHOST_TSuccess GHOST_WindowWayland::setClientSize(const uint32_t width, const uint32_t height) { - wl_egl_window_resize(w->egl_window, int(width), int(height), 0, 0); + wl_egl_window_resize(window_->egl_window, int(width), int(height), 0, 0); /* Override any pending size that may be set. */ - w->size_pending[0] = 0; - w->size_pending[1] = 0; + window_->size_pending[0] = 0; + window_->size_pending[1] = 0; - w->size[0] = width; - w->size[1] = height; + window_->size[0] = width; + window_->size[1] = height; notify_size(); @@ -669,40 +723,40 @@ GHOST_WindowWayland::~GHOST_WindowWayland() { releaseNativeHandles(); - wl_egl_window_destroy(w->egl_window); + wl_egl_window_destroy(window_->egl_window); #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_unref(w->decor_frame); -#else - if (w->xdg_toplevel_decoration) { - zxdg_toplevel_decoration_v1_destroy(w->xdg_toplevel_decoration); + if (use_libdecor) { + wgl_libdecor_window_destroy(window_->libdecor); } - xdg_toplevel_destroy(w->xdg_toplevel); - xdg_surface_destroy(w->xdg_surface); + else #endif + { + wgl_xdg_decor_window_destroy(window_->xdg_decor); + } /* Clear any pointers to this window. This is needed because there are no guarantees * that flushing the display will the "leave" handlers before handling events. */ - m_system->window_surface_unref(w->wl_surface); + system_->window_surface_unref(window_->wl_surface); - wl_surface_destroy(w->wl_surface); + wl_surface_destroy(window_->wl_surface); /* NOTE(@campbellbarton): Flushing will often run the appropriate handlers event * (#wl_surface_listener.leave in particular) to avoid attempted access to the freed surfaces. * This is not fool-proof though, hence the call to #window_surface_unref, see: T99078. */ - wl_display_flush(m_system->display()); + wl_display_flush(system_->wl_display()); - delete w; + delete window_; } uint16_t GHOST_WindowWayland::getDPIHint() { - return w->dpi; + return window_->dpi; } GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible) { - return m_system->setCursorVisibility(visible); + return system_->setCursorVisibility(visible); } GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state) @@ -711,57 +765,84 @@ GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state) case GHOST_kWindowStateNormal: /* Unset states. */ switch (getState()) { - case GHOST_kWindowStateMaximized: + case GHOST_kWindowStateMaximized: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_unset_maximized(w->decor_frame); -#else - xdg_toplevel_unset_maximized(w->xdg_toplevel); + if (use_libdecor) { + libdecor_frame_unset_maximized(window_->libdecor->frame); + } + else #endif + { + xdg_toplevel_unset_maximized(window_->xdg_decor->toplevel); + } break; - case GHOST_kWindowStateFullScreen: + } + case GHOST_kWindowStateFullScreen: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_unset_fullscreen(w->decor_frame); -#else - xdg_toplevel_unset_fullscreen(w->xdg_toplevel); + if (use_libdecor) { + libdecor_frame_unset_fullscreen(window_->libdecor->frame); + } + else #endif + { + xdg_toplevel_unset_fullscreen(window_->xdg_decor->toplevel); + } break; - default: + } + default: { break; + } } break; - case GHOST_kWindowStateMaximized: + case GHOST_kWindowStateMaximized: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_set_maximized(w->decor_frame); -#else - xdg_toplevel_set_maximized(w->xdg_toplevel); + if (use_libdecor) { + libdecor_frame_set_maximized(window_->libdecor->frame); + } + else #endif + { + xdg_toplevel_set_maximized(window_->xdg_decor->toplevel); + } break; - case GHOST_kWindowStateMinimized: + } + case GHOST_kWindowStateMinimized: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_set_minimized(w->decor_frame); -#else - xdg_toplevel_set_minimized(w->xdg_toplevel); + if (use_libdecor) { + libdecor_frame_set_minimized(window_->libdecor->frame); + } + else #endif + { + xdg_toplevel_set_minimized(window_->xdg_decor->toplevel); + } break; - case GHOST_kWindowStateFullScreen: + } + case GHOST_kWindowStateFullScreen: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_set_fullscreen(w->decor_frame, nullptr); -#else - xdg_toplevel_set_fullscreen(w->xdg_toplevel, nullptr); + if (use_libdecor) { + libdecor_frame_set_fullscreen(window_->libdecor->frame, nullptr); + } + else #endif + { + xdg_toplevel_set_fullscreen(window_->xdg_decor->toplevel, nullptr); + } break; - case GHOST_kWindowStateEmbedded: + } + case GHOST_kWindowStateEmbedded: { return GHOST_kFailure; + } } return GHOST_kSuccess; } GHOST_TWindowState GHOST_WindowWayland::getState() const { - if (w->is_fullscreen) { + if (window_->is_fullscreen) { return GHOST_kWindowStateFullScreen; } - if (w->is_maximised) { + if (window_->is_maximised) { return GHOST_kWindowStateMaximized; } return GHOST_kWindowStateNormal; @@ -780,26 +861,35 @@ GHOST_TSuccess GHOST_WindowWayland::setOrder(GHOST_TWindowOrder /*order*/) GHOST_TSuccess GHOST_WindowWayland::beginFullScreen() const { #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_set_fullscreen(w->decor_frame, nullptr); -#else - xdg_toplevel_set_fullscreen(w->xdg_toplevel, nullptr); + if (use_libdecor) { + libdecor_frame_set_fullscreen(window_->libdecor->frame, nullptr); + } + else #endif + { + xdg_toplevel_set_fullscreen(window_->xdg_decor->toplevel, nullptr); + } + return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWayland::endFullScreen() const { #ifdef WITH_GHOST_WAYLAND_LIBDECOR - libdecor_frame_unset_fullscreen(w->decor_frame); -#else - xdg_toplevel_unset_fullscreen(w->xdg_toplevel); + if (use_libdecor) { + libdecor_frame_unset_fullscreen(window_->libdecor->frame); + } + else #endif + { + xdg_toplevel_unset_fullscreen(window_->xdg_decor->toplevel); + } return GHOST_kSuccess; } bool GHOST_WindowWayland::isDialog() const { - return w->is_dialog; + return window_->is_dialog; } #ifdef GHOST_OPENGL_ALPHA @@ -808,9 +898,9 @@ void GHOST_WindowWayland::setOpaque() const struct wl_region *region; /* Make the window opaque. */ - region = wl_compositor_create_region(m_system->compositor()); - wl_region_add(region, 0, 0, UNPACK2(w->size)); - wl_surface_set_opaque_region(w->surface, region); + region = wl_compositor_create_region(system_->compositor()); + wl_region_add(region, 0, 0, UNPACK2(window_->size)); + wl_surface_set_opaque_region(window_->surface, region); wl_region_destroy(region); } #endif @@ -828,10 +918,10 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType break; case GHOST_kDrawingContextTypeOpenGL: for (int minor = 6; minor >= 0; --minor) { - context = new GHOST_ContextEGL(this->m_system, + context = new GHOST_ContextEGL(system_, m_wantStereoVisual, - EGLNativeWindowType(w->egl_window), - EGLNativeDisplayType(m_system->display()), + EGLNativeWindowType(window_->egl_window), + EGLNativeDisplayType(system_->wl_display()), EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, 4, minor, @@ -844,10 +934,10 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType } delete context; } - context = new GHOST_ContextEGL(this->m_system, + context = new GHOST_ContextEGL(system_, m_wantStereoVisual, - EGLNativeWindowType(w->egl_window), - EGLNativeDisplayType(m_system->display()), + EGLNativeWindowType(window_->egl_window), + EGLNativeDisplayType(system_->wl_display()), EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, 3, 3, @@ -856,7 +946,12 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType EGL_OPENGL_API); } - return (context->initializeDrawingContext() == GHOST_kSuccess) ? context : nullptr; + if (context->initializeDrawingContext()) { + return context; + } + + delete context; + return nullptr; } /** \} */ @@ -869,22 +964,22 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType uint16_t GHOST_WindowWayland::dpi() const { - return w->dpi; + return window_->dpi; } int GHOST_WindowWayland::scale() const { - return w->scale; + return window_->scale; } wl_surface *GHOST_WindowWayland::wl_surface() const { - return w->wl_surface; + return window_->wl_surface; } const std::vector<GWL_Output *> &GHOST_WindowWayland::outputs() { - return w->outputs; + return window_->outputs; } /** \} */ @@ -897,24 +992,24 @@ const std::vector<GWL_Output *> &GHOST_WindowWayland::outputs() GHOST_TSuccess GHOST_WindowWayland::close() { - return m_system->pushEvent( - new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowClose, this)); + return system_->pushEvent( + new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowClose, this)); } GHOST_TSuccess GHOST_WindowWayland::activate() { - if (m_system->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) { + if (system_->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) { return GHOST_kFailure; } - return m_system->pushEvent( - new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowActivate, this)); + return system_->pushEvent( + new GHOST_Event(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)); + system_->getWindowManager()->setWindowInactive(this); + return system_->pushEvent( + new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowDeactivate, this)); } GHOST_TSuccess GHOST_WindowWayland::notify_size() @@ -923,8 +1018,8 @@ GHOST_TSuccess GHOST_WindowWayland::notify_size() setOpaque(); #endif - return m_system->pushEvent( - new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowSize, this)); + return system_->pushEvent( + new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowSize, this)); } /** \} */ @@ -941,30 +1036,29 @@ GHOST_TSuccess GHOST_WindowWayland::notify_size() bool GHOST_WindowWayland::outputs_changed_update_scale() { uint32_t dpi_next; - const int scale_next = outputs_max_scale_or_default(this->outputs(), 0, &dpi_next); + const int scale_next = outputs_max_scale_or_default(outputs(), 0, &dpi_next); if (UNLIKELY(scale_next == 0)) { return false; } - GWL_Window *win = this->w; - const uint32_t dpi_curr = win->dpi; - const int scale_curr = win->scale; + const uint32_t dpi_curr = window_->dpi; + const int scale_curr = window_->scale; bool changed = false; if (scale_next != scale_curr) { /* Unlikely but possible there is a pending size change is set. */ - win->size_pending[0] = (win->size_pending[0] / scale_curr) * scale_next; - win->size_pending[1] = (win->size_pending[1] / scale_curr) * scale_next; + window_->size_pending[0] = (window_->size_pending[0] / scale_curr) * scale_next; + window_->size_pending[1] = (window_->size_pending[1] / scale_curr) * scale_next; - win->scale = scale_next; - wl_surface_set_buffer_scale(w->wl_surface, scale_next); + window_->scale = scale_next; + wl_surface_set_buffer_scale(window_->wl_surface, scale_next); changed = true; } if (dpi_next != dpi_curr) { /* Using the real DPI will cause wrong scaling of the UI * use a multiplier for the default DPI as workaround. */ - win->dpi = dpi_next; + window_->dpi = dpi_next; changed = true; /* As this is a low-level function, we might want adding this event to be optional, @@ -979,7 +1073,7 @@ bool GHOST_WindowWayland::outputs_changed_update_scale() bool GHOST_WindowWayland::outputs_enter(GWL_Output *output) { - std::vector<GWL_Output *> &outputs = w->outputs; + std::vector<GWL_Output *> &outputs = window_->outputs; auto it = std::find(outputs.begin(), outputs.end(), output); if (it != outputs.end()) { return false; @@ -990,7 +1084,7 @@ bool GHOST_WindowWayland::outputs_enter(GWL_Output *output) bool GHOST_WindowWayland::outputs_leave(GWL_Output *output) { - std::vector<GWL_Output *> &outputs = w->outputs; + std::vector<GWL_Output *> &outputs = window_->outputs; auto it = std::find(outputs.begin(), outputs.end(), output); if (it == outputs.end()) { return false; diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h index 9b4c17ecd95..e95f5386310 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.h +++ b/intern/ghost/intern/GHOST_WindowWayland.h @@ -115,9 +115,9 @@ class GHOST_WindowWayland : public GHOST_Window { bool outputs_changed_update_scale(); private: - GHOST_SystemWayland *m_system; - struct GWL_Window *w; - std::string title; + GHOST_SystemWayland *system_; + struct GWL_Window *window_; + std::string title_; /** * \param type: The type of rendering context create. |