diff options
Diffstat (limited to 'intern/ghost/intern')
28 files changed, 3000 insertions, 873 deletions
diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 158e979cdf2..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; @@ -161,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; @@ -172,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 93708983f37..d9f2df21ee0 100644 --- a/intern/ghost/intern/GHOST_ContextGLX.cpp +++ b/intern/ghost/intern/GHOST_ContextGLX.cpp @@ -140,7 +140,7 @@ GHOST_TSuccess GHOST_ContextGLX::initializeDrawingContext() /* End Inline GLEW. */ /* -------------------------------------------------------------------- */ #else - /* Important to initialize only glxew (_not_ GLEW), + /* Important to initialize only GLXEW (_not_ GLEW), * since this breaks w/ Mesa's `swrast`, see: T46431. */ glxewInit(); #endif /* USE_GLXEW_INIT_WORKAROUND */ 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 8e2859ca1e1..696848ce623 100644 --- a/intern/ghost/intern/GHOST_ISystem.cpp +++ b/intern/ghost/intern/GHOST_ISystem.cpp @@ -34,7 +34,7 @@ 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. */ @@ -61,7 +61,7 @@ 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; @@ -99,7 +99,7 @@ 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; @@ -160,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; } diff --git a/intern/ghost/intern/GHOST_ImeWin32.h b/intern/ghost/intern/GHOST_ImeWin32.h index 85c8ed7b4bd..cb6d8a770cf 100644 --- a/intern/ghost/intern/GHOST_ImeWin32.h +++ b/intern/ghost/intern/GHOST_ImeWin32.h @@ -266,7 +266,7 @@ class GHOST_ImeWin32 { * Parameters * * window_handle [in] (HWND) * Represents the window handle of the caller. - * * caret_rect [in] (const gfx::Rect&) + * * caret_rect [in] (`const gfx::Rect&`) * Represent the rectangle of the input caret. * This rectangle is used for controlling the positions of IME windows. * * complete [in] (bool) diff --git a/intern/ghost/intern/GHOST_NDOFManager.cpp b/intern/ghost/intern/GHOST_NDOFManager.cpp index f4c726c7450..5484da82a18 100644 --- a/intern/ghost/intern/GHOST_NDOFManager.cpp +++ b/intern/ghost/intern/GHOST_NDOFManager.cpp @@ -12,11 +12,14 @@ #include <climits> #include <cmath> -#include <cstdio> /* For error/info reporting. */ #include <cstring> /* For memory functions. */ -/* Printable version of each GHOST_TProgress value. */ -static const char *progress_string[] = { +/* -------------------------------------------------------------------- */ +/** \name NDOF Enum Strings + * \{ */ + +/* Printable values for #GHOST_TProgress enum (keep aligned). */ +static const char *ndof_progress_string[] = { "not started", "starting", "in progress", @@ -24,41 +27,30 @@ static const char *progress_string[] = { "finished", }; +/* 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", @@ -69,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", + "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, @@ -90,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, @@ -108,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, @@ -119,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, @@ -134,27 +154,70 @@ 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 * \{ */ @@ -166,16 +229,16 @@ 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 @@ -185,96 +248,102 @@ 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; + case 0xC658: /* Wireless (3DConnexion Universal Wireless Receiver in WIN32), see T82412. */ + { + 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 (device_type_ != NDOF_UnknownDevice) { + CLOG_INFO(LOG, 2, "using %s", ndof_device_names[device_type_]); } - if (m_buttonMask == 0) { - m_buttonMask = int(~(UINT_MAX << m_buttonCount)); + 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", m_buttonCount, (uint)m_buttonMask); + 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 @@ -287,16 +356,16 @@ bool GHOST_NDOFManager::setDevice(ushort vendor_id, ushort product_id) 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; } /** \} */ @@ -314,6 +383,18 @@ static GHOST_TKey ghost_map_keyboard_from_ndof_buttom(const NDOF_ButtonT 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; } @@ -334,7 +415,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); @@ -343,7 +424,7 @@ void GHOST_NDOFManager::sendButtonEvent(NDOF_ButtonT button, data->action = press ? GHOST_kPress : GHOST_kRelease; data->button = button; - m_system.pushEvent(event); + system_.pushEvent(event); } void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key, @@ -354,21 +435,21 @@ 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); - m_system.pushEvent(event); + system_.pushEvent(event); } void GHOST_NDOFManager::updateButton(int button_number, bool press, uint64_t time) { - if (button_number >= m_buttonCount) { + if (button_number >= hid_map_button_num_) { CLOG_INFO(LOG, 2, "button=%d, press=%d (out of range %d, ignoring!)", button_number, (int)press, - m_buttonCount); + hid_map_button_num_); return; } - const NDOF_ButtonT button = m_hidMap[button_number]; + 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); @@ -382,7 +463,7 @@ void GHOST_NDOFManager::updateButton(int button_number, bool press, uint64_t tim (int)press, ndof_button_names[button]); - GHOST_IWindow *window = m_system.getWindowManager()->getActiveWindow(); + 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); @@ -393,20 +474,20 @@ void GHOST_NDOFManager::updateButton(int button_number, bool press, uint64_t tim 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) { @@ -433,7 +514,7 @@ void GHOST_NDOFManager::setDeadZone(float dz) /* Negative values don't make sense, so clamp at zero. */ dz = 0.0f; } - m_deadZone = dz; + motion_dead_zone_ = 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)" : ""); @@ -458,20 +539,20 @@ static bool nearHomePosition(GHOST_TEventNDOFMotionData *ndof, float threshold) 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 == nullptr) { - m_motionState = GHOST_kNotStarted; /* Avoid large `dt` times when changing windows. */ + 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 @@ -479,26 +560,26 @@ 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->tx = scale * translation_[0]; + data->ty = scale * translation_[1]; + data->tz = scale * translation_[2]; - data->rx = scale * m_rotation[0]; - data->ry = scale * m_rotation[1]; - data->rz = scale * m_rotation[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: { 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; } @@ -517,7 +598,7 @@ bool GHOST_NDOFManager::sendMotionEvent() } else { data->progress = GHOST_kFinishing; - m_motionState = GHOST_kFinished; + motion_state_ = GHOST_kFinished; } break; } @@ -538,21 +619,21 @@ bool GHOST_NDOFManager::sendMotionEvent() data->ry, data->rz, data->dt, - progress_string[data->progress]); + 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", - m_translation[0], - m_translation[1], - m_translation[2], - m_rotation[0], - m_rotation[1], - m_rotation[2], - progress_string[data->progress]); + 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 73c1b17f891..2d5bba14aa4 100644 --- a/intern/ghost/intern/GHOST_NDOFManager.h +++ b/intern/ghost/intern/GHOST_NDOFManager.h @@ -9,7 +9,7 @@ #include "GHOST_System.h" typedef enum { - NDOF_UnknownDevice, + NDOF_UnknownDevice = 0, /* Current devices. */ NDOF_SpaceNavigator, @@ -29,8 +29,8 @@ typedef enum { /* NDOF device button event types */ typedef enum { - /* Used internally, never sent. */ - NDOF_BUTTON_NONE = 0, + /* 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, @@ -58,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, @@ -79,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 { @@ -140,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_System.h b/intern/ghost/intern/GHOST_System.h index 810f828a8a1..924a4bff790 100644 --- a/intern/ghost/intern/GHOST_System.h +++ b/intern/ghost/intern/GHOST_System.h @@ -76,7 +76,7 @@ class GHOST_System : public GHOST_ISystem { GHOST_ITimerTask *installTimer(uint64_t delay, uint64_t interval, GHOST_TimerProcPtr timerProc, - GHOST_TUserDataPtr userData = NULL); + GHOST_TUserDataPtr userData = nullptr); /** * Removes a timer. 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 04c7e103bde..67270d26ed3 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,6 +51,8 @@ /* 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> @@ -82,10 +85,23 @@ 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); + +static bool gwl_registry_entry_remove_by_name(GWL_Display *display, + uint32_t name, + int *r_interface_slot); +static void gwl_registry_entry_remove_all(GWL_Display *display); + +struct GWL_RegistryHandler; +static int gwl_registry_handler_interface_slot_max(); +static int gwl_registry_handler_interface_slot_from_string(const char *interface); +static const struct GWL_RegistryHandler *gwl_registry_handler_from_interface_slot( + int interface_slot); + /* -------------------------------------------------------------------- */ -/** \name Local Defines - * - * Control local functionality, compositors specific workarounds. +/** \name Workaround Compositor Specific Bugs * \{ */ /** @@ -116,11 +132,20 @@ 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 /** + * KDE (plasma 5.26.1) has a bug where the cursor surface needs to be committed + * (via `wl_surface_commit`) when it was hidden and is being set to visible again, see: T102048. + * See: https://bugs.kde.org/show_bug.cgi?id=461001 + */ +#define USE_KDE_TABLET_HIDDEN_CURSOR_HACK + +/** * 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. @@ -129,6 +154,20 @@ static bool use_gnome_confine_hack = false; # define USE_GNOME_NEEDS_LIBDECOR_HACK #endif +/* -------------------------------------------------------------------- */ +/** \name Local Defines + * + * Control local functionality, compositors specific workarounds. + * \{ */ + +/** + * Fix short-cut part of keyboard reading code not properly handling some keys, see: T102194. + * \note This is similar to X11 workaround by the same name, see: T47228. + */ +#define USE_NON_LATIN_KB_WORKAROUND + +#define WL_NAME_UNSET uint32_t(-1) + /** \} */ /* -------------------------------------------------------------------- */ @@ -140,8 +179,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 @@ -154,9 +192,14 @@ static bool use_gnome_confine_hack = false; /** * Tablet events. + * + * \note Gnome/GTK swap middle/right, where the same application in X11 will swap the middle/right + * mouse button when running under WAYLAND. KDE doesn't do this, and according to artists + * at the Blender studio, having the button closest to the nib be MMB is preferable, + * so use this as a default. If needs be - swapping these could be a preference. */ -#define BTN_STYLUS 0x14b /* Use as right-mouse. */ -#define BTN_STYLUS2 0x14c /* Use as middle-mouse. */ +#define BTN_STYLUS 0x14b /* Use as middle-mouse. */ +#define BTN_STYLUS2 0x14c /* Use as right-mouse. */ /* NOTE(@campbellbarton): Map to an additional button (not sure which hardware uses this). */ #define BTN_STYLUS3 0x149 @@ -165,6 +208,19 @@ static bool use_gnome_confine_hack = false; */ #define KEY_GRAVE 41 +#ifdef USE_NON_LATIN_KB_WORKAROUND +# define KEY_1 2 +# define KEY_2 3 +# define KEY_3 4 +# define KEY_4 5 +# define KEY_5 6 +# define KEY_6 7 +# define KEY_7 8 +# define KEY_8 9 +# define KEY_9 10 +# define KEY_0 11 +#endif + /** \} */ /* -------------------------------------------------------------------- */ @@ -190,48 +246,76 @@ 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 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_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; +} + /** \} */ /* -------------------------------------------------------------------- */ -/** \name Private Types & Defines +/** \name Internal #GWL_Cursor Type * \{ */ /** @@ -249,16 +333,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_surface *wl_surface_cursor = 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; }; @@ -269,6 +363,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; @@ -276,23 +371,55 @@ 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::atomic<bool> in_use = false; + std::unordered_set<std::string> types; + struct { + /** + * Prevents freeing after #wl_data_device_listener.leave, + * before #wl_data_device_listener.drop. + */ + bool in_use = false; + /** + * 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. * @@ -352,12 +479,58 @@ struct GWL_SeatStatePointer { * The wl_surface last used with this pointing device * (events with this pointing device will be sent here). */ - struct wl_surface *wl_surface = nullptr; + struct wl_surface *wl_surface_window = nullptr; GHOST_Buttons buttons = GHOST_Buttons(); }; /** + * 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 { @@ -368,7 +541,7 @@ struct GWL_SeatStateKeyboard { * The wl_surface last used with this pointing device * (events with this pointing device will be sent here). */ - struct wl_surface *wl_surface = nullptr; + struct wl_surface *wl_surface_window = nullptr; }; /** @@ -376,48 +549,110 @@ 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 WGL_LibDecor_System { +struct GWL_LibDecor_System { struct libdecor *context = nullptr; }; -static void wgl_libdecor_system_destroy(WGL_LibDecor_System *decor) +static void gwl_libdecor_system_destroy(GWL_LibDecor_System *decor) { if (decor->context) { libdecor_unref(decor->context); + decor->context = nullptr; } delete decor; } #endif -struct WGL_XDG_Decor_System { +struct GWL_XDG_Decor_System { struct xdg_wm_base *shell = nullptr; + uint32_t shell_name = WL_NAME_UNSET; + struct zxdg_decoration_manager_v1 *manager = nullptr; + uint32_t manager_name = WL_NAME_UNSET; }; -static void wgl_xdg_decor_system_destroy(WGL_XDG_Decor_System *decor) +static void gwl_xdg_decor_system_destroy(struct GWL_Display *display, GWL_XDG_Decor_System *decor) { if (decor->manager) { - zxdg_decoration_manager_v1_destroy(decor->manager); + gwl_registry_entry_remove_by_name(display, decor->manager_name, nullptr); + GHOST_ASSERT(decor->manager == nullptr, "Internal registry error"); } if (decor->shell) { - xdg_wm_base_destroy(decor->shell); + gwl_registry_entry_remove_by_name(display, decor->shell_name, nullptr); + GHOST_ASSERT(decor->shell == nullptr, "Internal registry error"); } delete decor; } +struct GWL_PrimarySelection_DataOffer { + struct zwp_primary_selection_offer_v1 *id = nullptr; + + 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; @@ -426,6 +661,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; @@ -440,9 +677,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; @@ -451,14 +688,24 @@ struct GWL_Seat { * Keep a state with no modifiers active, use for symbol lookups. */ struct xkb_state *xkb_state_empty = nullptr; + + /** + * Keep a state with shift enabled, use to access predictable number access for AZERTY keymaps. + * If shift is not supported by the key-map, this is set to NULL. + */ + struct xkb_state *xkb_state_empty_with_shift = nullptr; /** * Keep a state with number-lock enabled, use to access predictable key-pad symbols. * If number-lock is not supported by the key-map, this is set to NULL. */ struct xkb_state *xkb_state_empty_with_numlock = nullptr; +#ifdef USE_NON_LATIN_KB_WORKAROUND + bool xkb_use_non_latin_workaround = false; +#endif + /** Keys held matching `xkb_state`. */ - struct WGL_KeyboardDepressedState key_depressed; + struct GWL_KeyboardDepressedState key_depressed; #ifdef USE_GNOME_KEYBOARD_SUPPRESS_WARNING struct { @@ -483,9 +730,9 @@ struct GWL_Seat { GHOST_ITimerTask *timer = nullptr; } key_repeat; - struct wl_surface *wl_surface_focus_dnd = nullptr; + struct wl_surface *wl_surface_window_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; @@ -497,209 +744,434 @@ 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; }; +static GWL_SeatStatePointer *gwl_seat_state_pointer_active(GWL_Seat *seat) +{ + if (seat->pointer.serial == seat->cursor_source_serial) { + return &seat->pointer; + } + if (seat->tablet.serial == seat->cursor_source_serial) { + return &seat->tablet; + } + return nullptr; +} + +static GWL_SeatStatePointer *gwl_seat_state_pointer_from_cursor_surface( + GWL_Seat *seat, const wl_surface *wl_surface) +{ + if (ghost_wl_surface_own_cursor_pointer(wl_surface)) { + return &seat->pointer; + } + if (ghost_wl_surface_own_cursor_tablet(wl_surface)) { + return &seat->tablet; + } + GHOST_ASSERT(0, "Surface found without pointer/tablet tag"); + return nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_Display Type (#wl_display & #wl_compositor wrapper) + * \{ */ + +struct GWL_RegistryEntry; + struct GWL_Display { GHOST_SystemWayland *system = nullptr; + /** + * True when initializing registration, while updating all other entries wont cause problems, + * it will preform many redundant update calls. + */ + bool registry_skip_update_all = false; + + /** Registry entries, kept to allow updating & removal at run-time. */ + struct GWL_RegistryEntry *registry_entry = nullptr; + + struct wl_registry *wl_registry = nullptr; struct wl_display *wl_display = nullptr; struct wl_compositor *wl_compositor = nullptr; #ifdef WITH_GHOST_WAYLAND_LIBDECOR - WGL_LibDecor_System *libdecor = nullptr; + GWL_LibDecor_System *libdecor = nullptr; bool libdecor_required = false; #endif - WGL_XDG_Decor_System *xdg_decor = nullptr; + GWL_XDG_Decor_System *xdg_decor = nullptr; struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; struct wl_shm *wl_shm = nullptr; std::vector<GWL_Output *> outputs; std::vector<GWL_Seat *> seats; + /** + * Support a single active seat at once, this isn't an exact or correct mapping from WAYLAND. + * Only allow input from different seats, not full concurrent multi-seat support. + * + * The main purpose of having an active seat is an alternative from always using the first + * seat which prevents events from any other seat. + * + * NOTE(@campbellbarton): This could be extended and developed further extended to support + * an active seat per window (for e.g.), basic support is sufficient for now as currently isn't + * a widely used feature. + */ + int seats_active_index = 0; - 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; -}; - -#undef LOG - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Private Utility Functions - * \{ */ - -static GHOST_WindowManager *window_manager = nullptr; + /* Managers. */ + 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_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr; -/** Check this lock before accessing #GHOST_SystemWayland::clipboard_ from a thread. */ -static std::mutex system_clipboard_mutex; + struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr; + struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr; +}; /** - * Callback for WAYLAND to run when there is an error. + * Free the #GWL_Display and it's related members. * - * \note It's useful to set a break-point on this function as some errors are fatal - * (for all intents and purposes) but don't crash the process. + * \note This may run on a partially initialized struct, + * so it can't be assumed all members are set. */ -static void ghost_wayland_log_handler(const char *msg, va_list arg) +static void gwl_display_destroy(GWL_Display *display) { - fprintf(stderr, "GHOST/Wayland: "); - vfprintf(stderr, msg, arg); /* Includes newline. */ + /* For typical WAYLAND use this will always be set. + * However when WAYLAND isn't running, this will early-exit and be null. */ + if (display->wl_registry) { + wl_registry_destroy(display->wl_registry); + display->wl_registry = nullptr; + } - GHOST_TBacktraceFn backtrace_fn = GHOST_ISystem::getBacktraceFn(); - if (backtrace_fn) { - backtrace_fn(stderr); /* Includes newline. */ + /* Unregister items in reverse order. */ + gwl_registry_entry_remove_all(display); + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + if (display->libdecor) { + gwl_libdecor_system_destroy(display->libdecor); + display->libdecor = nullptr; + } + } + else +#endif + { + if (display->xdg_decor) { + gwl_xdg_decor_system_destroy(display, display->xdg_decor); + display->xdg_decor = nullptr; + } } -} -static GWL_SeatStatePointer *seat_state_pointer_active(GWL_Seat *seat) -{ - if (seat->pointer.serial == seat->cursor_source_serial) { - return &seat->pointer; + if (eglGetDisplay) { + ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl_display))); } - if (seat->tablet.serial == seat->cursor_source_serial) { - return &seat->tablet; + + if (display->wl_display) { + wl_display_disconnect(display->wl_display); } - return nullptr; + + delete display; } -static GWL_SeatStatePointer *seat_state_pointer_from_cursor_surface(GWL_Seat *seat, - const wl_surface *wl_surface) +static int gwl_display_seat_index(GWL_Display *display, const GWL_Seat *seat) { - if (ghost_wl_surface_own_cursor_pointer(wl_surface)) { - return &seat->pointer; - } - if (ghost_wl_surface_own_cursor_tablet(wl_surface)) { - return &seat->tablet; - } - GHOST_ASSERT(0, "Surface found without pointer/tablet tag"); - return nullptr; + std::vector<GWL_Seat *>::iterator iter = std::find( + display->seats.begin(), display->seats.end(), seat); + const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) : + -1; + GHOST_ASSERT(index != -1, "invalid internal state"); + return index; } -static void display_destroy(GWL_Display *display) +static GWL_Seat *gwl_display_seat_active_get(const GWL_Display *display) { - if (display->data_device_manager) { - wl_data_device_manager_destroy(display->data_device_manager); + if (UNLIKELY(display->seats.empty())) { + return nullptr; } + return display->seats[display->seats_active_index]; +} - if (display->tablet_manager) { - zwp_tablet_manager_v2_destroy(display->tablet_manager); +static bool gwl_display_seat_active_set(GWL_Display *display, const GWL_Seat *seat) +{ + if (UNLIKELY(display->seats.empty())) { + return false; } - - for (GWL_Output *output : display->outputs) { - wl_output_destroy(output->wl_output); - delete output; + const int index = gwl_display_seat_index(display, seat); + if (index == display->seats_active_index) { + return false; } + display->seats_active_index = index; + return true; +} - 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); - } - delete seat->data_source; - } - } +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_RegistryHandler + * \{ */ - { - std::lock_guard lock{seat->data_offer_dnd_mutex}; - if (seat->data_offer_dnd) { - wl_data_offer_destroy(seat->data_offer_dnd->id); - delete seat->data_offer_dnd; - } - } +struct GWL_RegisteryAdd_Params { + uint32_t name = 0; + /** Index within `gwl_registry_handlers`. */ + int interface_slot = 0; + uint32_t version = 0; +}; - { - std::lock_guard lock{seat->data_offer_copy_paste_mutex}; - if (seat->data_offer_copy_paste) { - wl_data_offer_destroy(seat->data_offer_copy_paste->id); - delete seat->data_offer_copy_paste; - } - } +/** + * Add callback for object registry. + * \note Any operations that depend on other interfaces being registered must be performed in the + * #GWL_RegistryHandler_UpdateFn callback as the order interfaces are added is out of our control. + * + * \param display: The display which holes a reference to the global object. + * \param params: Various arguments needed for registration. + */ +using GWL_RegistryHandler_AddFn = void (*)(GWL_Display *display, + const GWL_RegisteryAdd_Params *params); - if (seat->data_device) { - wl_data_device_release(seat->data_device); - } +struct GWL_RegisteryUpdate_Params { + uint32_t name = 0; + /** Index within `gwl_registry_handlers`. */ + int interface_slot = 0; + uint32_t version = 0; - if (seat->cursor.custom_data) { - munmap(seat->cursor.custom_data, seat->cursor.custom_data_size); - } + /** Set to #GWL_RegistryEntry.user_data. */ + void *user_data = nullptr; +}; - 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->wl_keyboard) { - if (seat->key_repeat.timer) { - keyboard_handle_key_repeat_cancel(seat); - } - wl_keyboard_destroy(seat->wl_keyboard); - } +/** + * Optional update callback to refresh internal data when another interface has been added/removed. + * + * \param display: The display which holes a reference to the global object. + * \param params: Various arguments needed for updating. + */ +using GWL_RegistryHandler_UpdateFn = void (*)(GWL_Display *display, + const GWL_RegisteryUpdate_Params *params); - /* Un-referencing checks for NULL case. */ - xkb_state_unref(seat->xkb_state); - xkb_state_unref(seat->xkb_state_empty); - xkb_state_unref(seat->xkb_state_empty_with_numlock); +/** + * Remove callback for object registry. + * \param display: The display which holes a reference to the global object. + * \param user_data: Optional reference to a sub element of `display`, + * use for outputs or seats for e.g. when the display may hold multiple references. + * \param on_exit: Enabled when freeing on exit. + * When true the consistency of references between objects should be kept valid. + * Otherwise it can be assumed that all objects will be freed and none will be used again, + * so there is no need to ensure a valid state. + */ +using GWL_RegistryEntry_RemoveFn = void (*)(GWL_Display *display, void *user_data, bool on_exit); + +struct GWL_RegistryHandler { + /** Pointer to the name (not the name it's self), needed as the values aren't set on startup. */ + const char *const *interface_p = nullptr; + + /** Add the interface. */ + GWL_RegistryHandler_AddFn add_fn = nullptr; + /** Optional update the interface (when other interfaces have been added/removed). */ + GWL_RegistryHandler_UpdateFn update_fn = nullptr; + /** Remove the interface. */ + GWL_RegistryEntry_RemoveFn remove_fn = nullptr; +}; - xkb_context_unref(seat->xkb_context); +/** \} */ - wl_seat_destroy(seat->wl_seat); - delete seat; - } +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_RegistryEntry + * \{ */ - if (display->wl_shm) { - wl_shm_destroy(display->wl_shm); - } +/** + * Registered global objects can be removed by the compositor, + * these entries are a registry of objects and callbacks to properly remove them. + * These are also used to remove all registered objects before exiting. + */ +struct GWL_RegistryEntry { + GWL_RegistryEntry *next = nullptr; + /** + * Optional pointer passed to `remove_fn`, typically the container in #GWL_Display + * in cases multiple instances of the same interface are supported. + */ + void *user_data = nullptr; + /** + * A unique identifier used as a handle by `wl_registry_listener.global_remove`. + */ + uint32_t name = WL_NAME_UNSET; + /** + * Version passed by the add callback. + */ + uint32_t version; + /** + * The index in `gwl_registry_handlers`, + * useful for accessing the interface name (for logging for example). + */ + int interface_slot = 0; +}; - if (display->relative_pointer_manager) { - zwp_relative_pointer_manager_v1_destroy(display->relative_pointer_manager); - } +static void gwl_registry_entry_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params, + void *user_data) +{ + GWL_RegistryEntry *reg = new GWL_RegistryEntry; - if (display->pointer_constraints) { - zwp_pointer_constraints_v1_destroy(display->pointer_constraints); - } + reg->interface_slot = params->interface_slot; + reg->name = params->name; + reg->version = params->version; + reg->user_data = user_data; + + reg->next = display->registry_entry; + display->registry_entry = reg; +} + +static bool gwl_registry_entry_remove_by_name(GWL_Display *display, + uint32_t name, + int *r_interface_slot) +{ + GWL_RegistryEntry *reg = display->registry_entry; + GWL_RegistryEntry **reg_link_p = &display->registry_entry; + bool found = false; - if (display->wl_compositor) { - wl_compositor_destroy(display->wl_compositor); + if (r_interface_slot) { + *r_interface_slot = -1; } -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor) { - if (display->libdecor) { - wgl_libdecor_system_destroy(display->libdecor); + while (reg) { + if (reg->name == name) { + GWL_RegistryEntry *reg_next = reg->next; + const GWL_RegistryHandler *handler = gwl_registry_handler_from_interface_slot( + reg->interface_slot); + handler->remove_fn(display, reg->user_data, false); + if (r_interface_slot) { + *r_interface_slot = reg->interface_slot; + } + delete reg; + *reg_link_p = reg_next; + found = true; + break; } - } - else -#endif - { - if (display->xdg_decor) { - wgl_xdg_decor_system_destroy(display->xdg_decor); + reg_link_p = ®->next; + reg = reg->next; + } + return found; +} + +static bool gwl_registry_entry_remove_by_interface_slot(GWL_Display *display, + int interface_slot, + bool on_exit) +{ + GWL_RegistryEntry *reg = display->registry_entry; + GWL_RegistryEntry **reg_link_p = &display->registry_entry; + bool found = false; + + while (reg) { + if (reg->interface_slot == interface_slot) { + GWL_RegistryEntry *reg_next = reg->next; + const GWL_RegistryHandler *handler = gwl_registry_handler_from_interface_slot( + interface_slot); + handler->remove_fn(display, reg->user_data, on_exit); + delete reg; + *reg_link_p = reg_next; + reg = reg_next; + found = true; + continue; } + reg_link_p = ®->next; + reg = reg->next; } + return found; +} - if (eglGetDisplay) { - ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl_display))); +/** + * Remove all global objects (on exit). + */ +static void gwl_registry_entry_remove_all(GWL_Display *display) +{ + const bool on_exit = true; + + /* NOTE(@campbellbarton): Free by slot instead of simply looping over + * `display->registry_entry` so the order of freeing is always predictable. + * Otherwise global objects would be feed in the order they are registered. + * While this works in my tests, it could cause difficult to reproduce bugs + * where lesser used compositors or changes to existing compositors could + * crash on exit based on the order of freeing objects is out of our control. + * + * To give a concrete example of how this could fail, it's possible removing + * a tablet interface could reference the pointer interface, or the output interface. + * Even though references between interfaces shouldn't be necessary in most cases + * when `on_exit` is enabled. */ + int interface_slot = gwl_registry_handler_interface_slot_max(); + while (interface_slot--) { + gwl_registry_entry_remove_by_interface_slot(display, interface_slot, on_exit); } - if (display->wl_display) { - wl_display_disconnect(display->wl_display); + GHOST_ASSERT(display->registry_entry == nullptr, "Failed to remove all entries!"); + display->registry_entry = nullptr; +} + +/** + * Run GWL_RegistryHandler.update_fn an all registered interface instances. + * This is needed to refresh the state of interfaces that may reference other interfaces. + * Called when interfaces are added/removed. + * + * \param interface_slot_exclude: Skip updating slots of this type. + * Note that while harmless dependencies only exist between different types, + * so there is no reason to update all other outputs that an output was removed (for e.g.). + * Pass as -1 to update all slots. + * + * NOTE(@campbellbarton): Updating all other items on a single change is typically worth avoiding. + * In practice this isn't a problem as so there are so few elements in `display->registry_entry`, + * so few use update functions and adding/removal at runtime is rarely called (plugging/unplugging) + * hardware for e.g. So while it's possible to store dependency links to avoid unnecessary + * looping over data - it ends up being a non issue. + */ +static void gwl_registry_entry_update_all(GWL_Display *display, const int interface_slot_exclude) +{ + GHOST_ASSERT(interface_slot_exclude == -1 || (uint(interface_slot_exclude) < + uint(gwl_registry_handler_interface_slot_max())), + "Invalid exclude slot"); + + for (GWL_RegistryEntry *reg = display->registry_entry; reg; reg = reg->next) { + if (reg->interface_slot == interface_slot_exclude) { + continue; + } + const GWL_RegistryHandler *handler = gwl_registry_handler_from_interface_slot( + reg->interface_slot); + if (handler->update_fn == nullptr) { + continue; + } + + GWL_RegisteryUpdate_Params params = { + .name = reg->name, + .interface_slot = reg->interface_slot, + .version = reg->version, + + .user_data = reg->user_data, + }; + handler->update_fn(display, ¶ms); } +} - delete display; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Private Utility Functions + * \{ */ + +/** + * Callback for WAYLAND to run when there is an error. + * + * \note It's useful to set a break-point on this function as some errors are fatal + * (for all intents and purposes) but don't crash the process. + */ +static void ghost_wayland_log_handler(const char *msg, va_list arg) +{ + fprintf(stderr, "GHOST/Wayland: "); + vfprintf(stderr, msg, arg); /* Includes newline. */ + + GHOST_TBacktraceFn backtrace_fn = GHOST_ISystem::getBacktraceFn(); + if (backtrace_fn) { + backtrace_fn(stderr); /* Includes newline. */ + } } static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym) @@ -837,9 +1309,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; } @@ -854,13 +1341,13 @@ 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; } static const int default_cursor_size = 24; -static const std::unordered_map<GHOST_TStandardCursor, const char *> cursors = { +static const std::unordered_map<GHOST_TStandardCursor, const char *> ghost_wl_cursors = { {GHOST_kStandardCursorDefault, "left_ptr"}, {GHOST_kStandardCursorRightArrow, "right_ptr"}, {GHOST_kStandardCursorLeftArrow, "left_ptr"}, @@ -901,23 +1388,23 @@ static const std::unordered_map<GHOST_TStandardCursor, const char *> cursors = { {GHOST_kStandardCursorCopy, "copy"}, }; -static constexpr const char *mime_text_plain = "text/plain"; -static constexpr const char *mime_text_utf8 = "text/plain;charset=utf-8"; -static constexpr const char *mime_text_uri = "text/uri-list"; +static constexpr const char *ghost_wl_mime_text_plain = "text/plain"; +static constexpr const char *ghost_wl_mime_text_utf8 = "text/plain;charset=utf-8"; +static constexpr const char *ghost_wl_mime_text_uri = "text/uri-list"; -static const std::unordered_map<std::string, GHOST_TDragnDropTypes> mime_dnd = { - {mime_text_plain, GHOST_kDragnDropTypeString}, - {mime_text_utf8, GHOST_kDragnDropTypeString}, - {mime_text_uri, GHOST_kDragnDropTypeFilenames}, +static const char *ghost_wl_mime_preference_order[] = { + ghost_wl_mime_text_uri, + ghost_wl_mime_text_utf8, + ghost_wl_mime_text_plain, }; - -static const std::vector<std::string> mime_preference_order = { - mime_text_uri, - mime_text_utf8, - mime_text_plain, +/* Aligned to `ghost_wl_mime_preference_order`. */ +static const GHOST_TDragnDropTypes ghost_wl_mime_preference_order_type[] = { + GHOST_kDragnDropTypeString, + GHOST_kDragnDropTypeString, + GHOST_kDragnDropTypeFilenames, }; -static const std::vector<std::string> mime_send = { +static const char *ghost_wl_mime_send[] = { "UTF8_STRING", "COMPOUND_TEXT", "TEXT", @@ -1045,9 +1532,9 @@ 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_IWindow *win = ghost_wl_surface_user_data(seat->keyboard.wl_surface_window); GHOST_SystemWayland *system = seat->system; /* Separate key up and down into separate passes so key down events always come after key up. @@ -1134,7 +1621,7 @@ static void relative_pointer_handle_relative_motion( const wl_fixed_t /*dy_unaccel*/) { GWL_Seat *seat = static_cast<GWL_Seat *>(data); - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { CLOG_INFO(LOG, 2, "relative_motion"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); @@ -1167,7 +1654,7 @@ static CLG_LogRef LOG_WL_DATA_SOURCE = {"ghost.wl.handle.data_source"}; static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event) { /* NOTE: `seat->data_offer_dnd_mutex` must already be locked. */ - if (wl_surface *wl_surface_focus = seat->wl_surface_focus_dnd) { + if (wl_surface *wl_surface_focus = seat->wl_surface_window_focus_dnd) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); const int event_xy[2] = { @@ -1176,40 +1663,144 @@ static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event) }; const uint64_t time = seat->system->getMilliSeconds(); - for (const std::string &type : mime_preference_order) { - seat->system->pushEvent(new GHOST_EventDragnDrop( - time, event, mime_dnd.at(type), win, UNPACK2(event_xy), nullptr)); + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order_type); i++) { + const GHOST_TDragnDropTypes type = ghost_wl_mime_preference_order_type[i]; + seat->system->pushEvent( + new GHOST_EventDragnDrop(time, event, type, win, UNPACK2(event_xy), nullptr)); } } } -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 char *read_file_as_buffer(const int fd, const bool nil_terminate, 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 + (nil_terminate ? 1 : 0))); + if (UNLIKELY(buf == nullptr)) { + CLOG_WARN(LOG, "unable to allocate file buffer: %zu bytes", len); + ok = false; + } + } + + if (ok) { + *r_len = len; + if (nil_terminate) { + buf[len] = '\0'; + } + } + else { + *r_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 char *read_buffer_from_data_offer(GWL_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + const bool nil_terminate, + size_t *r_len) { int pipefd[2]; - if (UNLIKELY(pipe(pipefd) != 0)) { - return {}; + const bool pipefd_ok = pipe(pipefd) == 0; + if (pipefd_ok) { + wl_data_offer_receive(data_offer->id, mime_receive, pipefd[1]); + close(pipefd[1]); + } + else { + CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno)); } - wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]); - close(pipefd[1]); - data_offer->in_use.store(false); + /* Only for DND (A no-op to disable for clipboard data-offer). */ + data_offer->dnd.in_use = false; if (mutex) { mutex->unlock(); } /* WARNING: `data_offer` may be freed from now on. */ + char *buf = nullptr; + if (pipefd_ok) { + buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len); + close(pipefd[0]); + } + return buf; +} - 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); +static char *read_buffer_from_primary_selection_offer(GWL_PrimarySelection_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + const bool nil_terminate, + size_t *r_len) +{ + int pipefd[2]; + const bool pipefd_ok = pipe(pipefd) == 0; + if (pipefd_ok) { + zwp_primary_selection_offer_v1_receive(data_offer->id, mime_receive, pipefd[1]); + close(pipefd[1]); + } + else { + CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno)); } - close(pipefd[0]); - return data; + if (mutex) { + mutex->unlock(); + } + /* WARNING: `data_offer` may be freed from now on. */ + char *buf = nullptr; + if (pipefd_ok) { + buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len); + close(pipefd[0]); + } + return buf; } /** @@ -1231,20 +1822,33 @@ static void data_source_handle_send(void *data, const int32_t fd) { GWL_Seat *seat = static_cast<GWL_Seat *>(data); - std::lock_guard lock{seat->data_source_mutex}; 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); - } - close(fd); + auto write_file_fn = [](GWL_Seat *seat, const int fd) { + if (UNLIKELY(write(fd, + seat->data_source->buffer_out.data, + seat->data_source->buffer_out.data_size) < 0)) { + CLOG_WARN(LOG, "error writing to clipboard: %s", std::strerror(errno)); + } + close(fd); + seat->data_source_mutex.unlock(); + }; + + seat->data_source_mutex.lock(); + std::thread write_thread(write_file_fn, seat, fd); + write_thread.detach(); } -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); } @@ -1313,7 +1917,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, @@ -1321,7 +1926,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, @@ -1329,7 +1935,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 = { @@ -1381,7 +1988,7 @@ static void data_device_handle_enter(void *data, seat->data_offer_dnd = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id)); GWL_DataOffer *data_offer = seat->data_offer_dnd; - data_offer->in_use.store(true); + data_offer->dnd.in_use = true; data_offer->dnd.xy[0] = x; data_offer->dnd.xy[1] = y; @@ -1390,11 +1997,15 @@ static void data_device_handle_enter(void *data, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); - for (const std::string &type : mime_preference_order) { - wl_data_offer_accept(id, serial, type.c_str()); + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) { + const char *type = ghost_wl_mime_preference_order[i]; + wl_data_offer_accept(id, serial, type); } - seat->wl_surface_focus_dnd = wl_surface; + seat->wl_surface_window_focus_dnd = wl_surface; + + seat->system->seat_active_set(seat); + dnd_events(seat, GHOST_kEventDraggingEntered); } @@ -1406,9 +2017,9 @@ static void data_device_handle_leave(void *data, struct wl_data_device * /*wl_da CLOG_INFO(LOG, 2, "leave"); dnd_events(seat, GHOST_kEventDraggingExited); - seat->wl_surface_focus_dnd = nullptr; + seat->wl_surface_window_focus_dnd = nullptr; - if (seat->data_offer_dnd && !seat->data_offer_dnd->in_use.load()) { + if (seat->data_offer_dnd && !seat->data_offer_dnd->dnd.in_use) { wl_data_offer_destroy(seat->data_offer_dnd->id); delete seat->data_offer_dnd; seat->data_offer_dnd = nullptr; @@ -1439,23 +2050,35 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat GWL_DataOffer *data_offer = seat->data_offer_dnd; - const std::string mime_receive = *std::find_first_of(mime_preference_order.begin(), - mime_preference_order.end(), - data_offer->types.begin(), - data_offer->types.end()); + /* Use a blank string for `mime_receive` to prevent crashes, although could also be `nullptr`. + * Failure to set this to a known type just means the file won't have any special handling. + * GHOST still generates a dropped file event. + * NOTE: this string can be compared with `mime_text_plain`, `mime_text_uri` etc... + * as the this always points to the same values. */ + const char *mime_receive = ""; + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) { + const char *type = ghost_wl_mime_preference_order[i]; + if (data_offer->types.count(type)) { + mime_receive = type; + break; + } + } - CLOG_INFO(LOG, 2, "drop mime_recieve=%s", mime_receive.c_str()); + CLOG_INFO(LOG, 2, "drop mime_recieve=%s", mime_receive); auto read_uris_fn = [](GWL_Seat *const seat, GWL_DataOffer *data_offer, - wl_surface *wl_surface, - const std::string mime_receive) { + wl_surface *wl_surface_window, + const char *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, nullptr, false, &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()); + CLOG_INFO(LOG, 2, "drop_read_uris mime_receive=%s, data=%s", mime_receive, data.c_str()); wl_data_offer_finish(data_offer->id); wl_data_offer_destroy(data_offer->id); @@ -1468,13 +2091,13 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat GHOST_SystemWayland *const system = seat->system; - if (mime_receive == mime_text_uri) { + if (mime_receive == ghost_wl_mime_text_uri) { static constexpr const char *file_proto = "file://"; /* NOTE: some applications CRLF (`\r\n`) GTK3 for e.g. & others don't `pcmanfm-qt`. * So support both, once `\n` is found, strip the preceding `\r` if found. */ static constexpr const char *lf = "\n"; - GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface); + GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_window); std::vector<std::string> uris; size_t pos = 0; @@ -1513,7 +2136,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat wl_fixed_to_int(scale * xy[1]), flist)); } - else if (ELEM(mime_receive, mime_text_plain, mime_text_utf8)) { + else if (ELEM(mime_receive, ghost_wl_mime_text_plain, ghost_wl_mime_text_utf8)) { /* TODO: enable use of internal functions 'txt_insert_buf' and * 'text_update_edited' to behave like dropped text was pasted. */ CLOG_INFO(LOG, 2, "drop_read_uris_fn (text_plain, text_utf8), unhandled!"); @@ -1521,10 +2144,10 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat wl_display_roundtrip(system->wl_display()); }; - /* Pass in `seat->wl_surface_focus_dnd` instead of accessing it from `seat` since the leave - * callback (#data_device_handle_leave) will clear the value once this function starts. */ + /* Pass in `seat->wl_surface_window_focus_dnd` instead of accessing it from `seat` since the + * leave callback (#data_device_handle_leave) will clear the value once this function starts. */ std::thread read_thread( - read_uris_fn, seat, data_offer, seat->wl_surface_focus_dnd, mime_receive); + read_uris_fn, seat, data_offer, seat->wl_surface_window_focus_dnd, mime_receive); read_thread.detach(); } @@ -1554,30 +2177,6 @@ static void data_device_handle_selection(void *data, /* Get new data offer. */ data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id)); seat->data_offer_copy_paste = data_offer; - - auto read_selection_fn = [](GWL_Seat *seat) { - GHOST_SystemWayland *const system = seat->system; - seat->data_offer_copy_paste_mutex.lock(); - - GWL_DataOffer *data_offer = seat->data_offer_copy_paste; - 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; - } - } - const std::string data = read_pipe( - data_offer, mime_receive, &seat->data_offer_copy_paste_mutex); - - { - std::lock_guard lock{system_clipboard_mutex}; - system->clipboard_set(data); - } - }; - - std::thread read_thread(read_selection_fn, seat); - read_thread.detach(); } static const struct wl_data_device_listener data_device_listener = { @@ -1631,7 +2230,7 @@ static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"}; static bool update_cursor_scale(GWL_Cursor &cursor, wl_shm *shm, GWL_SeatStatePointer *seat_state_pointer, - wl_surface *wl_cursor_surface) + wl_surface *wl_surface_cursor) { int scale = 0; for (const GWL_Output *output : seat_state_pointer->outputs) { @@ -1643,10 +2242,11 @@ static bool update_cursor_scale(GWL_Cursor &cursor, if (scale > 0 && seat_state_pointer->theme_scale != scale) { seat_state_pointer->theme_scale = scale; if (!cursor.is_custom) { - wl_surface_set_buffer_scale(wl_cursor_surface, scale); + wl_surface_set_buffer_scale(wl_surface_cursor, 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; @@ -1663,8 +2263,8 @@ static void cursor_surface_handle_enter(void *data, CLOG_INFO(LOG, 2, "handle_enter"); GWL_Seat *seat = static_cast<GWL_Seat *>(data); - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_from_cursor_surface(seat, - wl_surface); + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_from_cursor_surface( + seat, 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->wl_shm(), seat_state_pointer, wl_surface); @@ -1681,8 +2281,8 @@ static void cursor_surface_handle_leave(void *data, CLOG_INFO(LOG, 2, "handle_leave"); GWL_Seat *seat = static_cast<GWL_Seat *>(data); - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_from_cursor_surface(seat, - wl_surface); + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_from_cursor_surface( + seat, 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->wl_shm(), seat_state_pointer, wl_surface); @@ -1726,7 +2326,18 @@ static void pointer_handle_enter(void *data, seat->pointer.serial = serial; seat->pointer.xy[0] = surface_x; seat->pointer.xy[1] = surface_y; - seat->pointer.wl_surface = wl_surface; + + /* 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_window = wl_surface; + + seat->system->seat_active_set(seat); win->setCursorShape(win->getCursorShape()); @@ -1745,7 +2356,7 @@ static void pointer_handle_leave(void *data, struct wl_surface *wl_surface) { /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */ - static_cast<GWL_Seat *>(data)->pointer.wl_surface = nullptr; + static_cast<GWL_Seat *>(data)->pointer.wl_surface_window = nullptr; if (wl_surface && ghost_wl_surface_own(wl_surface)) { CLOG_INFO(LOG, 2, "leave"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface); @@ -1766,7 +2377,7 @@ static void pointer_handle_motion(void *data, seat->pointer.xy[0] = surface_x; seat->pointer.xy[1] = surface_y; - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { CLOG_INFO(LOG, 2, "motion"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); @@ -1830,31 +2441,99 @@ static void pointer_handle_button(void *data, seat->data_source_serial = serial; seat->pointer.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventButton( seat->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE)); } } -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_window) { + 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_window) { + 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. + * NOTE: the preference to invert scrolling (in GNOME at least) + * has already been applied so there is no need to read this preference. */ + -wl_fixed_to_int(seat->pointer_scroll.smooth_xy[0]), + -wl_fixed_to_int(seat->pointer_scroll.smooth_xy[1]), + 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*/, @@ -1868,18 +2547,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 = { @@ -1899,6 +2575,325 @@ 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_window) { + win = ghost_wl_surface_user_data(wl_surface_focus); + } + /* NOTE(@campbellbarton): Blender's use of track-pad coordinates is inconsistent and needs work. + * This isn't specific to WAYLAND, in practice they tend to work well enough in most cases. + * Some operators scale by the UI scale, some don't. + * Even this window scale is not correct because it doesn't account for: + * 1) Fractional window scale. + * 2) Blender's UI scale preference (which GHOST doesn't know about). + * + * If support for this were all that was needed it could be handled in GHOST, + * however as the operators are not even using coordinates compatible with each other, + * it would be better to resolve this by passing rotation & zoom levels directly, + * instead of attempting to handle them as cursor coordinates. + */ + 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_window) { + 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 + * + * NOTE(@campbellbarton): It's not clear if this interface is used by popular compositors. + * It looks like GNOME/KDE only support `zwp_pointer_gestures_v1_interface`. + * If this isn't used anywhere, it could be removed. + * \{ */ + +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 * \{ */ @@ -1983,11 +2978,13 @@ static void tablet_tool_handle_proximity_in(void *data, GWL_Seat *seat = tablet_tool->seat; seat->cursor_source_serial = serial; - seat->tablet.wl_surface = wl_surface; + seat->tablet.wl_surface_window = wl_surface; seat->tablet.serial = serial; seat->data_source_serial = serial; + seat->system->seat_active_set(seat); + /* Update #GHOST_TabletData. */ GHOST_TabletData &td = tablet_tool->data; /* Reset, to avoid using stale tilt/pressure. */ @@ -1996,7 +2993,7 @@ static void tablet_tool_handle_proximity_in(void *data, /* In case pressure isn't supported. */ td.Pressure = 1.0f; - GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->tablet.wl_surface); + GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->tablet.wl_surface_window); win->activate(); @@ -2026,7 +3023,7 @@ static void tablet_tool_handle_down(void *data, seat->data_source_serial = serial; seat->tablet.buttons.set(ebutton, true); - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventButton( seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data)); @@ -2044,7 +3041,7 @@ static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_ seat->tablet.buttons.set(ebutton, false); - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventButton( seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data)); @@ -2129,7 +3126,7 @@ static void tablet_tool_handle_wheel(void *data, GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data); GWL_Seat *seat = tablet_tool->seat; - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventWheel(seat->system->getMilliSeconds(), win, clicks)); } @@ -2158,10 +3155,10 @@ static void tablet_tool_handle_button(void *data, GHOST_TButton ebutton = GHOST_kButtonMaskLeft; switch (button) { case BTN_STYLUS: - ebutton = GHOST_kButtonMaskRight; + ebutton = GHOST_kButtonMaskMiddle; break; case BTN_STYLUS2: - ebutton = GHOST_kButtonMaskMiddle; + ebutton = GHOST_kButtonMaskRight; break; case BTN_STYLUS3: ebutton = GHOST_kButtonMaskButton4; @@ -2171,7 +3168,7 @@ static void tablet_tool_handle_button(void *data, seat->data_source_serial = serial; seat->tablet.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent(new GHOST_EventButton( seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data)); @@ -2187,7 +3184,7 @@ static void tablet_tool_handle_frame(void *data, GWL_Seat *seat = tablet_tool->seat; /* No need to check the surfaces origin, it's already known to be owned by GHOST. */ - if (wl_surface *wl_surface_focus = seat->tablet.wl_surface) { + if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); const wl_fixed_t scale = win->scale(); seat->system->pushEvent(new GHOST_EventCursor(seat->system->getMilliSeconds(), @@ -2202,7 +3199,7 @@ static void tablet_tool_handle_frame(void *data, } if (tablet_tool->proximity == false) { - seat->tablet.wl_surface = nullptr; + seat->tablet.wl_surface_window = nullptr; } } @@ -2330,9 +3327,23 @@ static void keyboard_handle_keymap(void *data, xkb_state_unref(seat->xkb_state_empty); seat->xkb_state_empty = xkb_state_new(keymap); + for (int i = 0; i < MOD_INDEX_NUM; i++) { + const GWL_ModifierInfo &mod_info = g_modifier_info_table[i]; + seat->xkb_keymap_mod_index[i] = xkb_keymap_mod_get_index(keymap, mod_info.xkb_id); + } + + xkb_state_unref(seat->xkb_state_empty_with_shift); + seat->xkb_state_empty_with_shift = nullptr; + { + const xkb_mod_index_t mod_shift = seat->xkb_keymap_mod_index[MOD_INDEX_SHIFT]; + if (mod_shift != XKB_MOD_INVALID) { + seat->xkb_state_empty_with_shift = xkb_state_new(keymap); + xkb_state_update_mask(seat->xkb_state_empty_with_shift, (1 << mod_shift), 0, 0, 0, 0, 0); + } + } + xkb_state_unref(seat->xkb_state_empty_with_numlock); seat->xkb_state_empty_with_numlock = nullptr; - { const xkb_mod_index_t mod2 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); const xkb_mod_index_t num = xkb_keymap_mod_get_index(keymap, "NumLock"); @@ -2343,10 +3354,21 @@ static void keyboard_handle_keymap(void *data, } } - for (int i = 0; i < MOD_INDEX_NUM; i++) { - const GWL_ModifierInfo &mod_info = g_modifier_info_table[i]; - seat->xkb_keymap_mod_index[i] = xkb_keymap_mod_get_index(keymap, mod_info.xkb_id); +#ifdef USE_NON_LATIN_KB_WORKAROUND + seat->xkb_use_non_latin_workaround = false; + if (seat->xkb_state_empty_with_shift) { + seat->xkb_use_non_latin_workaround = true; + for (xkb_keycode_t key_code = KEY_1 + EVDEV_OFFSET; key_code <= KEY_0 + EVDEV_OFFSET; + key_code++) { + const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(seat->xkb_state_empty_with_shift, + key_code); + if (!(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9)) { + seat->xkb_use_non_latin_workaround = false; + break; + } + } } +#endif keyboard_depressed_state_reset(seat); @@ -2372,12 +3394,14 @@ static void keyboard_handle_enter(void *data, GWL_Seat *seat = static_cast<GWL_Seat *>(data); seat->keyboard.serial = serial; - seat->keyboard.wl_surface = wl_surface; + seat->keyboard.wl_surface_window = wl_surface; + + seat->system->seat_active_set(seat); /* 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; @@ -2415,7 +3439,7 @@ static void keyboard_handle_leave(void *data, CLOG_INFO(LOG, 2, "leave"); GWL_Seat *seat = static_cast<GWL_Seat *>(data); - seat->keyboard.wl_surface = nullptr; + seat->keyboard.wl_surface_window = nullptr; /* Losing focus must stop repeating text. */ if (seat->key_repeat.timer) { @@ -2435,6 +3459,8 @@ static void keyboard_handle_leave(void *data, static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers( struct xkb_state *xkb_state_empty, struct xkb_state *xkb_state_empty_with_numlock, + struct xkb_state *xkb_state_empty_with_shift, + const bool xkb_use_non_latin_workaround, const xkb_keycode_t key) { /* Use an empty keyboard state to access key symbol without modifiers. */ @@ -2448,11 +3474,30 @@ static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers( /* Accounts for key-pad keys typically swapped for numbers when number-lock is enabled: * `Home Left Up Right Down Prior Page_Up Next Page_Dow End Begin Insert Delete`. */ - if (xkb_state_empty_with_numlock && (sym >= XKB_KEY_KP_Home && sym <= XKB_KEY_KP_Delete)) { - const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_numlock, key); - if (sym_test != XKB_KEY_NoSymbol) { - sym = sym_test; + if (sym >= XKB_KEY_KP_Home && sym <= XKB_KEY_KP_Delete) { + if (xkb_state_empty_with_numlock) { + const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_numlock, key); + if (sym_test != XKB_KEY_NoSymbol) { + sym = sym_test; + } + } + } + else { +#ifdef USE_NON_LATIN_KB_WORKAROUND + if (key >= (KEY_1 + EVDEV_OFFSET) && key <= (KEY_0 + EVDEV_OFFSET)) { + if (xkb_state_empty_with_shift && xkb_use_non_latin_workaround) { + const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_shift, key); + if (sym_test != XKB_KEY_NoSymbol) { + /* Should never happen as enabling `xkb_use_non_latin_workaround` checks this. */ + GHOST_ASSERT(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9, "Unexpected key"); + sym = sym_test; + } + } } +#else + (void)xkb_state_empty_with_shift; + (void)xkb_use_non_latin_workaround; +#endif } return sym; @@ -2495,7 +3540,15 @@ static void keyboard_handle_key(void *data, const xkb_keycode_t key_code = key + EVDEV_OFFSET; const xkb_keysym_t sym = xkb_state_key_get_one_sym_without_modifiers( - seat->xkb_state_empty, seat->xkb_state_empty_with_numlock, key_code); + seat->xkb_state_empty, + seat->xkb_state_empty_with_numlock, + seat->xkb_state_empty_with_shift, +#ifdef USE_NON_LATIN_KB_WORKAROUND + seat->xkb_use_non_latin_workaround, +#else + false, +#endif + key_code); if (sym == XKB_KEY_NoSymbol) { CLOG_INFO(LOG, 2, "key (code=%d, state=%u, no symbol, skipped)", int(key_code), state); return; @@ -2576,7 +3629,7 @@ static void keyboard_handle_key(void *data, keyboard_depressed_state_key_event(seat, gkey, etype); - if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface) { + if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface_window) { GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus); seat->system->pushEvent( new GHOST_EventKey(seat->system->getMilliSeconds(), etype, win, gkey, false, utf8_buf)); @@ -2587,11 +3640,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; } } @@ -2601,7 +3653,7 @@ static void keyboard_handle_key(void *data, task->getUserData()); GWL_Seat *seat = payload->seat; - if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface) { + if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface_window) { GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus); GHOST_SystemWayland *system = seat->system; /* Calculate this value every time in case modifier keys are pressed. */ @@ -2681,12 +3733,285 @@ 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; +} + +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); + + auto write_file_fn = [](GWL_PrimarySelection *primary, const int fd) { + if (UNLIKELY(write(fd, + primary->data_source->buffer_out.data, + primary->data_source->buffer_out.data_size) < 0)) { + CLOG_WARN(LOG, "error writing to primary clipboard: %s", std::strerror(errno)); + } + close(fd); + primary->data_source_mutex.unlock(); + }; + + primary->data_source_mutex.lock(); + std::thread write_thread(write_file_fn, primary, fd); + write_thread.detach(); +} + +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_cursor = 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, &cursor_surface_listener, seat); + ghost_wl_surface_tag_cursor_pointer(seat->cursor.wl_surface_cursor); + + 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_cursor) { + wl_surface_destroy(seat->cursor.wl_surface_cursor); + seat->cursor.wl_surface_cursor = 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) @@ -2699,27 +4024,27 @@ 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->wl_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); } } @@ -2929,11 +4254,17 @@ static void output_handle_done(void *data, struct wl_output * /*wl_output*/) static void output_handle_scale(void *data, struct wl_output * /*wl_output*/, const int32_t factor) { CLOG_INFO(LOG, 2, "scale"); - static_cast<GWL_Output *>(data)->scale = factor; + GWL_Output *output = static_cast<GWL_Output *>(data); + output->scale = factor; + GHOST_WindowManager *window_manager = output->system->getWindowManager(); if (window_manager) { for (GHOST_IWindow *iwin : window_manager->getWindows()) { GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin); + const std::vector<GWL_Output *> &outputs = win->outputs(); + if (std::find(outputs.begin(), outputs.end(), output) == outputs.cend()) { + continue; + } win->outputs_changed_update_scale(); } } @@ -3011,89 +4342,573 @@ static struct libdecor_interface libdecor_interface = { static CLG_LogRef LOG_WL_REGISTRY = {"ghost.wl.handle.registry"}; #define LOG (&LOG_WL_REGISTRY) -static void global_handle_add(void *data, - struct wl_registry *wl_registry, - const uint32_t name, - const char *interface, - const uint32_t version) +/* #GWL_Display.wl_compositor */ + +static void gwl_registry_compositor_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wl_compositor = static_cast<wl_compositor *>( + wl_registry_bind(display->wl_registry, params->name, &wl_compositor_interface, 3)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_compositor_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct wl_compositor **value_p = &display->wl_compositor; + wl_compositor_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.xdg_decor.shell */ + +static void gwl_registry_xdg_wm_base_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + GWL_XDG_Decor_System &decor = *display->xdg_decor; + decor.shell = static_cast<xdg_wm_base *>( + wl_registry_bind(display->wl_registry, params->name, &xdg_wm_base_interface, 1)); + xdg_wm_base_add_listener(decor.shell, &shell_listener, nullptr); + decor.shell_name = params->name; + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_xdg_wm_base_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + GWL_XDG_Decor_System &decor = *display->xdg_decor; + struct xdg_wm_base **value_p = &decor.shell; + uint32_t *name_p = &decor.shell_name; + xdg_wm_base_destroy(*value_p); + *value_p = nullptr; + *name_p = WL_NAME_UNSET; +} + +/* #GWL_Display.xdg_decor.manager */ + +static void gwl_registry_xdg_decoration_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + GWL_XDG_Decor_System &decor = *display->xdg_decor; + decor.manager = static_cast<zxdg_decoration_manager_v1 *>(wl_registry_bind( + display->wl_registry, params->name, &zxdg_decoration_manager_v1_interface, 1)); + decor.manager_name = params->name; + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_xdg_decoration_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + GWL_XDG_Decor_System &decor = *display->xdg_decor; + struct zxdg_decoration_manager_v1 **value_p = &decor.manager; + uint32_t *name_p = &decor.manager_name; + zxdg_decoration_manager_v1_destroy(*value_p); + *value_p = nullptr; + *name_p = WL_NAME_UNSET; +} + +/* #GWL_Display.xdg_output_manager */ + +static void gwl_registry_xdg_output_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->xdg_output_manager = static_cast<zxdg_output_manager_v1 *>( + wl_registry_bind(display->wl_registry, params->name, &zxdg_output_manager_v1_interface, 2)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_xdg_output_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zxdg_output_manager_v1 **value_p = &display->xdg_output_manager; + zxdg_output_manager_v1_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wl_output */ + +static void gwl_registry_wl_output_add(GWL_Display *display, const GWL_RegisteryAdd_Params *params) +{ + GWL_Output *output = new GWL_Output; + output->system = display->system; + output->wl_output = static_cast<wl_output *>( + wl_registry_bind(display->wl_registry, params->name, &wl_output_interface, 2)); + ghost_wl_output_tag(output->wl_output); + wl_output_set_user_data(output->wl_output, output); + + display->outputs.push_back(output); + wl_output_add_listener(output->wl_output, &output_listener, output); + gwl_registry_entry_add(display, params, static_cast<void *>(output)); +} +static void gwl_registry_wl_output_update(GWL_Display *display, + const GWL_RegisteryUpdate_Params *params) { - /* Log last since it can be noted if the interface was handled or not. */ - bool found = true; - - struct GWL_Display *display = static_cast<struct GWL_Display *>(data); - if (STREQ(interface, wl_compositor_interface.name)) { - display->wl_compositor = static_cast<wl_compositor *>( - wl_registry_bind(wl_registry, name, &wl_compositor_interface, 3)); - } - else if (STREQ(interface, xdg_wm_base_interface.name)) { - WGL_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(decor.shell, &shell_listener, nullptr); - } - else if (STREQ(interface, zxdg_decoration_manager_v1_interface.name)) { - WGL_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)); - } - 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)); - for (GWL_Output *output : display->outputs) { + GWL_Output *output = static_cast<GWL_Output *>(params->user_data); + if (display->xdg_output_manager) { + if (output->xdg_output == nullptr) { output->xdg_output = zxdg_output_manager_v1_get_xdg_output(display->xdg_output_manager, output->wl_output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } } - else if (STREQ(interface, wl_output_interface.name)) { - GWL_Output *output = new GWL_Output; - output->wl_output = static_cast<wl_output *>( - wl_registry_bind(wl_registry, name, &wl_output_interface, 2)); - ghost_wl_output_tag(output->wl_output); - wl_output_set_user_data(output->wl_output, output); + else { + output->xdg_output = nullptr; + } +} +static void gwl_registry_wl_output_remove(GWL_Display *display, + void *user_data, + const bool /*on_exit*/) +{ + /* While windows & cursors hold references to outputs, there is no need to manually remove + * these references as the compositor will remove references via #wl_surface_listener.leave. */ + GWL_Output *output = static_cast<GWL_Output *>(user_data); + wl_output_destroy(output->wl_output); + std::vector<GWL_Output *>::iterator iter = std::find( + display->outputs.begin(), display->outputs.end(), output); + const int index = (iter != display->outputs.cend()) ? + std::distance(display->outputs.begin(), iter) : + -1; + GHOST_ASSERT(index != -1, "invalid internal state"); + /* NOTE: always erase even when `on_exit` because `output->xdg_output` is cleared later. */ + display->outputs.erase(display->outputs.begin() + index); + delete output; +} + +/* #GWL_Display.seats */ + +static void gwl_registry_wl_seat_add(GWL_Display *display, const GWL_RegisteryAdd_Params *params) +{ + GWL_Seat *seat = new GWL_Seat; + seat->system = display->system; + seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + seat->data_source = new GWL_DataSource; + seat->wl_seat = static_cast<wl_seat *>( + wl_registry_bind(display->wl_registry, params->name, &wl_seat_interface, 5)); + display->seats.push_back(seat); + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); + gwl_registry_entry_add(display, params, static_cast<void *>(seat)); +} +static void gwl_registry_wl_seat_update(GWL_Display *display, + const GWL_RegisteryUpdate_Params *params) +{ + GWL_Seat *seat = static_cast<GWL_Seat *>(params->user_data); + + /* Register data device per seat for IPC between WAYLAND clients. */ + if (display->wl_data_device_manager) { + if (seat->wl_data_device == nullptr) { + 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); + } + } + else { + seat->wl_data_device = nullptr; + } - display->outputs.push_back(output); - wl_output_add_listener(output->wl_output, &output_listener, output); + if (display->wp_tablet_manager) { + if (seat->wp_tablet_seat == nullptr) { + 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); + } + } + else { + seat->wp_tablet_seat = nullptr; + } - if (display->xdg_output_manager) { - output->xdg_output = zxdg_output_manager_v1_get_xdg_output(display->xdg_output_manager, - output->wl_output); - zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); + if (display->wp_primary_selection_device_manager) { + if (seat->wp_primary_selection_device == nullptr) { + seat->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device( + display->wp_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); + } + } + else { + seat->wp_primary_selection_device = nullptr; + } +} +static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit) +{ + GWL_Seat *seat = static_cast<GWL_Seat *>(user_data); + + /* 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) { + 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; + } + } + + { + std::lock_guard lock{seat->data_offer_dnd_mutex}; + if (seat->data_offer_dnd) { + wl_data_offer_destroy(seat->data_offer_dnd->id); + delete seat->data_offer_dnd; + } + } + + { + std::lock_guard lock{seat->data_offer_copy_paste_mutex}; + if (seat->data_offer_copy_paste) { + wl_data_offer_destroy(seat->data_offer_copy_paste->id); + delete seat->data_offer_copy_paste; } } - else if (STREQ(interface, wl_seat_interface.name)) { - GWL_Seat *seat = new GWL_Seat; - seat->system = display->system; - seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - seat->data_source = new GWL_DataSource; - seat->wl_seat = static_cast<wl_seat *>( - wl_registry_bind(wl_registry, name, &wl_seat_interface, 5)); - display->seats.push_back(seat); - wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); + + { + GWL_PrimarySelection *primary = &seat->primary_selection; + std::lock_guard lock{primary->data_offer_mutex}; + gwl_primary_selection_discard_offer(primary); } - else if (STREQ(interface, wl_shm_interface.name)) { - display->wl_shm = static_cast<wl_shm *>( - wl_registry_bind(wl_registry, name, &wl_shm_interface, 1)); + + { + GWL_PrimarySelection *primary = &seat->primary_selection; + std::lock_guard lock{primary->data_source_mutex}; + gwl_primary_selection_discard_source(primary); } - else if (STREQ(interface, wl_data_device_manager_interface.name)) { - display->data_device_manager = static_cast<wl_data_device_manager *>( - wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3)); + + if (seat->wp_primary_selection_device) { + zwp_primary_selection_device_v1_destroy(seat->wp_primary_selection_device); } - else if (STREQ(interface, zwp_tablet_manager_v2_interface.name)) { - display->tablet_manager = static_cast<zwp_tablet_manager_v2 *>( - wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1)); + + if (seat->wl_data_device) { + wl_data_device_release(seat->wl_data_device); } - else if (STREQ(interface, zwp_relative_pointer_manager_v1_interface.name)) { - display->relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>( - wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1)); + + if (seat->cursor.custom_data) { + munmap(seat->cursor.custom_data, seat->cursor.custom_data_size); } - else if (STREQ(interface, zwp_pointer_constraints_v1_interface.name)) { - display->pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>( - wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1)); + + /* 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); + xkb_state_unref(seat->xkb_state_empty_with_shift); + xkb_state_unref(seat->xkb_state_empty_with_numlock); + + xkb_context_unref(seat->xkb_context); + + /* Remove the seat. */ + wl_seat_destroy(seat->wl_seat); + + std::vector<GWL_Seat *>::iterator iter = std::find( + display->seats.begin(), display->seats.end(), seat); + const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) : + -1; + GHOST_ASSERT(index != -1, "invalid internal state"); + + if (!on_exit) { + if (display->seats_active_index >= index) { + display->seats_active_index -= 1; + } + display->seats.erase(display->seats.begin() + index); } - else { - found = false; + delete seat; +} + +/* #GWL_Display.wl_shm */ +static void gwl_registry_wl_shm_add(GWL_Display *display, const GWL_RegisteryAdd_Params *params) +{ + display->wl_shm = static_cast<wl_shm *>( + wl_registry_bind(display->wl_registry, params->name, &wl_shm_interface, 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wl_shm_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct wl_shm **value_p = &display->wl_shm; + wl_shm_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wl_data_device_manager */ + +static void gwl_registry_wl_data_device_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wl_data_device_manager = static_cast<wl_data_device_manager *>( + wl_registry_bind(display->wl_registry, params->name, &wl_data_device_manager_interface, 3)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wl_data_device_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct wl_data_device_manager **value_p = &display->wl_data_device_manager; + wl_data_device_manager_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_tablet_manager */ + +static void gwl_registry_wp_tablet_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wp_tablet_manager = static_cast<zwp_tablet_manager_v2 *>( + wl_registry_bind(display->wl_registry, params->name, &zwp_tablet_manager_v2_interface, 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_tablet_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_tablet_manager_v2 **value_p = &display->wp_tablet_manager; + zwp_tablet_manager_v2_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_relative_pointer_manager */ + +static void gwl_registry_wp_relative_pointer_manager_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wp_relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>( + wl_registry_bind( + display->wl_registry, params->name, &zwp_relative_pointer_manager_v1_interface, 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_relative_pointer_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_relative_pointer_manager_v1 **value_p = &display->wp_relative_pointer_manager; + zwp_relative_pointer_manager_v1_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_pointer_constraints */ + +static void gwl_registry_wp_pointer_constraints_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wp_pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>(wl_registry_bind( + display->wl_registry, params->name, &zwp_pointer_constraints_v1_interface, 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_pointer_constraints_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_pointer_constraints_v1 **value_p = &display->wp_pointer_constraints; + zwp_pointer_constraints_v1_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_pointer_gestures */ + +static void gwl_registry_wp_pointer_gestures_add(GWL_Display *display, + const GWL_RegisteryAdd_Params *params) +{ + display->wp_pointer_gestures = static_cast<zwp_pointer_gestures_v1 *>( + wl_registry_bind(display->wl_registry, params->name, &zwp_pointer_gestures_v1_interface, 3)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_pointer_gestures_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_pointer_gestures_v1 **value_p = &display->wp_pointer_gestures; + zwp_pointer_gestures_v1_destroy(*value_p); + *value_p = nullptr; +} + +/* #GWL_Display.wp_primary_selection_device_manager */ + +static void gwl_registry_wp_primary_selection_device_manager_add( + struct GWL_Display *display, const GWL_RegisteryAdd_Params *params) +{ + display->wp_primary_selection_device_manager = + static_cast<zwp_primary_selection_device_manager_v1 *>( + wl_registry_bind(display->wl_registry, + params->name, + &zwp_primary_selection_device_manager_v1_interface, + 1)); + gwl_registry_entry_add(display, params, nullptr); +} +static void gwl_registry_wp_primary_selection_device_manager_remove(GWL_Display *display, + void * /*user_data*/, + const bool /*on_exit*/) +{ + struct zwp_primary_selection_device_manager_v1 **value_p = + &display->wp_primary_selection_device_manager; + zwp_primary_selection_device_manager_v1_destroy(*value_p); + *value_p = nullptr; +} + +/** + * Map interfaces to initialization functions. + * + * \note This list also defines the order interfaces are removed. + * On exit interface removal runs from last to first to avoid potential bugs + * caused by undefined order of removal. + * + * In general fundamental, low level objects such as the compositor and shared memory + * should be declared earlier and other interfaces that may use them should be declared later. + */ +static const GWL_RegistryHandler gwl_registry_handlers[] = { + /* Low level interfaces. */ + { + &wl_compositor_interface.name, + gwl_registry_compositor_add, + nullptr, + gwl_registry_compositor_remove, + }, + { + &wl_shm_interface.name, + gwl_registry_wl_shm_add, + nullptr, + gwl_registry_wl_shm_remove, + }, + { + &xdg_wm_base_interface.name, + gwl_registry_xdg_wm_base_add, + nullptr, + gwl_registry_xdg_wm_base_remove, + }, + /* Managers. */ + { + &zxdg_decoration_manager_v1_interface.name, + gwl_registry_xdg_decoration_manager_add, + nullptr, + gwl_registry_xdg_decoration_manager_remove, + }, + { + &zxdg_output_manager_v1_interface.name, + gwl_registry_xdg_output_manager_add, + nullptr, + gwl_registry_xdg_output_manager_remove, + }, + { + &wl_data_device_manager_interface.name, + gwl_registry_wl_data_device_manager_add, + nullptr, + gwl_registry_wl_data_device_manager_remove, + }, + { + &zwp_primary_selection_device_manager_v1_interface.name, + gwl_registry_wp_primary_selection_device_manager_add, + nullptr, + gwl_registry_wp_primary_selection_device_manager_remove, + }, + { + &zwp_tablet_manager_v2_interface.name, + gwl_registry_wp_tablet_manager_add, + nullptr, + gwl_registry_wp_tablet_manager_remove, + }, + { + &zwp_relative_pointer_manager_v1_interface.name, + gwl_registry_wp_relative_pointer_manager_add, + nullptr, + gwl_registry_wp_relative_pointer_manager_remove, + }, + /* Higher level interfaces. */ + { + &zwp_pointer_constraints_v1_interface.name, + gwl_registry_wp_pointer_constraints_add, + nullptr, + gwl_registry_wp_pointer_constraints_remove, + }, + { + &zwp_pointer_gestures_v1_interface.name, + gwl_registry_wp_pointer_gestures_add, + nullptr, + gwl_registry_wp_pointer_gestures_remove, + }, + /* Display outputs. */ + { + &wl_output_interface.name, + gwl_registry_wl_output_add, + gwl_registry_wl_output_update, + gwl_registry_wl_output_remove, + }, + /* Seats. + * Keep the seat near the end to ensure other types are created first. + * as the seat creates data based on other interfaces. */ + { + &wl_seat_interface.name, + gwl_registry_wl_seat_add, + gwl_registry_wl_seat_update, + gwl_registry_wl_seat_remove, + }, + {nullptr, nullptr, nullptr}, +}; + +/** + * Workaround for `gwl_registry_handlers` order of declaration, + * preventing `ARRAY_SIZE(gwl_registry_handlers) - 1` being used. + */ +static int gwl_registry_handler_interface_slot_max() +{ + return ARRAY_SIZE(gwl_registry_handlers) - 1; +} + +static int gwl_registry_handler_interface_slot_from_string(const char *interface) +{ + for (const GWL_RegistryHandler *handler = gwl_registry_handlers; handler->interface_p != nullptr; + handler++) { + if (STREQ(interface, *handler->interface_p)) { + return int(handler - gwl_registry_handlers); + } + } + return -1; +} + +static const GWL_RegistryHandler *gwl_registry_handler_from_interface_slot(int interface_slot) +{ + GHOST_ASSERT(uint32_t(interface_slot) < uint32_t(gwl_registry_handler_interface_slot_max()), + "Index out of range"); + return &gwl_registry_handlers[interface_slot]; +} + +static void global_handle_add(void *data, + [[maybe_unused]] struct wl_registry *wl_registry, + const uint32_t name, + const char *interface, + const uint32_t version) +{ + /* Log last since it's useful to know if the interface was handled or not. */ + GWL_Display *display = static_cast<GWL_Display *>(data); + GHOST_ASSERT(display->wl_registry == wl_registry, "Registry argument must match!"); + + const int interface_slot = gwl_registry_handler_interface_slot_from_string(interface); + bool added = false; + + if (interface_slot != -1) { + const GWL_RegistryHandler *handler = &gwl_registry_handlers[interface_slot]; + const GWL_RegistryEntry *registry_entry_prev = display->registry_entry; + + /* The interface name that is ensured not to be freed. */ + GWL_RegisteryAdd_Params params = { + .name = name, + .interface_slot = interface_slot, + .version = version, + }; + + handler->add_fn(display, ¶ms); + + added = display->registry_entry != registry_entry_prev; + } + else { + /* Not found. */ #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, @@ -3106,10 +4921,18 @@ static void global_handle_add(void *data, CLOG_INFO(LOG, 2, "add %s(interface=%s, version=%u, name=%u)", - found ? "" : "(skipped), ", + (interface_slot != -1) ? (added ? "" : "(found but not added)") : "(skipped), ", interface, version, name); + + /* Initialization avoids excessive calls by calling update after all have been initialized. */ + if (added) { + if (display->registry_skip_update_all == false) { + /* See doc-string for rationale on updating all on add/removal. */ + gwl_registry_entry_update_all(display, interface_slot); + } + } } /** @@ -3121,11 +4944,28 @@ static void global_handle_add(void *data, * name is no longer available. If the client bound to the global * using the bind request, the client should now destroy that object. */ -static void global_handle_remove(void * /*data*/, - struct wl_registry * /*wl_registry*/, +static void global_handle_remove(void *data, + [[maybe_unused]] struct wl_registry *wl_registry, const uint32_t name) { - CLOG_INFO(LOG, 2, "remove (name=%u)", name); + GWL_Display *display = static_cast<GWL_Display *>(data); + GHOST_ASSERT(display->wl_registry == wl_registry, "Registry argument must match!"); + + int interface_slot = 0; + const bool removed = gwl_registry_entry_remove_by_name(display, name, &interface_slot); + + CLOG_INFO(LOG, + 2, + "remove (name=%u, interface=%s)", + name, + removed ? *gwl_registry_handlers[interface_slot].interface_p : "(unknown)"); + + if (removed) { + if (display->registry_skip_update_all == false) { + /* See doc-string for rationale on updating all on add/removal. */ + gwl_registry_entry_update_all(display, interface_slot); + } + } } static const struct wl_registry_listener registry_listener = { @@ -3143,7 +4983,8 @@ static const struct wl_registry_listener registry_listener = { * WAYLAND specific implementation of the #GHOST_System interface. * \{ */ -GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Display) +GHOST_SystemWayland::GHOST_SystemWayland(bool background) + : GHOST_System(), display_(new GWL_Display) { wl_log_set_handler_client(ghost_wayland_log_handler); @@ -3151,25 +4992,41 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di /* Connect to the Wayland server. */ display_->wl_display = wl_display_connect(nullptr); if (!display_->wl_display) { - display_destroy(display_); + gwl_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 WGL_XDG_Decor_System; + display_->xdg_decor = new GWL_XDG_Decor_System; /* Register interfaces. */ - 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(display_->wl_display); - /* Call callbacks for registered listeners. */ - wl_display_roundtrip(display_->wl_display); - wl_registry_destroy(registry); + { + display_->registry_skip_update_all = true; + struct wl_registry *registry = wl_display_get_registry(display_->wl_display); + display_->wl_registry = registry; + wl_registry_add_listener(registry, ®istry_listener, display_); + /* First round-trip to receive all registry objects. */ + wl_display_roundtrip(display_->wl_display); + /* Second round-trip to receive all output events. */ + wl_display_roundtrip(display_->wl_display); + + /* Account for dependencies between interfaces. */ + gwl_registry_entry_update_all(display_, -1); + + display_->registry_skip_update_all = false; + } #ifdef WITH_GHOST_WAYLAND_LIBDECOR + /* 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; + } + if (display_->libdecor_required) { - wgl_xdg_decor_system_destroy(display_->xdg_decor); + gwl_xdg_decor_system_destroy(display_, display_->xdg_decor); display_->xdg_decor = nullptr; if (!has_libdecor) { @@ -3179,8 +5036,10 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di "WAYLAND found but libdecor was not, install libdecor for Wayland support, " "falling back to X11\n"); # endif - display_destroy(display_); + gwl_display_destroy(display_); throw std::runtime_error("Wayland: unable to find libdecor!"); + + use_libdecor = true; } } else { @@ -3190,45 +5049,28 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), display_(new GWL_Di #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { - display_->libdecor = new WGL_LibDecor_System; - WGL_LibDecor_System &decor = *display_->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_); + gwl_display_destroy(display_); throw std::runtime_error("Wayland: unable to create window decorations!"); } } else #endif { - WGL_XDG_Decor_System &decor = *display_->xdg_decor; + GWL_XDG_Decor_System &decor = *display_->xdg_decor; if (!decor.shell) { - display_destroy(display_); + gwl_display_destroy(display_); throw std::runtime_error("Wayland: unable to access xdg_shell!"); } } - - /* Register data device per seat for IPC between Wayland clients. */ - if (display_->data_device_manager) { - for (GWL_Seat *seat : display_->seats) { - seat->data_device = wl_data_device_manager_get_data_device(display_->data_device_manager, - seat->wl_seat); - wl_data_device_add_listener(seat->data_device, &data_device_listener, seat); - } - } - - if (display_->tablet_manager) { - for (GWL_Seat *seat : display_->seats) { - seat->tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(display_->tablet_manager, - seat->wl_seat); - zwp_tablet_seat_v2_add_listener(seat->tablet_seat, &tablet_seat_listener, seat); - } - } } GHOST_SystemWayland::~GHOST_SystemWayland() { - display_destroy(display_); + gwl_display_destroy(display_); } GHOST_TSuccess GHOST_SystemWayland::init() @@ -3283,12 +5125,11 @@ bool GHOST_SystemWayland::setConsoleWindowState(GHOST_TConsoleWindowState /*acti GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) const { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; - const xkb_mod_mask_t state = xkb_state_serialize_mods(seat->xkb_state, XKB_STATE_MODS_DEPRESSED); bool show_warning = true; @@ -3300,7 +5141,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)) { @@ -3345,11 +5186,11 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_active(seat); + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat); if (!seat_state_pointer) { return GHOST_kFailure; } @@ -3358,43 +5199,208 @@ GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const return GHOST_kSuccess; } -char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const +/** + * Return a mime type which is supported by GHOST and exists in `types` + * (defined by the data offer). + */ +static const char *system_clipboard_text_mime_type( + const std::unordered_set<std::string> &data_offer_types) +{ + const char *ghost_supported_types[] = {ghost_wl_mime_text_utf8, ghost_wl_mime_text_plain}; + for (size_t i = 0; i < ARRAY_SIZE(ghost_supported_types); i++) { + if (data_offer_types.count(ghost_supported_types[i])) { + return ghost_supported_types[i]; + } + } + return nullptr; +} + +static char *system_clipboard_get_primary_selection(GWL_Display *display) +{ + GWL_Seat *seat = gwl_display_seat_active_get(display); + if (UNLIKELY(!seat)) { + return nullptr; + } + GWL_PrimarySelection *primary = &seat->primary_selection; + std::mutex &mutex = primary->data_offer_mutex; + + mutex.lock(); + bool mutex_locked = true; + char *data = nullptr; + + GWL_PrimarySelection_DataOffer *data_offer = primary->data_offer; + if (data_offer != nullptr) { + const char *mime_receive = system_clipboard_text_mime_type(data_offer->types); + if (mime_receive) { + /* Receive the clipboard in a thread, performing round-trips while waiting. + * This is needed so pasting contents from our own `primary->data_source` doesn't hang. */ + struct ThreadResult { + char *data = nullptr; + std::atomic<bool> done = false; + } thread_result; + auto read_clipboard_fn = [](GWL_PrimarySelection_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + struct ThreadResult *thread_result) { + size_t data_len = 0; + thread_result->data = read_buffer_from_primary_selection_offer( + data_offer, mime_receive, mutex, true, &data_len); + thread_result->done = true; + }; + std::thread read_thread(read_clipboard_fn, data_offer, mime_receive, &mutex, &thread_result); + read_thread.detach(); + + while (!thread_result.done) { + wl_display_roundtrip(display->wl_display); + } + data = thread_result.data; + + /* Reading the data offer unlocks the mutex. */ + mutex_locked = false; + } + } + if (mutex_locked) { + mutex.unlock(); + } + return data; +} + +static char *system_clipboard_get(GWL_Display *display) +{ + GWL_Seat *seat = gwl_display_seat_active_get(display); + if (UNLIKELY(!seat)) { + return nullptr; + } + std::mutex &mutex = seat->data_offer_copy_paste_mutex; + + mutex.lock(); + bool mutex_locked = true; + char *data = nullptr; + + GWL_DataOffer *data_offer = seat->data_offer_copy_paste; + if (data_offer != nullptr) { + const char *mime_receive = system_clipboard_text_mime_type(data_offer->types); + if (mime_receive) { + /* Receive the clipboard in a thread, performing round-trips while waiting. + * This is needed so pasting contents from our own `seat->data_source` doesn't hang. */ + struct ThreadResult { + char *data = nullptr; + std::atomic<bool> done = false; + } thread_result; + auto read_clipboard_fn = [](GWL_DataOffer *data_offer, + const char *mime_receive, + std::mutex *mutex, + struct ThreadResult *thread_result) { + size_t data_len = 0; + thread_result->data = read_buffer_from_data_offer( + data_offer, mime_receive, mutex, true, &data_len); + thread_result->done = true; + }; + std::thread read_thread(read_clipboard_fn, data_offer, mime_receive, &mutex, &thread_result); + read_thread.detach(); + + while (!thread_result.done) { + wl_display_roundtrip(display->wl_display); + } + data = thread_result.data; + + /* Reading the data offer unlocks the mutex. */ + mutex_locked = false; + } + } + if (mutex_locked) { + mutex.unlock(); + } + return data; +} + +char *GHOST_SystemWayland::getClipboard(bool selection) const { - char *clipboard = static_cast<char *>(malloc(clipboard_.size() + 1)); - memcpy(clipboard, clipboard_.data(), clipboard_.size() + 1); - return clipboard; + char *data = nullptr; + if (selection) { + data = system_clipboard_get_primary_selection(display_); + } + else { + data = system_clipboard_get(display_); + } + return data; } -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(!display_->data_device_manager || display_->seats.empty())) { + if (!display->wp_primary_selection_device_manager) { + return; + } + GWL_Seat *seat = gwl_display_seat_active_get(display); + if (UNLIKELY(!seat)) { return; } + 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 (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) { + zwp_primary_selection_source_v1_offer(data_source->wp_source, ghost_wl_mime_send[i]); + } - GWL_Seat *seat = display_->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) +{ + if (!display->wl_data_device_manager) { + return; + } + GWL_Seat *seat = gwl_display_seat_active_get(display); + if (UNLIKELY(!seat)) { + return; + } 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( - display_->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()); + for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) { + wl_data_source_offer(data_source->wl_source, ghost_wl_mime_send[i]); } - 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 (selection) { + system_clipboard_put_primary_selection(display_, buffer); + } + else { + system_clipboard_put(display_, buffer); } } @@ -3423,7 +5429,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(); @@ -3442,12 +5448,12 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPositionClientRelative(const GHOST_ int32_t &x, int32_t &y) const { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - 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) { + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat); + if (!seat_state_pointer || !seat_state_pointer->wl_surface_window) { return GHOST_kFailure; } const GHOST_WindowWayland *win = static_cast<const GHOST_WindowWayland *>(window); @@ -3458,26 +5464,26 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindo const int32_t x, const int32_t y) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - 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(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; - GWL_SeatStatePointer *seat_state_pointer = seat_state_pointer_active(seat); + GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat); if (!seat_state_pointer) { return GHOST_kFailure; } - if (wl_surface *wl_surface_focus = seat_state_pointer->wl_surface) { + if (wl_surface *wl_surface_focus = seat_state_pointer->wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); return getCursorPositionClientRelative_impl(seat_state_pointer, win, x, y); } @@ -3486,14 +5492,14 @@ 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(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - 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. */ - if (wl_surface *wl_surface_focus = seat->pointer.wl_surface) { + if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); return setCursorPositionClientRelative_impl(seat, win, x, y); } @@ -3616,17 +5622,12 @@ 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, const GHOST_IWindow *parentWindow) { /* Globally store pointer to window manager. */ - if (!window_manager) { - window_manager = getWindowManager(); - } - GHOST_WindowWayland *window = new GHOST_WindowWayland( this, title, @@ -3636,7 +5637,7 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, height, state, parentWindow, - type, + glSettings.context_type, is_dialog, ((glSettings.flags & GHOST_glStereoVisual) != 0), exclusive); @@ -3664,22 +5665,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_cursor, 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)); @@ -3688,6 +5689,9 @@ static void cursor_buffer_show(const GWL_Seat *seat) tablet_tool->wl_surface_cursor, hotspot_x, hotspot_y); +#ifdef USE_KDE_TABLET_HIDDEN_CURSOR_HACK + wl_surface_commit(tablet_tool->wl_surface_cursor); +#endif } } } @@ -3742,21 +5746,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_cursor, scale); wl_pointer_set_cursor(seat->wl_pointer, seat->pointer.serial, - visible ? c->wl_surface : nullptr, + visible ? cursor->wl_surface_cursor : nullptr, hotspot_x, hotspot_y); } @@ -3764,7 +5768,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) { @@ -3842,40 +5846,40 @@ static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor shape) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - auto cursor_find = cursors.find(shape); - const char *cursor_name = (cursor_find == cursors.end()) ? - cursors.at(GHOST_kStandardCursorDefault) : + auto cursor_find = ghost_wl_cursors.find(shape); + const char *cursor_name = (cursor_find == ghost_wl_cursors.end()) ? + ghost_wl_cursors.at(GHOST_kStandardCursorDefault) : (*cursor_find).second; - GWL_Seat *seat = display_->seats[0]; - GWL_Cursor *c = &seat->cursor; + 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, display_->seats[0]->system->wl_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); @@ -3884,8 +5888,8 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor s GHOST_TSuccess GHOST_SystemWayland::hasCursorShape(const GHOST_TStandardCursor cursorShape) { - auto cursor_find = cursors.find(cursorShape); - if (cursor_find == cursors.end()) { + auto cursor_find = ghost_wl_cursors.find(cursorShape); + if (cursor_find == ghost_wl_cursors.end()) { return GHOST_kFailure; } const char *value = (*cursor_find).second; @@ -3903,12 +5907,12 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, const int hotY, const bool /*canInvertColor*/) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Cursor *cursor = &display_->seats[0]->cursor; - + GWL_Cursor *cursor = &seat->cursor; if (cursor->custom_data) { munmap(cursor->custom_data, cursor->custom_data_size); cursor->custom_data = nullptr; @@ -3966,14 +5970,19 @@ 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(display_->seats[0], buffer); + cursor_buffer_set(seat, buffer); return GHOST_kSuccess; } GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) { - GWL_Cursor *cursor = &display_->seats[0]->cursor; + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { + return GHOST_kFailure; + } + + GWL_Cursor *cursor = &seat->cursor; if (cursor->custom_data == nullptr) { return GHOST_kFailure; } @@ -3994,11 +6003,11 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(const bool visible) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } - GWL_Seat *seat = display_->seats[0]; cursor_visible_set(seat, visible, seat->cursor.is_hardware, CURSOR_VISIBLE_ALWAYS_SET); return GHOST_kSuccess; } @@ -4018,12 +6027,12 @@ bool GHOST_SystemWayland::supportsWindowPosition() bool GHOST_SystemWayland::getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode) { - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return false; } #ifdef USE_GNOME_CONFINE_HACK - GWL_Seat *seat = display_->seats[0]; const bool use_software_confine = seat->use_pointer_software_confine; #else const bool use_software_confine = false; @@ -4062,11 +6071,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; } @@ -4140,6 +6148,16 @@ wl_compositor *GHOST_SystemWayland::wl_compositor() return display_->wl_compositor; } +struct zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wp_primary_selection_manager() +{ + return display_->wp_primary_selection_device_manager; +} + +struct zwp_pointer_gestures_v1 *GHOST_SystemWayland::wp_pointer_gestures() +{ + return display_->wp_pointer_gestures; +} + #ifdef WITH_GHOST_WAYLAND_LIBDECOR libdecor *GHOST_SystemWayland::libdecor_context() @@ -4202,9 +6220,9 @@ GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *wl_surface) * Functionality only used for the WAYLAND implementation. * \{ */ -void GHOST_SystemWayland::clipboard_set(const std::string &clipboard) +void GHOST_SystemWayland::seat_active_set(const struct GWL_Seat *seat) { - clipboard_ = clipboard; + gwl_display_seat_active_set(display_, seat); } void GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface) @@ -4217,10 +6235,10 @@ void GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface) /* Only clear window surfaces (not cursors, off-screen surfaces etc). */ 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); - SURFACE_CLEAR_PTR(seat->wl_surface_focus_dnd); + SURFACE_CLEAR_PTR(seat->pointer.wl_surface_window); + SURFACE_CLEAR_PTR(seat->tablet.wl_surface_window); + SURFACE_CLEAR_PTR(seat->keyboard.wl_surface_window); + SURFACE_CLEAR_PTR(seat->wl_surface_window_focus_dnd); } #undef SURFACE_CLEAR_PTR } @@ -4234,11 +6252,12 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod const int scale) { /* Ignore, if the required protocols are not supported. */ - if (UNLIKELY(!display_->relative_pointer_manager || !display_->pointer_constraints)) { + if (UNLIKELY(!display_->wp_relative_pointer_manager || !display_->wp_pointer_constraints)) { return GHOST_kFailure; } - if (UNLIKELY(display_->seats.empty())) { + GWL_Seat *seat = gwl_display_seat_active_get(display_); + if (UNLIKELY(!seat)) { return GHOST_kFailure; } /* No change, success. */ @@ -4246,8 +6265,6 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod return GHOST_kSuccess; } - GWL_Seat *seat = display_->seats[0]; - #ifdef USE_GNOME_CONFINE_HACK const bool was_software_confine = seat->use_pointer_software_confine; const bool use_software_confine = setCursorGrab_use_software_confine(mode, wl_surface); @@ -4273,11 +6290,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; @@ -4306,7 +6323,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) { @@ -4316,7 +6333,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, @@ -4330,7 +6348,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); } @@ -4346,15 +6364,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; } } @@ -4365,12 +6383,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( - display_->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( - display_->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, @@ -4387,8 +6405,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( - display_->pointer_constraints, + seat->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer( + display_->wp_pointer_constraints, wl_surface, seat->wl_pointer, nullptr, diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index 5d2cfe45582..a8e8d8ddc45 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -23,6 +23,7 @@ # include <libdecor.h> #endif +#include <mutex> #include <string> class GHOST_WindowWayland; @@ -51,6 +52,8 @@ void ghost_wl_dynload_libraries_exit(); #endif struct GWL_Output { + GHOST_SystemWayland *system = nullptr; + struct wl_output *wl_output = nullptr; struct zxdg_output_v1 *xdg_output = nullptr; /** Dimensions in pixels. */ @@ -84,11 +87,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; @@ -128,7 +132,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, @@ -150,8 +153,8 @@ 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); @@ -159,6 +162,8 @@ class GHOST_SystemWayland : public GHOST_System { 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 *libdecor_context(); @@ -173,7 +178,8 @@ class GHOST_SystemWayland : public GHOST_System { /* WAYLAND utility functions. */ - void clipboard_set(const std::string &clipboard); + /** Set this seat to be active. */ + void seat_active_set(const struct GWL_Seat *seat); /** Clear all references to this surface to prevent accessing NULL pointers. */ void window_surface_unref(const wl_surface *wl_surface); @@ -192,5 +198,4 @@ class GHOST_SystemWayland : public GHOST_System { private: struct GWL_Display *display_; - std::string clipboard_; }; 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 4523f6fa37c..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, 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_Window.h b/intern/ghost/intern/GHOST_Window.h index 1c0991bba30..396691fa161 100644 --- a/intern/ghost/intern/GHOST_Window.h +++ b/intern/ghost/intern/GHOST_Window.h @@ -74,7 +74,7 @@ class GHOST_Window : public GHOST_IWindow { */ virtual bool getValid() const override { - return m_context != NULL; + return m_context != nullptr; } /** @@ -283,8 +283,9 @@ class GHOST_Window : public GHOST_IWindow { float getNativePixelSize(void) override { - if (m_nativePixelSize > 0.0f) + if (m_nativePixelSize > 0.0f) { return m_nativePixelSize; + } return 1.0f; } @@ -298,7 +299,8 @@ class GHOST_Window : public GHOST_IWindow { } #ifdef WITH_INPUT_IME - virtual void beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed) override + virtual void beginIME( + int32_t /*x*/, int32_t /*y*/, int32_t /*w*/, int32_t /*h*/, bool /*completed*/) override { /* do nothing temporarily if not in windows */ } 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 b29c5efd8d4..ad94a02b514 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -53,7 +53,7 @@ struct WGL_LibDecor_Window { bool configured = false; }; -static void wgl_libdecor_window_destroy(WGL_LibDecor_Window *decor) +static void gwl_libdecor_window_destroy(WGL_LibDecor_Window *decor) { libdecor_frame_unref(decor->frame); delete decor; @@ -67,7 +67,7 @@ struct WGL_XDG_Decor_Window { 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) +static void gwl_xdg_decor_window_destroy(WGL_XDG_Decor_Window *decor) { if (decor->toplevel_decor) { zxdg_toplevel_decoration_v1_destroy(decor->toplevel_decor); @@ -91,12 +91,10 @@ struct GWL_Window { /** The scale value written to #wl_surface_set_buffer_scale. */ int scale = 0; /** - * The DPI, either: - * - `scale * base_dpi` - * - `wl_fixed_to_int(scale_fractional * base_dpi)` - * When fractional scaling is available. + * The fractional scale used to calculate the DPI. + * (always set, even when scaling is rounded to whole units). */ - uint32_t dpi = 0; + wl_fixed_t scale_fractional = 0; #ifdef WITH_GHOST_WAYLAND_LIBDECOR WGL_LibDecor_Window *libdecor = nullptr; @@ -147,7 +145,7 @@ static int output_scale_cmp(const GWL_Output *output_a, const GWL_Output *output static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs, const int32_t scale_default, - uint32_t *r_dpi) + wl_fixed_t *r_scale_fractional) { const GWL_Output *output_max = nullptr; for (const GWL_Output *reg_output : outputs) { @@ -157,18 +155,16 @@ static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs } if (output_max) { - if (r_dpi) { - *r_dpi = output_max->has_scale_fractional ? - /* Fractional DPI. */ - wl_fixed_to_int(output_max->scale_fractional * base_dpi) : - /* Simple non-fractional DPI. */ - (output_max->scale * base_dpi); + if (r_scale_fractional) { + *r_scale_fractional = output_max->has_scale_fractional ? + output_max->scale_fractional : + wl_fixed_from_int(output_max->scale); } return output_max->scale; } - if (r_dpi) { - *r_dpi = scale_default * base_dpi; + if (r_scale_fractional) { + *r_scale_fractional = wl_fixed_from_int(scale_default); } return scale_default; } @@ -479,7 +475,7 @@ 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. */ - window_->scale = outputs_max_scale_or_default(system_->outputs(), 1, &window_->dpi); + window_->scale = outputs_max_scale_or_default(system_->outputs(), 1, &window_->scale_fractional); /* Window surfaces. */ window_->wl_surface = wl_compositor_create_surface(system_->wl_compositor()); @@ -727,12 +723,12 @@ GHOST_WindowWayland::~GHOST_WindowWayland() #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { - wgl_libdecor_window_destroy(window_->libdecor); + gwl_libdecor_window_destroy(window_->libdecor); } else #endif { - wgl_xdg_decor_window_destroy(window_->xdg_decor); + gwl_xdg_decor_window_destroy(window_->xdg_decor); } /* Clear any pointers to this window. This is needed because there are no guarantees @@ -751,7 +747,9 @@ GHOST_WindowWayland::~GHOST_WindowWayland() uint16_t GHOST_WindowWayland::getDPIHint() { - return window_->dpi; + /* Using the physical DPI will cause wrong scaling of the UI + * use a multiplier for the default DPI as a workaround. */ + return wl_fixed_to_int(window_->scale_fractional * base_dpi); } GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible) @@ -946,7 +944,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; } /** \} */ @@ -957,14 +960,14 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType * Expose some members via methods. * \{ */ -uint16_t GHOST_WindowWayland::dpi() const +int GHOST_WindowWayland::scale() const { - return window_->dpi; + return window_->scale; } -int GHOST_WindowWayland::scale() const +wl_fixed_t GHOST_WindowWayland::scale_fractional() const { - return window_->scale; + return window_->scale_fractional; } wl_surface *GHOST_WindowWayland::wl_surface() const @@ -1030,30 +1033,39 @@ 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(outputs(), 0, &dpi_next); + wl_fixed_t scale_fractional_next = 0; + const int scale_next = outputs_max_scale_or_default(outputs(), 0, &scale_fractional_next); if (UNLIKELY(scale_next == 0)) { return false; } - const uint32_t dpi_curr = window_->dpi; + const wl_fixed_t scale_fractional_curr = window_->scale_fractional; 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. */ - window_->size_pending[0] = (window_->size_pending[0] / scale_curr) * scale_next; - window_->size_pending[1] = (window_->size_pending[1] / scale_curr) * scale_next; - window_->scale = scale_next; wl_surface_set_buffer_scale(window_->wl_surface, scale_next); + + /* It's important to resize the window immediately, to avoid the window changing size + * and flickering in a constant feedback loop (in some bases). */ + if ((window_->size_pending[0] != 0) && (window_->size_pending[1] != 0)) { + /* Unlikely but possible there is a pending size change is set. */ + window_->size[0] = window_->size_pending[0]; + window_->size[1] = window_->size_pending[1]; + window_->size_pending[0] = 0; + window_->size_pending[1] = 0; + } + window_->size[0] = (window_->size[0] / scale_curr) * scale_next; + window_->size[1] = (window_->size[1] / scale_curr) * scale_next; + wl_egl_window_resize(window_->egl_window, UNPACK2(window_->size), 0, 0); + window_->ghost_window->notify_size(); + 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. */ - window_->dpi = dpi_next; + if (scale_fractional_next != scale_fractional_curr) { + window_->scale_fractional = scale_fractional_next; changed = true; /* As this is a low-level function, we might want adding this event to be optional, diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h index e95f5386310..ec473c4a710 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.h +++ b/intern/ghost/intern/GHOST_WindowWayland.h @@ -12,6 +12,8 @@ #include <vector> +#include <wayland-util.h> /* For #wl_fixed_t */ + class GHOST_SystemWayland; struct GWL_Output; @@ -95,8 +97,8 @@ class GHOST_WindowWayland : public GHOST_Window { /* WAYLAND direct-data access. */ - uint16_t dpi() const; int scale() const; + wl_fixed_t scale_fractional() const; struct wl_surface *wl_surface() const; const std::vector<GWL_Output *> &outputs(); |