diff options
Diffstat (limited to 'intern/ghost')
28 files changed, 1978 insertions, 429 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 1739659ab88..1b5cdb3cda0 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC intern/GHOST_SystemPaths.h intern/GHOST_TimerManager.h intern/GHOST_TimerTask.h + intern/GHOST_Util.h intern/GHOST_Window.h intern/GHOST_WindowManager.h ) @@ -281,6 +282,7 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) ${wayland-egl_INCLUDE_DIRS} ${xkbcommon_INCLUDE_DIRS} ${wayland-cursor_INCLUDE_DIRS} + ${dbus_INCLUDE_DIRS} ) list(APPEND SRC @@ -320,6 +322,11 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) xdg-shell "${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml" ) + # xdg-decoration. + generate_protocol_bindings( + xdg-decoration + "${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" + ) # Pointer-constraints. generate_protocol_bindings( pointer-constraints @@ -438,6 +445,7 @@ endif() if(WITH_XR_OPENXR) list(APPEND SRC intern/GHOST_Xr.cpp + intern/GHOST_XrAction.cpp intern/GHOST_XrContext.cpp intern/GHOST_XrEvent.cpp intern/GHOST_XrGraphicsBinding.cpp @@ -446,6 +454,7 @@ if(WITH_XR_OPENXR) GHOST_IXrContext.h intern/GHOST_IXrGraphicsBinding.h + intern/GHOST_XrAction.h intern/GHOST_XrContext.h intern/GHOST_XrException.h intern/GHOST_XrSession.h diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index d79111b742f..2bd9af6df5c 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -276,7 +276,7 @@ extern int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle); * wait (block) until the next event before returning. * \return Indication of the presence of events. */ -extern int GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, int waitForEvent); +extern bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent); /** * Retrieves events from the queue and send them to the event consumers. @@ -1059,7 +1059,110 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context * \returns GHOST_kSuccess if any event was handled, otherwise GHOST_kFailure. */ GHOST_TSuccess GHOST_XrEventsHandle(GHOST_XrContextHandle xr_context); -#endif + +/* actions */ +/** + * Create an OpenXR action set for input/output. + */ +int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_context, const GHOST_XrActionSetInfo *info); + +/** + * Destroy a previously created OpenXR action set. + */ +void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_context, const char *action_set_name); + +/** + * Create OpenXR input/output actions. + */ +int GHOST_XrCreateActions(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionInfo *infos); + +/** + * Destroy previously created OpenXR actions. + */ +void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const char *const *action_names); + +/** + * Create spaces for pose-based OpenXR actions. + */ +int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos); + +/** + * Destroy previously created spaces for OpenXR actions. + */ +void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos); + +/** + * Create input/output path bindings for OpenXR actions. + */ +int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos); + +/** + * Destroy previously created bindings for OpenXR actions. + */ +void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_context, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos); + +/** + * Attach all created action sets to the current OpenXR session. + */ +int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_context); + +/** + * Update button/tracking states for OpenXR actions. + * + * \param action_set_name: The name of the action set to sync. If NULL, all action sets + * attached to the session will be synced. + */ +int GHOST_XrSyncActions(GHOST_XrContextHandle xr_context, const char *action_set_name); + +/** + * Apply an OpenXR haptic output action. + */ +int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name, + const GHOST_TInt64 *duration, + const float *frequency, + const float *amplitude); + +/** + * Stop a previously applied OpenXR haptic output action. + */ +void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name); + +/** + * Get action set custom data (owned by Blender, not GHOST). + */ +void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_context, + const char *action_set_name); + +/** + * Get action custom data (owned by Blender, not GHOST). + */ +void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_context, + const char *action_set_name, + const char *action_name); + +#endif /* WITH_XR_OPENXR */ #ifdef __cplusplus } diff --git a/intern/ghost/GHOST_IXrContext.h b/intern/ghost/GHOST_IXrContext.h index dd266a3b6ae..86fe78814a7 100644 --- a/intern/ghost/GHOST_IXrContext.h +++ b/intern/ghost/GHOST_IXrContext.h @@ -22,6 +22,8 @@ #include "GHOST_Types.h" +class GHOST_XrSession; + class GHOST_IXrContext { public: virtual ~GHOST_IXrContext() = default; @@ -31,6 +33,10 @@ class GHOST_IXrContext { virtual bool isSessionRunning() const = 0; virtual void drawSessionViews(void *draw_customdata) = 0; + /* Needed for the GHOST C api. */ + virtual GHOST_XrSession *getSession() = 0; + virtual const GHOST_XrSession *getSession() const = 0; + virtual void dispatchErrorMessage(const class GHOST_XrException *) const = 0; virtual void setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn, diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index bd7ba6657f0..3a8d0fbfecf 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -416,7 +416,10 @@ typedef enum { GHOST_kGrabNormal, /** Wrap the mouse location to prevent limiting screen bounds. */ GHOST_kGrabWrap, - /** Hide the mouse while grabbing and restore the original location on release (numbuts). */ + /** + * Hide the mouse while grabbing and restore the original location on release + * (used for number buttons and some other draggable UI elements). + */ GHOST_kGrabHide, } GHOST_TGrabCursorMode; @@ -631,7 +634,9 @@ typedef enum GHOST_TXrGraphicsBinding { typedef void (*GHOST_XrErrorHandlerFn)(const struct GHOST_XrError *); +typedef void (*GHOST_XrSessionCreateFn)(void); typedef void (*GHOST_XrSessionExitFn)(void *customdata); +typedef void (*GHOST_XrCustomdataFreeFn)(void *customdata); typedef void *(*GHOST_XrGraphicsContextBindFn)(void); typedef void (*GHOST_XrGraphicsContextUnbindFn)(GHOST_ContextHandle graphics_context); @@ -662,6 +667,7 @@ typedef struct { typedef struct { GHOST_XrPose base_pose; + GHOST_XrSessionCreateFn create_fn; GHOST_XrSessionExitFn exit_fn; void *exit_customdata; } GHOST_XrSessionBeginInfo; @@ -688,4 +694,54 @@ typedef struct GHOST_XrError { void *customdata; } GHOST_XrError; -#endif +typedef struct GHOST_XrActionSetInfo { + const char *name; + + GHOST_XrCustomdataFreeFn customdata_free_fn; + void *customdata; /* wmXrActionSet */ +} GHOST_XrActionSetInfo; + +/** XR action type. Enum values match those in OpenXR's + * XrActionType enum for consistency. */ +typedef enum GHOST_XrActionType { + GHOST_kXrActionTypeBooleanInput = 1, + GHOST_kXrActionTypeFloatInput = 2, + GHOST_kXrActionTypeVector2fInput = 3, + GHOST_kXrActionTypePoseInput = 4, + GHOST_kXrActionTypeVibrationOutput = 100, +} GHOST_XrActionType; + +typedef struct GHOST_XrActionInfo { + const char *name; + GHOST_XrActionType type; + GHOST_TUns32 count_subaction_paths; + const char **subaction_paths; + /** States for each subaction path. */ + void *states; + + GHOST_XrCustomdataFreeFn customdata_free_fn; + void *customdata; /* wmXrAction */ +} GHOST_XrActionInfo; + +typedef struct GHOST_XrActionSpaceInfo { + const char *action_name; + GHOST_TUns32 count_subaction_paths; + const char **subaction_paths; + /** Poses for each subaction path. */ + const GHOST_XrPose *poses; +} GHOST_XrActionSpaceInfo; + +typedef struct GHOST_XrActionBindingInfo { + const char *action_name; + GHOST_TUns32 count_interaction_paths; + /** Interaction path: User (subaction) path + component path. */ + const char **interaction_paths; +} GHOST_XrActionBindingInfo; + +typedef struct GHOST_XrActionProfileInfo { + const char *profile_path; + GHOST_TUns32 count_bindings; + const GHOST_XrActionBindingInfo *bindings; +} GHOST_XrActionProfileInfo; + +#endif /* WITH_XR_OPENXR */ diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index af65e1cd4d2..955f35274ea 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -33,6 +33,7 @@ #include "intern/GHOST_Debug.h" #ifdef WITH_XR_OPENXR # include "GHOST_IXrContext.h" +# include "intern/GHOST_XrSession.h" #endif #include "intern/GHOST_CallbackEventConsumer.h" #include "intern/GHOST_XrException.h" @@ -248,11 +249,11 @@ int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle) return (int)system->getFullScreen(); } -int GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, int waitForEvent) +bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent) { GHOST_ISystem *system = (GHOST_ISystem *)systemhandle; - return (int)system->processEvents(waitForEvent ? true : false); + return system->processEvents(waitForEvent); } void GHOST_DispatchEvents(GHOST_SystemHandle systemhandle) @@ -953,4 +954,145 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context return 0; /* Only reached if exception is thrown. */ } -#endif +int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_contexthandle, + const GHOST_XrActionSetInfo *info) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionSet(*info), xr_context); + return 0; +} + +void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionSet(action_set_name), xr_context); +} + +int GHOST_XrCreateActions(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActions(action_set_name, count, infos), xr_context); + return 0; +} + +void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const char *const *action_names) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActions(action_set_name, count, action_names), xr_context); +} + +int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionSpaces(action_set_name, count, infos), + xr_context); + return 0; +} + +void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionSpaces(action_set_name, count, infos), xr_context); +} + +int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->createActionBindings(action_set_name, count, infos), + xr_context); + return 0; +} + +void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + GHOST_TUns32 count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->destroyActionBindings(action_set_name, count, infos), xr_context); +} + +int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_contexthandle) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->attachActionSets(), xr_context); + return 0; +} + +int GHOST_XrSyncActions(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->syncActions(action_set_name), xr_context); + return 0; +} + +int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name, + const GHOST_TInt64 *duration, + const float *frequency, + const float *amplitude) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->applyHapticAction( + action_set_name, action_name, *duration, *frequency, *amplitude), + xr_context); + return 0; +} + +void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL(xr_session->stopHapticAction(action_set_name, action_name), xr_context); +} + +void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getActionSetCustomdata(action_set_name), xr_context); + return 0; +} + +void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_contexthandle, + const char *action_set_name, + const char *action_name) +{ + GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle; + GHOST_XrSession *xr_session = xr_context->getSession(); + GHOST_XR_CAPI_CALL_RET(xr_session->getActionCustomdata(action_set_name, action_name), + xr_context); + return 0; +} + +#endif /* WITH_XR_OPENXR */ diff --git a/intern/ghost/intern/GHOST_DropTargetX11.cpp b/intern/ghost/intern/GHOST_DropTargetX11.cpp index 82ed9de230d..a62db31c952 100644 --- a/intern/ghost/intern/GHOST_DropTargetX11.cpp +++ b/intern/ghost/intern/GHOST_DropTargetX11.cpp @@ -112,8 +112,7 @@ GHOST_DropTargetX11::~GHOST_DropTargetX11() } } -/* based on a code from Saul Rennison - * http://stackoverflow.com/questions/2673207/c-c-url-decode-library */ +/* Based on: https://stackoverflow.com/a/2766963/432509 */ typedef enum DecodeState_e { STATE_SEARCH = 0, ///< searching for an ampersand to convert diff --git a/intern/ghost/intern/GHOST_IconX11.h b/intern/ghost/intern/GHOST_IconX11.h index 615a7dae6b5..403d611b71d 100644 --- a/intern/ghost/intern/GHOST_IconX11.h +++ b/intern/ghost/intern/GHOST_IconX11.h @@ -24,7 +24,8 @@ #pragma once -/* +/** + * \code{.py} * import bpy * import textwrap * @@ -42,6 +43,7 @@ * print("%d,%d," % (w, h)) * text = ", ".join(["0x%x" % p for p in pixels]) * print(textwrap.fill(text, width=120), end=",\n") + * \endcode */ /** diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index 050c90097c5..6ddb7f031f5 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -1711,7 +1711,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr) dx = [event scrollingDeltaX]; dy = [event scrollingDeltaY]; - /* However, wacom tablet (intuos5) needs old deltas, + /* However, Wacom tablet (intuos5) needs old deltas, * it then has momentum and phase at zero. */ if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) { dx = [event deltaX]; diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 81d9824ed26..83b9b2ba36b 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -40,6 +40,7 @@ #include <unordered_map> #include <unordered_set> +#include "GHOST_WaylandCursorSettings.h" #include <pointer-constraints-client-protocol.h> #include <relative-pointer-client-protocol.h> #include <wayland-cursor.h> @@ -52,15 +53,6 @@ #include <cstring> -struct output_t { - struct wl_output *output; - int32_t width, height; - int transform; - int scale; - std::string make; - std::string model; -}; - struct buffer_t { void *data; size_t size; @@ -72,6 +64,12 @@ struct cursor_t { struct wl_buffer *buffer; struct wl_cursor_image image; struct buffer_t *file_buffer = nullptr; + struct wl_cursor_theme *theme = nullptr; + int size; + std::string theme_name; + // outputs on which the cursor is visible + std::unordered_set<const output_t *> outputs; + int scale = 1; }; struct data_offer_t { @@ -142,10 +140,14 @@ struct display_t { struct wl_display *display; struct wl_compositor *compositor = nullptr; struct xdg_wm_base *xdg_shell = nullptr; + struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr; struct wl_shm *shm = nullptr; std::vector<output_t *> outputs; std::vector<input_t *> inputs; - struct wl_cursor_theme *cursor_theme = nullptr; + struct { + std::string theme; + int size; + } cursor; struct wl_data_device_manager *data_device_manager = nullptr; struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr; struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr; @@ -154,6 +156,8 @@ struct display_t { std::vector<struct wl_egl_window *> os_egl_windows; }; +static GHOST_WindowManager *window_manager = nullptr; + static void display_destroy(display_t *d) { if (d->data_device_manager) { @@ -188,6 +192,9 @@ static void display_destroy(display_t *d) if (input->cursor.surface) { wl_surface_destroy(input->cursor.surface); } + if (input->cursor.theme) { + wl_cursor_theme_destroy(input->cursor.theme); + } if (input->pointer) { wl_pointer_destroy(input->pointer); } @@ -210,10 +217,6 @@ static void display_destroy(display_t *d) delete input; } - if (d->cursor_theme) { - wl_cursor_theme_destroy(d->cursor_theme); - } - if (d->shm) { wl_shm_destroy(d->shm); } @@ -238,6 +241,10 @@ static void display_destroy(display_t *d) wl_compositor_destroy(d->compositor); } + if (d->xdg_decoration_manager) { + zxdg_decoration_manager_v1_destroy(d->xdg_decoration_manager); + } + if (d->xdg_shell) { xdg_wm_base_destroy(d->xdg_shell); } @@ -478,7 +485,9 @@ static void dnd_events(const input_t *const input, const GHOST_TEventType event) static std::string read_pipe(data_offer_t *data_offer, const std::string mime_receive) { int pipefd[2]; - pipe(pipefd); + if (pipe(pipefd) != 0) { + return {}; + } wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]); close(pipefd[1]); @@ -513,7 +522,9 @@ static void data_source_send(void *data, int32_t fd) { const char *const buffer = static_cast<char *>(data); - write(fd, buffer, strlen(buffer) + 1); + if (write(fd, buffer, strlen(buffer) + 1) < 0) { + GHOST_PRINT("error writing to clipboard: " << std::strerror(errno) << std::endl); + } close(fd); } @@ -789,13 +800,80 @@ static void cursor_buffer_release(void *data, struct wl_buffer *wl_buffer) cursor_t *cursor = static_cast<cursor_t *>(data); wl_buffer_destroy(wl_buffer); - cursor->buffer = nullptr; + + if (wl_buffer == cursor->buffer) { + /* the mapped buffer was from a custom cursor */ + cursor->buffer = nullptr; + } } const struct wl_buffer_listener cursor_buffer_listener = { cursor_buffer_release, }; +static GHOST_IWindow *get_window(struct wl_surface *surface) +{ + if (!surface) { + return nullptr; + } + + for (GHOST_IWindow *win : window_manager->getWindows()) { + if (surface == static_cast<const GHOST_WindowWayland *>(win)->surface()) { + return win; + } + } + return nullptr; +} + +static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm) +{ + int scale = 0; + for (const output_t *output : cursor.outputs) { + if (output->scale > scale) + scale = output->scale; + } + + if (scale > 0 && cursor.scale != scale) { + cursor.scale = scale; + wl_surface_set_buffer_scale(cursor.surface, scale); + wl_cursor_theme_destroy(cursor.theme); + cursor.theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm); + return true; + } + return false; +} + +static void cursor_surface_enter(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) +{ + input_t *input = static_cast<input_t *>(data); + for (const output_t *reg_output : input->system->outputs()) { + if (reg_output->output == output) { + input->cursor.outputs.insert(reg_output); + } + } + update_cursor_scale(input->cursor, input->system->shm()); +} + +static void cursor_surface_leave(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) +{ + input_t *input = static_cast<input_t *>(data); + for (const output_t *reg_output : input->system->outputs()) { + if (reg_output->output == output) { + input->cursor.outputs.erase(reg_output); + } + } + update_cursor_scale(input->cursor, input->system->shm()); +} + +struct wl_surface_listener cursor_surface_listener = { + cursor_surface_enter, + cursor_surface_leave, +}; + static void pointer_enter(void *data, struct wl_pointer * /*wl_pointer*/, uint32_t serial, @@ -803,22 +881,28 @@ static void pointer_enter(void *data, wl_fixed_t surface_x, wl_fixed_t surface_y) { - if (!surface) { + GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(get_window(surface)); + + if (!win) { return; } + + win->activate(); + input_t *input = static_cast<input_t *>(data); input->pointer_serial = serial; - input->x = wl_fixed_to_int(surface_x); - input->y = wl_fixed_to_int(surface_y); + input->x = win->scale() * wl_fixed_to_int(surface_x); + input->y = win->scale() * wl_fixed_to_int(surface_y); input->focus_pointer = surface; - input->system->pushEvent( - new GHOST_EventCursor(input->system->getMilliSeconds(), - GHOST_kEventCursorMove, - static_cast<GHOST_WindowWayland *>(wl_surface_get_user_data(surface)), - input->x, - input->y, - GHOST_TABLET_DATA_NONE)); + win->setCursorShape(win->getCursorShape()); + + input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), + GHOST_kEventCursorMove, + static_cast<GHOST_WindowWayland *>(win), + input->x, + input->y, + GHOST_TABLET_DATA_NONE)); } static void pointer_leave(void *data, @@ -826,9 +910,14 @@ static void pointer_leave(void *data, uint32_t /*serial*/, struct wl_surface *surface) { - if (surface != nullptr) { - static_cast<input_t *>(data)->focus_pointer = nullptr; + GHOST_IWindow *win = get_window(surface); + + if (!win) { + return; } + + static_cast<input_t *>(data)->focus_pointer = nullptr; + static_cast<GHOST_WindowWayland *>(win)->deactivate(); } static void pointer_motion(void *data, @@ -839,21 +928,20 @@ static void pointer_motion(void *data, { input_t *input = static_cast<input_t *>(data); - GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>( - wl_surface_get_user_data(input->focus_pointer)); + GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(get_window(input->focus_pointer)); if (!win) { return; } - input->x = wl_fixed_to_int(surface_x); - input->y = wl_fixed_to_int(surface_y); + input->x = win->scale() * wl_fixed_to_int(surface_x); + input->y = win->scale() * wl_fixed_to_int(surface_y); input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), GHOST_kEventCursorMove, win, - wl_fixed_to_int(surface_x), - wl_fixed_to_int(surface_y), + input->x, + input->y, GHOST_TABLET_DATA_NONE)); } @@ -864,6 +952,14 @@ static void pointer_button(void *data, uint32_t button, uint32_t state) { + input_t *input = static_cast<input_t *>(data); + + GHOST_IWindow *win = get_window(input->focus_pointer); + + if (!win) { + return; + } + GHOST_TEventType etype = GHOST_kEventUnknown; switch (state) { case WL_POINTER_BUTTON_STATE_RELEASED: @@ -887,9 +983,6 @@ static void pointer_button(void *data, break; } - input_t *input = static_cast<input_t *>(data); - GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>( - wl_surface_get_user_data(input->focus_pointer)); input->data_source->source_serial = serial; input->buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); input->system->pushEvent(new GHOST_EventButton( @@ -902,12 +995,18 @@ static void pointer_axis(void *data, uint32_t axis, wl_fixed_t value) { + input_t *input = static_cast<input_t *>(data); + + GHOST_IWindow *win = get_window(input->focus_pointer); + + if (!win) { + return; + } + if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) { return; } - input_t *input = static_cast<input_t *>(data); - GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>( - wl_surface_get_user_data(input->focus_pointer)); + input->system->pushEvent( new GHOST_EventWheel(input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1)); } @@ -1137,7 +1236,12 @@ static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capa input->cursor.visible = true; input->cursor.buffer = nullptr; input->cursor.file_buffer = new buffer_t; + if (!get_cursor_settings(input->cursor.theme_name, input->cursor.size)) { + input->cursor.theme_name = std::string(); + input->cursor.size = default_cursor_size; + } wl_pointer_add_listener(input->pointer, &pointer_listener, data); + wl_surface_add_listener(input->cursor.surface, &cursor_surface_listener, data); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { @@ -1160,8 +1264,8 @@ static void output_geometry(void *data, struct wl_output * /*wl_output*/, int32_t /*x*/, int32_t /*y*/, - int32_t /*physical_width*/, - int32_t /*physical_height*/, + int32_t physical_width, + int32_t physical_height, int32_t /*subpixel*/, const char *make, const char *model, @@ -1171,6 +1275,8 @@ static void output_geometry(void *data, output->transform = transform; output->make = std::string(make); output->model = std::string(model); + output->width_mm = physical_width; + output->height_mm = physical_height; } static void output_mode(void *data, @@ -1181,8 +1287,8 @@ static void output_mode(void *data, int32_t /*refresh*/) { output_t *output = static_cast<output_t *>(data); - output->width = width; - output->height = height; + output->width_pxl = width; + output->height_pxl = height; } /** @@ -1227,13 +1333,17 @@ static void global_add(void *data, struct display_t *display = static_cast<struct display_t *>(data); if (!strcmp(interface, wl_compositor_interface.name)) { display->compositor = static_cast<wl_compositor *>( - wl_registry_bind(wl_registry, name, &wl_compositor_interface, 1)); + wl_registry_bind(wl_registry, name, &wl_compositor_interface, 3)); } else if (!strcmp(interface, xdg_wm_base_interface.name)) { display->xdg_shell = static_cast<xdg_wm_base *>( wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1)); xdg_wm_base_add_listener(display->xdg_shell, &shell_listener, nullptr); } + else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)) { + display->xdg_decoration_manager = static_cast<zxdg_decoration_manager_v1 *>( + wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1)); + } else if (!strcmp(interface, wl_output_interface.name)) { output_t *output = new output_t; output->scale = 1; @@ -1335,16 +1445,6 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t) wl_data_device_add_listener(input->data_device, &data_device_listener, input); } } - - const char *theme = std::getenv("XCURSOR_THEME"); - const char *size = std::getenv("XCURSOR_SIZE"); - const int sizei = size ? std::stoi(size) : default_cursor_size; - - d->cursor_theme = wl_cursor_theme_load(theme, sizei, d->shm); - if (!d->cursor_theme) { - display_destroy(d); - throw std::runtime_error("Wayland: unable to access cursor themes!"); - } } GHOST_SystemWayland::~GHOST_SystemWayland() @@ -1471,8 +1571,8 @@ void GHOST_SystemWayland::getMainDisplayDimensions(GHOST_TUns32 &width, GHOST_TU { if (getNumDisplays() > 0) { /* We assume first output as main. */ - width = uint32_t(d->outputs[0]->width); - height = uint32_t(d->outputs[0]->height); + width = uint32_t(d->outputs[0]->width_pxl) / d->outputs[0]->scale; + height = uint32_t(d->outputs[0]->height_pxl) / d->outputs[0]->scale; } } @@ -1481,7 +1581,7 @@ void GHOST_SystemWayland::getAllDisplayDimensions(GHOST_TUns32 &width, GHOST_TUn getMainDisplayDimensions(width, height); } -GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings glSettings) +GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*glSettings*/) { /* Create new off-screen window. */ wl_surface *os_surface = wl_compositor_create_surface(compositor()); @@ -1530,6 +1630,11 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, 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, @@ -1574,6 +1679,21 @@ xdg_wm_base *GHOST_SystemWayland::shell() return d->xdg_shell; } +zxdg_decoration_manager_v1 *GHOST_SystemWayland::decoration_manager() +{ + return d->xdg_decoration_manager; +} + +const std::vector<output_t *> &GHOST_SystemWayland::outputs() const +{ + return d->outputs; +} + +wl_shm *GHOST_SystemWayland::shm() const +{ + return d->shm; +} + void GHOST_SystemWayland::setSelection(const std::string &selection) { this->selection = selection; @@ -1581,23 +1701,20 @@ void GHOST_SystemWayland::setSelection(const std::string &selection) static void set_cursor_buffer(input_t *input, wl_buffer *buffer) { - input->cursor.visible = (buffer != nullptr); + cursor_t *c = &input->cursor; - wl_surface_attach(input->cursor.surface, buffer, 0, 0); - wl_surface_commit(input->cursor.surface); + c->visible = (buffer != nullptr); - if (input->cursor.visible) { - wl_surface_damage(input->cursor.surface, - 0, - 0, - int32_t(input->cursor.image.width), - int32_t(input->cursor.image.height)); - wl_pointer_set_cursor(input->pointer, - input->pointer_serial, - input->cursor.surface, - int32_t(input->cursor.image.hotspot_x), - int32_t(input->cursor.image.hotspot_y)); - } + wl_surface_attach(c->surface, buffer, 0, 0); + + wl_surface_damage(c->surface, 0, 0, int32_t(c->image.width), int32_t(c->image.height)); + wl_pointer_set_cursor(input->pointer, + input->pointer_serial, + c->visible ? c->surface : nullptr, + int32_t(c->image.hotspot_x) / c->scale, + int32_t(c->image.hotspot_y) / c->scale); + + wl_surface_commit(c->surface); } GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) @@ -1608,7 +1725,15 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) const std::string cursor_name = cursors.count(shape) ? cursors.at(shape) : cursors.at(GHOST_kStandardCursorDefault); - wl_cursor *cursor = wl_cursor_theme_get_cursor(d->cursor_theme, cursor_name.c_str()); + input_t *input = d->inputs[0]; + cursor_t *c = &input->cursor; + + if (!c->theme) { + /* The cursor surface hasn't entered an output yet. Initialize theme with scale 1. */ + c->theme = wl_cursor_theme_load(c->theme_name.c_str(), c->size, d->inputs[0]->system->shm()); + } + + wl_cursor *cursor = wl_cursor_theme_get_cursor(c->theme, cursor_name.c_str()); if (!cursor) { GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl); @@ -1620,11 +1745,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) if (!buffer) { return GHOST_kFailure; } - cursor_t *c = &d->inputs[0]->cursor; + c->buffer = buffer; c->image = *image; - set_cursor_buffer(d->inputs[0], buffer); + set_cursor_buffer(input, buffer); return GHOST_kSuccess; } @@ -1735,6 +1860,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(bool visible) GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mode, wl_surface *surface) { + /* ignore, if the required protocols are not supported */ + if (!d->relative_pointer_manager || !d->pointer_constraints) { + return GHOST_kFailure; + } + if (d->inputs.empty()) { return GHOST_kFailure; } @@ -1754,6 +1884,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo break; case GHOST_kGrabNormal: + break; case GHOST_kGrabWrap: input->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( d->relative_pointer_manager, input->pointer); diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index 10b9ef6bd62..3a08a0d3b16 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -26,6 +26,7 @@ #include "GHOST_WindowWayland.h" #include <wayland-client.h> +#include <xdg-decoration-client-protocol.h> #include <xdg-shell-client-protocol.h> #include <string> @@ -34,6 +35,16 @@ class GHOST_WindowWayland; struct display_t; +struct output_t { + struct wl_output *output; + int32_t width_pxl, height_pxl; // dimensions in pixel + int32_t width_mm, height_mm; // dimensions in millimeter + int transform; + int scale; + std::string make; + std::string model; +}; + class GHOST_SystemWayland : public GHOST_System { public: GHOST_SystemWayland(); @@ -84,6 +95,12 @@ class GHOST_SystemWayland : public GHOST_System { xdg_wm_base *shell(); + zxdg_decoration_manager_v1 *decoration_manager(); + + const std::vector<output_t *> &outputs() const; + + wl_shm *shm() const; + void setSelection(const std::string &selection); GHOST_TSuccess setCursorShape(GHOST_TStandardCursor shape); diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp index 430e8216ae3..c3a243cc22c 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cpp +++ b/intern/ghost/intern/GHOST_SystemWin32.cpp @@ -32,6 +32,7 @@ #include <commctrl.h> #include <psapi.h> #include <shellapi.h> +#include <shellscalingapi.h> #include <shlobj.h> #include <tlhelp32.h> #include <windowsx.h> @@ -97,41 +98,6 @@ # define VK_GR_LESS 0xE2 #endif // VK_GR_LESS -#ifndef VK_MEDIA_NEXT_TRACK -# define VK_MEDIA_NEXT_TRACK 0xB0 -#endif // VK_MEDIA_NEXT_TRACK -#ifndef VK_MEDIA_PREV_TRACK -# define VK_MEDIA_PREV_TRACK 0xB1 -#endif // VK_MEDIA_PREV_TRACK -#ifndef VK_MEDIA_STOP -# define VK_MEDIA_STOP 0xB2 -#endif // VK_MEDIA_STOP -#ifndef VK_MEDIA_PLAY_PAUSE -# define VK_MEDIA_PLAY_PAUSE 0xB3 -#endif // VK_MEDIA_PLAY_PAUSE - -// Window message newer than Windows 7 -#ifndef WM_DPICHANGED -# define WM_DPICHANGED 0x02E0 -#endif // WM_DPICHANGED - -// WM_POINTER API messages minimum Windows 7 -#ifndef WM_POINTERENTER -# define WM_POINTERENTER 0x0249 -#endif // WM_POINTERENTER -#ifndef WM_POINTERDOWN -# define WM_POINTERDOWN 0x0246 -#endif // WM_POINTERDOWN -#ifndef WM_POINTERUPDATE -# define WM_POINTERUPDATE 0x0245 -#endif // WM_POINTERUPDATE -#ifndef WM_POINTERUP -# define WM_POINTERUP 0x0247 -#endif // WM_POINTERUP -#ifndef WM_POINTERLEAVE -# define WM_POINTERLEAVE 0x024A -#endif // WM_POINTERLEAVE - /* Workaround for some laptop touchpads, some of which seems to * have driver issues which makes it so window function receives * the message, but PeekMessage doesn't pick those messages for @@ -173,24 +139,6 @@ static void initRawInput() #undef DEVICE_COUNT } -#ifndef DPI_ENUMS_DECLARED -typedef enum PROCESS_DPI_AWARENESS { - PROCESS_DPI_UNAWARE = 0, - PROCESS_SYSTEM_DPI_AWARE = 1, - PROCESS_PER_MONITOR_DPI_AWARE = 2 -} PROCESS_DPI_AWARENESS; - -typedef enum MONITOR_DPI_TYPE { - MDT_EFFECTIVE_DPI = 0, - MDT_ANGULAR_DPI = 1, - MDT_RAW_DPI = 2, - MDT_DEFAULT = MDT_EFFECTIVE_DPI -} MONITOR_DPI_TYPE; - -# define USER_DEFAULT_SCREEN_DPI 96 - -# define DPI_ENUMS_DECLARED -#endif typedef HRESULT(API *GHOST_WIN32_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); typedef BOOL(API *GHOST_WIN32_EnableNonClientDpiScaling)(HWND); @@ -205,15 +153,7 @@ GHOST_SystemWin32::GHOST_SystemWin32() // Tell Windows we are per monitor DPI aware. This disables the default // blurry scaling and enables WM_DPICHANGED to allow us to draw at proper DPI. - HMODULE m_shcore = ::LoadLibrary("Shcore.dll"); - if (m_shcore) { - GHOST_WIN32_SetProcessDpiAwareness fpSetProcessDpiAwareness = - (GHOST_WIN32_SetProcessDpiAwareness)::GetProcAddress(m_shcore, "SetProcessDpiAwareness"); - - if (fpSetProcessDpiAwareness) { - fpSetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); - } - } + SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); // Check if current keyboard layout uses AltGr and save keylayout ID for // specialized handling if keys like VK_OEM_*. I.e. french keylayout @@ -581,14 +521,7 @@ GHOST_TSuccess GHOST_SystemWin32::init() InitCommonControls(); /* Disable scaling on high DPI displays on Vista */ - HMODULE - user32 = ::LoadLibraryA("user32.dll"); - typedef BOOL(WINAPI * LPFNSETPROCESSDPIAWARE)(); - LPFNSETPROCESSDPIAWARE SetProcessDPIAware = (LPFNSETPROCESSDPIAWARE)GetProcAddress( - user32, "SetProcessDPIAware"); - if (SetProcessDPIAware) - SetProcessDPIAware(); - FreeLibrary(user32); + SetProcessDPIAware(); initRawInput(); m_lfstart = ::GetTickCount(); @@ -1442,12 +1375,23 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, */ break; case WM_SYSCOMMAND: - /* The WM_SYSCHAR message is sent to the window when system commands such as + /* The WM_SYSCOMMAND message is sent to the window when system commands such as * maximize, minimize or close the window are triggered. Also it is sent when ALT * button is press for menu. To prevent this we must return preventing DefWindowProc. + * + * Note that the four low-order bits of the wParam parameter are used internally by the + * OS. To obtain the correct result when testing the value of wParam, an application + * must combine the value 0xFFF0 with the wParam value by using the bitwise AND operator. */ - if (wParam == SC_KEYMENU) { - eventHandled = true; + switch (wParam & 0xFFF0) { + case SC_KEYMENU: + eventHandled = true; + break; + case SC_RESTORE: + ::ShowWindow(hwnd, SW_RESTORE); + window->setState(window->getState()); + eventHandled = true; + break; } break; //////////////////////////////////////////////////////////////////////// @@ -1518,14 +1462,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, * since DefWindowProc propagates it up the parent chain * until it finds a window that processes it. */ - - /* Get the window under the mouse and send event to its queue. */ - POINT mouse_pos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - HWND mouse_hwnd = ChildWindowFromPoint(HWND_DESKTOP, mouse_pos); - GHOST_WindowWin32 *mouse_window = (GHOST_WindowWin32 *)::GetWindowLongPtr(mouse_hwnd, - GWLP_USERDATA); - - processWheelEvent(mouse_window ? mouse_window : window, wParam, lParam); + processWheelEvent(window, wParam, lParam); eventHandled = true; #ifdef BROKEN_PEEK_TOUCHPAD PostMessage(hwnd, WM_USER, 0, 0); diff --git a/intern/ghost/intern/GHOST_Util.h b/intern/ghost/intern/GHOST_Util.h new file mode 100644 index 00000000000..8be5e373b28 --- /dev/null +++ b/intern/ghost/intern/GHOST_Util.h @@ -0,0 +1,45 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#pragma once + +#include <functional> + +/** + * RAII wrapper for typical C `void *` custom data. + * Used for exception safe custom-data handling during constructor calls. + */ +struct GHOST_C_CustomDataWrapper { + using FreeFn = std::function<void(void *)>; + + void *custom_data_; + FreeFn free_fn_; + + GHOST_C_CustomDataWrapper(void *custom_data, FreeFn free_fn) + : custom_data_(custom_data), free_fn_(free_fn) + { + } + ~GHOST_C_CustomDataWrapper() + { + if (free_fn_ != nullptr && custom_data_ != nullptr) { + free_fn_(custom_data_); + } + } +}; diff --git a/intern/ghost/intern/GHOST_WaylandCursorSettings.h b/intern/ghost/intern/GHOST_WaylandCursorSettings.h new file mode 100644 index 00000000000..c7d2d82ff84 --- /dev/null +++ b/intern/ghost/intern/GHOST_WaylandCursorSettings.h @@ -0,0 +1,130 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#pragma once +#include <dbus/dbus.h> +#include <string> + +static DBusMessage *get_setting_sync(DBusConnection *const connection, + const char *key, + const char *value) +{ + DBusError error; + dbus_bool_t success; + DBusMessage *message; + DBusMessage *reply; + + dbus_error_init(&error); + + message = dbus_message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings", + "Read"); + + success = dbus_message_append_args( + message, DBUS_TYPE_STRING, &key, DBUS_TYPE_STRING, &value, DBUS_TYPE_INVALID); + + if (!success) { + return NULL; + } + + reply = dbus_connection_send_with_reply_and_block( + connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error); + + dbus_message_unref(message); + + if (dbus_error_is_set(&error)) { + return NULL; + } + + return reply; +} + +static bool parse_type(DBusMessage *const reply, const int type, void *value) +{ + DBusMessageIter iter[3]; + + dbus_message_iter_init(reply, &iter[0]); + if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) { + return false; + } + + dbus_message_iter_recurse(&iter[0], &iter[1]); + if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) { + return false; + } + + dbus_message_iter_recurse(&iter[1], &iter[2]); + if (dbus_message_iter_get_arg_type(&iter[2]) != type) { + return false; + } + + dbus_message_iter_get_basic(&iter[2], value); + + return true; +} + +static bool get_cursor_settings(std::string &theme, int &size) +{ + static const char name[] = "org.gnome.desktop.interface"; + static const char key_theme[] = "cursor-theme"; + static const char key_size[] = "cursor-size"; + + DBusError error; + DBusConnection *connection; + DBusMessage *reply; + const char *value_theme = NULL; + + dbus_error_init(&error); + + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + + if (dbus_error_is_set(&error)) { + return false; + } + + reply = get_setting_sync(connection, name, key_theme); + if (!reply) { + return false; + } + + if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) { + dbus_message_unref(reply); + return false; + } + + theme = std::string(value_theme); + + dbus_message_unref(reply); + + reply = get_setting_sync(connection, name, key_size); + if (!reply) { + return false; + } + + if (!parse_type(reply, DBUS_TYPE_INT32, &size)) { + dbus_message_unref(reply); + return false; + } + + dbus_message_unref(reply); + + return true; +} diff --git a/intern/ghost/intern/GHOST_WindowCocoa.mm b/intern/ghost/intern/GHOST_WindowCocoa.mm index 6a5a088d163..1776b0d5ce0 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.mm +++ b/intern/ghost/intern/GHOST_WindowCocoa.mm @@ -1131,7 +1131,8 @@ GHOST_TSuccess GHOST_WindowCocoa::hasCursorShape(GHOST_TStandardCursor shape) return success; } -/** Reverse the bits in a GHOST_TUns8 +/* Reverse the bits in a GHOST_TUns8 */ +#if 0 static GHOST_TUns8 uns8ReverseBits(GHOST_TUns8 ch) { ch= ((ch >> 1) & 0x55) | ((ch << 1) & 0xAA); @@ -1139,7 +1140,7 @@ static GHOST_TUns8 uns8ReverseBits(GHOST_TUns8 ch) ch= ((ch >> 4) & 0x0F) | ((ch << 4) & 0xF0); return ch; } -*/ +#endif /** Reverse the bits in a GHOST_TUns16 */ static GHOST_TUns16 uns16ReverseBits(GHOST_TUns16 shrt) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 1e1d0814d3a..cbac2d6eaa1 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -29,11 +29,19 @@ #include <wayland-egl.h> +static constexpr size_t base_dpi = 96; + struct window_t { GHOST_WindowWayland *w; wl_surface *surface; + // outputs on which the window is currently shown on + std::unordered_set<const output_t *> outputs; + GHOST_TUns16 dpi = 0; + int scale = 1; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; + struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration = nullptr; + enum zxdg_toplevel_decoration_v1_mode decoration_mode; wl_egl_window *egl_window; int32_t pending_width, pending_height; bool is_maximised; @@ -93,17 +101,30 @@ static const xdg_toplevel_listener toplevel_listener = { toplevel_close, }; +static void toplevel_decoration_configure( + void *data, + struct zxdg_toplevel_decoration_v1 * /*zxdg_toplevel_decoration_v1*/, + uint32_t mode) +{ + static_cast<window_t *>(data)->decoration_mode = zxdg_toplevel_decoration_v1_mode(mode); +} + +static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listener = { + toplevel_decoration_configure, +}; + static void surface_configure(void *data, xdg_surface *xdg_surface, uint32_t serial) { window_t *win = static_cast<window_t *>(data); - int w, h; - wl_egl_window_get_attached_size(win->egl_window, &w, &h); - if (win->pending_width != 0 && win->pending_height != 0 && win->pending_width != w && - win->pending_height != h) { - win->width = win->pending_width; - win->height = win->pending_height; - wl_egl_window_resize(win->egl_window, win->pending_width, win->pending_height, 0, 0); + if (win->xdg_surface != xdg_surface) { + return; + } + + if (win->pending_width != 0 && win->pending_height != 0) { + win->width = win->scale * win->pending_width; + win->height = win->scale * win->pending_height; + wl_egl_window_resize(win->egl_window, win->width, win->height, 0, 0); win->pending_width = 0; win->pending_height = 0; win->w->notify_size(); @@ -123,6 +144,52 @@ static const xdg_surface_listener surface_listener = { surface_configure, }; +static bool update_scale(GHOST_WindowWayland *window) +{ + int scale = 0; + for (const output_t *output : window->outputs_active()) { + if (output->scale > scale) + scale = output->scale; + } + + if (scale > 0 && window->scale() != scale) { + window->scale() = scale; + // using the real DPI will cause wrong scaling of the UI + // use a multiplier for the default DPI as workaround + window->dpi() = scale * base_dpi; + wl_surface_set_buffer_scale(window->surface(), scale); + return true; + } + return false; +} + +static void surface_enter(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) +{ + GHOST_WindowWayland *w = static_cast<GHOST_WindowWayland *>(data); + for (const output_t *reg_output : w->outputs()) { + if (reg_output->output == output) { + w->outputs_active().insert(reg_output); + } + } + update_scale(w); +} + +static void surface_leave(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) +{ + GHOST_WindowWayland *w = static_cast<GHOST_WindowWayland *>(data); + for (const output_t *reg_output : w->outputs()) { + if (reg_output->output == output) { + w->outputs_active().erase(reg_output); + } + } + update_scale(w); +} + +struct wl_surface_listener wl_surface_listener = { + surface_enter, + surface_leave, +}; + /** \} */ /* -------------------------------------------------------------------- */ @@ -161,17 +228,28 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, /* Window surfaces. */ w->surface = wl_compositor_create_surface(m_system->compositor()); + wl_surface_add_listener(w->surface, &wl_surface_listener, this); + w->egl_window = wl_egl_window_create(w->surface, int(width), int(height)); w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->shell(), w->surface); w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface); + if (m_system->decoration_manager()) { + w->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + m_system->decoration_manager(), w->xdg_toplevel); + zxdg_toplevel_decoration_v1_add_listener( + w->xdg_toplevel_decoration, &toplevel_decoration_v1_listener, w); + zxdg_toplevel_decoration_v1_set_mode(w->xdg_toplevel_decoration, + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } + wl_surface_set_user_data(w->surface, this); xdg_surface_add_listener(w->xdg_surface, &surface_listener, w); xdg_toplevel_add_listener(w->xdg_toplevel, &toplevel_listener, w); - if (parentWindow) { + if (parentWindow && is_dialog) { xdg_toplevel_set_parent( w->xdg_toplevel, dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->w->xdg_toplevel); } @@ -192,6 +270,9 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, if (setDrawingContextType(type) == GHOST_kFailure) { GHOST_PRINT("Failed to create EGL context" << std::endl); } + + /* set swap interval to 0 to prevent blocking */ + setSwapInterval(0); } GHOST_TSuccess GHOST_WindowWayland::close() @@ -226,6 +307,31 @@ GHOST_TSuccess GHOST_WindowWayland::notify_size() new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowSize, this)); } +wl_surface *GHOST_WindowWayland::surface() const +{ + return w->surface; +} + +const std::vector<output_t *> &GHOST_WindowWayland::outputs() const +{ + return m_system->outputs(); +} + +std::unordered_set<const output_t *> &GHOST_WindowWayland::outputs_active() +{ + return w->outputs; +} + +uint16_t &GHOST_WindowWayland::dpi() +{ + return w->dpi; +} + +int &GHOST_WindowWayland::scale() +{ + return w->scale; +} + GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode) { return m_system->setCursorGrab(mode, w->surface); @@ -310,6 +416,9 @@ GHOST_WindowWayland::~GHOST_WindowWayland() releaseNativeHandles(); wl_egl_window_destroy(w->egl_window); + if (w->xdg_toplevel_decoration) { + zxdg_toplevel_decoration_v1_destroy(w->xdg_toplevel_decoration); + } xdg_toplevel_destroy(w->xdg_toplevel); xdg_surface_destroy(w->xdg_surface); wl_surface_destroy(w->surface); @@ -317,6 +426,11 @@ GHOST_WindowWayland::~GHOST_WindowWayland() delete w; } +GHOST_TUns16 GHOST_WindowWayland::getDPIHint() +{ + return w->dpi; +} + GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible) { return m_system->setCursorVisibility(visible); diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h index b62b5c24d60..dbddc7c469e 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.h +++ b/intern/ghost/intern/GHOST_WindowWayland.h @@ -24,9 +24,14 @@ #include "GHOST_Window.h" +#include <unordered_set> +#include <vector> + class GHOST_SystemWayland; struct window_t; +struct wl_surface; +struct output_t; class GHOST_WindowWayland : public GHOST_Window { public: @@ -47,6 +52,8 @@ class GHOST_WindowWayland : public GHOST_Window { ~GHOST_WindowWayland() override; + GHOST_TUns16 getDPIHint() override; + GHOST_TSuccess close(); GHOST_TSuccess activate(); @@ -55,6 +62,16 @@ class GHOST_WindowWayland : public GHOST_Window { GHOST_TSuccess notify_size(); + wl_surface *surface() const; + + const std::vector<output_t *> &outputs() const; + + std::unordered_set<const output_t *> &outputs_active(); + + uint16_t &dpi(); + + int &scale(); + protected: GHOST_TSuccess setWindowCursorGrab(GHOST_TGrabCursorMode mode) override; diff --git a/intern/ghost/intern/GHOST_WindowWin32.cpp b/intern/ghost/intern/GHOST_WindowWin32.cpp index 70901954df2..eeafe333633 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.cpp +++ b/intern/ghost/intern/GHOST_WindowWin32.cpp @@ -84,9 +84,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system, m_wantAlphaBackground(alphaBackground), m_normal_state(GHOST_kWindowStateNormal), m_user32(NULL), - m_fpGetPointerInfoHistory(NULL), - m_fpGetPointerPenInfoHistory(NULL), - m_fpGetPointerTouchInfoHistory(NULL), m_parentWindowHwnd(parentwindow ? parentwindow->m_hWnd : HWND_DESKTOP), m_debug_context(is_debug) { @@ -121,22 +118,30 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system, monitor.dwFlags = 0; GetMonitorInfo(MonitorFromRect(&win_rect, MONITOR_DEFAULTTONEAREST), &monitor); - /* Adjust our requested size to allow for caption and borders and constrain to monitor. */ - AdjustWindowRectEx(&win_rect, WS_CAPTION, FALSE, 0); + /* Constrain requested size and position to fit within this monitor. */ width = min(monitor.rcWork.right - monitor.rcWork.left, win_rect.right - win_rect.left); - left = min(max(monitor.rcWork.left, win_rect.left), monitor.rcWork.right - width); height = min(monitor.rcWork.bottom - monitor.rcWork.top, win_rect.bottom - win_rect.top); - top = min(max(monitor.rcWork.top, win_rect.top), monitor.rcWork.bottom - height); - - m_hWnd = ::CreateWindowExW(extended_style, // window extended style - s_windowClassName, // pointer to registered class name - title_16, // pointer to window name - style, // window style - left, // horizontal position of window - top, // vertical position of window - width, // window width - height, // window height - m_parentWindowHwnd, // handle to parent or owner window + win_rect.left = min(max(monitor.rcWork.left, win_rect.left), monitor.rcWork.right - width); + win_rect.right = win_rect.left + width; + win_rect.top = min(max(monitor.rcWork.top, win_rect.top), monitor.rcWork.bottom - height); + win_rect.bottom = win_rect.top + height; + + /* Adjust to allow for caption, borders, shadows, scaling, etc. Resulting values can be + * correctly outside of monitor bounds. Note: You cannot specify WS_OVERLAPPED when calling. */ + AdjustWindowRectEx(&win_rect, style & ~WS_OVERLAPPED, FALSE, extended_style); + + /* But never allow a top position that can hide part of the title bar. */ + win_rect.top = max(monitor.rcWork.top, win_rect.top); + + m_hWnd = ::CreateWindowExW(extended_style, // window extended style + s_windowClassName, // pointer to registered class name + title_16, // pointer to window name + style, // window style + win_rect.left, // horizontal position of window + win_rect.top, // vertical position of window + win_rect.right - win_rect.left, // window width + win_rect.bottom - win_rect.top, // window height + m_parentWindowHwnd, // handle to parent or owner window 0, // handle to menu or child-window identifier ::GetModuleHandle(0), // handle to application instance 0); // pointer to window-creation data @@ -145,19 +150,7 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system, m_user32 = ::LoadLibrary("user32.dll"); if (m_hWnd) { - if (m_user32) { - // Touch enabled screens with pen support by default have gestures - // enabled, which results in a delay between the pointer down event - // and the first move when using the stylus. RegisterTouchWindow - // disables the new gesture architecture enabling the events to be - // sent immediately to the application rather than being absorbed by - // the gesture API. - GHOST_WIN32_RegisterTouchWindow pRegisterTouchWindow = (GHOST_WIN32_RegisterTouchWindow) - GetProcAddress(m_user32, "RegisterTouchWindow"); - if (pRegisterTouchWindow) { - pRegisterTouchWindow(m_hWnd, 0); - } - } + RegisterTouchWindow(m_hWnd, 0); // Register this window as a droptarget. Requires m_hWnd to be valid. // Note that OleInitialize(0) has to be called prior to this. Done in GHOST_SystemWin32. @@ -224,16 +217,6 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system, } } - // Initialize Windows Ink - if (m_user32) { - m_fpGetPointerInfoHistory = (GHOST_WIN32_GetPointerInfoHistory)::GetProcAddress( - m_user32, "GetPointerInfoHistory"); - m_fpGetPointerPenInfoHistory = (GHOST_WIN32_GetPointerPenInfoHistory)::GetProcAddress( - m_user32, "GetPointerPenInfoHistory"); - m_fpGetPointerTouchInfoHistory = (GHOST_WIN32_GetPointerTouchInfoHistory)::GetProcAddress( - m_user32, "GetPointerTouchInfoHistory"); - } - // Initialize Wintab m_wintab.handle = ::LoadLibrary("Wintab32.dll"); if (m_wintab.handle && m_system->getTabletAPI() != GHOST_kTabletNative) { @@ -318,9 +301,6 @@ GHOST_WindowWin32::~GHOST_WindowWin32() if (m_user32) { FreeLibrary(m_user32); m_user32 = NULL; - m_fpGetPointerInfoHistory = NULL; - m_fpGetPointerPenInfoHistory = NULL; - m_fpGetPointerTouchInfoHistory = NULL; } if (m_customCursor) { @@ -521,7 +501,7 @@ GHOST_TSuccess GHOST_WindowWin32::setState(GHOST_TWindowState state) switch (state) { case GHOST_kWindowStateMinimized: - wp.showCmd = SW_SHOWMINIMIZED; + wp.showCmd = SW_MINIMIZE; break; case GHOST_kWindowStateMaximized: wp.showCmd = SW_SHOWMAXIMIZED; @@ -533,11 +513,18 @@ GHOST_TSuccess GHOST_WindowWin32::setState(GHOST_TWindowState state) wp.showCmd = SW_SHOWMAXIMIZED; wp.ptMaxPosition.x = 0; wp.ptMaxPosition.y = 0; - style &= ~WS_CAPTION; + style &= ~(WS_CAPTION | WS_MAXIMIZE); break; case GHOST_kWindowStateNormal: default: - wp.showCmd = SW_SHOWNORMAL; + if (curstate == GHOST_kWindowStateFullScreen && + m_normal_state == GHOST_kWindowStateMaximized) { + wp.showCmd = SW_SHOWMAXIMIZED; + m_normal_state = GHOST_kWindowStateNormal; + } + else { + wp.showCmd = SW_SHOWNORMAL; + } break; } ::SetWindowLongPtr(m_hWnd, GWL_STYLE, style); @@ -935,15 +922,14 @@ GHOST_TSuccess GHOST_WindowWin32::getPointerInfo( GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)GHOST_System::getSystem(); GHOST_TUns32 outCount; - if (!(m_fpGetPointerInfoHistory && m_fpGetPointerInfoHistory(pointerId, &outCount, NULL))) { + if (!(GetPointerInfoHistory(pointerId, &outCount, NULL))) { return GHOST_kFailure; } auto pointerPenInfo = std::vector<POINTER_PEN_INFO>(outCount); outPointerInfo.resize(outCount); - if (!(m_fpGetPointerPenInfoHistory && - m_fpGetPointerPenInfoHistory(pointerId, &outCount, pointerPenInfo.data()))) { + if (!(GetPointerPenInfoHistory(pointerId, &outCount, pointerPenInfo.data()))) { return GHOST_kFailure; } diff --git a/intern/ghost/intern/GHOST_WindowWin32.h b/intern/ghost/intern/GHOST_WindowWin32.h index d86a5c12040..a13bd876667 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.h +++ b/intern/ghost/intern/GHOST_WindowWin32.h @@ -53,177 +53,12 @@ typedef BOOL(API *GHOST_WIN32_WTPacket)(HCTX, UINT, LPVOID); typedef BOOL(API *GHOST_WIN32_WTEnable)(HCTX, BOOL); typedef BOOL(API *GHOST_WIN32_WTOverlap)(HCTX, BOOL); -// typedef to user32 functions to disable gestures on windows -typedef BOOL(API *GHOST_WIN32_RegisterTouchWindow)(HWND hwnd, ULONG ulFlags); - // typedefs for user32 functions to allow dynamic loading of Windows 10 DPI scaling functions typedef UINT(API *GHOST_WIN32_GetDpiForWindow)(HWND); #ifndef USER_DEFAULT_SCREEN_DPI # define USER_DEFAULT_SCREEN_DPI 96 #endif // USER_DEFAULT_SCREEN_DPI -// typedefs for user32 functions to allow pointer functions -enum tagPOINTER_INPUT_TYPE { - PT_POINTER = 1, // Generic pointer - PT_TOUCH = 2, // Touch - PT_PEN = 3, // Pen - PT_MOUSE = 4, // Mouse -#if (WINVER >= 0x0603) - PT_TOUCHPAD = 5, // Touchpad -#endif /* WINVER >= 0x0603 */ -}; - -typedef enum tagPOINTER_BUTTON_CHANGE_TYPE { - POINTER_CHANGE_NONE, - POINTER_CHANGE_FIRSTBUTTON_DOWN, - POINTER_CHANGE_FIRSTBUTTON_UP, - POINTER_CHANGE_SECONDBUTTON_DOWN, - POINTER_CHANGE_SECONDBUTTON_UP, - POINTER_CHANGE_THIRDBUTTON_DOWN, - POINTER_CHANGE_THIRDBUTTON_UP, - POINTER_CHANGE_FOURTHBUTTON_DOWN, - POINTER_CHANGE_FOURTHBUTTON_UP, - POINTER_CHANGE_FIFTHBUTTON_DOWN, - POINTER_CHANGE_FIFTHBUTTON_UP, -} POINTER_BUTTON_CHANGE_TYPE; - -typedef DWORD POINTER_INPUT_TYPE; -typedef UINT32 POINTER_FLAGS; - -#define POINTER_FLAG_NONE 0x00000000 -#define POINTER_FLAG_NEW 0x00000001 -#define POINTER_FLAG_INRANGE 0x00000002 -#define POINTER_FLAG_INCONTACT 0x00000004 -#define POINTER_FLAG_FIRSTBUTTON 0x00000010 -#define POINTER_FLAG_SECONDBUTTON 0x00000020 -#define POINTER_FLAG_THIRDBUTTON 0x00000040 -#define POINTER_FLAG_FOURTHBUTTON 0x00000080 -#define POINTER_FLAG_FIFTHBUTTON 0x00000100 -#define POINTER_FLAG_PRIMARY 0x00002000 -#define POINTER_FLAG_CONFIDENCE 0x000004000 -#define POINTER_FLAG_CANCELED 0x000008000 -#define POINTER_FLAG_DOWN 0x00010000 -#define POINTER_FLAG_UPDATE 0x00020000 -#define POINTER_FLAG_UP 0x00040000 -#define POINTER_FLAG_WHEEL 0x00080000 -#define POINTER_FLAG_HWHEEL 0x00100000 -#define POINTER_FLAG_CAPTURECHANGED 0x00200000 -#define POINTER_FLAG_HASTRANSFORM 0x00400000 - -typedef struct tagPOINTER_INFO { - POINTER_INPUT_TYPE pointerType; - UINT32 pointerId; - UINT32 frameId; - POINTER_FLAGS pointerFlags; - HANDLE sourceDevice; - HWND hwndTarget; - POINT ptPixelLocation; - POINT ptHimetricLocation; - POINT ptPixelLocationRaw; - POINT ptHimetricLocationRaw; - DWORD dwTime; - UINT32 historyCount; - INT32 InputData; - DWORD dwKeyStates; - UINT64 PerformanceCount; - POINTER_BUTTON_CHANGE_TYPE ButtonChangeType; -} POINTER_INFO; - -typedef UINT32 PEN_FLAGS; -#define PEN_FLAG_NONE 0x00000000 // Default -#define PEN_FLAG_BARREL 0x00000001 // The barrel button is pressed -#define PEN_FLAG_INVERTED 0x00000002 // The pen is inverted -#define PEN_FLAG_ERASER 0x00000004 // The eraser button is pressed - -typedef UINT32 PEN_MASK; -#define PEN_MASK_NONE 0x00000000 // Default - none of the optional fields are valid -#define PEN_MASK_PRESSURE 0x00000001 // The pressure field is valid -#define PEN_MASK_ROTATION 0x00000002 // The rotation field is valid -#define PEN_MASK_TILT_X 0x00000004 // The tiltX field is valid -#define PEN_MASK_TILT_Y 0x00000008 // The tiltY field is valid - -typedef struct tagPOINTER_PEN_INFO { - POINTER_INFO pointerInfo; - PEN_FLAGS penFlags; - PEN_MASK penMask; - UINT32 pressure; - UINT32 rotation; - INT32 tiltX; - INT32 tiltY; -} POINTER_PEN_INFO; - -/* - * Flags that appear in pointer input message parameters - */ -#define POINTER_MESSAGE_FLAG_NEW 0x00000001 // New pointer -#define POINTER_MESSAGE_FLAG_INRANGE 0x00000002 // Pointer has not departed -#define POINTER_MESSAGE_FLAG_INCONTACT 0x00000004 // Pointer is in contact -#define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010 // Primary action -#define POINTER_MESSAGE_FLAG_SECONDBUTTON 0x00000020 // Secondary action -#define POINTER_MESSAGE_FLAG_THIRDBUTTON 0x00000040 // Third button -#define POINTER_MESSAGE_FLAG_FOURTHBUTTON 0x00000080 // Fourth button -#define POINTER_MESSAGE_FLAG_FIFTHBUTTON 0x00000100 // Fifth button -#define POINTER_MESSAGE_FLAG_PRIMARY 0x00002000 // Pointer is primary -#define POINTER_MESSAGE_FLAG_CONFIDENCE \ - 0x00004000 // Pointer is considered unlikely to be accidental -#define POINTER_MESSAGE_FLAG_CANCELED 0x00008000 // Pointer is departing in an abnormal manner - -typedef UINT32 TOUCH_FLAGS; -#define TOUCH_FLAG_NONE 0x00000000 // Default - -typedef UINT32 TOUCH_MASK; -#define TOUCH_MASK_NONE 0x00000000 // Default - none of the optional fields are valid -#define TOUCH_MASK_CONTACTAREA 0x00000001 // The rcContact field is valid -#define TOUCH_MASK_ORIENTATION 0x00000002 // The orientation field is valid -#define TOUCH_MASK_PRESSURE 0x00000004 // The pressure field is valid - -typedef struct tagPOINTER_TOUCH_INFO { - POINTER_INFO pointerInfo; - TOUCH_FLAGS touchFlags; - TOUCH_MASK touchMask; - RECT rcContact; - RECT rcContactRaw; - UINT32 orientation; - UINT32 pressure; -} POINTER_TOUCH_INFO; - -/* - * Macros to retrieve information from pointer input message parameters - */ -#define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam)) -#define IS_POINTER_FLAG_SET_WPARAM(wParam, flag) (((DWORD)HIWORD(wParam) & (flag)) == (flag)) -#define IS_POINTER_NEW_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_NEW) -#define IS_POINTER_INRANGE_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INRANGE) -#define IS_POINTER_INCONTACT_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INCONTACT) -#define IS_POINTER_FIRSTBUTTON_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIRSTBUTTON) -#define IS_POINTER_SECONDBUTTON_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_SECONDBUTTON) -#define IS_POINTER_THIRDBUTTON_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_THIRDBUTTON) -#define IS_POINTER_FOURTHBUTTON_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FOURTHBUTTON) -#define IS_POINTER_FIFTHBUTTON_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIFTHBUTTON) -#define IS_POINTER_PRIMARY_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_PRIMARY) -#define HAS_POINTER_CONFIDENCE_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CONFIDENCE) -#define IS_POINTER_CANCELED_WPARAM(wParam) \ - IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CANCELED) - -typedef BOOL(WINAPI *GHOST_WIN32_GetPointerInfoHistory)(UINT32 pointerId, - UINT32 *entriesCount, - POINTER_INFO *pointerInfo); -typedef BOOL(WINAPI *GHOST_WIN32_GetPointerPenInfoHistory)(UINT32 pointerId, - UINT32 *entriesCount, - POINTER_PEN_INFO *penInfo); -typedef BOOL(WINAPI *GHOST_WIN32_GetPointerTouchInfoHistory)(UINT32 pointerId, - UINT32 *entriesCount, - POINTER_TOUCH_INFO *touchInfo); - struct GHOST_PointerInfoWin32 { GHOST_TInt32 pointerId; GHOST_TInt32 isPrimary; @@ -242,7 +77,7 @@ typedef enum { } GHOST_MouseCaptureEventWin32; /** - * GHOST window on M$ Windows OSs. + * GHOST window on MS Windows OSs. */ class GHOST_WindowWin32 : public GHOST_Window { public: @@ -555,7 +390,7 @@ class GHOST_WindowWin32 : public GHOST_Window { /* Wintab API */ struct { - /** WinTab DLL handle. */ + /** `WinTab.dll` handle. */ HMODULE handle = NULL; /** API functions */ @@ -574,11 +409,8 @@ class GHOST_WindowWin32 : public GHOST_Window { GHOST_TWindowState m_normal_state; - /** user32 dll handle*/ + /** `user32.dll` handle */ HMODULE m_user32; - GHOST_WIN32_GetPointerInfoHistory m_fpGetPointerInfoHistory; - GHOST_WIN32_GetPointerPenInfoHistory m_fpGetPointerPenInfoHistory; - GHOST_WIN32_GetPointerTouchInfoHistory m_fpGetPointerTouchInfoHistory; HWND m_parentWindowHwnd; diff --git a/intern/ghost/intern/GHOST_XrAction.cpp b/intern/ghost/intern/GHOST_XrAction.cpp new file mode 100644 index 00000000000..172ac40c84f --- /dev/null +++ b/intern/ghost/intern/GHOST_XrAction.cpp @@ -0,0 +1,477 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +#include <cassert> +#include <cstring> + +#include "GHOST_Types.h" + +#include "GHOST_XrException.h" +#include "GHOST_Xr_intern.h" + +#include "GHOST_XrAction.h" + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionSpace + * + * \{ */ + +GHOST_XrActionSpace::GHOST_XrActionSpace(XrInstance instance, + XrSession session, + XrAction action, + const GHOST_XrActionSpaceInfo &info, + uint32_t subaction_idx) +{ + const char *subaction_path = info.subaction_paths[subaction_idx]; + CHECK_XR(xrStringToPath(instance, subaction_path, &m_subaction_path), + (std::string("Failed to get user path \"") + subaction_path + "\".").data()); + + XrActionSpaceCreateInfo action_space_info{XR_TYPE_ACTION_SPACE_CREATE_INFO}; + action_space_info.action = action; + action_space_info.subactionPath = m_subaction_path; + copy_ghost_pose_to_openxr_pose(info.poses[subaction_idx], action_space_info.poseInActionSpace); + + CHECK_XR(xrCreateActionSpace(session, &action_space_info, &m_space), + (std::string("Failed to create space \"") + subaction_path + "\" for action \"" + + info.action_name + "\".") + .data()); +} + +GHOST_XrActionSpace::~GHOST_XrActionSpace() +{ + if (m_space != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroySpace(m_space)); + } +} + +XrSpace GHOST_XrActionSpace::getSpace() const +{ + return m_space; +} + +const XrPath &GHOST_XrActionSpace::getSubactionPath() const +{ + return m_subaction_path; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionProfile + * + * \{ */ + +GHOST_XrActionProfile::GHOST_XrActionProfile(XrInstance instance, + XrAction action, + const char *profile_path, + const GHOST_XrActionBindingInfo &info) +{ + CHECK_XR( + xrStringToPath(instance, profile_path, &m_profile), + (std::string("Failed to get interaction profile path \"") + profile_path + "\".").data()); + + /* Create bindings. */ + XrInteractionProfileSuggestedBinding bindings_info{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + bindings_info.interactionProfile = m_profile; + bindings_info.countSuggestedBindings = 1; + + for (uint32_t interaction_idx = 0; interaction_idx < info.count_interaction_paths; + ++interaction_idx) { + const char *interaction_path = info.interaction_paths[interaction_idx]; + if (m_bindings.find(interaction_path) != m_bindings.end()) { + continue; + } + + XrActionSuggestedBinding sbinding; + sbinding.action = action; + CHECK_XR(xrStringToPath(instance, interaction_path, &sbinding.binding), + (std::string("Failed to get interaction path \"") + interaction_path + "\".").data()); + bindings_info.suggestedBindings = &sbinding; + + /* Although the bindings will be re-suggested in GHOST_XrSession::attachActionSets(), it + * greatly improves error checking to suggest them here first. */ + CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info), + (std::string("Failed to create binding for profile \"") + profile_path + + "\" and action \"" + info.action_name + + "\". Are the profile and action paths correct?") + .data()); + + m_bindings.insert({interaction_path, sbinding.binding}); + } +} + +void GHOST_XrActionProfile::getBindings( + XrAction action, std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + std::map<XrPath, std::vector<XrActionSuggestedBinding>>::iterator it = r_bindings.find( + m_profile); + if (it == r_bindings.end()) { + it = r_bindings + .emplace(std::piecewise_construct, std::make_tuple(m_profile), std::make_tuple()) + .first; + } + + std::vector<XrActionSuggestedBinding> &sbindings = it->second; + + for (auto &[path, binding] : m_bindings) { + XrActionSuggestedBinding sbinding; + sbinding.action = action; + sbinding.binding = binding; + + sbindings.push_back(std::move(sbinding)); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrAction + * + * \{ */ + +GHOST_XrAction::GHOST_XrAction(XrInstance instance, + XrActionSet action_set, + const GHOST_XrActionInfo &info) + : m_type(info.type), + m_states(info.states), + m_custom_data_( + std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn)) +{ + m_subaction_paths.resize(info.count_subaction_paths); + + for (uint32_t i = 0; i < info.count_subaction_paths; ++i) { + CHECK_XR(xrStringToPath(instance, info.subaction_paths[i], &m_subaction_paths[i]), + (std::string("Failed to get user path \"") + info.subaction_paths[i] + "\".").data()); + } + + XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO}; + strcpy(action_info.actionName, info.name); + strcpy(action_info.localizedActionName, info.name); /* Just use same name for localized. This can + be changed in the future if necessary. */ + + switch (info.type) { + case GHOST_kXrActionTypeBooleanInput: + action_info.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; + break; + case GHOST_kXrActionTypeFloatInput: + action_info.actionType = XR_ACTION_TYPE_FLOAT_INPUT; + break; + case GHOST_kXrActionTypeVector2fInput: + action_info.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT; + break; + case GHOST_kXrActionTypePoseInput: + action_info.actionType = XR_ACTION_TYPE_POSE_INPUT; + break; + case GHOST_kXrActionTypeVibrationOutput: + action_info.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT; + break; + } + action_info.countSubactionPaths = info.count_subaction_paths; + action_info.subactionPaths = m_subaction_paths.data(); + + CHECK_XR(xrCreateAction(action_set, &action_info, &m_action), + (std::string("Failed to create action \"") + info.name + + "\". Action name and/or paths are invalid. Name must not contain upper " + "case letters or special characters other than '-', '_', or '.'.") + .data()); +} + +GHOST_XrAction::~GHOST_XrAction() +{ + if (m_action != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroyAction(m_action)); + } +} + +bool GHOST_XrAction::createSpace(XrInstance instance, + XrSession session, + const GHOST_XrActionSpaceInfo &info) +{ + uint32_t subaction_idx = 0; + for (; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + if (m_spaces.find(info.subaction_paths[subaction_idx]) != m_spaces.end()) { + return false; + } + } + + for (subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + m_spaces.emplace(std::piecewise_construct, + std::make_tuple(info.subaction_paths[subaction_idx]), + std::make_tuple(instance, session, m_action, info, subaction_idx)); + } + + return true; +} + +void GHOST_XrAction::destroySpace(const char *subaction_path) +{ + if (m_spaces.find(subaction_path) != m_spaces.end()) { + m_spaces.erase(subaction_path); + } +} + +bool GHOST_XrAction::createBinding(XrInstance instance, + const char *profile_path, + const GHOST_XrActionBindingInfo &info) +{ + if (m_profiles.find(profile_path) != m_profiles.end()) { + return false; + } + + m_profiles.emplace(std::piecewise_construct, + std::make_tuple(profile_path), + std::make_tuple(instance, m_action, profile_path, info)); + + return true; +} + +void GHOST_XrAction::destroyBinding(const char *interaction_profile_path) +{ + if (m_profiles.find(interaction_profile_path) != m_profiles.end()) { + m_profiles.erase(interaction_profile_path); + } +} + +void GHOST_XrAction::updateState(XrSession session, + const char *action_name, + XrSpace reference_space, + const XrTime &predicted_display_time) +{ + XrActionStateGetInfo state_info{XR_TYPE_ACTION_STATE_GET_INFO}; + state_info.action = m_action; + + const size_t count_subaction_paths = m_subaction_paths.size(); + for (size_t subaction_idx = 0; subaction_idx < count_subaction_paths; ++subaction_idx) { + state_info.subactionPath = m_subaction_paths[subaction_idx]; + + switch (m_type) { + case GHOST_kXrActionTypeBooleanInput: { + XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN}; + CHECK_XR(xrGetActionStateBoolean(session, &state_info, &state), + (std::string("Failed to get state for boolean action \"") + action_name + "\".") + .data()); + if (state.isActive) { + ((bool *)m_states)[subaction_idx] = state.currentState; + } + break; + } + case GHOST_kXrActionTypeFloatInput: { + XrActionStateFloat state{XR_TYPE_ACTION_STATE_FLOAT}; + CHECK_XR( + xrGetActionStateFloat(session, &state_info, &state), + (std::string("Failed to get state for float action \"") + action_name + "\".").data()); + if (state.isActive) { + ((float *)m_states)[subaction_idx] = state.currentState; + } + break; + } + case GHOST_kXrActionTypeVector2fInput: { + XrActionStateVector2f state{XR_TYPE_ACTION_STATE_VECTOR2F}; + CHECK_XR(xrGetActionStateVector2f(session, &state_info, &state), + (std::string("Failed to get state for vector2f action \"") + action_name + "\".") + .data()); + if (state.isActive) { + memcpy(((float(*)[2])m_states)[subaction_idx], &state.currentState, sizeof(float[2])); + } + break; + } + case GHOST_kXrActionTypePoseInput: { + XrActionStatePose state{XR_TYPE_ACTION_STATE_POSE}; + CHECK_XR( + xrGetActionStatePose(session, &state_info, &state), + (std::string("Failed to get state for pose action \"") + action_name + "\".").data()); + if (state.isActive) { + XrSpace pose_space = XR_NULL_HANDLE; + for (auto &[path, space] : m_spaces) { + if (space.getSubactionPath() == state_info.subactionPath) { + pose_space = space.getSpace(); + break; + } + } + + if (pose_space != XR_NULL_HANDLE) { + XrSpaceLocation space_location{XR_TYPE_SPACE_LOCATION}; + CHECK_XR( + xrLocateSpace( + pose_space, reference_space, predicted_display_time, &space_location), + (std::string("Failed to query pose space for action \"") + action_name + "\".") + .data()); + copy_openxr_pose_to_ghost_pose(space_location.pose, + ((GHOST_XrPose *)m_states)[subaction_idx]); + } + } + break; + } + case GHOST_kXrActionTypeVibrationOutput: { + break; + } + } + } +} + +void GHOST_XrAction::applyHapticFeedback(XrSession session, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude) +{ + XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION}; + vibration.duration = (duration == 0) ? XR_MIN_HAPTIC_DURATION : + static_cast<XrDuration>(duration); + vibration.frequency = frequency; + vibration.amplitude = amplitude; + + XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO}; + haptic_info.action = m_action; + + for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end(); + ++it) { + haptic_info.subactionPath = *it; + CHECK_XR(xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration), + (std::string("Failed to apply haptic action \"") + action_name + "\".").data()); + } +} + +void GHOST_XrAction::stopHapticFeedback(XrSession session, const char *action_name) +{ + XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO}; + haptic_info.action = m_action; + + for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end(); + ++it) { + haptic_info.subactionPath = *it; + CHECK_XR(xrStopHapticFeedback(session, &haptic_info), + (std::string("Failed to stop haptic action \"") + action_name + "\".").data()); + } +} + +void *GHOST_XrAction::getCustomdata() +{ + if (m_custom_data_ == nullptr) { + return nullptr; + } + return m_custom_data_->custom_data_; +} + +void GHOST_XrAction::getBindings( + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + for (auto &[path, profile] : m_profiles) { + profile.getBindings(m_action, r_bindings); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name GHOST_XrActionSet + * + * \{ */ + +GHOST_XrActionSet::GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info) + : m_custom_data_( + std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn)) +{ + XrActionSetCreateInfo action_set_info{XR_TYPE_ACTION_SET_CREATE_INFO}; + strcpy(action_set_info.actionSetName, info.name); + strcpy(action_set_info.localizedActionSetName, + info.name); /* Just use same name for localized. This can be changed in the future if + necessary. */ + action_set_info.priority = 0; /* Use same (default) priority for all action sets. */ + + CHECK_XR(xrCreateActionSet(instance, &action_set_info, &m_action_set), + (std::string("Failed to create action set \"") + info.name + + "\". Name must not contain upper case letters or special characters " + "other than '-', '_', or '.'.") + .data()); +} + +GHOST_XrActionSet::~GHOST_XrActionSet() +{ + /* This needs to be done before xrDestroyActionSet() to avoid an assertion in the GHOST_XrAction + * destructor (which calls xrDestroyAction()). */ + m_actions.clear(); + + if (m_action_set != XR_NULL_HANDLE) { + CHECK_XR_ASSERT(xrDestroyActionSet(m_action_set)); + } +} + +bool GHOST_XrActionSet::createAction(XrInstance instance, const GHOST_XrActionInfo &info) +{ + if (m_actions.find(info.name) != m_actions.end()) { + return false; + } + + m_actions.emplace(std::piecewise_construct, + std::make_tuple(info.name), + std::make_tuple(instance, m_action_set, info)); + + return true; +} + +void GHOST_XrActionSet::destroyAction(const char *action_name) +{ + if (m_actions.find(action_name) != m_actions.end()) { + m_actions.erase(action_name); + } +} + +GHOST_XrAction *GHOST_XrActionSet::findAction(const char *action_name) +{ + std::map<std::string, GHOST_XrAction>::iterator it = m_actions.find(action_name); + if (it == m_actions.end()) { + return nullptr; + } + return &it->second; +} + +void GHOST_XrActionSet::updateStates(XrSession session, + XrSpace reference_space, + const XrTime &predicted_display_time) +{ + for (auto &[name, action] : m_actions) { + action.updateState(session, name.data(), reference_space, predicted_display_time); + } +} + +XrActionSet GHOST_XrActionSet::getActionSet() const +{ + return m_action_set; +} + +void *GHOST_XrActionSet::getCustomdata() +{ + if (m_custom_data_ == nullptr) { + return nullptr; + } + return m_custom_data_->custom_data_; +} + +void GHOST_XrActionSet::getBindings( + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const +{ + for (auto &[name, action] : m_actions) { + action.getBindings(r_bindings); + } +} + +/** \} */ diff --git a/intern/ghost/intern/GHOST_XrAction.h b/intern/ghost/intern/GHOST_XrAction.h new file mode 100644 index 00000000000..bdc6cafb4a9 --- /dev/null +++ b/intern/ghost/intern/GHOST_XrAction.h @@ -0,0 +1,145 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup GHOST + */ + +/* Note: Requires OpenXR headers to be included before this one for OpenXR types (XrSpace, XrPath, + * etc.). */ + +#pragma once + +#include <map> +#include <memory> +#include <string> + +#include "GHOST_Util.h" + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionSpace { + public: + GHOST_XrActionSpace() = delete; /* Default constructor for map storage. */ + GHOST_XrActionSpace(XrInstance instance, + XrSession session, + XrAction action, + const GHOST_XrActionSpaceInfo &info, + uint32_t subaction_idx); + ~GHOST_XrActionSpace(); + + XrSpace getSpace() const; + const XrPath &getSubactionPath() const; + + private: + XrSpace m_space = XR_NULL_HANDLE; + XrPath m_subaction_path = XR_NULL_PATH; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionProfile { + public: + GHOST_XrActionProfile() = delete; /* Default constructor for map storage. */ + GHOST_XrActionProfile(XrInstance instance, + XrAction action, + const char *profile_path, + const GHOST_XrActionBindingInfo &info); + ~GHOST_XrActionProfile() = default; + + void getBindings(XrAction action, + std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrPath m_profile = XR_NULL_PATH; + /* Bindings identified by interaction (user (subaction) + component) path. */ + std::map<std::string, XrPath> m_bindings; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrAction { + public: + GHOST_XrAction() = delete; /* Default constructor for map storage. */ + GHOST_XrAction(XrInstance instance, XrActionSet action_set, const GHOST_XrActionInfo &info); + ~GHOST_XrAction(); + + bool createSpace(XrInstance instance, XrSession session, const GHOST_XrActionSpaceInfo &info); + void destroySpace(const char *subaction_path); + + bool createBinding(XrInstance instance, + const char *profile_path, + const GHOST_XrActionBindingInfo &info); + void destroyBinding(const char *profile_path); + + void updateState(XrSession session, + const char *action_name, + XrSpace reference_space, + const XrTime &predicted_display_time); + void applyHapticFeedback(XrSession session, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude); + void stopHapticFeedback(XrSession session, const char *action_name); + + void *getCustomdata(); + void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrAction m_action = XR_NULL_HANDLE; + GHOST_XrActionType m_type; + std::vector<XrPath> m_subaction_paths; + /** States for each subaction path. */ + void *m_states; + + std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrAction */ + + /* Spaces identified by user (subaction) path. */ + std::map<std::string, GHOST_XrActionSpace> m_spaces; + /* Profiles identified by interaction profile path. */ + std::map<std::string, GHOST_XrActionProfile> m_profiles; +}; + +/* -------------------------------------------------------------------- */ + +class GHOST_XrActionSet { + public: + GHOST_XrActionSet() = delete; /* Default constructor for map storage. */ + GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info); + ~GHOST_XrActionSet(); + + bool createAction(XrInstance instance, const GHOST_XrActionInfo &info); + void destroyAction(const char *action_name); + GHOST_XrAction *findAction(const char *action_name); + + void updateStates(XrSession session, + XrSpace reference_space, + const XrTime &predicted_display_time); + + XrActionSet getActionSet() const; + void *getCustomdata(); + void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const; + + private: + XrActionSet m_action_set = XR_NULL_HANDLE; + + std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrActionSet */ + + std::map<std::string, GHOST_XrAction> m_actions; +}; + +/* -------------------------------------------------------------------- */ diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp index 2bf67c121f8..daad0b8190a 100644 --- a/intern/ghost/intern/GHOST_XrContext.cpp +++ b/intern/ghost/intern/GHOST_XrContext.cpp @@ -55,7 +55,6 @@ void *GHOST_XrContext::s_error_handler_customdata = nullptr; /* -------------------------------------------------------------------- */ /** \name Create, Initialize and Destruct - * * \{ */ GHOST_XrContext::GHOST_XrContext(const GHOST_XrContextCreateInfo *create_info) @@ -153,7 +152,6 @@ void GHOST_XrContext::storeInstanceProperties() /* -------------------------------------------------------------------- */ /** \name Debug Printing - * * \{ */ void GHOST_XrContext::printInstanceInfo() @@ -242,14 +240,13 @@ void GHOST_XrContext::initDebugMessenger() /* -------------------------------------------------------------------- */ /** \name Error handling - * * \{ */ void GHOST_XrContext::dispatchErrorMessage(const GHOST_XrException *exception) const { GHOST_XrError error; - error.user_message = exception->m_msg; + error.user_message = exception->m_msg.data(); error.customdata = s_error_handler_customdata; if (isDebugMode()) { @@ -273,7 +270,6 @@ void GHOST_XrContext::setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *c /* -------------------------------------------------------------------- */ /** \name OpenXR API-Layers and Extensions - * * \{ */ /** @@ -378,7 +374,7 @@ void GHOST_XrContext::getAPILayersToEnable(std::vector<const char *> &r_ext_name for (const std::string &layer : try_layers) { if (openxr_layer_is_available(m_oxr->layers, layer)) { - r_ext_names.push_back(layer.c_str()); + r_ext_names.push_back(layer.data()); } } } @@ -488,6 +484,7 @@ GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToUse( void GHOST_XrContext::startSession(const GHOST_XrSessionBeginInfo *begin_info) { + m_custom_funcs.session_create_fn = begin_info->create_fn; m_custom_funcs.session_exit_fn = begin_info->exit_fn; m_custom_funcs.session_exit_customdata = begin_info->exit_customdata; @@ -538,6 +535,16 @@ void GHOST_XrContext::handleSessionStateChange(const XrEventDataSessionStateChan * Public as in, exposed in the Ghost API. * \{ */ +GHOST_XrSession *GHOST_XrContext::getSession() +{ + return m_session.get(); +} + +const GHOST_XrSession *GHOST_XrContext::getSession() const +{ + return m_session.get(); +} + void GHOST_XrContext::setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn, GHOST_XrGraphicsContextUnbindFn unbind_fn) { @@ -564,7 +571,6 @@ bool GHOST_XrContext::needsUpsideDownDrawing() const /* -------------------------------------------------------------------- */ /** \name Ghost Internal Accessors and Mutators - * * \{ */ GHOST_TXrOpenXRRuntimeID GHOST_XrContext::getOpenXRRuntimeID() const diff --git a/intern/ghost/intern/GHOST_XrContext.h b/intern/ghost/intern/GHOST_XrContext.h index 59c7786ed7a..f29d7349f7e 100644 --- a/intern/ghost/intern/GHOST_XrContext.h +++ b/intern/ghost/intern/GHOST_XrContext.h @@ -35,6 +35,7 @@ struct GHOST_XrCustomFuncs { /** Function to release (possibly free) a graphics context. */ GHOST_XrGraphicsContextUnbindFn gpu_ctx_unbind_fn = nullptr; + GHOST_XrSessionCreateFn session_create_fn = nullptr; GHOST_XrSessionExitFn session_exit_fn = nullptr; void *session_exit_customdata = nullptr; @@ -72,6 +73,10 @@ class GHOST_XrContext : public GHOST_IXrContext { bool isSessionRunning() const override; void drawSessionViews(void *draw_customdata) override; + /** Needed for the GHOST C api. */ + GHOST_XrSession *getSession() override; + const GHOST_XrSession *getSession() const override; + static void setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata); void dispatchErrorMessage(const class GHOST_XrException *exception) const override; diff --git a/intern/ghost/intern/GHOST_XrException.h b/intern/ghost/intern/GHOST_XrException.h index 30c33eaf98f..e93164e04c8 100644 --- a/intern/ghost/intern/GHOST_XrException.h +++ b/intern/ghost/intern/GHOST_XrException.h @@ -21,6 +21,7 @@ #pragma once #include <exception> +#include <string> class GHOST_XrException : public std::exception { friend class GHOST_XrContext; @@ -33,10 +34,10 @@ class GHOST_XrException : public std::exception { const char *what() const noexcept override { - return m_msg; + return m_msg.data(); } private: - const char *m_msg; + std::string m_msg; int m_result; }; diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp index a1fd7fc2781..a7438fae13c 100644 --- a/intern/ghost/intern/GHOST_XrSession.cpp +++ b/intern/ghost/intern/GHOST_XrSession.cpp @@ -28,6 +28,7 @@ #include "GHOST_C-api.h" #include "GHOST_IXrGraphicsBinding.h" +#include "GHOST_XrAction.h" #include "GHOST_XrContext.h" #include "GHOST_XrException.h" #include "GHOST_XrSwapchain.h" @@ -46,6 +47,8 @@ struct OpenXRSessionData { XrSpace view_space; std::vector<XrView> views; std::vector<GHOST_XrSwapchain> swapchains; + + std::map<std::string, GHOST_XrActionSet> action_sets; }; struct GHOST_XrDrawInfo { @@ -59,7 +62,6 @@ struct GHOST_XrDrawInfo { /* -------------------------------------------------------------------- */ /** \name Create, Initialize and Destruct - * * \{ */ GHOST_XrSession::GHOST_XrSession(GHOST_XrContext &xr_context) @@ -72,6 +74,7 @@ GHOST_XrSession::~GHOST_XrSession() unbindGraphicsContext(); m_oxr->swapchains.clear(); + m_oxr->action_sets.clear(); if (m_oxr->reference_space != XR_NULL_HANDLE) { CHECK_XR_ASSERT(xrDestroySpace(m_oxr->reference_space)); @@ -110,7 +113,6 @@ void GHOST_XrSession::initSystem() /* -------------------------------------------------------------------- */ /** \name State Management - * * \{ */ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose) @@ -179,7 +181,7 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) std::ostringstream strstream; strstream << "Available graphics context version does not meet the following requirements: " << requirement_str; - throw GHOST_XrException(strstream.str().c_str()); + throw GHOST_XrException(strstream.str().data()); } m_gpu_binding->initFromGhostContext(*m_gpu_ctx); @@ -196,6 +198,9 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info) prepareDrawing(); create_reference_spaces(*m_oxr, begin_info->base_pose); + + /* Create and bind actions here. */ + m_context->getCustomFuncs().session_create_fn(); } void GHOST_XrSession::requestEnd() @@ -225,10 +230,9 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( assert(m_oxr->session == XR_NULL_HANDLE || m_oxr->session == lifecycle.session); switch (lifecycle.state) { - case XR_SESSION_STATE_READY: { + case XR_SESSION_STATE_READY: beginSession(); break; - } case XR_SESSION_STATE_STOPPING: endSession(); break; @@ -245,7 +249,6 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent( /* -------------------------------------------------------------------- */ /** \name Drawing - * * \{ */ void GHOST_XrSession::prepareDrawing() @@ -352,18 +355,6 @@ void GHOST_XrSession::draw(void *draw_customdata) endFrameDrawing(layers); } -static void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose) -{ - /* Set and convert to Blender coordinate space. */ - r_ghost_pose.position[0] = oxr_pose.position.x; - r_ghost_pose.position[1] = oxr_pose.position.y; - r_ghost_pose.position[2] = oxr_pose.position.z; - r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w; - r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x; - r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y; - r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z; -} - static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info) { /* Set and convert to Blender coordinate space. */ @@ -457,7 +448,6 @@ bool GHOST_XrSession::needsUpsideDownDrawing() const /* -------------------------------------------------------------------- */ /** \name State Queries - * * \{ */ bool GHOST_XrSession::isRunning() const @@ -505,3 +495,340 @@ void GHOST_XrSession::unbindGraphicsContext() } /** \} */ /* Graphics Context Injection */ + +/* -------------------------------------------------------------------- */ +/** \name Actions + * + * \{ */ + +static GHOST_XrActionSet *find_action_set(OpenXRSessionData *oxr, const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet>::iterator it = oxr->action_sets.find(action_set_name); + if (it == oxr->action_sets.end()) { + return nullptr; + } + return &it->second; +} + +bool GHOST_XrSession::createActionSet(const GHOST_XrActionSetInfo &info) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + if (action_sets.find(info.name) != action_sets.end()) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + action_sets.emplace( + std::piecewise_construct, std::make_tuple(info.name), std::make_tuple(instance, info)); + + return true; +} + +void GHOST_XrSession::destroyActionSet(const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + if (action_sets.find(action_set_name) != action_sets.end()) { + action_sets.erase(action_set_name); + } +} + +bool GHOST_XrSession::createActions(const char *action_set_name, + uint32_t count, + const GHOST_XrActionInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + for (uint32_t i = 0; i < count; ++i) { + if (!action_set->createAction(instance, infos[i])) { + return false; + } + } + + return true; +} + +void GHOST_XrSession::destroyActions(const char *action_set_name, + uint32_t count, + const char *const *action_names) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t i = 0; i < count; ++i) { + action_set->destroyAction(action_names[i]); + } +} + +bool GHOST_XrSession::createActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + XrSession session = m_oxr->session; + + for (uint32_t action_idx = 0; action_idx < count; ++action_idx) { + const GHOST_XrActionSpaceInfo &info = infos[action_idx]; + + GHOST_XrAction *action = action_set->findAction(info.action_name); + if (action == nullptr) { + continue; + } + + if (!action->createSpace(instance, session, info)) { + return false; + } + } + + return true; +} + +void GHOST_XrSession::destroyActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t action_idx = 0; action_idx < count; ++action_idx) { + const GHOST_XrActionSpaceInfo &info = infos[action_idx]; + + GHOST_XrAction *action = action_set->findAction(info.action_name); + if (action == nullptr) { + continue; + } + + for (uint32_t subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) { + action->destroySpace(info.subaction_paths[subaction_idx]); + } + } +} + +bool GHOST_XrSession::createActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrInstance instance = m_context->getInstance(); + + for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) { + const GHOST_XrActionProfileInfo &info = infos[profile_idx]; + const char *profile_path = info.profile_path; + + for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) { + const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx]; + + GHOST_XrAction *action = action_set->findAction(binding.action_name); + if (action == nullptr) { + continue; + } + + action->createBinding(instance, profile_path, binding); + } + } + + return true; +} + +void GHOST_XrSession::destroyActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) { + const GHOST_XrActionProfileInfo &info = infos[profile_idx]; + const char *profile_path = info.profile_path; + + for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) { + const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx]; + + GHOST_XrAction *action = action_set->findAction(binding.action_name); + if (action == nullptr) { + continue; + } + + action->destroyBinding(profile_path); + } + } +} + +bool GHOST_XrSession::attachActionSets() +{ + /* Suggest action bindings for all action sets. */ + std::map<XrPath, std::vector<XrActionSuggestedBinding>> profile_bindings; + for (auto &[name, action_set] : m_oxr->action_sets) { + action_set.getBindings(profile_bindings); + } + + if (profile_bindings.size() < 1) { + return false; + } + + XrInteractionProfileSuggestedBinding bindings_info{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + XrInstance instance = m_context->getInstance(); + + for (auto &[profile, bindings] : profile_bindings) { + bindings_info.interactionProfile = profile; + bindings_info.countSuggestedBindings = (uint32_t)bindings.size(); + bindings_info.suggestedBindings = bindings.data(); + + CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info), + "Failed to suggest interaction profile bindings."); + } + + /* Attach action sets. */ + XrSessionActionSetsAttachInfo attach_info{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; + attach_info.countActionSets = (uint32_t)m_oxr->action_sets.size(); + + /* Create an aligned copy of the action sets to pass to xrAttachSessionActionSets(). */ + std::vector<XrActionSet> action_sets(attach_info.countActionSets); + uint32_t i = 0; + for (auto &[name, action_set] : m_oxr->action_sets) { + action_sets[i++] = action_set.getActionSet(); + } + attach_info.actionSets = action_sets.data(); + + CHECK_XR(xrAttachSessionActionSets(m_oxr->session, &attach_info), + "Failed to attach XR action sets."); + + return true; +} + +bool GHOST_XrSession::syncActions(const char *action_set_name) +{ + std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets; + + XrActionsSyncInfo sync_info{XR_TYPE_ACTIONS_SYNC_INFO}; + sync_info.countActiveActionSets = (action_set_name != nullptr) ? 1 : + (uint32_t)action_sets.size(); + if (sync_info.countActiveActionSets < 1) { + return false; + } + + std::vector<XrActiveActionSet> active_action_sets(sync_info.countActiveActionSets); + GHOST_XrActionSet *action_set = nullptr; + + if (action_set_name != nullptr) { + action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + XrActiveActionSet &active_action_set = active_action_sets[0]; + active_action_set.actionSet = action_set->getActionSet(); + active_action_set.subactionPath = XR_NULL_PATH; + } + else { + uint32_t i = 0; + for (auto &[name, action_set] : action_sets) { + XrActiveActionSet &active_action_set = active_action_sets[i++]; + active_action_set.actionSet = action_set.getActionSet(); + active_action_set.subactionPath = XR_NULL_PATH; + } + } + sync_info.activeActionSets = active_action_sets.data(); + + CHECK_XR(xrSyncActions(m_oxr->session, &sync_info), "Failed to synchronize XR actions."); + + /* Update action states (i.e. Blender custom data). */ + XrSession session = m_oxr->session; + XrSpace reference_space = m_oxr->reference_space; + const XrTime &predicted_display_time = m_draw_info->frame_state.predictedDisplayTime; + + if (action_set != nullptr) { + action_set->updateStates(session, reference_space, predicted_display_time); + } + else { + for (auto &[name, action_set] : action_sets) { + action_set.updateStates(session, reference_space, predicted_display_time); + } + } + + return true; +} + +bool GHOST_XrSession::applyHapticAction(const char *action_set_name, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return false; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return false; + } + + action->applyHapticFeedback(m_oxr->session, action_name, duration, frequency, amplitude); + + return true; +} + +void GHOST_XrSession::stopHapticAction(const char *action_set_name, const char *action_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return; + } + + action->stopHapticFeedback(m_oxr->session, action_name); +} + +void *GHOST_XrSession::getActionSetCustomdata(const char *action_set_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return nullptr; + } + + return action_set->getCustomdata(); +} + +void *GHOST_XrSession::getActionCustomdata(const char *action_set_name, const char *action_name) +{ + GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name); + if (action_set == nullptr) { + return nullptr; + } + + GHOST_XrAction *action = action_set->findAction(action_name); + if (action == nullptr) { + return nullptr; + } + + return action->getCustomdata(); +} + +/** \} */ /* Actions */ diff --git a/intern/ghost/intern/GHOST_XrSession.h b/intern/ghost/intern/GHOST_XrSession.h index 79a586411e9..d09c78e1ea7 100644 --- a/intern/ghost/intern/GHOST_XrSession.h +++ b/intern/ghost/intern/GHOST_XrSession.h @@ -52,6 +52,43 @@ class GHOST_XrSession { void draw(void *draw_customdata); + /** Action functions to be called pre-session start. + * Note: The "destroy" functions can also be called post-session start. */ + bool createActionSet(const GHOST_XrActionSetInfo &info); + void destroyActionSet(const char *action_set_name); + bool createActions(const char *action_set_name, uint32_t count, const GHOST_XrActionInfo *infos); + void destroyActions(const char *action_set_name, + uint32_t count, + const char *const *action_names); + bool createActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos); + void destroyActionSpaces(const char *action_set_name, + uint32_t count, + const GHOST_XrActionSpaceInfo *infos); + bool createActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos); + void destroyActionBindings(const char *action_set_name, + uint32_t count, + const GHOST_XrActionProfileInfo *infos); + bool attachActionSets(); + + /** Action functions to be called post-session start. */ + bool syncActions( + const char *action_set_name = nullptr); /* If action_set_name is nullptr, all attached + * action sets will be synced. */ + bool applyHapticAction(const char *action_set_name, + const char *action_name, + const GHOST_TInt64 &duration, + const float &frequency, + const float &litude); + void stopHapticAction(const char *action_set_name, const char *action_name); + + /* Custom data (owned by Blender, not GHOST) accessors. */ + void *getActionSetCustomdata(const char *action_set_name); + void *getActionCustomdata(const char *action_set_name, const char *action_name); + private: /** Pointer back to context managing this session. Would be nice to avoid, but needed to access * custom callbacks set before session start. */ diff --git a/intern/ghost/intern/GHOST_Xr_intern.h b/intern/ghost/intern/GHOST_Xr_intern.h index 137541c4528..0616e426da3 100644 --- a/intern/ghost/intern/GHOST_Xr_intern.h +++ b/intern/ghost/intern/GHOST_Xr_intern.h @@ -45,3 +45,27 @@ (void)_res; \ } \ (void)0 + +inline void copy_ghost_pose_to_openxr_pose(const GHOST_XrPose &ghost_pose, XrPosef &r_oxr_pose) +{ + /* Set and convert to OpenXR coordinate space. */ + r_oxr_pose.position.x = ghost_pose.position[0]; + r_oxr_pose.position.y = ghost_pose.position[1]; + r_oxr_pose.position.z = ghost_pose.position[2]; + r_oxr_pose.orientation.w = ghost_pose.orientation_quat[0]; + r_oxr_pose.orientation.x = ghost_pose.orientation_quat[1]; + r_oxr_pose.orientation.y = ghost_pose.orientation_quat[2]; + r_oxr_pose.orientation.z = ghost_pose.orientation_quat[3]; +} + +inline void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose) +{ + /* Set and convert to Blender coordinate space. */ + r_ghost_pose.position[0] = oxr_pose.position.x; + r_ghost_pose.position[1] = oxr_pose.position.y; + r_ghost_pose.position[2] = oxr_pose.position.z; + r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w; + r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x; + r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y; + r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z; +} diff --git a/intern/ghost/test/gears/GHOST_C-Test.c b/intern/ghost/test/gears/GHOST_C-Test.c index 4283f990cfb..8cd1b5acb89 100644 --- a/intern/ghost/test/gears/GHOST_C-Test.c +++ b/intern/ghost/test/gears/GHOST_C-Test.c @@ -477,7 +477,7 @@ int main(int argc, char **argv) /* Enter main loop */ while (!sExitRequested) { - if (!GHOST_ProcessEvents(shSystem, 0)) { + if (!GHOST_ProcessEvents(shSystem, false)) { #ifdef WIN32 /* If there were no events, be nice to other applications */ Sleep(10); diff --git a/intern/ghost/test/multitest/MultiTest.c b/intern/ghost/test/multitest/MultiTest.c index 8c8858fc6d8..3b424f1ca89 100644 --- a/intern/ghost/test/multitest/MultiTest.c +++ b/intern/ghost/test/multitest/MultiTest.c @@ -926,7 +926,7 @@ void multitestapp_exit(MultiTestApp *app) void multitestapp_run(MultiTestApp *app) { while (!app->exit) { - GHOST_ProcessEvents(app->sys, 1); + GHOST_ProcessEvents(app->sys, true); GHOST_DispatchEvents(app->sys); } } |