Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,640 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
PUBLIC .
../guardedalloc
)
set(INC_SYS
)
set(SRC
intern/GHOST_Buttons.cc
intern/GHOST_CallbackEventConsumer.cc
intern/GHOST_Context.cc
intern/GHOST_ContextNone.cc
intern/GHOST_EventManager.cc
intern/GHOST_ISystem.cc
intern/GHOST_ISystemPaths.cc
intern/GHOST_ModifierKeys.cc
intern/GHOST_PathUtils.cc
intern/GHOST_Rect.cc
intern/GHOST_System.cc
intern/GHOST_TimerManager.cc
intern/GHOST_Window.cc
intern/GHOST_WindowManager.cc
GHOST_CallbackEventConsumer.hh
GHOST_IContext.hh
GHOST_IEvent.hh
GHOST_IEventConsumer.hh
GHOST_ISystem.hh
GHOST_ISystemPaths.hh
GHOST_ITimerTask.hh
GHOST_IWindow.hh
GHOST_Rect.hh
GHOST_Types.hh
intern/GHOST_Buttons.hh
intern/GHOST_Context.hh
intern/GHOST_ContextNone.hh
intern/GHOST_Debug.hh
intern/GHOST_Event.hh
intern/GHOST_EventButton.hh
intern/GHOST_EventCursor.hh
intern/GHOST_EventDragnDrop.hh
intern/GHOST_EventKey.hh
intern/GHOST_EventManager.hh
intern/GHOST_EventString.hh
intern/GHOST_EventTrackpad.hh
intern/GHOST_EventWheel.hh
intern/GHOST_ModifierKeys.hh
intern/GHOST_PathUtils.hh
intern/GHOST_System.hh
intern/GHOST_SystemPaths.hh
intern/GHOST_TimerManager.hh
intern/GHOST_TimerTask.hh
intern/GHOST_Util.hh
intern/GHOST_Window.hh
intern/GHOST_WindowManager.hh
intern/GHOST_utildefines.hh
intern/GHOST_utildefines_variadic.hh
)
set(LIB
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::clog
)
if(WITH_INPUT_IME)
add_definitions(-DWITH_INPUT_IME)
endif()
if(WITH_OPENGL_BACKEND)
list(APPEND LIB
PRIVATE bf::dependencies::epoxy
)
add_definitions(-DWITH_OPENGL_BACKEND)
endif()
if(WITH_VULKAN_BACKEND)
list(APPEND SRC
intern/GHOST_ContextVK.cc
intern/GHOST_ContextVK.hh
)
list(APPEND LIB
PUBLIC bf::dependencies::optional::vulkan
PRIVATE bf::extern::vulkan_memory_allocator
)
add_definitions(-DWITH_VULKAN_BACKEND)
endif()
if(WITH_GHOST_DEBUG)
list(APPEND SRC
intern/GHOST_EventPrinter.cc
intern/GHOST_EventPrinter.hh
)
add_definitions(-DWITH_GHOST_DEBUG)
endif()
if(WITH_INPUT_NDOF)
add_definitions(-DWITH_INPUT_NDOF)
list(APPEND SRC
intern/GHOST_NDOFManager.cc
intern/GHOST_EventNDOF.hh
intern/GHOST_NDOFManager.hh
)
# Only some platforms define this.
if(DEFINED NDOF_INCLUDE_DIRS)
list(APPEND INC_SYS
${NDOF_INCLUDE_DIRS}
)
endif()
if(DEFINED NDOF_LIBRARIES)
list(APPEND LIB
${NDOF_LIBRARIES}
)
endif()
endif()
list(APPEND SRC
intern/GHOST_SystemHeadless.hh
intern/GHOST_WindowNULL.hh
)
if(WITH_HEADLESS)
add_definitions(-DWITH_HEADLESS)
elseif(WITH_GHOST_SDL)
list(APPEND SRC
intern/GHOST_ContextSDL.cc
intern/GHOST_SystemSDL.cc
intern/GHOST_WindowSDL.cc
intern/GHOST_ContextSDL.hh
intern/GHOST_SystemSDL.hh
intern/GHOST_WindowSDL.hh
)
add_definitions(-DWITH_GHOST_SDL)
list(APPEND LIB
bf::dependencies::optional::sdl
)
elseif(APPLE AND NOT WITH_GHOST_X11)
list(APPEND SRC
intern/GHOST_SystemCocoa.mm
intern/GHOST_WindowCocoa.mm
intern/GHOST_SystemCocoa.hh
intern/GHOST_WindowCocoa.hh
intern/GHOST_WindowViewCocoa.hh
)
if(WITH_INPUT_NDOF)
list(APPEND SRC
intern/GHOST_NDOFManagerCocoa.mm
intern/GHOST_NDOFManagerCocoa.hh
)
endif()
elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
if(WITH_GHOST_X11)
list(APPEND INC_SYS
${X11_X11_INCLUDE_PATH}
)
list(APPEND LIB
${X11_X11_LIB}
${X11_Xrender_LIB}
)
list(APPEND SRC
intern/GHOST_SystemX11.cc
intern/GHOST_WindowX11.cc
intern/GHOST_IconX11.hh
intern/GHOST_SystemX11.hh
intern/GHOST_WindowX11.hh
)
if(WITH_OPENGL_BACKEND)
list(APPEND SRC
intern/GHOST_ContextGLX.cc
intern/GHOST_ContextGLX.hh
)
endif()
if(WITH_GHOST_XDND)
add_definitions(-DWITH_XDND)
list(APPEND LIB
extern_xdnd
)
list(APPEND INC
../../extern/xdnd
)
list(APPEND SRC
intern/GHOST_DropTargetX11.cc
intern/GHOST_DropTargetX11.hh
)
endif()
if(X11_XF86keysym_INCLUDE_PATH)
add_definitions(-DWITH_XF86KEYSYM)
list(APPEND INC_SYS
${X11_XF86keysym_INCLUDE_PATH}
)
endif()
if(WITH_X11_XFIXES)
add_definitions(-DWITH_X11_XFIXES)
list(APPEND INC_SYS
${X11_Xfixes_INCLUDE_PATH}
)
list(APPEND LIB
${X11_Xfixes_LIB}
)
endif()
if(WITH_X11_XINPUT)
add_definitions(-DWITH_X11_XINPUT)
list(APPEND INC_SYS
${X11_Xinput_INCLUDE_PATH}
)
list(APPEND LIB
${X11_Xinput_LIB}
)
endif()
add_definitions(-DWITH_GHOST_X11)
endif()
if(WITH_GHOST_WAYLAND)
list(APPEND INC_SYS
${wayland-client_INCLUDE_DIRS}
${wayland-egl_INCLUDE_DIRS}
${xkbcommon_INCLUDE_DIRS}
${wayland-cursor_INCLUDE_DIRS}
)
list(APPEND LIB
${xkbcommon_LINK_LIBRARIES}
)
if(WITH_GHOST_WAYLAND_DYNLOAD)
list(APPEND INC_SYS
../wayland_dynload/extern
)
list(APPEND LIB
bf_intern_wayland_dynload
)
add_definitions(-DWITH_GHOST_WAYLAND_DYNLOAD)
else()
list(APPEND LIB
${wayland-client_LINK_LIBRARIES}
${wayland-egl_LINK_LIBRARIES}
${wayland-cursor_LINK_LIBRARIES}
)
endif()
if(WITH_GHOST_CSD)
list(APPEND SRC
intern/GHOST_WindowWaylandCSD.cc
intern/GHOST_WindowWaylandCSD.hh
)
add_definitions(-DWITH_GHOST_CSD)
endif()
include(CheckSymbolExists)
set(CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE")
check_symbol_exists(memfd_create "sys/mman.h" HAVE_MEMFD_CREATE)
unset(CMAKE_REQUIRED_DEFINITIONS)
if(HAVE_MEMFD_CREATE)
add_definitions(-DHAVE_MEMFD_CREATE)
endif()
check_symbol_exists(poll "poll.h" HAVE_POLL)
if(HAVE_POLL)
add_definitions(-DHAVE_POLL)
endif()
list(APPEND SRC
intern/GHOST_SystemWayland.cc
intern/GHOST_WindowWayland.cc
intern/GHOST_SystemWayland.hh
intern/GHOST_WaylandUtils.hh
intern/GHOST_WindowWayland.hh
)
set(INC_DST ${CMAKE_CURRENT_BINARY_DIR}/libwayland)
# Generate protocols bindings.
# Modifies in parent scope:
# - `SRC`: appended with generated protocol source and header files.
function(generate_protocol_bindings PROT_DEF)
# File name without directory or extension (use for header name).
get_filename_component(_name ${PROT_DEF} NAME_WLE)
add_custom_command(
OUTPUT ${INC_DST}/${_name}-client-protocol.h
COMMAND ${CMAKE_COMMAND} -E make_directory ${INC_DST}
COMMAND ${WAYLAND_SCANNER} client-header ${PROT_DEF} ${INC_DST}/${_name}-client-protocol.h
)
add_custom_command(
OUTPUT ${INC_DST}/${_name}-client-protocol.c
COMMAND ${CMAKE_COMMAND} -E make_directory ${INC_DST}
COMMAND ${WAYLAND_SCANNER} private-code ${PROT_DEF} ${INC_DST}/${_name}-client-protocol.c
DEPENDS ${INC_DST}/${_name}-client-protocol.h
)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
# Prevent warnings/failure to compile with generated `WL_PRIVATE` declarations.
set_source_files_properties(
"${INC_DST}/${_name}-client-protocol.c"
PROPERTIES COMPILE_FLAGS "-Wno-missing-variable-declarations"
)
endif()
list(APPEND SRC
${INC_DST}/${_name}-client-protocol.c
${INC_DST}/${_name}-client-protocol.h
)
set(SRC "${SRC}" PARENT_SCOPE)
endfunction()
list(APPEND INC_SYS
${INC_DST}
)
# `xdg-shell`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml"
)
# `xdg-decoration`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
)
# `xdg-output`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-output/xdg-output-unstable-v1.xml"
)
# `xdg-activation`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/xdg-activation/xdg-activation-v1.xml"
)
# Fractional scale.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/fractional-scale/fractional-scale-v1.xml"
)
# Viewporter (only required when fractional scale is in use).
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/stable/viewporter/viewporter.xml"
)
# Color management (HDR properties)
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/color-management/color-management-v1.xml"
)
# Pointer-constraints.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
)
# Relative-pointer.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
)
# Pointer-gestures (multi-touch).
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml"
)
# Tablet.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/stable/tablet/tablet-v2.xml"
)
# Primary-selection.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/primary-selection/primary-selection-unstable-v1.xml"
)
if(WITH_INPUT_IME)
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/text-input/text-input-unstable-v3.xml"
)
endif()
# Desktop compositor controlled cursor rendering.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/cursor-shape/cursor-shape-v1.xml"
)
# Support setting the window icon directly (replace legacy `.desktop` files).
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml"
)
unset(INC_DST)
add_definitions(-DWITH_GHOST_WAYLAND)
if(NOT WITH_GHOST_WAYLAND_APP_ID STREQUAL "")
add_definitions(-DWITH_GHOST_WAYLAND_APP_ID=${WITH_GHOST_WAYLAND_APP_ID})
endif()
endif()
if(WITH_INPUT_NDOF)
list(APPEND SRC
intern/GHOST_NDOFManagerUnix.cc
intern/GHOST_NDOFManagerUnix.hh
)
endif()
elseif(WIN32)
# # Warnings as errors, this is too strict!
# if(MSVC)
# string(APPEND CMAKE_CXX_FLAGS " /WX")
# endif()
list(APPEND INC_SYS
${WINTAB_INC}
)
list(APPEND SRC
intern/GHOST_ContextD3D.cc
intern/GHOST_DropTargetWin32.cc
intern/GHOST_SystemWin32.cc
intern/GHOST_TrackpadWin32.cc
intern/GHOST_WindowWin32.cc
intern/GHOST_Wintab.cc
intern/GHOST_ContextD3D.hh
intern/GHOST_DropTargetWin32.hh
intern/GHOST_SystemWin32.hh
intern/GHOST_TaskbarWin32.hh
intern/GHOST_TrackpadWin32.hh
intern/GHOST_WindowWin32.hh
intern/GHOST_Wintab.hh
)
list(APPEND SRC
intern/GHOST_ContextWGL.cc
intern/GHOST_ContextWGL.hh
)
if(WITH_INPUT_IME)
list(APPEND SRC
intern/GHOST_ImeWin32.cc
intern/GHOST_ImeWin32.hh
)
endif()
if(WITH_INPUT_NDOF)
list(APPEND SRC
intern/GHOST_NDOFManagerWin32.cc
intern/GHOST_NDOFManagerWin32.hh
)
endif()
endif()
if(UNIX AND NOT APPLE)
if(WITH_OPENGL_BACKEND)
list(APPEND SRC
intern/GHOST_ContextEGL.cc
intern/GHOST_ContextEGL.hh
)
endif()
endif()
if(APPLE)
if(WITH_METAL_BACKEND)
list(APPEND SRC
intern/GHOST_ContextMTL.mm
intern/GHOST_ContextMTL.hh
)
endif()
endif()
if(APPLE)
list(APPEND SRC
intern/GHOST_SystemPathsCocoa.hh
intern/GHOST_SystemPathsCocoa.mm
)
elseif(UNIX)
list(APPEND SRC
intern/GHOST_SystemPathsUnix.cc
intern/GHOST_SystemPathsUnix.hh
)
# System-wide (non-portable) installs locate architecture-dependent libraries under `lib`
# (typically `/usr/lib`), following the FHS convention - alongside `share/`.
if(NOT WITH_INSTALL_PORTABLE)
add_definitions(-DPREFIX="${CMAKE_INSTALL_PREFIX}")
# NOTE: A Python module is itself a library: its wrapper libraries are bundled in the package
# beside the add-on, so it doesn't need a separate system libraries path.
if(NOT WITH_PYTHON_MODULE)
# The standard variable for this path is `CMAKE_INSTALL_LIBDIR` (typically `lib` or `lib64`).
# It isn't set by default, but systems that build packages may define it.
#
# NOTE: `include(GNUInstallDirs)` could auto-detect this path.
# Since default builds aren't system-wide, leave it to the
# package-build scripts to pass in the correct directory.
set(_lib "lib")
if(DEFINED CMAKE_INSTALL_LIBDIR)
if(CMAKE_INSTALL_LIBDIR)
set(_lib "${CMAKE_INSTALL_LIBDIR}")
endif()
endif()
add_definitions(-DBLENDER_INSTALL_LIBDIR="${_lib}")
unset(_lib)
endif()
endif()
elseif(WIN32)
list(APPEND SRC
intern/GHOST_SystemPathsWin32.cc
intern/GHOST_SystemPathsWin32.hh
)
list(APPEND INC
../utfconv
)
endif()
if(WITH_XR_OPENXR)
list(APPEND SRC
intern/GHOST_Xr.cc
intern/GHOST_XrAction.cc
intern/GHOST_XrContext.cc
intern/GHOST_XrControllerModel.cc
intern/GHOST_XrEvent.cc
intern/GHOST_XrGraphicsBinding.cc
intern/GHOST_XrSession.cc
intern/GHOST_XrSwapchain.cc
GHOST_IXrContext.hh
GHOST_Xr-api.hh
intern/GHOST_IXrGraphicsBinding.hh
intern/GHOST_XrAction.hh
intern/GHOST_XrContext.hh
intern/GHOST_XrControllerModel.hh
intern/GHOST_XrException.hh
intern/GHOST_XrSession.hh
intern/GHOST_XrSwapchain.hh
intern/GHOST_Xr_intern.hh
intern/GHOST_Xr_openxr_includes.hh
# Header only library.
../../extern/tinygltf/tiny_gltf.h
)
if(APPLE)
list(APPEND SRC
intern/GHOST_XrGraphicsBindingMetal.mm
intern/GHOST_XrGraphicsBindingMetal.hh
)
endif()
if(WIN32)
list(APPEND SRC
intern/GHOST_XrGraphicsBindingD3D.cc
intern/GHOST_XrGraphicsBindingD3D.hh
)
endif()
list(APPEND INC_SYS
../../extern/json/include
../../extern/tinygltf
)
list(APPEND INC_SYS
${XR_OPENXR_SDK_INCLUDE_DIR}
)
list(APPEND LIB
${XR_OPENXR_SDK_LIBRARIES}
PRIVATE bf::dependencies::eigen
)
set(XR_PLATFORM_DEFINES
)
if(WITH_OPENGL_BACKEND)
list(APPEND XR_PLATFORM_DEFINES -DXR_USE_GRAPHICS_API_OPENGL)
endif()
if(WITH_VULKAN_BACKEND)
list(APPEND XR_PLATFORM_DEFINES -DXR_USE_GRAPHICS_API_VULKAN)
list(APPEND SRC
intern/GHOST_XrGraphicsBindingVulkan.cc
intern/GHOST_XrGraphicsBindingVulkan.hh
)
endif()
if(WITH_METAL_BACKEND)
list(APPEND XR_PLATFORM_DEFINES -DXR_USE_GRAPHICS_API_METAL)
endif()
# Add compiler defines as required by the OpenXR specification.
if(WIN32)
list(APPEND XR_PLATFORM_DEFINES
-DXR_USE_PLATFORM_WIN32
-DXR_USE_GRAPHICS_API_D3D11
)
list(APPEND LIB
shlwapi
)
elseif(UNIX AND NOT APPLE)
list(APPEND XR_PLATFORM_DEFINES -DXR_OS_LINUX)
if(WITH_GHOST_WAYLAND)
list(APPEND XR_PLATFORM_DEFINES -DXR_USE_PLATFORM_WAYLAND)
endif()
if(WITH_GHOST_X11)
list(APPEND XR_PLATFORM_DEFINES -DXR_USE_PLATFORM_EGL)
list(APPEND XR_PLATFORM_DEFINES -DXR_USE_PLATFORM_XLIB)
endif()
endif()
add_definitions(-DWITH_XR_OPENXR ${XR_PLATFORM_DEFINES})
unset(XR_PLATFORM_DEFINES)
endif()
blender_add_lib(bf_intern_ghost "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_CallbackEventConsumer class.
*/
#pragma once
#include "GHOST_IEventConsumer.hh"
#include "GHOST_Types.hh"
/**
* Definition of a callback routine that receives events.
* \param event: The event received.
* \param user_data: The callback's user data, supplied to #GHOST_CreateSystem.
*/
using GHOST_EventCallbackProcPtr = bool (*)(const GHOST_IEvent *event,
GHOST_TUserDataPtr user_data);
/**
* Event consumer that will forward events to a call-back routine.
* Especially useful for the C-API.
*/
class GHOST_CallbackEventConsumer : public GHOST_IEventConsumer {
public:
/**
* Constructor.
* \param eventCallback: The call-back routine invoked.
* \param user_data: The data passed back through the call-back routine.
*/
GHOST_CallbackEventConsumer(GHOST_EventCallbackProcPtr eventCallback,
GHOST_TUserDataPtr user_data);
/**
* Destructor.
*/
~GHOST_CallbackEventConsumer() override = default;
/**
* This method is called by an event producer when an event is available.
* \param event: The event that can be handled or ignored.
* \return Indication as to whether the event was handled.
*/
bool processEvent(const GHOST_IEvent *event) override;
protected:
/** The call-back routine invoked. */
GHOST_EventCallbackProcPtr event_callback_;
/** The data passed back through the call-back routine. */
GHOST_TUserDataPtr user_data_;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_CallbackEventConsumer")
};

View File

@@ -0,0 +1,111 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_IContext interface class.
*/
#pragma once
#include "GHOST_Types.hh"
#ifdef WITH_VULKAN_BACKEND
# include <functional>
#endif
/**
* Interface for GHOST context.
*
* You can create a off-screen context (windowless) with the system's
* #GHOST_ISystem::createOffscreenContext method.
* \see GHOST_ISystem#createOffscreenContext
*/
class GHOST_IContext {
public:
/**
* Destructor.
*/
virtual ~GHOST_IContext() = default;
/**
* Returns the thread's currently active drawing context.
*/
static GHOST_IContext *getActiveDrawingContext();
/**
* Activates the drawing context.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess activateDrawingContext() = 0;
/**
* Release the drawing context of the calling thread.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess releaseDrawingContext() = 0;
/**
* Gets the OpenGL frame-buffer associated with the OpenGL context
* \return The ID of an OpenGL frame-buffer object.
*/
virtual unsigned int getDefaultFramebuffer() = 0;
/**
* Acquire next buffer for drawing.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess swapBufferAcquire() = 0;
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess swapBufferRelease() = 0;
#ifdef WITH_VULKAN_BACKEND
/**
* Get Vulkan handles for the given context.
*
* These handles are the same for a given context.
* Should only be called when using a Vulkan context.
* Other contexts will not return any handles and leave the
* handles where the parameters are referring to unmodified.
*
* \param r_handles: After calling this structure is filled with
* the vulkan handles of the context.
*/
virtual GHOST_TSuccess getVulkanHandles(GHOST_VulkanHandles &r_handles) = 0;
/**
* Acquire the current swap-chain format.
*
* \param r_swap_chain_data: After calling this function the structure
* referenced by this parameter will contain the surface format and
* the size of the frame buffer and image in pixels.
*/
virtual GHOST_TSuccess getVulkanSwapChainFormat(
GHOST_VulkanSwapChainData *r_swap_chain_data) = 0;
/**
* Set the pre and post callbacks for vulkan swap-chain in the given context.
*
* \param swap_buffer_draw_callback: Function pointer to be called when acquired swap buffer is
* released, allowing Vulkan backend to update the swap chain.
* \param swap_buffer_acquired_callback: Function to be called at when swap buffer is acquired.
* Allowing Vulkan backend to update the frame-buffer. It is also called when no swap chain
* exists indicating that the window was minimized.
* \param openxr_acquire_framebuffer_image_callback: Function to be called when an image needs to
* be acquired to be drawn to an OpenXR swap-chain.
* \param openxr_release_framebuffer_image_callback: Function to be called after an image has
* been drawn to the OpenXR swap-chain.
*/
virtual GHOST_TSuccess setVulkanSwapBuffersCallbacks(
std::function<void(const GHOST_VulkanSwapChainData *, bool)> swap_buffer_draw_callback,
std::function<void(void)> swap_buffer_acquired_callback,
std::function<void(GHOST_VulkanOpenXRData *)> openxr_acquire_framebuffer_image_callback,
std::function<void(GHOST_VulkanOpenXRData *)> openxr_release_framebuffer_image_callback) = 0;
#endif
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_IContext")
};

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_IEvent interface class.
*/
#pragma once
#include "GHOST_Types.hh"
#include <cstddef>
class GHOST_IWindow;
/**
* Interface class for events received from GHOST.
* You should not need to inherit this class. The system will pass these events
* to the #GHOST_IEventConsumer::processEvent() method of event consumers.<br>
* Use the #getType() method to retrieve the type of event and the #getData()
* method to get the event data out. Using the event type you can cast the
* event data to the correct event data structure.
* \see GHOST_IEventConsumer#processEvent
* \see GHOST_TEventType
*/
class GHOST_IEvent {
public:
/**
* Destructor.
*/
virtual ~GHOST_IEvent() = default;
/**
* Returns the event type.
* \return The event type.
*/
virtual GHOST_TEventType getType() const = 0;
/**
* Returns the time this event was generated.
* \return The event generation time.
*/
virtual uint64_t getTime() const = 0;
/**
* Returns the window this event was generated on,
* or nullptr if it is a 'system' event.
* \return The generating window.
*/
virtual GHOST_IWindow *getWindow() const = 0;
/**
* Returns the event data.
* \return The event data.
*/
virtual GHOST_TEventDataPtr getData() const = 0;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_IEvent")
};

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_IEventConsumer interface class.
*/
#pragma once
#include "GHOST_IEvent.hh"
/**
* Interface class for objects interested in receiving events.
* Objects interested in events should inherit this class and implement the
* processEvent() method. They should then be registered with the system that
* they want to receive events. The system will call the processEvent() method
* for every installed event consumer to pass events.
* \see GHOST_ISystem#addEventConsumer
*/
class GHOST_IEventConsumer {
public:
/**
* Destructor.
*/
virtual ~GHOST_IEventConsumer() = default;
/**
* This method is called by the system when it has events to dispatch.
* \see GHOST_ISystem#dispatchEvents
* \param event: The event that can be handled or ignored.
* \return Indication as to whether the event was handled.
*/
virtual bool processEvent(const GHOST_IEvent *event) = 0;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_IEventConsumer")
};

View File

@@ -0,0 +1,557 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* %Main interface file for C++ API with declaration of GHOST_ISystem interface
* class.
* Contains the DOXYGEN documentation main page.
*/
#pragma once
#include <cstdlib>
#include "GHOST_IContext.hh"
#include "GHOST_ITimerTask.hh"
#include "GHOST_IWindow.hh"
#include "GHOST_Types.hh"
class GHOST_IEventConsumer;
/**
* \page GHOSTPage GHOST
*
* \section intro Introduction
*
* GHOST is yet another acronym. It stands for "Generic Handy Operating System
* Toolkit". It has been created to replace the OpenGL utility tool kit
* <a href="http://www.opengl.org/resources/libraries/glut/">GLUT</a>.
* GLUT was used in <a href="http://www.blender3d.com">Blender</a> until the
* point that Blender needed to be ported to Apple's Mac OSX. Blender needed a
* number of modifications in GLUT to work but the GLUT sources for OSX were
* unavailable at the time. The decision was made to build our own replacement
* for GLUT. In those days, NaN Technologies BV was the company that developed
* Blender.
* <br><br>
* Enough history. What does GHOST have to offer?<br>
* In short: everything that Blender needed from GLUT to run on all its supported
* operating systems and some extra's.
* This includes :
*
* - Time(r) management.
* - Display/window management (windows are only created on the main display).
* - Event management.
* - Cursor shape management (no custom cursors for now).
* - Access to the state of the mouse buttons and the keyboard.
* - Menus for windows with events generated when they are accessed (this is
* work in progress).
* - Video mode switching.
* - Copy/Paste buffers.
* - System paths.
*
* Font management has been moved to a separate library.
*
* \section platforms Platforms
*
* GHOST supports the following platforms:
*
* - macOS Cocoa.
* - Microsoft Windows.
* - X11 (Linux).
* - Wayland (Linux).
* - SDL3.
* - null (headless mode).
*
* \section Building GHOST
*
* GHOST is not build standalone however there are tests in intern/ghost/test
*
* \section interface Interface
* The GHOST C++ API consists of the following files:
*
* - GHOST_IEvent.hh
* - GHOST_IEventConsumer.hh
* - GHOST_ISystem.hh
* - GHOST_ITimerTask.hh
* - GHOST_IWindow.hh
* - GHOST_Rect.hh
* - GHOST_Types.hh
* - GHOST_Xr-api.hh
*
* For an example of using the GHOST API, have a look at the GHOST_Test.cpp
* program in the ?/ghost/test/gears/ directory.
*
* \section work Work in progress
* \todo write WIP section
*/
/** \interface GHOST_ISystem
* Interface for classes that provide access to the operating system.
* There should be only one system class in an application.
* Therefore, the routines to create and dispose the system are static.
* Provides:
* -# Time(r) management.
* -# Display/window management (windows are only created on the main display).
* -# Event management.
* -# Cursor shape management (no custom cursors for now).
* -# Access to the state of the mouse buttons and the keyboard.
* -# Menus for windows with events generated when they are accessed (this is
* work in progress).
*/
class GHOST_ISystem {
public:
/**
* Creates the one and only system.
* \param verbose: report back-ends that were attempted no back-end could be loaded.
* \param background: loading the system for background rendering (no visible windows).
* \return An indication of success.
*/
static GHOST_TSuccess createSystem(bool verbose = true, bool background = false);
static GHOST_TSuccess createSystemBackground();
/**
* Disposes the one and only system.
* \return An indication of success.
*/
static GHOST_TSuccess disposeSystem();
/**
* Returns a pointer to the one and only system (nil if it hasn't been created).
* \return A pointer to the system.
*/
static GHOST_ISystem *getSystem();
/**
* Return an identifier for the one and only system.
* \warning while it may be tempting this should never be used to check for supported features,
* in that case, the GHOST API should be extended to query capabilities.
* This is needed for X11/WAYLAND on Unix, without this - there is no convenient way for users to
* check if WAYLAND or XWAYLAND are in use since they are dynamically selected at startup.
* When dynamically switching between X11/WAYLAND is removed, this function can go too.
*/
static const char *getSystemBackend();
static GHOST_TBacktraceFn getBacktraceFn();
static void setBacktraceFn(GHOST_TBacktraceFn backtrace_fn);
static bool getUseWindowFrame();
static void setUseWindowFrame(bool use_window_frame);
protected:
/**
* Constructor.
* Protected default constructor to force use of static createSystem member.
*/
GHOST_ISystem() {}
/**
* Destructor.
* Protected default constructor to force use of static dispose member.
*/
virtual ~GHOST_ISystem() = default;
public:
/***************************************************************************************
* Time(r) functionality
***************************************************************************************/
/**
* Returns the system time.
* Returns the number of milliseconds since the start of the system.
* \note The exact method used is platform dependent however monotonic methods should be used
* instead of wall-clock time.
*
* \return The number of milliseconds.
*/
virtual uint64_t getMilliSeconds() const = 0;
/**
* Installs a timer.
*
* \note On most operating systems, messages need to be processed in order
* for the timer callbacks to be invoked.
*
* \param delay: The time to wait for the first call to the #timer_proc (in milliseconds).
* \param interval: The interval between calls to the #timer_proc.
* \param timer_proc: The callback invoked when the interval expires.
* \param user_data: Placeholder for user data.
* \return A timer task (0 if timer task installation failed).
*/
virtual GHOST_ITimerTask *installTimer(uint64_t delay,
uint64_t interval,
GHOST_TimerProcPtr timer_proc,
GHOST_TUserDataPtr user_data = nullptr) = 0;
/**
* Removes a timer.
* \param timerTask: Timer task to be removed.
* \return Indication of success.
*/
virtual GHOST_TSuccess removeTimer(GHOST_ITimerTask *timerTask) = 0;
/***************************************************************************************
* Display/window management functionality
***************************************************************************************/
/**
* Returns the number of displays on this system.
* \return The number of displays.
*/
virtual uint8_t getNumDisplays() const = 0;
/**
* Returns the dimensions of the main display on this system.
* \return The dimension of the main display.
*/
virtual void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const = 0;
/**
* Returns the combine dimensions of all monitors.
* \return The dimension of the workspace.
*/
virtual void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const = 0;
/**
* Create a new window.
* The new window is added to the list of windows managed.
* Never explicitly delete the window, use disposeWindow() instead.
* \param title: The name of the window
* (displayed in the title bar of the window if the OS supports it).
* \param left: The coordinate of the left edge of the window.
* \param top: The coordinate of the top edge of the window.
* \param width: The width the window.
* \param height: The height the window.
* \param state: The state of the window when opened.
* \param gpu_settings: Misc GPU settings.
* \param exclusive: Use to show the window on top and ignore others (used full-screen).
* \param is_dialog: Stay on top of parent window, no icon in taskbar, can't be minimized.
* \param parent_window: Parent (embedder) window
* \return The new window (or 0 if creation failed).
*/
virtual GHOST_IWindow *createWindow(const char *title,
int32_t left,
int32_t top,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
const bool exclusive = false,
const bool is_dialog = false,
const GHOST_IWindow *parent_window = nullptr) = 0;
/**
* Dispose a window.
* \param window: Pointer to the window to be disposed.
* \return Indication of success.
*/
virtual GHOST_TSuccess disposeWindow(GHOST_IWindow *window) = 0;
/**
* Create a new off-screen context.
* Never explicitly delete the context, use #disposeContext() instead.
* \return The new context (or 0 if creation failed).
*/
virtual GHOST_IContext *createOffscreenContext(GHOST_GPUSettings gpu_settings) = 0;
/**
* Dispose of a context.
* \param context: Pointer to the context to be disposed.
* \return Indication of success.
*/
virtual GHOST_TSuccess disposeContext(GHOST_IContext *context) = 0;
/**
* Returns whether a window is valid.
* \param window: Pointer to the window to be checked.
* \return Indication of validity.
*/
virtual bool validWindow(GHOST_IWindow *window) = 0;
/**
* Native pixel size support (MacBook 'retina').
* \return The pixel size in float.
*/
virtual bool useNativePixel() = 0;
/**
* Return features supported by the system back-end.
*
* The resulting value doesn't change at run-time.
*/
virtual GHOST_TCapabilityFlag getCapabilities() const = 0;
/**
* Focus window after opening, or put them in the background.
*/
virtual void useWindowFocus(const bool use_focus) = 0;
/**
* Focus and raise windows on mouse hover.
*/
virtual void setAutoFocus(const bool auto_focus) = 0;
/**
* Get the Window under the cursor. Although coordinates of the mouse are supplied, platform-
* specific implementations are free to ignore these and query the mouse location themselves, due
* to them possibly being incorrect under certain conditions, for example when using multiple
* monitors that vary in scale and/or DPI.
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return The window under the cursor or nullptr if none.
*/
virtual GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y) = 0;
/***************************************************************************************
* Event management functionality
***************************************************************************************/
/**
* Retrieves events from the system and stores them in the queue.
* \param waitForEvent: Flag to wait for an event (or return immediately).
* \return Indication of the presence of events.
*/
virtual bool processEvents(bool waitForEvent) = 0;
/**
* Retrieves events from the queue and send them to the event consumers.
* The event stack will be empty afterwards.
*/
virtual void dispatchEvents() = 0;
/**
* Adds the given event consumer to our list.
* \param consumer: The event consumer to add.
* \return Indication of success.
*/
virtual GHOST_TSuccess addEventConsumer(GHOST_IEventConsumer *consumer) = 0;
/**
* Removes the given event consumer to our list.
* \param consumer: The event consumer to remove.
* \return Indication of success.
*/
virtual GHOST_TSuccess removeEventConsumer(GHOST_IEventConsumer *consumer) = 0;
/***************************************************************************************
* Cursor management functionality
***************************************************************************************/
/**
* Returns the current location of the cursor (location in window coordinates)
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
virtual GHOST_TSuccess getCursorPositionClientRelative(const GHOST_IWindow *window,
int32_t &x,
int32_t &y) const = 0;
/**
* Updates the location of the cursor (location in window coordinates).
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
virtual GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window,
int32_t x,
int32_t y) = 0;
/**
* Returns the current location of the cursor (location in screen coordinates)
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
virtual GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const = 0;
/**
* \return the size of the cursor in logical pixels (before Hi-DPI scaling is applied).
*/
virtual uint32_t getCursorPreferredLogicalSize() const = 0;
/**
* Updates the location of the cursor (location in screen coordinates).
* Not all operating systems allow the cursor to be moved (without the input device being moved).
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
virtual GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) = 0;
/***************************************************************************************
* Access to mouse button and keyboard states.
***************************************************************************************/
/**
* Returns the state of a modifier key (outside the message queue).
* \param mask: The modifier key state to retrieve.
* \param is_down: The state of a modifier key (true == pressed).
* \return Indication of success.
*/
virtual GHOST_TSuccess getModifierKeyState(GHOST_TModifierKey mask, bool &is_down) const = 0;
/**
* Returns the state of a mouse button (outside the message queue).
* \param mask: The button state to retrieve.
* \param is_down: Button state.
* \return Indication of success.
*/
virtual GHOST_TSuccess getButtonState(GHOST_TButton mask, bool &is_down) const = 0;
/**
* Enable multi-touch gestures if supported.
* \param use: Enable or disable.
*/
virtual void setMultitouchGestures(const bool use) = 0;
/**
* Set which tablet API to use. Only affects Windows, other platforms have a single API.
* \param api: Enum indicating which API to use.
*/
virtual void setTabletAPI(GHOST_TTabletAPI api) = 0;
/**
* Get the color of the pixel at the current mouse cursor location
* \param r_color: returned sRGB float colors
* \return Success value (true == successful and supported by platform)
*/
virtual GHOST_TSuccess getPixelAtCursor(float r_color[3]) const = 0;
#ifdef WITH_INPUT_NDOF
/**
* Sets 3D mouse dead-zone.
* \param deadzone: Dead-zone of the 3D mouse (both for rotation and pan) relative to full range
*/
virtual void setNDOFDeadZone(float deadzone) = 0;
#endif
/**
* Set the Console State
* \param action: console state
* \return current status (true: visible, 0: hidden)
*/
virtual bool setConsoleWindowState(GHOST_TConsoleWindowState action) = 0;
/***************************************************************************************
* Access to clipboard.
***************************************************************************************/
/**
* Return the clipboard buffer or null.
*
* \param selection: Use the "primary" selection.
* Check the #GHOST_kCapabilityClipboardPrimary for backends that support this.
* \return Returns the clipboard data as a null terminated string or null when unavailable.
*/
virtual char *getClipboard(bool selection) const = 0;
/**
* Put data to the Clipboard
* \param buffer: The buffer to copy to the clipboard.
* \param selection: The clipboard to copy too only used on X11.
*/
virtual void putClipboard(const char *buffer, bool selection) const = 0;
/**
* Returns GHOST_kSuccess if the clipboard contains an image.
*/
virtual GHOST_TSuccess hasClipboardImage() const = 0;
/**
* Get image data from the Clipboard
* \param r_width: the returned image width in pixels.
* \param r_height: the returned image height in pixels.
* \return pointer uint array in RGBA byte order. Caller must free.
*/
virtual uint *getClipboardImage(int *r_width, int *r_height) const = 0;
/**
* Put image data to the Clipboard
* \param rgba: uint array in RGBA byte order.
* \param width: the image width in pixels.
* \param height: the image height in pixels.
*/
virtual GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const = 0;
/***************************************************************************************
* Window "Client Side Decorations" (CSD)
***************************************************************************************/
virtual void setWindowCSD(const GHOST_CSD_Params &params) = 0;
virtual const GHOST_CSD_Layout &getWindowCSD_Layout() const = 0;
/***************************************************************************************
* Window Icon Generator.
***************************************************************************************/
/**
* Set a callback used to generate window icons.
*
* Back-ends that support window icons will call this to fill pixel data.
*/
virtual void setIconGenerator(const GHOST_IconGenerator *icon_generator) = 0;
/***************************************************************************************
* System Message Box.
***************************************************************************************/
/**
* Show a system message box
*
* \param title: The title of the message box.
* \param message: The message to display.
* \param help_label: Help button label.
* \param continue_label: Continue button label.
* \param link: An optional hyperlink.
* \param dialog_options: Options how to display the message.
*/
virtual GHOST_TSuccess showMessageBox(const char * /*title*/,
const char * /*message*/,
const char * /*help_label*/,
const char * /*continue_label*/,
const char * /*link*/,
GHOST_DialogOptions /*dialog_options*/) const = 0;
/***************************************************************************************
* Debugging
***************************************************************************************/
/**
* Specify whether debug messages are to be shown.
* \param debug: Flag for systems to debug.
*/
virtual void initDebug(GHOST_Debug debug) = 0;
/**
* Check whether debug messages are to be shown.
*/
virtual bool isDebugEnabled() = 0;
protected:
/**
* Initialize the system.
* \return Indication of success.
*/
virtual GHOST_TSuccess init() = 0;
/**
* Shut the system down.
* \return Indication of success.
*/
virtual GHOST_TSuccess exit() = 0;
/** The one and only system */
static GHOST_ISystem *system_;
static const char *system_backend_id_;
/** Function to call that sets the back-trace. */
static GHOST_TBacktraceFn backtrace_fn_;
/**
* When false, don't use window frame.
*/
static bool use_window_frame_;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_ISystem")
};

View File

@@ -0,0 +1,101 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include <optional>
#include <string>
#include "GHOST_Types.hh"
class GHOST_ISystemPaths {
public:
/**
* Creates the one and only system.
* \return An indication of success.
*/
static GHOST_TSuccess create();
/**
* Disposes the one and only system.
* \return An indication of success.
*/
static GHOST_TSuccess dispose();
/**
* Returns a pointer to the one and only system.
* \return A pointer to the system.
*/
static GHOST_ISystemPaths *get();
protected:
/**
* Constructor.
* Protected default constructor to force use of static createSystem member.
*/
GHOST_ISystemPaths() {}
/**
* Destructor.
* Protected default constructor to force use of static dispose member.
*/
virtual ~GHOST_ISystemPaths() = default;
public:
/**
* Determine the base directory in which shared resources are located. It will first try to use
* "unpack and run" path, then look for properly installed path, including versioning.
* \return Unsigned char string pointing to system directory (eg `/usr/share/blender/`).
*/
virtual const char *getSystemDir(int version, const char *versionstr) const = 0;
/**
* The base directory where architecture-dependent files are located,
* mirroring #getSystemDir under the install lib tree (eg `/usr/lib/blender/`).
*
* \return String pointing to the system libraries directory,
* or null when this build has no separate system library directory.
*
* For portable builds this function always returns null, for specific details
* see CMake's `BLENDER_INSTALL_LIBDIR` define for when this is/isn't used and why.
*/
virtual const char *getSystemLibsDir(int /*version*/, const char * /*versionstr*/) const
{
return nullptr;
}
/**
* Determine the base directory in which user configuration is stored, including versioning.
* If needed, it will create the base directory.
* \return Unsigned char string pointing to user directory (eg `~/.blender/`).
*/
virtual const char *getUserDir(int version, const char *versionstr) const = 0;
/**
* Determine a special ("well known") and easy to reach user directory.
* \return If successfull, a string containing the user directory path (eg `~/Documents/`).
*/
virtual std::optional<std::string> getUserSpecialDir(GHOST_TUserSpecialDirTypes type) const = 0;
/**
* Determine the directory of the current binary
* \return Unsigned char string pointing to the binary directory.
*/
virtual const char *getBinaryDir() const = 0;
/**
* Add the file to the operating system most recently used files
*/
virtual void addToSystemRecentFiles(const char *filepath) const = 0;
private:
/** The one and only system paths. */
static GHOST_ISystemPaths *system_paths_;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_ISystemPaths")
};

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_ITimerTask interface class.
*/
#pragma once
#include "GHOST_Types.hh"
/**
* Interface for a timer task.
* Timer tasks are created by the system and can be installed by the system.
* After installation, the timer callback-procedure or "timer_proc" will be called
* periodically. You should not need to inherit this class. It is passed to the
* application in the timer-callback.<br>
* <br>
* Note that GHOST processes timers in the UI thread. You should ask GHOST
* process messages in order for the timer-callbacks to be called.
* \see GHOST_ISystem#installTimer
* \see GHOST_TimerProcPtr
*/
class GHOST_ITimerTask {
public:
/**
* Destructor.
*/
virtual ~GHOST_ITimerTask() = default;
/**
* Returns the timer callback.
* \return The timer callback.
*/
virtual GHOST_TimerProcPtr getTimerProc() const = 0;
/**
* Changes the timer callback.
* \param timer_proc: The timer callback.
*/
virtual void setTimerProc(const GHOST_TimerProcPtr timer_proc) = 0;
/**
* Returns the timer user data.
* \return The timer user data.
*/
virtual GHOST_TUserDataPtr getUserData() const = 0;
/**
* Changes the time user data.
* \param user_data: The timer user data.
*/
virtual void setUserData(const GHOST_TUserDataPtr user_data) = 0;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_ITimerTask")
};

View File

@@ -0,0 +1,387 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_IWindow interface class.
*/
#pragma once
#include "GHOST_Rect.hh"
#include "GHOST_Types.hh"
#include <cstdlib>
#include <string>
class GHOST_IContext;
/**
* Interface for GHOST windows.
*
* You can create a window with the system's GHOST_ISystem::createWindow
* method.
* \see GHOST_ISystem#createWindow
*
* There are two coordinate systems:
*
* - The screen coordinate system. The origin of the screen is located in the
* upper left corner of the screen.
* - The client rectangle coordinate system. The client rectangle of a window
* is the area that is drawable by the application (excluding title bars etc.).
*/
class GHOST_IWindow {
public:
/**
* Destructor.
*/
virtual ~GHOST_IWindow() = default;
/**
* Returns indication as to whether the window is valid.
* \return The validity of the window.
*/
virtual bool getValid() const = 0;
/**
* Returns the type of drawing context used in this window.
* \return The current type of drawing context.
*/
virtual GHOST_TDrawingContextType getDrawingContextType() = 0;
/**
* Tries to install a rendering context in this window.
* \param type: The type of rendering context installed.
* \return Indication as to whether installation has succeeded.
*/
virtual GHOST_TSuccess setDrawingContextType(GHOST_TDrawingContextType type) = 0;
/**
* Returns the drawing context used in this window.
* \return The current drawing context.
*/
virtual GHOST_IContext *getDrawingContext() = 0;
/**
* Sets the title displayed in the title bar.
* \param title: The title to display in the title bar.
*/
virtual void setTitle(const char *title) = 0;
/**
* Returns the title displayed in the title bar.
* \return The title displayed in the title bar.
*/
virtual std::string getTitle() const = 0;
/**
* Sets the file name represented by this window.
* \param filepath: The file directory.
*/
virtual void setPath(const char *filepath) = 0;
/**
* Return the current window decoration style flags.
*/
virtual GHOST_TWindowDecorationStyleFlags getWindowDecorationStyleFlags() = 0;
/**
* Set the window decoration style flags.
* \param style_flags: Window decoration style flags.
*/
virtual void setWindowDecorationStyleFlags(GHOST_TWindowDecorationStyleFlags style_flags) = 0;
/**
* Set the window decoration style settings.
* \param decoration_settings: Window decoration style settings.
*/
virtual void setWindowDecorationStyleSettings(
GHOST_WindowDecorationStyleSettings decoration_settings) = 0;
/**
* Apply the window decoration style using the current flags and settings.
*/
virtual GHOST_TSuccess applyWindowDecorationStyle() = 0;
/**
* Returns the window rectangle dimensions.
* These are screen coordinates.
* \param bounds: The bounding rectangle of the window.
*/
virtual void getWindowBounds(GHOST_Rect &bounds) const = 0;
/**
* Returns the client rectangle dimensions.
* The left and top members of the rectangle are always zero.
* \param bounds: The bounding rectangle of the client area of the window.
*/
virtual void getClientBounds(GHOST_Rect &bounds) const = 0;
/**
* Resizes client rectangle width.
* \param width: The new width of the client area of the window.
*/
virtual GHOST_TSuccess setClientWidth(uint32_t width) = 0;
/**
* Resizes client rectangle height.
* \param height: The new height of the client area of the window.
*/
virtual GHOST_TSuccess setClientHeight(uint32_t height) = 0;
/**
* Resizes client rectangle.
* \param width: The new width of the client area of the window.
* \param height: The new height of the client area of the window.
*/
virtual GHOST_TSuccess setClientSize(uint32_t width, uint32_t height) = 0;
/**
* Converts a point in screen coordinates to client rectangle coordinates
* \param inX: The x-coordinate on the screen.
* \param inY: The y-coordinate on the screen.
* \param outX: The x-coordinate in the client rectangle.
* \param outY: The y-coordinate in the client rectangle.
*/
virtual void screenToClient(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const = 0;
/**
* Converts a point in client rectangle coordinates to screen coordinates.
* \param inX: The x-coordinate in the client rectangle.
* \param inY: The y-coordinate in the client rectangle.
* \param outX: The x-coordinate on the screen.
* \param outY: The y-coordinate on the screen.
*/
virtual void clientToScreen(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const = 0;
/**
* Tells if the ongoing drag & drop object can be accepted upon mouse drop
*/
virtual void setAcceptDragOperation(bool can_accept) = 0;
/**
* Returns the state of the window (normal, minimized, maximized).
* \return The state of the window.
*/
virtual GHOST_TWindowState getState() const = 0;
/**
* Sets the state of the window (normal, minimized, maximized).
* \param state: The state of the window.
* \return Indication of success.
*/
virtual GHOST_TSuccess setState(GHOST_TWindowState state) = 0;
/**
* Sets the window "modified" status, indicating unsaved changes.
* \param is_unsaved_changes: Unsaved changes or not.
* \return Indication of success.
*/
virtual GHOST_TSuccess setModifiedState(bool is_unsaved_changes) = 0;
/**
* Sets the order of the window (bottom, top).
* \param order: The order of the window.
* \return Indication of success.
*/
virtual GHOST_TSuccess setOrder(GHOST_TWindowOrder order) = 0;
/**
* Acquire the next buffer of the swap chain.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess swapBufferAcquire() = 0;
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess swapBufferRelease() = 0;
/**
* Sets the swap interval for #swapBuffers.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess setSwapInterval(int interval) = 0;
/**
* Gets the current swap interval for #swapBuffers.
* \param interval_out: pointer to location to return swap interval.
* (left untouched if there is an error)
* \return A boolean success indicator of if swap interval was successfully read.
*/
virtual GHOST_TSuccess getSwapInterval(int &interval_out) = 0;
/**
* Activates the drawing context of this window.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess activateDrawingContext() = 0;
/**
* Gets the OpenGL frame-buffer associated with the window's contents.
* \return The name of an OpenGL frame-buffer object.
*/
virtual unsigned int getDefaultFramebuffer() = 0;
#ifdef WITH_VULKAN_BACKEND
/** \copydoc #GHOST_GetVulkanSwapChainFormat */
virtual GHOST_TSuccess getVulkanSwapChainFormat(
GHOST_VulkanSwapChainData *r_swap_chain_data) = 0;
#endif
/**
* Invalidates the contents of this window.
* \return Indication of success.
*/
virtual GHOST_TSuccess invalidate() = 0;
/**
* Returns the window user data.
* \return The window user data.
*/
virtual GHOST_TUserDataPtr getUserData() const = 0;
/**
* Changes the window user data.
* \param user_data: The window user data.
*/
virtual void setUserData(const GHOST_TUserDataPtr user_data) = 0;
virtual bool isDialog() const = 0;
/***************************************************************************************
* Progress bar functionality
***************************************************************************************/
/**
* Sets the progress bar value displayed in the window/application icon
* \param progress: The progress percentage (0.0 to 1.0).
*/
virtual GHOST_TSuccess setProgressBar(float progress) = 0;
/**
* Hides the progress bar in the icon.
*/
virtual GHOST_TSuccess endProgressBar() = 0;
/***************************************************************************************
* Cursor management functionality
***************************************************************************************/
/**
* Returns the current cursor shape.
* \return The current cursor shape.
*/
virtual GHOST_TStandardCursor getCursorShape() const = 0;
/**
* Set the shape of the cursor.
* \param cursor_shape: The new cursor shape type id.
* \return Indication of success.
*/
virtual GHOST_TSuccess setCursorShape(GHOST_TStandardCursor cursor_shape) = 0;
virtual void getCursorGrabState(GHOST_TGrabCursorMode &mode,
GHOST_TAxisFlag &axis_flag,
GHOST_Rect &bounds,
bool &use_software_cursor) = 0;
/**
* Test if the standard cursor shape is supported by current platform.
* \return Indication of success.
*/
virtual GHOST_TSuccess hasCursorShape(GHOST_TStandardCursor cursor_shape) = 0;
/**
* Set the shape of the cursor to a custom cursor.
* \param bitmap: The bitmap data for the cursor.
* \param mask: The mask data for the cursor.
* \param size: The X,Y size of the cursor in pixels.
* \param hot_spot: The X,Y coordinate of the cursor hot-spot.
* \return Indication of success.
*/
virtual GHOST_TSuccess setCustomCursorShape(const uint8_t *bitmap,
const uint8_t *mask,
const int size[2],
const int hot_spot[2],
bool can_invert_color) = 0;
/**
* Set the cursor generator.
*
* \param cursor_generator: An object which generates cursors.
* Ownership is transferred to GHOST which is responsible for calling it's free method.
*/
virtual GHOST_TSuccess setCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) = 0;
virtual GHOST_TSuccess getCursorBitmap(GHOST_CursorBitmapRef *bitmap) = 0;
/**
* Returns the visibility state of the cursor.
* \return The visibility state of the cursor.
*/
virtual bool getCursorVisibility() const = 0;
/**
* Shows or hides the cursor.
* \param visible: The new visibility state of the cursor.
* \return Indication of success.
*/
virtual GHOST_TSuccess setCursorVisibility(bool visible) = 0;
/**
* Grabs the cursor for a modal operation.
* \param mode: The new grab mode of the cursor.
* \return Indication of success.
*/
virtual GHOST_TSuccess setCursorGrab(GHOST_TGrabCursorMode /*mode*/,
GHOST_TAxisFlag /*wrap_axis*/,
GHOST_Rect * /*bounds*/,
int32_t /*mouse_ungrab_xy*/[2])
{
return GHOST_kSuccess;
}
/**
* If this window was opened using native pixel size, return the scaling factor.
*/
virtual float getNativePixelSize() = 0;
/**
* Returns the recommended DPI for this window.
* \return The recommended DPI for this window.
*/
virtual uint16_t getDPIHint() = 0;
/**
* Returns high dynamic range color information about this window.
* \return HDR info.
* */
virtual GHOST_WindowHDRInfo getHDRInfo() = 0;
#ifdef WITH_INPUT_IME
/**
* Enable IME attached to the given window, i.e. allows user-input
* events to be dispatched to the IME.
* \param x: Requested x-coordinate of the rectangle.
* \param y: Requested y-coordinate of the rectangle.
* \param w: Requested width of the rectangle.
* \param h: Requested height of the rectangle.
* \param completed: Whether or not to complete the ongoing composition.
* - true: Start a new composition
* - false: Move the IME windows to the given position without finishing it.
*/
virtual void beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed) = 0;
/**
* Disable the IME attached to the given window, i.e. prohibits any user-input
* events from being dispatched to the IME.
*/
virtual void endIME() = 0;
#endif /* WITH_INPUT_IME */
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_IWindow")
};

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_Types.hh"
class GHOST_XrSession;
struct GHOST_XrSessionBeginInfo;
class GHOST_IXrContext {
public:
virtual ~GHOST_IXrContext() = default;
virtual void startSession(const GHOST_XrSessionBeginInfo *begin_info) = 0;
virtual void endSession() = 0;
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,
GHOST_XrGraphicsContextUnbindFn unbind_fn) = 0;
virtual void setDrawViewFunc(GHOST_XrDrawViewFn draw_view_fn) = 0;
virtual void setPassthroughEnabledFunc(GHOST_XrPassthroughEnabledFn passthrough_enabled_fn) = 0;
virtual void setDisablePassthroughFunc(GHOST_XrDisablePassthroughFn disable_passthrough_fn) = 0;
virtual bool needsUpsideDownDrawing() const = 0;
};

View File

@@ -0,0 +1,273 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Macro's used in GHOST debug target.
*/
#pragma once
#include "GHOST_Types.hh"
/**
* Implements rectangle functionality.
* The four extreme coordinates are stored as left, top, right and bottom.
* To be valid, a rectangle should have a left coordinate smaller than or equal to right.
* To be valid, a rectangle should have a top coordinate smaller than or equal to bottom.
*/
class GHOST_Rect {
public:
/**
* Constructs a rectangle with the given values.
* \param l: requested left coordinate of the rectangle.
* \param t: requested top coordinate of the rectangle.
* \param r: requested right coordinate of the rectangle.
* \param b: requested bottom coordinate of the rectangle.
*/
GHOST_Rect(int32_t l = 0, int32_t t = 0, int32_t r = 0, int32_t b = 0)
: l_(l), t_(t), r_(r), b_(b)
{
}
/**
* Destructor.
*/
virtual ~GHOST_Rect() = default;
/**
* Access to rectangle width.
* \return width of the rectangle.
*/
virtual inline int32_t getWidth() const;
/**
* Access to rectangle height.
* \return height of the rectangle.
*/
virtual inline int32_t getHeight() const;
/**
* Sets all members of the rectangle.
* \param l: requested left coordinate of the rectangle.
* \param t: requested top coordinate of the rectangle.
* \param r: requested right coordinate of the rectangle.
* \param b: requested bottom coordinate of the rectangle.
*/
virtual inline void set(int32_t l, int32_t t, int32_t r, int32_t b);
/**
* Returns whether this rectangle is empty.
* Empty rectangles are rectangles that have width==0 and/or height==0.
* \return boolean value (true==empty rectangle)
*/
virtual inline bool isEmpty() const;
/**
* Returns whether this rectangle is valid.
* Valid rectangles are rectangles that have l_ <= r_ and t_ <= b_.
* Thus, empty rectangles are valid.
* \return boolean value (true==valid rectangle)
*/
virtual inline bool isValid() const;
/**
* Grows (or shrinks the rectangle).
* The method avoids negative insets making the rectangle invalid
* \param i: The amount of offset given to each extreme (negative values shrink the rectangle).
*/
virtual void inset(int32_t i);
/**
* Does a union of the rectangle given and this rectangle.
* The result is stored in this rectangle.
* \param r: The rectangle that is input for the union operation.
*/
virtual inline void unionRect(const GHOST_Rect &r);
/**
* Grows the rectangle to included a point.
* \param x: The x-coordinate of the point.
* \param y: The y-coordinate of the point.
*/
virtual inline void unionPoint(int32_t x, int32_t y);
/**
* Grows the rectangle to included a point.
* \param x: The x-coordinate of the point.
* \param y: The y-coordinate of the point.
*/
virtual inline void wrapPoint(int32_t &x, int32_t &y, int32_t ofs, GHOST_TAxisFlag axis);
/**
* Confine x & y within the rectangle (inclusive).
* \param x: The x-coordinate of the point.
* \param y: The y-coordinate of the point.
*/
virtual inline void clampPoint(int32_t &x, int32_t &y);
/**
* Returns whether the point is inside this rectangle.
* Point on the boundary is considered inside.
* \param x: x-coordinate of point to test.
* \param y: y-coordinate of point to test.
* \return boolean value (true if point is inside).
*/
virtual inline bool isInside(int32_t x, int32_t y) const;
/**
* Returns whether the rectangle is inside this rectangle.
* \param r: rectangle to test.
* \return visibility (not, partially or fully visible).
*/
virtual GHOST_TVisibility getVisibility(GHOST_Rect &r) const;
/**
* Sets rectangle members.
* Sets rectangle members such that it is centered at the given location.
* \param cx: requested center x-coordinate of the rectangle.
* \param cy: requested center y-coordinate of the rectangle.
*/
virtual void setCenter(int32_t cx, int32_t cy);
/**
* Sets rectangle members.
* Sets rectangle members such that it is centered at the given location,
* with the width requested.
* \param cx: requested center x-coordinate of the rectangle.
* \param cy: requested center y-coordinate of the rectangle.
* \param w: requested width of the rectangle.
* \param h: requested height of the rectangle.
*/
virtual void setCenter(int32_t cx, int32_t cy, int32_t w, int32_t h);
/**
* Clips a rectangle.
* Updates the rectangle given such that it will fit within this one.
* This can result in an empty rectangle.
* \param r: the rectangle to clip.
* \return whether clipping has occurred
*/
virtual bool clip(GHOST_Rect &r) const;
/** Left coordinate of the rectangle */
int32_t l_;
/** Top coordinate of the rectangle */
int32_t t_;
/** Right coordinate of the rectangle */
int32_t r_;
/** Bottom coordinate of the rectangle */
int32_t b_;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_Rect")
};
inline int32_t GHOST_Rect::getWidth() const
{
return r_ - l_;
}
inline int32_t GHOST_Rect::getHeight() const
{
return b_ - t_;
}
inline void GHOST_Rect::set(int32_t l, int32_t t, int32_t r, int32_t b)
{
l_ = l;
t_ = t;
r_ = r;
b_ = b;
}
inline bool GHOST_Rect::isEmpty() const
{
return (getWidth() == 0) || (getHeight() == 0);
}
inline bool GHOST_Rect::isValid() const
{
return (l_ <= r_) && (t_ <= b_);
}
inline void GHOST_Rect::unionRect(const GHOST_Rect &r)
{
if (r.l_ < l_) {
l_ = r.l_;
}
if (r.r_ > r_) {
r_ = r.r_;
}
if (r.t_ < t_) {
t_ = r.t_;
}
if (r.b_ > b_) {
b_ = r.b_;
}
}
inline void GHOST_Rect::unionPoint(int32_t x, int32_t y)
{
if (x < l_) {
l_ = x;
}
if (x > r_) {
r_ = x;
}
if (y < t_) {
t_ = y;
}
if (y > b_) {
b_ = y;
}
}
inline void GHOST_Rect::wrapPoint(int32_t &x, int32_t &y, int32_t ofs, GHOST_TAxisFlag axis)
{
int32_t w = getWidth();
int32_t h = getHeight();
/* highly unlikely but avoid eternal loop */
if (w - ofs * 2 <= 0 || h - ofs * 2 <= 0) {
return;
}
if (axis & GHOST_kAxisX) {
while (x - ofs < l_) {
x += w - (ofs * 2);
}
while (x + ofs > r_) {
x -= w - (ofs * 2);
}
}
if (axis & GHOST_kAxisY) {
while (y - ofs < t_) {
y += h - (ofs * 2);
}
while (y + ofs > b_) {
y -= h - (ofs * 2);
}
}
}
inline void GHOST_Rect::clampPoint(int32_t &x, int32_t &y)
{
if (x < l_) {
x = l_;
}
else if (x > r_) {
x = r_;
}
if (y < t_) {
y = t_;
}
else if (y > b_) {
y = b_;
}
}
inline bool GHOST_Rect::isInside(int32_t x, int32_t y) const
{
return (x >= l_) && (x <= r_) && (y >= t_) && (y <= b_);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,245 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* \brief Abstraction for XR (VR, AR, MR, ..) access via OpenXR.
*/
#pragma once
#include "GHOST_IXrContext.hh"
#include "GHOST_Types.hh"
/* XR-context */
/**
* Set a custom callback to be executed whenever an error occurs. Should be set before calling
* #GHOST_XrContextCreate() to get error handling during context creation too.
*
* \param customdata: Handle to some data that will get passed to \a handler_fn should an error be
* thrown.
*/
void GHOST_XrErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata);
/**
* \brief Initialize the Ghost XR-context.
*
* Includes setting up the OpenXR runtime link, querying available extensions and API layers,
* enabling extensions and API layers.
*
* \param create_info: Options for creating the XR-context, e.g. debug-flags and ordered array of
* graphics bindings to try enabling.
*/
GHOST_IXrContext *GHOST_XrContextCreate(const GHOST_XrContextCreateInfo *create_info);
/**
* Free a XR-context involving OpenXR runtime link destruction and freeing of all internal data.
*/
void GHOST_XrContextDestroy(GHOST_IXrContext *xr_context);
/**
* Set callbacks for binding and unbinding a graphics context for a session. The binding callback
* may create a new graphics context thereby. In fact that's the sole reason for this callback
* approach to binding. Just make sure to have an unbind function set that properly destructs.
*
* \param bind_fn: Function to retrieve (possibly create) a graphics context.
* \param unbind_fn: Function to release (possibly free) a graphics context.
*/
void GHOST_XrGraphicsContextBindFuncs(GHOST_IXrContext *xr_context,
GHOST_XrGraphicsContextBindFn bind_fn,
GHOST_XrGraphicsContextUnbindFn unbind_fn);
/**
* Set the drawing callback for views. A view would typically be either the left or the right eye,
* although other configurations are possible. When #GHOST_XrSessionDrawViews() is called to draw
* an XR frame, \a draw_view_fn is executed for each view.
*
* \param draw_view_fn: The callback to draw a single view for an XR frame.
*/
void GHOST_XrDrawViewFunc(GHOST_IXrContext *xr_context, GHOST_XrDrawViewFn draw_view_fn);
/**
* Set the callback to check if passthrough is enabled.
* If enabled, the passthrough composition layer is added in GHOST_XrSession::draw().
*
* \param passthrough_enabled_fn: The callback to check if passthrough is enabled.
*/
void GHOST_XrPassthroughEnabledFunc(GHOST_IXrContext *xr_context,
GHOST_XrPassthroughEnabledFn passthrough_enabled_fn);
/**
* Set the callback to force disable passthrough in case it is not supported.
* Called in GHOST_XrSession::draw().
*
* \param disable_passthrough_fn: The callback to disable passthrough.
*/
void GHOST_XrDisablePassthroughFunc(GHOST_IXrContext *xr_context,
GHOST_XrDisablePassthroughFn disable_passthrough_fn);
/* sessions */
/**
* Create internal session data for \a xr_context and ask the OpenXR runtime to invoke a session.
*
* \param begin_info: Options for the session creation.
*/
void GHOST_XrSessionStart(GHOST_IXrContext *xr_context,
const GHOST_XrSessionBeginInfo *begin_info);
/**
* Destruct internal session data for \a xr_context and ask the OpenXR runtime to stop a session.
*/
void GHOST_XrSessionEnd(GHOST_IXrContext *xr_context);
/**
* Draw a single frame by calling the view drawing callback defined by #GHOST_XrDrawViewFunc() for
* each view and submit it to the OpenXR runtime.
*
* \param customdata: Handle to some data that will get passed to the view drawing callback.
*/
void GHOST_XrSessionDrawViews(GHOST_IXrContext *xr_context, void *customdata);
/**
* Check if a \a xr_context has a session that, according to the OpenXR definition would be
* considered to be 'running'
* (https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#session_running).
*/
int GHOST_XrSessionIsRunning(const GHOST_IXrContext *xr_context);
/**
* Check if \a xr_context has a session that requires an upside-down frame-buffer (compared to
* GPU). If true, the render result should be flipped vertically for correct output.
* \note Only to be called after session start, may otherwise result in a false negative.
*/
int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_IXrContext *xr_context);
/* events */
/**
* Invoke handling of all OpenXR events for \a xr_context. Should be called on every main-loop
* iteration and will early-exit if \a xr_context is nullptr (so caller doesn't have to check).
*
* \returns GHOST_kSuccess if any event was handled, otherwise GHOST_kFailure.
*/
GHOST_TSuccess GHOST_XrEventsHandle(GHOST_IXrContext *xr_context);
/* actions */
/**
* Create an OpenXR action set for input/output.
*/
int GHOST_XrCreateActionSet(GHOST_IXrContext *xr_context, const GHOST_XrActionSetInfo *info);
/**
* Destroy a previously created OpenXR action set.
*/
void GHOST_XrDestroyActionSet(GHOST_IXrContext *xr_context, const char *action_set_name);
/**
* Create OpenXR input/output actions.
*/
int GHOST_XrCreateActions(GHOST_IXrContext *xr_context,
const char *action_set_name,
uint32_t count,
const GHOST_XrActionInfo *infos);
/**
* Destroy previously created OpenXR actions.
*/
void GHOST_XrDestroyActions(GHOST_IXrContext *xr_context,
const char *action_set_name,
uint32_t count,
const char *const *action_names);
/**
* Create input/output path bindings for OpenXR actions.
*/
int GHOST_XrCreateActionBindings(GHOST_IXrContext *xr_context,
const char *action_set_name,
uint32_t count,
const GHOST_XrActionProfileInfo *infos);
/**
* Destroy previously created bindings for OpenXR actions.
*/
void GHOST_XrDestroyActionBindings(GHOST_IXrContext *xr_context,
const char *action_set_name,
uint32_t count,
const char *const *action_names,
const char *const *profile_paths);
/**
* Attach all created action sets to the current OpenXR session.
*/
int GHOST_XrAttachActionSets(GHOST_IXrContext *xr_context);
/**
* Update button/tracking states for OpenXR actions.
*
* \param action_set_name: The name of the action set to sync. If nullptr, all action sets
* attached to the session will be synced.
*/
int GHOST_XrSyncActions(GHOST_IXrContext *xr_context, const char *action_set_name);
/**
* Apply an OpenXR haptic output action.
*/
int GHOST_XrApplyHapticAction(GHOST_IXrContext *xr_context_handle,
const char *action_set_name,
const char *action_name,
const char *subaction_path,
const int64_t *duration,
const float *frequency,
const float *amplitude);
/**
* Stop a previously applied OpenXR haptic output action.
*/
void GHOST_XrStopHapticAction(GHOST_IXrContext *xr_context_handle,
const char *action_set_name,
const char *action_name,
const char *subaction_path);
/**
* Get action set custom data (owned by Blender, not GHOST).
*/
void *GHOST_XrGetActionSetCustomdata(GHOST_IXrContext *xr_context, const char *action_set_name);
/**
* Get action custom data (owned by Blender, not GHOST).
*/
void *GHOST_XrGetActionCustomdata(GHOST_IXrContext *xr_context,
const char *action_set_name,
const char *action_name);
/**
* Get the number of actions in an action set.
*/
unsigned int GHOST_XrGetActionCount(GHOST_IXrContext *xr_context, const char *action_set_name);
/**
* Get custom data for all actions in an action set.
*/
void GHOST_XrGetActionCustomdataArray(GHOST_IXrContext *xr_context,
const char *action_set_name,
void **r_customdata_array);
/* controller model */
/**
* Load the OpenXR controller model.
*/
int GHOST_XrLoadControllerModel(GHOST_IXrContext *xr_context, const char *subaction_path);
/**
* Unload the OpenXR controller model.
*/
void GHOST_XrUnloadControllerModel(GHOST_IXrContext *xr_context, const char *subaction_path);
/**
* Update component transforms for the OpenXR controller model.
*/
int GHOST_XrUpdateControllerModelComponents(GHOST_IXrContext *xr_context,
const char *subaction_path);
/**
* Get vertex data for the OpenXR controller model.
*/
int GHOST_XrGetControllerModelData(GHOST_IXrContext *xr_context,
const char *subaction_path,
GHOST_XrControllerModelData *r_data);

View File

@@ -0,0 +1,78 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include "GHOST_Buttons.hh"
GHOST_Buttons::GHOST_Buttons()
{
clear();
}
bool GHOST_Buttons::get(GHOST_TButton mask) const
{
switch (mask) {
case GHOST_kButtonMaskLeft:
return button_left_;
case GHOST_kButtonMaskMiddle:
return button_middle_;
case GHOST_kButtonMaskRight:
return button_right_;
case GHOST_kButtonMaskButton4:
return button4_;
case GHOST_kButtonMaskButton5:
return button5_;
case GHOST_kButtonMaskButton6:
return button6_;
case GHOST_kButtonMaskButton7:
return button7_;
default:
return false;
}
}
void GHOST_Buttons::set(GHOST_TButton mask, bool down)
{
switch (mask) {
case GHOST_kButtonMaskLeft:
button_left_ = down;
break;
case GHOST_kButtonMaskMiddle:
button_middle_ = down;
break;
case GHOST_kButtonMaskRight:
button_right_ = down;
break;
case GHOST_kButtonMaskButton4:
button4_ = down;
break;
case GHOST_kButtonMaskButton5:
button5_ = down;
break;
case GHOST_kButtonMaskButton6:
button6_ = down;
break;
case GHOST_kButtonMaskButton7:
button7_ = down;
break;
default:
break;
}
}
void GHOST_Buttons::clear()
{
button_left_ = false;
button_middle_ = false;
button_right_ = false;
button4_ = false;
button5_ = false;
button6_ = false;
button7_ = false;
}
GHOST_Buttons::~GHOST_Buttons() = default;

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_Buttons struct.
*/
#pragma once
#include "GHOST_Types.hh"
/**
* This struct stores the state of the mouse buttons.
* Buttons can be set using button masks.
*/
struct GHOST_Buttons {
/**
* Constructor.
*/
GHOST_Buttons();
~GHOST_Buttons();
/**
* Returns the state of a single button.
* \param mask: Key button to return.
* \return The state of the button (pressed == true).
*/
bool get(GHOST_TButton mask) const;
/**
* Updates the state of a single button.
* \param mask: Button state to update.
* \param down: The new state of the button.
*/
void set(GHOST_TButton mask, bool down);
/**
* Sets the state of all buttons to up.
*/
void clear();
uint8_t button_left_ : 1;
uint8_t button_middle_ : 1;
uint8_t button_right_ : 1;
uint8_t button4_ : 1;
uint8_t button5_ : 1;
uint8_t button6_ : 1;
uint8_t button7_ : 1;
};

View File

@@ -0,0 +1,25 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
/**
* Copyright (C) 2001 NaN Technologies B.V.
*/
#include "GHOST_CallbackEventConsumer.hh"
GHOST_CallbackEventConsumer::GHOST_CallbackEventConsumer(GHOST_EventCallbackProcPtr eventCallback,
GHOST_TUserDataPtr user_data)
{
event_callback_ = eventCallback;
user_data_ = user_data;
}
bool GHOST_CallbackEventConsumer::processEvent(const GHOST_IEvent *event)
{
return event_callback_(event, user_data_);
}

View File

@@ -0,0 +1,143 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_Context class.
*/
#include "GHOST_Context.hh"
#ifdef _WIN32
# include <windows.h>
# include <epoxy/wgl.h>
# include <tchar.h>
#
# ifndef ERROR_PROFILE_DOES_NOT_MATCH_DEVICE
# define ERROR_PROFILE_DOES_NOT_MATCH_DEVICE 0x7E7
# endif
#endif
#ifdef WITH_OPENGL_BACKEND
# include <epoxy/gl.h>
#endif
#include <cstdio>
#include <cstring>
GHOST_IContext *GHOST_IContext::getActiveDrawingContext()
{
return GHOST_Context::getActiveDrawingContext();
}
#ifdef _WIN32
bool win32_silent_chk(bool result)
{
if (!result) {
SetLastError(NO_ERROR);
}
return result;
}
bool win32_chk(bool result, const char *file, int line, const char *text)
{
if (!result) {
LPTSTR formattedMsg = nullptr;
DWORD error = GetLastError();
const char *msg;
DWORD count = 0;
/* Some drivers returns a HRESULT instead of a standard error message.
* i.e: 0xC0072095 instead of 0x2095 for ERROR_INVALID_VERSION_ARB
* So strip down the error to the valid error code range. */
switch (error & 0x0000FFFF) {
case ERROR_INVALID_VERSION_ARB:
msg =
"The specified OpenGL version and feature set are either invalid or not supported.\n";
break;
case ERROR_INVALID_PROFILE_ARB:
msg =
"The specified OpenGL profile and feature set are either invalid or not supported.\n";
break;
case ERROR_INVALID_PIXEL_TYPE_ARB:
msg = "The specified pixel type is invalid.\n";
break;
case ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB:
msg =
("The device contexts specified are not compatible. "
"This can occur if the device contexts are managed by "
"different drivers or possibly on different graphics adapters.\n");
break;
# ifdef WITH_GLEW_ES
case ERROR_INCOMPATIBLE_AFFINITY_MASKS_NV:
msg = "The device context(s) and rendering context have non-matching affinity masks.\n";
break;
case ERROR_MISSING_AFFINITY_MASK_NV:
msg = "The rendering context does not have an affinity mask set.\n";
break;
# endif
case ERROR_PROFILE_DOES_NOT_MATCH_DEVICE:
msg =
("The specified profile is intended for a device of a "
"different type than the specified device.\n");
break;
default: {
count = FormatMessage((FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS),
nullptr,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)(&formattedMsg),
0,
nullptr);
msg = count > 0 ? formattedMsg : "<no system message>\n";
break;
}
}
# ifndef NDEBUG
_ftprintf(
stderr, "%s:%d: [%s] -> Win32 Error# (%lu): %s", file, line, text, ulong(error), msg);
# else
(void)file;
(void)line;
(void)text;
_ftprintf(stderr, "Win32 Error# (%lu): %s", ulong(error), msg);
# endif
SetLastError(NO_ERROR);
if (count != 0) {
LocalFree(formattedMsg);
}
}
return result;
}
#endif // _WIN32
#ifdef WITH_OPENGL_BACKEND
void GHOST_Context::initClearGL()
{
glClearColor(0.294, 0.294, 0.294, 0.000);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.000, 0.000, 0.000, 0.000);
}
#endif

View File

@@ -0,0 +1,195 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_Context class.
*/
#pragma once
#include "GHOST_IContext.hh"
#include "GHOST_Types.hh"
#include <cstdlib> /* For `nullptr`. */
class GHOST_Context : public GHOST_IContext {
protected:
static thread_local inline GHOST_Context *active_context_;
public:
/**
* Constructor.
* \param context_params: Parameters to use when initializing the context.
*/
GHOST_Context(const GHOST_ContextParams &context_params) : context_params_(context_params) {}
/**
* Destructor.
*/
~GHOST_Context() override
{
if (active_context_ == this) {
active_context_ = nullptr;
}
};
/**
* Returns the thread's currently active drawing context.
*/
static GHOST_Context *getActiveDrawingContext()
{
return active_context_;
}
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override = 0;
/** \copydoc #GHOST_IContext::swapBuffers */
GHOST_TSuccess swapBufferRelease() override = 0;
/** \copydoc #GHOST_IContext::activateDrawingContext */
GHOST_TSuccess activateDrawingContext() override = 0;
/** \copydoc #GHOST_IContext::releaseDrawingContext */
GHOST_TSuccess releaseDrawingContext() override = 0;
/**
* Call immediately after new to initialize. If this fails then immediately delete the object.
* \return Indication as to whether initialization has succeeded.
*/
virtual GHOST_TSuccess initializeDrawingContext() = 0;
/**
* Updates the drawing context of this window. Needed
* whenever the window is changed.
* \return Indication of success.
*/
virtual GHOST_TSuccess updateDrawingContext()
{
return GHOST_kFailure;
}
/**
* Checks if it is OK for a remove the native display
* \return Indication as to whether removal has succeeded.
*/
virtual GHOST_TSuccess releaseNativeHandles() = 0;
/**
* Sets the swap interval for #swapBuffers.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess setSwapInterval(int /*interval*/)
{
return GHOST_kFailure;
}
/**
* Gets the current swap interval for #swapBuffers.
* \param interval_out: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
virtual GHOST_TSuccess getSwapInterval(int & /*interval_out*/)
{
return GHOST_kFailure;
}
/**
* Get user data.
*/
void *getUserData()
{
return user_data_;
}
/**
* Set user data (intended for the caller to use as needed).
*/
void setUserData(void *user_data)
{
user_data_ = user_data;
}
/**
* Stereo visual created. Only necessary for 'real' stereo support,
* ie quad buffered stereo. This is not always possible, depends on
* the graphics h/w
*/
bool isStereoVisual() const
{
return context_params_.is_stereo_visual;
}
/** Get the VSync value. */
virtual GHOST_TVSyncModes getVSync()
{
return context_params_.vsync;
}
/**
* Returns if the context is rendered upside down compared to OpenGL.
*/
virtual bool isUpsideDown() const
{
return false;
}
/** \copydoc #GHOST_IContext::getDefaultFramebuffer */
unsigned int getDefaultFramebuffer() override
{
return 0;
}
#ifdef WITH_VULKAN_BACKEND
/** \copydoc #GHOST_IContext::getVulkanHandles */
virtual GHOST_TSuccess getVulkanHandles(GHOST_VulkanHandles & /* r_handles */) override
{
return GHOST_kFailure;
};
/** \copydoc #GHOST_IContext::getVulkanSwapChainFormat */
virtual GHOST_TSuccess getVulkanSwapChainFormat(
GHOST_VulkanSwapChainData * /*r_swap_chain_data*/) override
{
return GHOST_kFailure;
}
/** \copydoc #GHOST_IContext::setVulkanSwapBuffersCallbacks */
virtual GHOST_TSuccess setVulkanSwapBuffersCallbacks(
std::function<void(const GHOST_VulkanSwapChainData *, bool)> /*swap_buffer_draw_callback*/,
std::function<void(void)> /*swap_buffer_acquired_callback*/,
std::function<void(GHOST_VulkanOpenXRData *)> /*openxr_acquire_framebuffer_image_callback*/,
std::function<void(GHOST_VulkanOpenXRData *)> /*openxr_release_framebuffer_image_callback*/)
override
{
return GHOST_kFailure;
}
#endif
protected:
GHOST_ContextParams context_params_;
/** Caller specified, not for internal use. */
void *user_data_ = nullptr;
#ifdef WITH_OPENGL_BACKEND
static void initClearGL();
#endif
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_Context")
};
#ifdef _WIN32
bool win32_chk(bool result, const char *file = nullptr, int line = 0, const char *text = nullptr);
bool win32_silent_chk(bool result);
# ifndef NDEBUG
# define WIN32_CHK(x) win32_chk((x), __FILE__, __LINE__, #x)
# else
# define WIN32_CHK(x) win32_chk(x)
# endif
# define WIN32_CHK_SILENT(x, silent) ((silent) ? win32_silent_chk(x) : WIN32_CHK(x))
#endif /* _WIN32 */

View File

@@ -0,0 +1,406 @@
/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* For testing purposes, it can be useful to do some DirectX only drawing. A patch for this can be
* found here: https://developer.blender.org/P1284
*/
#include <iostream>
#include <string>
#include <epoxy/wgl.h>
#include "GHOST_ContextD3D.hh"
#include "GHOST_ContextWGL.hh" /* For shared drawing */
HMODULE GHOST_ContextD3D::s_d3d_lib = nullptr;
PFN_D3D11_CREATE_DEVICE GHOST_ContextD3D::s_D3D11CreateDeviceFn = nullptr;
GHOST_ContextD3D::GHOST_ContextD3D(const GHOST_ContextParams &context_params, HWND hWnd)
: GHOST_Context(context_params), h_wnd_(hWnd)
{
}
GHOST_ContextD3D::~GHOST_ContextD3D()
{
device_->Release();
device_ctx_->ClearState();
device_ctx_->Release();
}
GHOST_TSuccess GHOST_ContextD3D::swapBufferRelease()
{
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextD3D::activateDrawingContext()
{
active_context_ = this;
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextD3D::releaseDrawingContext()
{
active_context_ = nullptr;
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextD3D::setupD3DLib()
{
if (s_d3d_lib == nullptr) {
s_d3d_lib = LoadLibraryA("d3d11.dll");
WIN32_CHK(s_d3d_lib != nullptr);
if (s_d3d_lib == nullptr) {
fprintf(stderr, "LoadLibrary(\"d3d11.dll\") failed!\n");
return GHOST_kFailure;
}
}
if (s_D3D11CreateDeviceFn == nullptr) {
s_D3D11CreateDeviceFn = (PFN_D3D11_CREATE_DEVICE)GetProcAddress(s_d3d_lib,
"D3D11CreateDevice");
WIN32_CHK(s_D3D11CreateDeviceFn != nullptr);
if (s_D3D11CreateDeviceFn == nullptr) {
fprintf(stderr, "GetProcAddress(s_d3d_lib, \"D3D11CreateDevice\") failed!\n");
return GHOST_kFailure;
}
}
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextD3D::initializeDrawingContext()
{
if (setupD3DLib() == GHOST_kFailure) {
return GHOST_kFailure;
}
HRESULT hres = s_D3D11CreateDeviceFn(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
/* For debugging you may want to pass D3D11_CREATE_DEVICE_DEBUG here, but that requires
* additional setup, see
* https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-devices-layers#debug-layer.
*/
0,
nullptr,
0,
D3D11_SDK_VERSION,
&device_,
nullptr,
&device_ctx_);
WIN32_CHK(hres == S_OK);
active_context_ = this;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextD3D::releaseNativeHandles()
{
return GHOST_kFailure;
}
class GHOST_SharedOpenGLResource {
struct SharedData {
HANDLE device;
GLuint fbo;
HANDLE render_target{nullptr};
} shared_;
enum RenderTarget { TARGET_RENDERBUF, TARGET_TEX2D };
public:
GHOST_SharedOpenGLResource(ID3D11Device *device,
ID3D11DeviceContext *device_ctx,
unsigned int width,
unsigned int height,
DXGI_FORMAT format,
ID3D11RenderTargetView *render_target = nullptr)
: device_(device), device_ctx_(device_ctx), cur_width_(width), cur_height_(height)
{
if (!render_target) {
D3D11_TEXTURE2D_DESC texDesc{};
D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc{};
ID3D11Texture2D *tex;
texDesc.Width = width;
texDesc.Height = height;
texDesc.Format = format;
texDesc.SampleDesc.Count = 1;
texDesc.ArraySize = 1;
texDesc.MipLevels = 1;
texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
device->CreateTexture2D(&texDesc, nullptr, &tex);
if (!tex) {
/* If texture creation fails, we just return and leave the render target unset. So it needs
* to be nullptr-checked before use. */
fprintf(stderr, "Error creating texture for shared DirectX-OpenGL resource\n");
return;
}
renderTargetViewDesc.Format = texDesc.Format;
renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
renderTargetViewDesc.Texture2D.MipSlice = 0;
device->CreateRenderTargetView(tex, &renderTargetViewDesc, &render_target);
tex->Release();
}
render_target_ = render_target;
if (render_target_) {
ID3D11Resource *backbuffer_res = nullptr;
render_target_->GetResource(&backbuffer_res);
if (backbuffer_res) {
backbuffer_res->QueryInterface<ID3D11Texture2D>(&render_target_tex_);
backbuffer_res->Release();
}
}
if (!render_target_ || !render_target_tex_) {
fprintf(stderr, "Error creating render target for shared DirectX-OpenGL resource\n");
return;
}
}
~GHOST_SharedOpenGLResource()
{
if (render_target_tex_) {
render_target_tex_->Release();
}
if (render_target_) {
render_target_->Release();
}
if (is_initialized_) {
#if 0 /* TODO: Causes an access violation since Blender 3.4 (a296b8f694d1). */
if (shared_.render_target
# if 1
/* TODO: #wglDXUnregisterObjectNV() causes an access violation on AMD when the shared
* resource is a GL texture. Since there is currently no good alternative, just skip
* unregistering the shared resource. */
&& !use_gl_texture2d_
# endif
) {
wglDXUnregisterObjectNV(shared_.device, shared_.render_target);
}
if (shared_.device) {
wglDXCloseDeviceNV(shared_.device);
}
glDeleteFramebuffers(1, &shared_.fbo);
if (use_gl_texture2d_) {
glDeleteTextures(1, &gl_render_target_);
}
else {
glDeleteRenderbuffers(1, &gl_render_target_);
}
#else
glDeleteFramebuffers(1, &shared_.fbo);
if (use_gl_texture2d_) {
glDeleteTextures(1, &gl_render_target_);
}
#endif
}
}
/* Returns true if the shared object was successfully registered, false otherwise. */
bool reregisterSharedObject(RenderTarget target)
{
if (shared_.render_target) {
wglDXUnregisterObjectNV(shared_.device, shared_.render_target);
}
if (!render_target_tex_) {
return false;
}
if (target == TARGET_TEX2D) {
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA8,
cur_width_,
cur_height_,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
nullptr);
}
shared_.render_target = wglDXRegisterObjectNV(shared_.device,
render_target_tex_,
gl_render_target_,
(target == TARGET_TEX2D) ? GL_TEXTURE_2D :
GL_RENDERBUFFER,
WGL_ACCESS_READ_WRITE_NV);
if (!shared_.render_target) {
fprintf(stderr, "Error registering shared object using wglDXRegisterObjectNV()\n");
return false;
}
return true;
}
GHOST_TSuccess initialize()
{
shared_.device = wglDXOpenDeviceNV(device_);
if (shared_.device == nullptr) {
fprintf(stderr, "Error opening shared device using wglDXOpenDeviceNV()\n");
return GHOST_kFailure;
}
/* Build the render-buffer. */
glGenRenderbuffers(1, &gl_render_target_);
glBindRenderbuffer(GL_RENDERBUFFER, gl_render_target_);
if (!reregisterSharedObject(TARGET_RENDERBUF)) {
glBindRenderbuffer(GL_RENDERBUFFER, 0);
if (gl_render_target_) {
glDeleteRenderbuffers(1, &gl_render_target_);
}
/* Fall back to texture 2d. */
use_gl_texture2d_ = true;
glGenTextures(1, &gl_render_target_);
glBindTexture(GL_TEXTURE_2D, gl_render_target_);
reregisterSharedObject(TARGET_TEX2D);
}
/* Build the frame-buffer. */
glGenFramebuffers(1, &shared_.fbo);
glBindFramebuffer(GL_FRAMEBUFFER, shared_.fbo);
if (use_gl_texture2d_) {
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl_render_target_, 0);
}
else {
glFramebufferRenderbuffer(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, gl_render_target_);
}
is_initialized_ = true;
return GHOST_kSuccess;
}
void ensureUpdated(unsigned int width, unsigned int height)
{
if (is_initialized_ == false) {
initialize();
}
if ((cur_width_ != width) || (cur_height_ != height)) {
cur_width_ = width;
cur_height_ = height;
reregisterSharedObject(use_gl_texture2d_ ? TARGET_TEX2D : TARGET_RENDERBUF);
}
}
GHOST_TSuccess blit(unsigned int width, unsigned int height)
{
GLint fbo;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fbo);
if (!render_target_ || !render_target_tex_) {
return GHOST_kFailure;
}
ensureUpdated(width, height);
#ifdef NDEBUG
const float clear_col[] = {0.8f, 0.5f, 1.0f, 1.0f};
device_ctx_->ClearRenderTargetView(render_target_, clear_col);
#endif
device_ctx_->OMSetRenderTargets(1, &render_target_, nullptr);
beginGLOnly();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shared_.fbo);
GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (err != GL_FRAMEBUFFER_COMPLETE) {
fprintf(
stderr, "Error: Framebuffer for shared DirectX-OpenGL resource incomplete %u\n", err);
return GHOST_kFailure;
}
/* No glBlitNamedFramebuffer, gotta be 3.3 compatible. */
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
endGLOnly();
return GHOST_kSuccess;
}
ID3D11RenderTargetView *render_target_{nullptr};
ID3D11Texture2D *render_target_tex_{nullptr};
private:
void beginGLOnly()
{
wglDXLockObjectsNV(shared_.device, 1, &shared_.render_target);
}
void endGLOnly()
{
wglDXUnlockObjectsNV(shared_.device, 1, &shared_.render_target);
}
ID3D11Device *device_;
ID3D11DeviceContext *device_ctx_;
GLuint gl_render_target_;
unsigned int cur_width_, cur_height_;
bool is_initialized_{false};
bool use_gl_texture2d_{false};
};
GHOST_SharedOpenGLResource *GHOST_ContextD3D::createSharedOpenGLResource(
unsigned int width,
unsigned int height,
DXGI_FORMAT format,
ID3D11RenderTargetView *render_target)
{
if (!(WGL_NV_DX_interop && WGL_NV_DX_interop2)) {
fprintf(stderr,
"Error: Can't render OpenGL framebuffer using Direct3D. NV_DX_interop extension not "
"available.");
return nullptr;
}
GHOST_SharedOpenGLResource *shared_res = new GHOST_SharedOpenGLResource(
device_, device_ctx_, width, height, format, render_target);
return shared_res;
}
GHOST_SharedOpenGLResource *GHOST_ContextD3D::createSharedOpenGLResource(unsigned int width,
unsigned int height,
DXGI_FORMAT format)
{
return createSharedOpenGLResource(width, height, format, nullptr);
}
void GHOST_ContextD3D::disposeSharedOpenGLResource(GHOST_SharedOpenGLResource *shared_res)
{
delete shared_res;
}
GHOST_TSuccess GHOST_ContextD3D::blitFromOpenGLContext(GHOST_SharedOpenGLResource *shared_res,
unsigned int width,
unsigned int height)
{
return shared_res->blit(width, height);
}
ID3D11Texture2D *GHOST_ContextD3D::getSharedTexture2D(GHOST_SharedOpenGLResource *shared_res)
{
return shared_res->render_target_tex_;
}

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2014-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#ifndef WIN32
# error WIN32 only!
#endif // WIN32
#include <D3D11.h>
#include "GHOST_Context.hh"
class GHOST_ContextD3D : public GHOST_Context {
/* XR code needs low level graphics data to send to OpenXR. */
friend class GHOST_XrGraphicsBindingD3D;
friend class GHOST_XrGraphicsBindingOpenGLD3D;
friend class GHOST_XrGraphicsBindingVulkanD3D;
public:
GHOST_ContextD3D(const GHOST_ContextParams &context_params, HWND hWnd);
~GHOST_ContextD3D() override;
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override
{
return GHOST_kSuccess;
}
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
GHOST_TSuccess swapBufferRelease() override;
/**
* Activates the drawing context of this window.
* \return A boolean success indicator.
*/
GHOST_TSuccess activateDrawingContext() override;
/**
* Release the drawing context of the calling thread.
* \return A boolean success indicator.
*/
GHOST_TSuccess releaseDrawingContext() override;
/**
* Call immediately after new to initialize. If this fails then immediately delete the object.
* \return Indication as to whether initialization has succeeded.
*/
GHOST_TSuccess initializeDrawingContext() override;
/**
* Updates the drawing context of this window. Needed
* whenever the window is changed.
* \return Indication of success.
*/
GHOST_TSuccess updateDrawingContext() override
{
return GHOST_kFailure;
}
/**
* Checks if it is OK for a remove the native display
* \return Indication as to whether removal has succeeded.
*/
GHOST_TSuccess releaseNativeHandles() override;
/**
* Sets the swap interval for #swapBuffers.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
GHOST_TSuccess setSwapInterval(int /*interval*/) override
{
return GHOST_kFailure;
}
/**
* Gets the current swap interval for #swapBuffers.
* \param interval_out: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
GHOST_TSuccess getSwapInterval(int & /*interval_out*/) override
{
return GHOST_kFailure;
}
/**
* Gets the OpenGL frame-buffer associated with the OpenGL context
* \return The ID of an OpenGL frame-buffer object.
*/
unsigned int getDefaultFramebuffer() override
{
return 0;
}
class GHOST_SharedOpenGLResource *createSharedOpenGLResource(
unsigned int width,
unsigned int height,
DXGI_FORMAT format,
ID3D11RenderTargetView *render_target);
class GHOST_SharedOpenGLResource *createSharedOpenGLResource(unsigned int width,
unsigned int height,
DXGI_FORMAT format);
void disposeSharedOpenGLResource(class GHOST_SharedOpenGLResource *shared_res);
GHOST_TSuccess blitFromOpenGLContext(class GHOST_SharedOpenGLResource *shared_res,
unsigned int width,
unsigned int height);
ID3D11Texture2D *getSharedTexture2D(class GHOST_SharedOpenGLResource *shared_res);
bool isUpsideDown() const override
{
return true;
}
private:
GHOST_TSuccess setupD3DLib();
static HMODULE s_d3d_lib;
static PFN_D3D11_CREATE_DEVICE s_D3D11CreateDeviceFn;
HWND h_wnd_;
ID3D11Device *device_;
ID3D11DeviceContext *device_ctx_;
};

View File

@@ -0,0 +1,659 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_ContextEGL class.
*/
#include "GHOST_ContextEGL.hh"
#include <set>
#include <sstream>
#include <vector>
#include <cassert>
#include <cstdio>
#include <cstring>
#define CASE_CODE_RETURN_STR(code) \
case code: \
return #code;
static const char *get_egl_error_enum_string(EGLint error)
{
switch (error) {
CASE_CODE_RETURN_STR(EGL_SUCCESS)
CASE_CODE_RETURN_STR(EGL_NOT_INITIALIZED)
CASE_CODE_RETURN_STR(EGL_BAD_ACCESS)
CASE_CODE_RETURN_STR(EGL_BAD_ALLOC)
CASE_CODE_RETURN_STR(EGL_BAD_ATTRIBUTE)
CASE_CODE_RETURN_STR(EGL_BAD_CONTEXT)
CASE_CODE_RETURN_STR(EGL_BAD_CONFIG)
CASE_CODE_RETURN_STR(EGL_BAD_CURRENT_SURFACE)
CASE_CODE_RETURN_STR(EGL_BAD_DISPLAY)
CASE_CODE_RETURN_STR(EGL_BAD_SURFACE)
CASE_CODE_RETURN_STR(EGL_BAD_MATCH)
CASE_CODE_RETURN_STR(EGL_BAD_PARAMETER)
CASE_CODE_RETURN_STR(EGL_BAD_NATIVE_PIXMAP)
CASE_CODE_RETURN_STR(EGL_BAD_NATIVE_WINDOW)
CASE_CODE_RETURN_STR(EGL_CONTEXT_LOST)
default:
return nullptr;
}
}
static const char *get_egl_error_message_string(EGLint error)
{
switch (error) {
case EGL_SUCCESS:
return "The last function succeeded without error.";
case EGL_NOT_INITIALIZED:
return (
"EGL is not initialized, or could not be initialized, "
"for the specified EGL display connection.");
case EGL_BAD_ACCESS:
return (
"EGL cannot access a requested resource "
"(for example a context is bound in another thread).");
case EGL_BAD_ALLOC:
return "EGL failed to allocate resources for the requested operation.";
case EGL_BAD_ATTRIBUTE:
return "An unrecognized attribute or attribute value was passed in the attribute list.";
case EGL_BAD_CONTEXT:
return "An EGLContext argument does not name a valid EGL rendering context.";
case EGL_BAD_CONFIG:
return "An EGLConfig argument does not name a valid EGL frame buffer configuration.";
case EGL_BAD_CURRENT_SURFACE:
return (
"The current surface of the calling thread is a window, "
"pixel buffer or pixmap that is no longer valid.");
case EGL_BAD_DISPLAY:
return "An EGLDisplay argument does not name a valid EGL display connection.";
case EGL_BAD_SURFACE:
return (
"An EGLSurface argument does not name a valid surface "
"(window, pixel buffer or pixmap) configured for GL rendering.");
case EGL_BAD_MATCH:
return (
"Arguments are inconsistent "
"(for example, a valid context requires buffers not supplied by a valid surface).");
case EGL_BAD_PARAMETER:
return "One or more argument values are invalid.";
case EGL_BAD_NATIVE_PIXMAP:
return "A NativePixmapType argument does not refer to a valid native pixmap.";
case EGL_BAD_NATIVE_WINDOW:
return "A NativeWindowType argument does not refer to a valid native window.";
case EGL_CONTEXT_LOST:
return (
"A power management event has occurred. "
"The application must destroy all contexts and reinitialize OpenGL ES state "
"and objects to continue rendering.");
default:
return nullptr;
}
}
static void egl_print_error(const char *message, const EGLint error)
{
const char *code = get_egl_error_enum_string(error);
const char *msg = get_egl_error_message_string(error);
fprintf(stderr,
"%sEGL Error (0x%04X): %s: %s\n",
message,
uint(error),
code ? code : "<Unknown>",
msg ? msg : "<Unknown>");
}
static bool egl_chk(bool result,
const char *file = nullptr,
int line = 0,
const char *text = nullptr)
{
if (!result) {
const EGLint error = eglGetError();
#ifndef NDEBUG
const char *code = get_egl_error_enum_string(error);
const char *msg = get_egl_error_message_string(error);
fprintf(stderr,
"%s:%d: [%s] -> EGL Error (0x%04X): %s: %s\n",
file,
line,
text,
uint(error),
code ? code : "<Unknown>",
msg ? msg : "<Unknown>");
#else
egl_print_error("", error);
(void)(file);
(void)(line);
(void)(text);
#endif
}
return result;
}
#ifndef NDEBUG
# define EGL_CHK(x) egl_chk((x), __FILE__, __LINE__, #x)
#else
# define EGL_CHK(x) egl_chk(x)
#endif
EGLContext GHOST_ContextEGL::s_gl_sharedContext = EGL_NO_CONTEXT;
EGLint GHOST_ContextEGL::s_gl_sharedCount = 0;
EGLContext GHOST_ContextEGL::s_gles_sharedContext = EGL_NO_CONTEXT;
EGLint GHOST_ContextEGL::s_gles_sharedCount = 0;
EGLContext GHOST_ContextEGL::s_vg_sharedContext = EGL_NO_CONTEXT;
EGLint GHOST_ContextEGL::s_vg_sharedCount = 0;
#ifdef _MSC_VER
# pragma warning(disable : 4715)
#endif
template<typename T> T &choose_api(EGLenum api, T &a, T &b, T &c)
{
switch (api) {
case EGL_OPENGL_API:
return a;
case EGL_OPENGL_ES_API:
return b;
case EGL_OPENVG_API:
return c;
default:
abort();
}
}
GHOST_ContextEGL::GHOST_ContextEGL(const GHOST_System *const system,
const GHOST_ContextParams &context_params,
EGLNativeWindowType nativeWindow,
EGLNativeDisplayType nativeDisplay,
EGLint contextProfileMask,
EGLint contextMajorVersion,
EGLint contextMinorVersion,
EGLint contextFlags,
EGLint contextResetNotificationStrategy,
EGLenum api)
: GHOST_Context(context_params),
system_(system),
native_display_(nativeDisplay),
native_window_(nativeWindow),
context_profile_mask_(contextProfileMask),
context_major_version_(contextMajorVersion),
context_minor_version_(contextMinorVersion),
context_flags_(contextFlags),
context_reset_notification_strategy_(contextResetNotificationStrategy),
api_(api),
context_(EGL_NO_CONTEXT),
surface_(EGL_NO_SURFACE),
display_(EGL_NO_DISPLAY),
config_(EGL_NO_CONFIG_KHR),
swap_interval_(1),
shared_context_(
choose_api(api, s_gl_sharedContext, s_gles_sharedContext, s_vg_sharedContext)),
shared_count_(choose_api(api, s_gl_sharedCount, s_gles_sharedCount, s_vg_sharedCount)),
surface_from_native_window_(false)
{
}
GHOST_ContextEGL::~GHOST_ContextEGL()
{
if (display_ != EGL_NO_DISPLAY) {
bindAPI(api_);
if (context_ != EGL_NO_CONTEXT) {
if (context_ == ::eglGetCurrentContext()) {
EGL_CHK(::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
}
if (context_ != shared_context_ || shared_count_ == 1) {
assert(shared_count_ > 0);
shared_count_--;
if (shared_count_ == 0) {
shared_context_ = EGL_NO_CONTEXT;
}
EGL_CHK(::eglDestroyContext(display_, context_));
}
}
if (surface_ != EGL_NO_SURFACE) {
EGL_CHK(::eglDestroySurface(display_, surface_));
}
}
}
GHOST_TSuccess GHOST_ContextEGL::swapBufferRelease()
{
return EGL_CHK(::eglSwapBuffers(display_, surface_)) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextEGL::setSwapInterval(int interval)
{
if (epoxy_egl_version(display_) >= 11) {
if (EGL_CHK(::eglSwapInterval(display_, interval))) {
swap_interval_ = interval;
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextEGL::getSwapInterval(int &interval_out)
{
/* This is a bit of a kludge because there does not seem to
* be a way to query the swap interval with EGL. */
interval_out = swap_interval_;
return GHOST_kSuccess;
}
EGLDisplay GHOST_ContextEGL::getDisplay() const
{
return display_;
}
EGLConfig GHOST_ContextEGL::getConfig() const
{
return config_;
}
EGLContext GHOST_ContextEGL::getContext() const
{
return context_;
}
GHOST_TSuccess GHOST_ContextEGL::activateDrawingContext()
{
if (display_) {
active_context_ = this;
bindAPI(api_);
return EGL_CHK(::eglMakeCurrent(display_, surface_, surface_, context_)) ? GHOST_kSuccess :
GHOST_kFailure;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextEGL::releaseDrawingContext()
{
if (display_) {
active_context_ = nullptr;
bindAPI(api_);
return EGL_CHK(::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) ?
GHOST_kSuccess :
GHOST_kFailure;
}
return GHOST_kFailure;
}
inline bool GHOST_ContextEGL::bindAPI(EGLenum api)
{
if (epoxy_egl_version(display_) >= 12) {
return (EGL_CHK(eglBindAPI(api)) == EGL_TRUE);
}
return false;
}
static const std::string &api_string(EGLenum api)
{
static const std::string a("OpenGL");
static const std::string b("OpenGL ES");
static const std::string c("OpenVG");
return choose_api(api, a, b, c);
}
GHOST_TSuccess GHOST_ContextEGL::initializeDrawingContext()
{
/* Objects have to be declared here due to the use of `goto`. */
std::vector<EGLint> attrib_list;
EGLint num_config = 0;
if (context_params_.is_stereo_visual) {
fprintf(stderr, "Warning! Stereo OpenGL ES contexts are not supported.\n");
}
context_params_.is_stereo_visual = false; /* It doesn't matter what the Window wants. */
EGLDisplay prev_display = eglGetCurrentDisplay();
EGLSurface prev_draw = eglGetCurrentSurface(EGL_DRAW);
EGLSurface prev_read = eglGetCurrentSurface(EGL_READ);
EGLContext prev_context = eglGetCurrentContext();
EGLint egl_major = 0, egl_minor = 0;
if (!EGL_CHK((display_ = ::eglGetDisplay(native_display_)) != EGL_NO_DISPLAY)) {
goto error;
}
{
const EGLBoolean init_display_result = ::eglInitialize(display_, &egl_major, &egl_minor);
const EGLint init_display_error = (init_display_result) ? 0 : eglGetError();
if (!init_display_result || (egl_major == 0 && egl_minor == 0)) {
/* We failed to create a regular render window, retry and see if we can create a headless
* render context. */
::eglTerminate(display_);
const char *egl_extension_st = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
assert(egl_extension_st != nullptr);
assert(egl_extension_st == nullptr ||
strstr(egl_extension_st, "EGL_MESA_platform_surfaceless") != nullptr);
if (egl_extension_st == nullptr ||
strstr(egl_extension_st, "EGL_MESA_platform_surfaceless") == nullptr)
{
egl_print_error("Failed to create display GPU context: ", init_display_error);
fprintf(
stderr,
"Failed to create headless GPU context: No EGL_MESA_platform_surfaceless extension");
goto error;
}
display_ = eglGetPlatformDisplayEXT(
EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr);
const EGLBoolean headless_result = ::eglInitialize(display_, &egl_major, &egl_minor);
const EGLint init_headless_error = (headless_result) ? 0 : eglGetError();
if (!headless_result) {
egl_print_error("Failed to create display GPU context: ", init_display_error);
egl_print_error("Failed to create headless GPU context: ", init_headless_error);
goto error;
}
}
}
#ifdef WITH_GHOST_DEBUG
fprintf(stderr, "EGL Version %d.%d\n", egl_major, egl_minor);
#endif
if (!EGL_CHK(::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT))) {
goto error;
}
if (!bindAPI(api_)) {
goto error;
}
/* Build attribute list. */
attrib_list.reserve(20);
if (api_ == EGL_OPENGL_ES_API && epoxy_egl_version(display_) >= 12) {
/* According to the spec it seems that you are required to set EGL_RENDERABLE_TYPE,
* but some implementations (ANGLE) do not seem to care. */
if (context_major_version_ == 1) {
attrib_list.push_back(EGL_RENDERABLE_TYPE);
attrib_list.push_back(EGL_OPENGL_ES_BIT);
}
else if (context_major_version_ == 2) {
attrib_list.push_back(EGL_RENDERABLE_TYPE);
attrib_list.push_back(EGL_OPENGL_ES2_BIT);
}
else if (context_major_version_ == 3) {
attrib_list.push_back(EGL_RENDERABLE_TYPE);
attrib_list.push_back(EGL_OPENGL_ES3_BIT_KHR);
}
else {
fprintf(stderr,
"Warning! Unable to request an ES context of version %d.%d\n",
context_major_version_,
context_minor_version_);
}
if (!((context_major_version_ == 1) ||
(context_major_version_ == 2 && epoxy_egl_version(display_) >= 13) ||
(context_major_version_ == 3 &&
epoxy_has_egl_extension(display_, "KHR_create_context")) ||
(context_major_version_ == 3 && epoxy_egl_version(display_) >= 15)))
{
fprintf(stderr,
"Warning! May not be able to create a version %d.%d ES context with version %d.%d "
"of EGL\n",
context_major_version_,
context_minor_version_,
egl_major,
egl_minor);
}
}
else {
attrib_list.push_back(EGL_RENDERABLE_TYPE);
attrib_list.push_back(EGL_OPENGL_BIT);
}
attrib_list.push_back(EGL_RED_SIZE);
attrib_list.push_back(8);
attrib_list.push_back(EGL_GREEN_SIZE);
attrib_list.push_back(8);
attrib_list.push_back(EGL_BLUE_SIZE);
attrib_list.push_back(8);
if (native_window_ == 0) {
/* Off-screen surface. */
attrib_list.push_back(EGL_SURFACE_TYPE);
attrib_list.push_back(EGL_PBUFFER_BIT);
}
attrib_list.push_back(EGL_NONE);
if (!EGL_CHK(::eglChooseConfig(display_, &(attrib_list[0]), &config_, 1, &num_config))) {
goto error;
}
/* A common error is to assume that ChooseConfig worked because it returned EGL_TRUE. */
if (num_config != 1) { /* `num_config` should be exactly 1. */
goto error;
}
if (native_window_ != 0) {
std::vector<EGLint> surface_attrib_list;
surface_attrib_list.reserve(3);
#ifdef WITH_GHOST_WAYLAND
/* Fix transparency issue on: `Wayland + Nouveau/Zink+NVK`. Due to unsupported texture formats
* drivers can hit transparency code-paths resulting in showing the desktop in viewports.
*
* See #102994. */
/* EGL_EXT_present_opaque isn't added to the latest release of epoxy, but is part of the latest
* EGL https://github.com/KhronosGroup/EGL-Registry/blob/main/api/egl.xml */
if (epoxy_has_egl_extension(display_, "EGL_EXT_present_opaque")) {
# ifndef EGL_PRESENT_OPAQUE_EXT
# define EGL_PRESENT_OPAQUE_EXT 0x31DF
# endif
surface_attrib_list.push_back(EGL_PRESENT_OPAQUE_EXT);
surface_attrib_list.push_back(EGL_TRUE);
}
#endif
surface_attrib_list.push_back(EGL_NONE);
surface_ = ::eglCreateWindowSurface(
display_, config_, native_window_, surface_attrib_list.data());
surface_from_native_window_ = true;
}
else {
static const EGLint pb_attrib_list[] = {
EGL_WIDTH,
1,
EGL_HEIGHT,
1,
EGL_NONE,
};
surface_ = ::eglCreatePbufferSurface(display_, config_, pb_attrib_list);
}
if (!EGL_CHK(surface_ != EGL_NO_SURFACE)) {
goto error;
}
attrib_list.clear();
if (epoxy_egl_version(display_) >= 15 || epoxy_has_egl_extension(display_, "KHR_create_context"))
{
if (api_ == EGL_OPENGL_API || api_ == EGL_OPENGL_ES_API) {
if (context_major_version_ != 0) {
attrib_list.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
attrib_list.push_back(context_major_version_);
}
if (context_minor_version_ != 0) {
attrib_list.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
attrib_list.push_back(context_minor_version_);
}
if (context_flags_ != 0) {
attrib_list.push_back(EGL_CONTEXT_FLAGS_KHR);
attrib_list.push_back(context_flags_);
}
}
else {
if (context_major_version_ != 0 || context_minor_version_ != 0) {
fprintf(stderr,
"Warning! Cannot request specific versions of %s contexts.",
api_string(api_).c_str());
}
if (context_flags_ != 0) {
fprintf(stderr, "Warning! Flags cannot be set on %s contexts.", api_string(api_).c_str());
}
}
if (api_ == EGL_OPENGL_API) {
if (context_profile_mask_ != 0) {
attrib_list.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
attrib_list.push_back(context_profile_mask_);
}
}
else {
if (context_profile_mask_ != 0) {
fprintf(
stderr, "Warning! Cannot select profile for %s contexts.", api_string(api_).c_str());
}
}
if (api_ == EGL_OPENGL_API || epoxy_egl_version(display_) >= 15) {
if (context_reset_notification_strategy_ != 0) {
attrib_list.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR);
attrib_list.push_back(context_reset_notification_strategy_);
}
}
else {
if (context_reset_notification_strategy_ != 0) {
fprintf(stderr,
"Warning! EGL %d.%d cannot set the reset notification strategy on %s contexts.",
egl_major,
egl_minor,
api_string(api_).c_str());
}
}
}
else {
if (api_ == EGL_OPENGL_ES_API) {
if (context_major_version_ != 0) {
attrib_list.push_back(EGL_CONTEXT_CLIENT_VERSION);
attrib_list.push_back(context_major_version_);
}
}
else {
if (context_major_version_ != 0 || context_minor_version_ != 0) {
fprintf(stderr,
"Warning! EGL %d.%d is unable to select between versions of %s.",
egl_major,
egl_minor,
api_string(api_).c_str());
}
}
if (context_flags_ != 0) {
fprintf(stderr, "Warning! EGL %d.%d is unable to set context flags.", egl_major, egl_minor);
}
if (context_profile_mask_ != 0) {
fprintf(stderr,
"Warning! EGL %d.%d is unable to select between profiles.",
egl_major,
egl_minor);
}
if (context_reset_notification_strategy_ != 0) {
fprintf(stderr,
"Warning! EGL %d.%d is unable to set the reset notification strategies.",
egl_major,
egl_minor);
}
}
attrib_list.push_back(EGL_NONE);
context_ = ::eglCreateContext(display_, config_, shared_context_, &(attrib_list[0]));
if (!EGL_CHK(context_ != EGL_NO_CONTEXT)) {
goto error;
}
if (shared_context_ == EGL_NO_CONTEXT) {
shared_context_ = context_;
}
shared_count_++;
if (!EGL_CHK(::eglMakeCurrent(display_, surface_, surface_, context_))) {
goto error;
}
{
const GHOST_TVSyncModes vsync = getVSync();
if (vsync != GHOST_kVSyncModeUnset) {
setSwapInterval(int(vsync));
}
}
if (native_window_ != 0) {
initClearGL();
::eglSwapBuffers(display_, surface_);
}
active_context_ = this;
return GHOST_kSuccess;
error:
if (prev_display != EGL_NO_DISPLAY) {
EGL_CHK(eglMakeCurrent(prev_display, prev_draw, prev_read, prev_context));
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextEGL::releaseNativeHandles()
{
native_display_ = nullptr;
native_window_ = 0;
if (surface_from_native_window_) {
surface_ = EGL_NO_SURFACE;
}
return GHOST_kSuccess;
}

View File

@@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#ifndef WITH_OPENGL_BACKEND
# error "ContextEGL requires WITH_OPENGL_BACKEND"
#endif
#include "GHOST_Context.hh"
#include "GHOST_System.hh"
#include <epoxy/egl.h>
#include <epoxy/gl.h>
#ifndef GHOST_OPENGL_EGL_CONTEXT_FLAGS
# define GHOST_OPENGL_EGL_CONTEXT_FLAGS 0
#endif
#ifndef GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY
# define GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY 0
#endif
class GHOST_ContextEGL : public GHOST_Context {
/* XR code needs low level graphics data to send to OpenXR. */
friend class GHOST_XrGraphicsBindingOpenGL;
public:
/**
* Constructor.
*/
GHOST_ContextEGL(const GHOST_System *const system,
const GHOST_ContextParams &context_params,
EGLNativeWindowType nativeWindow,
EGLNativeDisplayType nativeDisplay,
EGLint contextProfileMask,
EGLint contextMajorVersion,
EGLint contextMinorVersion,
EGLint contextFlags,
EGLint contextResetNotificationStrategy,
EGLenum api);
/**
* Destructor.
*/
~GHOST_ContextEGL() override;
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override
{
return GHOST_kSuccess;
}
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
GHOST_TSuccess swapBufferRelease() override;
/**
* Activates the drawing context of this window.
* \return A boolean success indicator.
*/
GHOST_TSuccess activateDrawingContext() override;
/**
* Release the drawing context of the calling thread.
* \return A boolean success indicator.
*/
GHOST_TSuccess releaseDrawingContext() override;
/**
* Call immediately after new to initialize. If this fails then immediately delete the object.
* \return Indication as to whether initialization has succeeded.
*/
GHOST_TSuccess initializeDrawingContext() override;
/**
* Removes references to native handles from this context and then returns
* \return GHOST_kSuccess if it is OK for the parent to release the handles and
* GHOST_kFailure if releasing the handles will interfere with sharing
*/
GHOST_TSuccess releaseNativeHandles() override;
/**
* Sets the swap interval for #swapBuffers.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
GHOST_TSuccess setSwapInterval(int interval) override;
/**
* Gets the current swap interval for #swapBuffers.
* \param interval_out: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
GHOST_TSuccess getSwapInterval(int &interval_out) override;
EGLDisplay getDisplay() const;
EGLConfig getConfig() const;
EGLContext getContext() const;
private:
bool bindAPI(EGLenum api);
const GHOST_System *const system_;
EGLNativeDisplayType native_display_;
EGLNativeWindowType native_window_;
const EGLint context_profile_mask_;
const EGLint context_major_version_;
const EGLint context_minor_version_;
const EGLint context_flags_;
const EGLint context_reset_notification_strategy_;
const EGLenum api_;
EGLContext context_;
EGLSurface surface_;
EGLDisplay display_;
EGLConfig config_;
EGLint swap_interval_;
EGLContext &shared_context_;
EGLint &shared_count_;
/**
* True when the surface is created from `native_window_`.
*/
bool surface_from_native_window_;
static EGLContext s_gl_sharedContext;
static EGLint s_gl_sharedCount;
static EGLContext s_gles_sharedContext;
static EGLint s_gles_sharedCount;
static EGLContext s_vg_sharedContext;
static EGLint s_vg_sharedCount;
};

View File

@@ -0,0 +1,453 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_ContextGLX class.
*/
#include "GHOST_ContextGLX.hh"
#include "GHOST_SystemX11.hh"
#include <vector>
#include <cassert>
#include <cstdio>
#include <cstring>
/* Needed for Intel drivers (works with MESA-software-rasterizer (`swrast`) & NVIDIA). */
#define USE_GLXEW_INIT_WORKAROUND
#ifdef USE_GLXEW_INIT_WORKAROUND
static GLuint _glewStrLen(const GLubyte *s);
static GLboolean _glewSearchExtension(const char *name, const GLubyte *start, const GLubyte *end);
#endif
GLXContext GHOST_ContextGLX::s_sharedContext = None;
int GHOST_ContextGLX::s_sharedCount = 0;
GHOST_ContextGLX::GHOST_ContextGLX(const GHOST_ContextParams &context_params,
Window window,
Display *display,
GLXFBConfig fbconfig,
int contextProfileMask,
int contextMajorVersion,
int contextMinorVersion,
int contextFlags,
int contextResetNotificationStrategy)
: GHOST_Context(context_params),
display_(display),
fbconfig_(fbconfig),
window_(window),
context_profile_mask_(contextProfileMask),
context_major_version_(contextMajorVersion),
context_minor_version_(contextMinorVersion),
context_flags_(contextFlags),
context_reset_notification_strategy_(contextResetNotificationStrategy),
context_(None)
{
assert(display_ != nullptr);
}
GHOST_ContextGLX::~GHOST_ContextGLX()
{
if (display_ != nullptr) {
if (context_ != None) {
if (window_ != 0 && context_ == ::glXGetCurrentContext()) {
::glXMakeCurrent(display_, None, nullptr);
}
if (context_ != s_sharedContext || s_sharedCount == 1) {
assert(s_sharedCount > 0);
s_sharedCount--;
if (s_sharedCount == 0) {
s_sharedContext = nullptr;
}
::glXDestroyContext(display_, context_);
}
}
}
}
GHOST_TSuccess GHOST_ContextGLX::swapBufferRelease()
{
::glXSwapBuffers(display_, window_);
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextGLX::activateDrawingContext()
{
if (display_ == nullptr) {
return GHOST_kFailure;
}
active_context_ = this;
return ::glXMakeCurrent(display_, window_, context_) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextGLX::releaseDrawingContext()
{
if (display_ == nullptr) {
return GHOST_kFailure;
}
active_context_ = nullptr;
return ::glXMakeCurrent(display_, None, nullptr) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextGLX::initializeDrawingContext()
{
GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
/* -------------------------------------------------------------------- */
/* Begin Inline GLEW. */
#ifdef USE_GLXEW_INIT_WORKAROUND
const GLubyte *extStart = (GLubyte *)"";
const GLubyte *extEnd;
if (glXQueryExtension(display_, nullptr, nullptr)) {
extStart = (const GLubyte *)glXGetClientString(display_, GLX_EXTENSIONS);
if ((extStart == nullptr) ||
(glXChooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC)glXGetProcAddressARB(
(const GLubyte *)"glXChooseFBConfig")) == nullptr ||
(glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB(
(const GLubyte *)"glXCreateContextAttribsARB")) == nullptr ||
(glXCreatePbuffer = (PFNGLXCREATEPBUFFERPROC)glXGetProcAddressARB(
(const GLubyte *)"glXCreatePbuffer")) == nullptr)
{
extStart = (GLubyte *)"";
}
}
extEnd = extStart + _glewStrLen(extStart);
# undef GLXEW_ARB_create_context
const bool GLXEW_ARB_create_context = _glewSearchExtension(
"GLX_ARB_create_context", extStart, extEnd);
# undef GLXEW_ARB_create_context_profile
const bool GLXEW_ARB_create_context_profile = _glewSearchExtension(
"GLX_ARB_create_context_profile", extStart, extEnd);
# undef GLXEW_ARB_create_context_robustness
const bool GLXEW_ARB_create_context_robustness = _glewSearchExtension(
"GLX_ARB_create_context_robustness", extStart, extEnd);
# ifdef WITH_GLEW_ES
# undef GLXEW_EXT_create_context_es_profile
const bool GLXEW_EXT_create_context_es_profile = _glewSearchExtension(
"GLX_EXT_create_context_es_profile", extStart, extEnd);
# undef GLXEW_EXT_create_context_es2_profile
const bool GLXEW_EXT_create_context_es2_profile = _glewSearchExtension(
"GLX_EXT_create_context_es2_profile", extStart, extEnd);
# endif /* WITH_GLEW_ES */
/* End Inline GLEW. */
/* -------------------------------------------------------------------- */
#else
/* Important to initialize only GLXEW (_not_ GLEW),
* since this breaks w/ Mesa's `swrast`, see: #46431. */
glxewInit();
#endif /* USE_GLXEW_INIT_WORKAROUND */
if (GLXEW_ARB_create_context) {
int profileBitCore = context_profile_mask_ & GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
int profileBitCompat = context_profile_mask_ & GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
#ifdef WITH_GLEW_ES
int profileBitES = context_profile_mask_ & GLX_CONTEXT_ES_PROFILE_BIT_EXT;
#endif
if (!GLXEW_ARB_create_context_profile && profileBitCore) {
fprintf(stderr, "Warning! OpenGL core profile not available.\n");
}
if (!GLXEW_ARB_create_context_profile && profileBitCompat) {
fprintf(stderr, "Warning! OpenGL compatibility profile not available.\n");
}
#ifdef WITH_GLEW_ES
if (!GLXEW_EXT_create_context_es_profile && profileBitES && context_major_version_ == 1) {
fprintf(stderr, "Warning! OpenGL ES profile not available.\n");
}
if (!GLXEW_EXT_create_context_es2_profile && profileBitES && context_major_version_ == 2) {
fprintf(stderr, "Warning! OpenGL ES2 profile not available.\n");
}
#endif
int profileMask = 0;
if (GLXEW_ARB_create_context_profile && profileBitCore) {
profileMask |= profileBitCore;
}
if (GLXEW_ARB_create_context_profile && profileBitCompat) {
profileMask |= profileBitCompat;
}
#ifdef WITH_GLEW_ES
if (GLXEW_EXT_create_context_es_profile && profileBitES) {
profileMask |= profileBitES;
}
#endif
if (profileMask != context_profile_mask_) {
fprintf(stderr, "Warning! Ignoring untested OpenGL context profile mask bits.");
}
/* max 10 attributes plus terminator */
int attribs[11];
int i = 0;
if (profileMask) {
attribs[i++] = GLX_CONTEXT_PROFILE_MASK_ARB;
attribs[i++] = profileMask;
}
if (context_major_version_ != 0) {
attribs[i++] = GLX_CONTEXT_MAJOR_VERSION_ARB;
attribs[i++] = context_major_version_;
attribs[i++] = GLX_CONTEXT_MINOR_VERSION_ARB;
attribs[i++] = context_minor_version_;
}
if (context_flags_ != 0) {
attribs[i++] = GLX_CONTEXT_FLAGS_ARB;
attribs[i++] = context_flags_;
}
if (context_reset_notification_strategy_ != 0) {
if (GLXEW_ARB_create_context_robustness) {
attribs[i++] = GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB;
attribs[i++] = context_reset_notification_strategy_;
}
else {
fprintf(stderr, "Warning! Cannot set the reset notification strategy.");
}
}
attribs[i++] = 0;
/* Some drivers don't like having a true off-screen context.
* Create a pixel buffer instead of a window to render to.
* even if it will never be used for drawing. */
int pbuffer_attribs[] = {GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, None};
/* Create a GL 3.x context */
if (fbconfig_) {
context_ = glXCreateContextAttribsARB(display_, fbconfig_, s_sharedContext, true, attribs);
if (!window_) {
window_ = (Window)glXCreatePbuffer(display_, fbconfig_, pbuffer_attribs);
}
}
else {
GLXFBConfig *framebuffer_config = nullptr;
{
int glx_attribs[64];
int fbcount = 0;
GHOST_X11_GL_GetAttributes(glx_attribs, 64, context_params_.is_stereo_visual, false, true);
framebuffer_config = glXChooseFBConfig(
display_, DefaultScreen(display_), glx_attribs, &fbcount);
}
if (framebuffer_config) {
context_ = glXCreateContextAttribsARB(
display_, framebuffer_config[0], s_sharedContext, True, attribs);
if (!window_) {
window_ = (Window)glXCreatePbuffer(display_, framebuffer_config[0], pbuffer_attribs);
}
fbconfig_ = framebuffer_config[0];
XFree(framebuffer_config);
}
}
}
else {
/* Don't create legacy context */
fprintf(stderr, "Error! GLX_ARB_create_context not available.\n");
}
GHOST_TSuccess success;
if (context_ != nullptr) {
const uchar *version;
if (!s_sharedContext) {
s_sharedContext = context_;
}
s_sharedCount++;
glXMakeCurrent(display_, window_, context_);
/* For performance measurements with VSync disabled. */
{
const GHOST_TVSyncModes vsync = getVSync();
if (vsync != GHOST_kVSyncModeUnset) {
setSwapInterval(int(vsync));
}
}
if (window_) {
initClearGL();
::glXSwapBuffers(display_, window_);
}
version = glGetString(GL_VERSION);
if (!version || version[0] < '3' || ((version[0] == '3') && (version[2] < '3'))) {
success = GHOST_kFailure;
}
else {
success = GHOST_kSuccess;
}
}
else {
/* freeing well clean up the context initialized above */
success = GHOST_kFailure;
}
GHOST_X11_ERROR_HANDLERS_RESTORE(handler_store);
active_context_ = this;
return success;
}
GHOST_TSuccess GHOST_ContextGLX::releaseNativeHandles()
{
window_ = 0;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextGLX::setSwapInterval(int interval)
{
if (epoxy_has_glx_extension(display_, DefaultScreen(display_), "GLX_EXT_swap_control")) {
::glXSwapIntervalEXT(display_, window_, interval);
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextGLX::getSwapInterval(int &interval_out)
{
if (epoxy_has_glx_extension(display_, DefaultScreen(display_), "GLX_EXT_swap_control")) {
uint interval = 0;
::glXQueryDrawable(display_, window_, GLX_SWAP_INTERVAL_EXT, &interval);
interval_out = int(interval);
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
/**
* Utility function to get GLX attributes.
*
* \param for_fb_config: There are some small differences in
* #glXChooseVisual and #glXChooseFBConfig's attribute encoding.
*
* \note Similar to SDL's 'X11_GL_GetAttributes'
*/
int GHOST_X11_GL_GetAttributes(
int *attribs, int attribs_max, bool is_stereo_visual, bool need_alpha, bool for_fb_config)
{
int i = 0;
if (is_stereo_visual) {
attribs[i++] = GLX_STEREO;
if (for_fb_config) {
attribs[i++] = True;
}
}
if (for_fb_config) {
attribs[i++] = GLX_RENDER_TYPE;
attribs[i++] = GLX_RGBA_BIT;
}
else {
attribs[i++] = GLX_RGBA;
}
attribs[i++] = GLX_DOUBLEBUFFER;
if (for_fb_config) {
attribs[i++] = True;
}
attribs[i++] = GLX_RED_SIZE;
attribs[i++] = True;
attribs[i++] = GLX_BLUE_SIZE;
attribs[i++] = True;
attribs[i++] = GLX_GREEN_SIZE;
attribs[i++] = True;
if (need_alpha) {
attribs[i++] = GLX_ALPHA_SIZE;
attribs[i++] = True;
}
attribs[i++] = 0;
GHOST_ASSERT(i <= attribs_max, "attribute size too small");
(void)attribs_max;
return i;
}
/* Excuse inlining part of GLEW. */
#ifdef USE_GLXEW_INIT_WORKAROUND
static GLuint _glewStrLen(const GLubyte *s)
{
GLuint i = 0;
if (s == nullptr) {
return 0;
}
while (s[i] != '\0') {
i++;
}
return i;
}
static GLuint _glewStrCLen(const GLubyte *s, GLubyte c)
{
GLuint i = 0;
if (s == nullptr) {
return 0;
}
while (s[i] != '\0' && s[i] != c) {
i++;
}
return (s[i] == '\0' || s[i] == c) ? i : 0;
}
static GLboolean _glewStrSame(const GLubyte *a, const GLubyte *b, GLuint n)
{
GLuint i = 0;
if (a == nullptr || b == nullptr) {
return (a == nullptr && b == nullptr && n == 0) ? GL_TRUE : GL_FALSE;
}
while (i < n && a[i] != '\0' && b[i] != '\0' && a[i] == b[i]) {
i++;
}
return i == n ? GL_TRUE : GL_FALSE;
}
static GLboolean _glewSearchExtension(const char *name, const GLubyte *start, const GLubyte *end)
{
const GLubyte *p;
GLuint len = _glewStrLen((const GLubyte *)name);
p = start;
while (p < end) {
GLuint n = _glewStrCLen(p, ' ');
if (len == n && _glewStrSame((const GLubyte *)name, p, n)) {
return GL_TRUE;
}
p += n + 1;
}
return GL_FALSE;
}
#endif /* USE_GLXEW_INIT_WORKAROUND */

View File

@@ -0,0 +1,118 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_Context.hh"
#include <epoxy/glx.h>
#ifndef GHOST_OPENGL_GLX_CONTEXT_FLAGS
/* leave as convenience define for the future */
# define GHOST_OPENGL_GLX_CONTEXT_FLAGS 0
#endif
#ifndef GHOST_OPENGL_GLX_RESET_NOTIFICATION_STRATEGY
# define GHOST_OPENGL_GLX_RESET_NOTIFICATION_STRATEGY 0
#endif
class GHOST_ContextGLX : public GHOST_Context {
/* XR code needs low level graphics data to send to OpenXR. */
friend class GHOST_XrGraphicsBindingOpenGL;
public:
/**
* Constructor.
*/
GHOST_ContextGLX(const GHOST_ContextParams &context_params,
Window window,
Display *display,
GLXFBConfig fbconfig,
int contextProfileMask,
int contextMajorVersion,
int contextMinorVersion,
int contextFlags,
int contextResetNotificationStrategy);
/**
* Destructor.
*/
~GHOST_ContextGLX() override;
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override
{
return GHOST_kSuccess;
}
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
GHOST_TSuccess swapBufferRelease() override;
/**
* Activates the drawing context of this window.
* \return A boolean success indicator.
*/
GHOST_TSuccess activateDrawingContext() override;
/**
* Release the drawing context of the calling thread.
* \return A boolean success indicator.
*/
GHOST_TSuccess releaseDrawingContext() override;
/**
* Call immediately after new to initialize. If this fails then immediately delete the object.
* \return Indication as to whether initialization has succeeded.
*/
GHOST_TSuccess initializeDrawingContext() override;
/**
* Removes references to native handles from this context and then returns
* \return GHOST_kSuccess if it is OK for the parent to release the handles and
* GHOST_kFailure if releasing the handles will interfere with sharing
*/
GHOST_TSuccess releaseNativeHandles() override;
/**
* Sets the swap interval for #swapBuffers.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
GHOST_TSuccess setSwapInterval(int interval) override;
/**
* Gets the current swap interval for #swapBuffers.
* \param interval_out: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
GHOST_TSuccess getSwapInterval(int &interval_out) override;
private:
Display *display_;
GLXFBConfig fbconfig_;
Window window_;
const int context_profile_mask_;
const int context_major_version_;
const int context_minor_version_;
const int context_flags_;
const int context_reset_notification_strategy_;
GLXContext context_;
/** The first created OpenGL context (for sharing display lists) */
static GLXContext s_sharedContext;
static int s_sharedCount;
};
/* used to get GLX info */
int GHOST_X11_GL_GetAttributes(
int *attribs, int attribs_max, bool is_stereo_visual, bool need_alpha, bool for_fb_config);

View File

@@ -0,0 +1,187 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_Context.hh"
#include "GHOST_Types.hh"
#include <Cocoa/Cocoa.h>
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
@class CAMetalLayer;
@class MTLCommandQueue;
@class MTLDevice;
@class MTLRenderPipelineState;
@class MTLTexture;
@class NSView;
class GHOST_ContextMTL : public GHOST_Context {
friend class GHOST_XrGraphicsBindingMetal;
public:
/* Defines the number of simultaneous command buffers which can be in flight.
* The default limit of `64` is considered to be optimal for Blender. Too many command buffers
* will result in workload fragmentation and additional system-level overhead. This limit should
* also only be increased if the application is consistently exceeding the limit, and there are
* no command buffer leaks.
*
* If this limit is reached, starting a new command buffer will fail. The Metal back-end will
* therefore stall until completion and log a warning when this limit is reached in order to
* ensure correct function of the app.
*
* It is generally preferable to reduce the prevalence of GPU_flush or GPU Context switches
* (which will both break command submissions), rather than increasing this limit. */
static const int max_command_buffer_count = 64;
public:
/**
* Constructor.
*/
GHOST_ContextMTL(const GHOST_ContextParams &context_params,
NSView *metalView,
CAMetalLayer *metalLayer);
/**
* Destructor.
*/
~GHOST_ContextMTL() override;
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override
{
return GHOST_kSuccess;
}
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
GHOST_TSuccess swapBufferRelease() override;
/**
* Activates the drawing context of this window.
* \return A boolean success indicator.
*/
GHOST_TSuccess activateDrawingContext() override;
/**
* Release the drawing context of the calling thread.
* \return A boolean success indicator.
*/
GHOST_TSuccess releaseDrawingContext() override;
unsigned int getDefaultFramebuffer() override;
/**
* Call immediately after new to initialize. If this fails then immediately delete the object.
* \return Indication as to whether initialization has succeeded.
*/
GHOST_TSuccess initializeDrawingContext() override;
/**
* Removes references to native handles from this context and then returns
* \return GHOST_kSuccess if it is OK for the parent to release the handles and
* GHOST_kFailure if releasing the handles will interfere with sharing
*/
GHOST_TSuccess releaseNativeHandles() override;
/**
* Sets the swap interval for #swapBuffers.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
GHOST_TSuccess setSwapInterval(int interval) override;
/**
* Gets the current swap interval for #swapBuffers.
* \param interval_out: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
GHOST_TSuccess getSwapInterval(int &interval_out) override;
/**
* Updates the drawing context of this window.
* Needed whenever the window is changed.
* \return Indication of success.
*/
GHOST_TSuccess updateDrawingContext() override;
/**
* Returns a texture that Metal code can use as a render target. The current
* contents of this texture will be composited on top of the frame-buffer
* each time `swapBuffers` is called.
*/
id<MTLTexture> metalOverlayTexture();
/**
* Return a pointer to the Metal command queue used by this context.
*/
MTLCommandQueue *metalCommandQueue();
/**
* Return a pointer to the Metal device associated with this context.
*/
MTLDevice *metalDevice();
/**
* Callback registration.
*/
void metalRegisterPresentCallback(void (*callback)(
MTLRenderPassDescriptor *, id<MTLRenderPipelineState>, id<MTLTexture>, id<CAMetalDrawable>));
void metalRegisterXrBlitCallback(void (*callback)(id<MTLTexture>, int, int, int, int));
private:
/** Metal state */
NSView *metal_view_;
CAMetalLayer *metal_layer_;
MTLRenderPipelineState *metal_render_pipeline_;
bool owns_metal_device_;
/** The virtualized default frame-buffer's texture. */
/**
* Texture that you can render into with Metal. The texture will be
* composited on top of `default_framebuffer_metal_texture_` whenever
* `swapBuffers` is called.
*/
static const int METAL_SWAPCHAIN_SIZE = 3;
struct MTLSwapchainTexture {
id<MTLTexture> texture;
unsigned int index;
};
MTLSwapchainTexture default_framebuffer_metal_texture_[METAL_SWAPCHAIN_SIZE];
unsigned int current_swapchain_index = 0;
/* Present callback.
* We use this such that presentation can be controlled from within the Metal
* Context. This is required for optimal performance and clean control flow.
* Also helps ensure flickering does not occur by present being dependent
* on existing submissions. */
void (*contextPresentCallback)(MTLRenderPassDescriptor *,
id<MTLRenderPipelineState>,
id<MTLTexture>,
id<CAMetalDrawable>);
/* XR texture blitting callback. */
void (*xrBlitCallback)(id<MTLTexture>, int, int, int, int);
int mtl_SwapInterval;
static int s_sharedCount;
/* Single device queue for multiple contexts. */
static MTLCommandQueue *s_sharedMetalCommandQueue;
/* Metal functions */
void metalInit();
void metalFree();
void metalInitFramebuffer();
void metalUpdateFramebuffer();
void metalSwapBuffers();
void initClear() {};
};

View File

@@ -0,0 +1,437 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_ContextMTL class.
*/
/* Don't generate OpenGL deprecation warning. This is a known thing, and is not something easily
* solvable in a short term. */
#ifdef __clang__
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
#include "GHOST_ContextMTL.hh"
#import <Cocoa/Cocoa.h>
#import <Metal/Metal.h>
#import <QuartzCore/QuartzCore.h>
#include <cassert>
#include <mutex>
#include <vector>
static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT_EDR = MTLPixelFormatRGBA16Float;
static void ghost_fatal_error_dialog(const char *msg)
{
@autoreleasepool {
NSString *message = [NSString stringWithFormat:@"Error opening window:\n%s", msg];
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = @"Blender";
alert.informativeText = message;
alert.alertStyle = NSAlertStyleCritical;
[alert addButtonWithTitle:@"Quit"];
[alert runModal];
}
exit(1);
}
MTLCommandQueue *GHOST_ContextMTL::s_sharedMetalCommandQueue = nil;
int GHOST_ContextMTL::s_sharedCount = 0;
GHOST_ContextMTL::GHOST_ContextMTL(const GHOST_ContextParams &context_params,
NSView *metalView,
CAMetalLayer *metalLayer)
: GHOST_Context(context_params),
metal_view_(metalView),
metal_layer_(metalLayer),
metal_render_pipeline_(nil)
{
@autoreleasepool {
/* Initialize Metal Swap-chain. */
current_swapchain_index = 0;
for (int i = 0; i < METAL_SWAPCHAIN_SIZE; i++) {
default_framebuffer_metal_texture_[i].texture = nil;
default_framebuffer_metal_texture_[i].index = i;
}
if (metal_view_) {
owns_metal_device_ = false;
metalInit();
}
else {
/* Prepare offscreen GHOST Context Metal device. */
id<MTLDevice> metalDevice = MTLCreateSystemDefaultDevice();
if (context_params_.is_debug) {
printf("Selected Metal Device: %s\n", [metalDevice.name UTF8String]);
}
owns_metal_device_ = true;
if (metalDevice) {
metal_layer_ = [[CAMetalLayer alloc] init];
metal_layer_.edgeAntialiasingMask = 0;
metal_layer_.masksToBounds = NO;
metal_layer_.opaque = YES;
metal_layer_.framebufferOnly = YES;
metal_layer_.presentsWithTransaction = NO;
[metal_layer_ removeAllAnimations];
metal_layer_.device = metalDevice;
metal_layer_.allowsNextDrawableTimeout = NO;
{
const GHOST_TVSyncModes vsync = getVSync();
if (vsync != GHOST_kVSyncModeUnset) {
metal_layer_.displaySyncEnabled = (vsync == GHOST_kVSyncModeOff) ? NO : YES;
}
}
/* Enable EDR support. This is done by:
* 1. Using a floating point render target, so that values outside 0..1 can be used
* 2. Informing the OS that we are EDR aware, and intend to use values outside 0..1
* 3. Setting the extended sRGB color space so that the OS knows how to interpret the
* values.
*/
metal_layer_.wantsExtendedDynamicRangeContent = YES;
metal_layer_.pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT_EDR;
const CFStringRef name = kCGColorSpaceExtendedSRGB;
CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name);
metal_layer_.colorspace = colorspace;
CGColorSpaceRelease(colorspace);
metalInit();
}
else {
ghost_fatal_error_dialog(
"[ERROR] Failed to create Metal device for offscreen GHOST Context.\n");
}
}
/* Initialize swap-interval. */
mtl_SwapInterval = 60;
}
}
GHOST_ContextMTL::~GHOST_ContextMTL()
{
/* Multiple threads can release their own context at the same time. */
static std::mutex mutex;
std::scoped_lock lock(mutex);
metalFree();
if (owns_metal_device_) {
if (metal_layer_) {
[metal_layer_ release];
metal_layer_ = nil;
}
}
assert(s_sharedCount);
s_sharedCount--;
[s_sharedMetalCommandQueue release];
if (s_sharedCount == 0) {
s_sharedMetalCommandQueue = nil;
}
}
GHOST_TSuccess GHOST_ContextMTL::swapBufferRelease()
{
if (metal_view_) {
metalSwapBuffers();
}
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextMTL::setSwapInterval(int interval)
{
mtl_SwapInterval = interval;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextMTL::getSwapInterval(int &interval_out)
{
interval_out = mtl_SwapInterval;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextMTL::activateDrawingContext()
{
active_context_ = this;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextMTL::releaseDrawingContext()
{
active_context_ = nullptr;
return GHOST_kSuccess;
}
unsigned int GHOST_ContextMTL::getDefaultFramebuffer()
{
/* NOTE(Metal): This is not valid. */
return 0;
}
GHOST_TSuccess GHOST_ContextMTL::updateDrawingContext()
{
if (metal_view_) {
metalUpdateFramebuffer();
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
id<MTLTexture> GHOST_ContextMTL::metalOverlayTexture()
{
/* Increment Swap-chain - Only needed if context is requesting a new texture */
current_swapchain_index = (current_swapchain_index + 1) % METAL_SWAPCHAIN_SIZE;
/* Ensure backing texture is ready for current swapchain index */
updateDrawingContext();
/* Return texture. */
return default_framebuffer_metal_texture_[current_swapchain_index].texture;
}
MTLCommandQueue *GHOST_ContextMTL::metalCommandQueue()
{
return s_sharedMetalCommandQueue;
}
MTLDevice *GHOST_ContextMTL::metalDevice()
{
id<MTLDevice> device = metal_layer_.device;
return (MTLDevice *)device;
}
void GHOST_ContextMTL::metalRegisterPresentCallback(void (*callback)(
MTLRenderPassDescriptor *, id<MTLRenderPipelineState>, id<MTLTexture>, id<CAMetalDrawable>))
{
this->contextPresentCallback = callback;
}
void GHOST_ContextMTL::metalRegisterXrBlitCallback(
void (*callback)(id<MTLTexture>, int, int, int, int))
{
this->xrBlitCallback = callback;
}
GHOST_TSuccess GHOST_ContextMTL::initializeDrawingContext()
{
@autoreleasepool {
if (metal_view_) {
metalInitFramebuffer();
}
}
active_context_ = this;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextMTL::releaseNativeHandles()
{
metal_view_ = nil;
return GHOST_kSuccess;
}
void GHOST_ContextMTL::metalInit()
{
@autoreleasepool {
id<MTLDevice> device = metal_layer_.device;
/* Create a command queue for blit/present operation.
* NOTE: All context should share a single command queue
* to ensure correct ordering of work submitted from multiple contexts. */
if (s_sharedMetalCommandQueue == nil) {
s_sharedMetalCommandQueue = (MTLCommandQueue *)[device
newCommandQueueWithMaxCommandBufferCount:GHOST_ContextMTL::max_command_buffer_count];
}
/* Ensure active GHOSTContext retains a reference to the shared context. */
[s_sharedMetalCommandQueue retain];
s_sharedCount++;
/* Create shaders for blit operation. */
NSString *source = @R"msl(
using namespace metal;
struct Vertex {
float4 position [[position]];
float2 texCoord [[attribute(0)]];
};
vertex Vertex vertex_shader(uint v_id [[vertex_id]]) {
Vertex vtx;
vtx.position.x = float(v_id & 1) * 4.0 - 1.0;
vtx.position.y = float(v_id >> 1) * 4.0 - 1.0;
vtx.position.z = 0.0;
vtx.position.w = 1.0;
vtx.texCoord = vtx.position.xy * 0.5 + 0.5;
return vtx;
}
constexpr sampler s {};
fragment float4 fragment_shader(Vertex v [[stage_in]],
texture2d<float> t [[texture(0)]]) {
/* Final blit should ensure alpha is 1.0. This resolves
* rendering artifacts for blitting of final back-buffer.
* Also clamp the RGB values to avoid artifacts. */
float4 out_tex = t.sample(s, v.texCoord);
out_tex.rgb = min(out_tex.rgb, 16384.0);
out_tex.a = 1.0;
return out_tex;
}
)msl";
MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
options.languageVersion = MTLLanguageVersion1_1;
NSError *error = nil;
id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error];
if (error) {
ghost_fatal_error_dialog(
"GHOST_ContextMTL::metalInit: newLibraryWithSource:options:error: failed!");
}
/* Create a render pipeline for blit operation. */
MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"];
desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"];
[desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat =
METAL_FRAMEBUFFERPIXEL_FORMAT_EDR;
/* Ensure library is released. */
[library autorelease];
metal_render_pipeline_ = (MTLRenderPipelineState *)[device
newRenderPipelineStateWithDescriptor:desc
error:&error];
if (error) {
ghost_fatal_error_dialog(
"GHOST_ContextMTL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!");
}
[desc.fragmentFunction release];
[desc.vertexFunction release];
}
}
void GHOST_ContextMTL::metalFree()
{
if (metal_render_pipeline_) {
[metal_render_pipeline_ release];
metal_render_pipeline_ = nil;
}
for (int i = 0; i < METAL_SWAPCHAIN_SIZE; i++) {
if (default_framebuffer_metal_texture_[i].texture) {
[default_framebuffer_metal_texture_[i].texture release];
default_framebuffer_metal_texture_[i].texture = nil;
}
}
}
void GHOST_ContextMTL::metalInitFramebuffer()
{
updateDrawingContext();
}
void GHOST_ContextMTL::metalUpdateFramebuffer()
{
@autoreleasepool {
const NSSize drawableSize = metal_layer_.drawableSize;
const size_t width = size_t(drawableSize.width);
const size_t height = size_t(drawableSize.height);
if (default_framebuffer_metal_texture_[current_swapchain_index].texture &&
default_framebuffer_metal_texture_[current_swapchain_index].texture.width == width &&
default_framebuffer_metal_texture_[current_swapchain_index].texture.height == height)
{
return;
}
/* Free old texture */
[default_framebuffer_metal_texture_[current_swapchain_index].texture release];
id<MTLDevice> device = metal_layer_.device;
MTLTextureDescriptor *overlayDesc = [MTLTextureDescriptor
texture2DDescriptorWithPixelFormat:METAL_FRAMEBUFFERPIXEL_FORMAT_EDR
width:width
height:height
mipmapped:NO];
overlayDesc.storageMode = MTLStorageModePrivate;
overlayDesc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
id<MTLTexture> overlayTex = [device newTextureWithDescriptor:overlayDesc];
if (!overlayTex) {
ghost_fatal_error_dialog(
"GHOST_ContextMTL::metalUpdateFramebuffer: failed to create Metal overlay texture!");
}
else {
overlayTex.label = [NSString
stringWithFormat:@"Metal Overlay for GHOST Context %p", this]; //@"";
}
default_framebuffer_metal_texture_[current_swapchain_index].texture = overlayTex;
/* Clear texture on create */
id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
{
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
attachment.texture = default_framebuffer_metal_texture_[current_swapchain_index].texture;
attachment.loadAction = MTLLoadActionClear;
attachment.clearColor = MTLClearColorMake(0.294, 0.294, 0.294, 1.000);
attachment.storeAction = MTLStoreActionStore;
}
{
id<MTLRenderCommandEncoder> enc = [cmdBuffer
renderCommandEncoderWithDescriptor:passDescriptor];
[enc endEncoding];
}
[cmdBuffer commit];
metal_layer_.drawableSize = CGSizeMake(CGFloat(width), CGFloat(height));
}
}
void GHOST_ContextMTL::metalSwapBuffers()
{
@autoreleasepool {
updateDrawingContext();
id<CAMetalDrawable> drawable = [metal_layer_ nextDrawable];
if (!drawable) {
return;
}
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
{
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
attachment.texture = drawable.texture;
attachment.loadAction = MTLLoadActionClear;
attachment.clearColor = MTLClearColorMake(1.0, 0.294, 0.294, 1.000);
attachment.storeAction = MTLStoreActionStore;
}
assert(contextPresentCallback);
assert(default_framebuffer_metal_texture_[current_swapchain_index].texture != nil);
(*contextPresentCallback)(passDescriptor,
(id<MTLRenderPipelineState>)metal_render_pipeline_,
default_framebuffer_metal_texture_[current_swapchain_index].texture,
drawable);
}
}

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_ContextNone class.
*/
#include "GHOST_ContextNone.hh"
GHOST_TSuccess GHOST_ContextNone::swapBufferRelease()
{
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextNone::activateDrawingContext()
{
active_context_ = this;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextNone::releaseDrawingContext()
{
active_context_ = nullptr;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextNone::updateDrawingContext()
{
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextNone::initializeDrawingContext()
{
active_context_ = this;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextNone::releaseNativeHandles()
{
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextNone::setSwapInterval(int interval)
{
swap_interval_ = interval;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextNone::getSwapInterval(int &interval_out)
{
interval_out = swap_interval_;
return GHOST_kSuccess;
}

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* Declaration of GHOST_Context class.
*/
#pragma once
#include "GHOST_Context.hh"
class GHOST_ContextNone : public GHOST_Context {
public:
GHOST_ContextNone(const GHOST_ContextParams &context_params) : GHOST_Context(context_params) {}
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override
{
return GHOST_kSuccess;
}
/**
* Dummy function
* \return Always succeeds
*/
GHOST_TSuccess swapBufferRelease() override;
/**
* Dummy function
* \return Always succeeds.
*/
GHOST_TSuccess activateDrawingContext() override;
/**
* Dummy function
* \return Always succeeds.
*/
GHOST_TSuccess releaseDrawingContext() override;
/**
* Dummy function
* \return Always succeeds.
*/
GHOST_TSuccess updateDrawingContext() override;
/**
* Dummy function
* \return Always succeeds.
*/
GHOST_TSuccess initializeDrawingContext() override;
/**
* Dummy function
* \return Always succeeds.
*/
GHOST_TSuccess releaseNativeHandles() override;
/**
* Dummy function
* \return Always succeeds.
*/
GHOST_TSuccess setSwapInterval(int interval) override;
/**
* Dummy function
* \param interval_out: Gets whatever was set by #setSwapInterval.
* \return Always succeeds.
*/
GHOST_TSuccess getSwapInterval(int &interval_out) override;
private:
int swap_interval_ = 1;
};

View File

@@ -0,0 +1,176 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_ContextSDL class.
*/
#include "GHOST_ContextSDL.hh"
#include <cassert>
#include <cstring>
SDL_GLContext GHOST_ContextSDL::s_sharedContext = nullptr;
int GHOST_ContextSDL::s_sharedCount = 0;
GHOST_ContextSDL::GHOST_ContextSDL(const GHOST_ContextParams &context_params,
SDL_Window *window,
int contextProfileMask,
int contextMajorVersion,
int contextMinorVersion,
int contextFlags,
int contextResetNotificationStrategy)
: GHOST_Context(context_params),
window_(window),
hidden_window_(nullptr),
context_profile_mask_(contextProfileMask),
context_major_version_(contextMajorVersion),
context_minor_version_(contextMinorVersion),
context_flags_(contextFlags),
context_reset_notification_strategy_(contextResetNotificationStrategy),
context_(nullptr)
{
// assert(window_ != nullptr);
}
GHOST_ContextSDL::~GHOST_ContextSDL()
{
if (context_ == nullptr) {
return;
}
if (window_ != nullptr && context_ == SDL_GL_GetCurrentContext()) {
SDL_GL_MakeCurrent(window_, nullptr);
}
if (context_ != s_sharedContext || s_sharedCount == 1) {
assert(s_sharedCount > 0);
s_sharedCount--;
if (s_sharedCount == 0) {
s_sharedContext = nullptr;
}
SDL_GL_DestroyContext(context_);
}
if (hidden_window_ != nullptr) {
SDL_DestroyWindow(hidden_window_);
}
}
GHOST_TSuccess GHOST_ContextSDL::swapBufferRelease()
{
SDL_GL_SwapWindow(window_);
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextSDL::activateDrawingContext()
{
if (context_ == nullptr) {
return GHOST_kFailure;
}
active_context_ = this;
return SDL_GL_MakeCurrent(window_, context_) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextSDL::releaseDrawingContext()
{
if (context_ == nullptr) {
return GHOST_kFailure;
}
active_context_ = nullptr;
/* Untested, may not work. */
return SDL_GL_MakeCurrent(nullptr, nullptr) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextSDL::initializeDrawingContext()
{
const bool needAlpha = false;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, context_profile_mask_);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, context_major_version_);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, context_minor_version_);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, context_flags_);
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
if (needAlpha) {
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
}
if (context_params_.is_stereo_visual) {
SDL_GL_SetAttribute(SDL_GL_STEREO, 1);
}
if (window_ == nullptr) {
hidden_window_ = SDL_CreateWindow("Offscreen Context Windows",
1,
1,
SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS |
SDL_WINDOW_HIDDEN);
window_ = hidden_window_;
}
context_ = SDL_GL_CreateContext(window_);
GHOST_TSuccess success;
if (context_ != nullptr) {
if (!s_sharedContext) {
s_sharedContext = context_;
}
s_sharedCount++;
success = SDL_GL_MakeCurrent(window_, context_) ? GHOST_kSuccess : GHOST_kFailure;
{
const GHOST_TVSyncModes vsync = getVSync();
if (vsync != GHOST_kVSyncModeUnset) {
setSwapInterval(int(vsync));
}
}
initClearGL();
SDL_GL_SwapWindow(window_);
active_context_ = this;
success = GHOST_kSuccess;
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_TSuccess GHOST_ContextSDL::releaseNativeHandles()
{
window_ = nullptr;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextSDL::setSwapInterval(int interval)
{
if (!SDL_GL_SetSwapInterval(interval)) {
return GHOST_kFailure;
}
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextSDL::getSwapInterval(int &interval_out)
{
if (!SDL_GL_GetSwapInterval(&interval_out)) {
return GHOST_kFailure;
}
return GHOST_kSuccess;
}

View File

@@ -0,0 +1,111 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_Context.hh"
#include <SDL3/SDL.h>
#ifndef GHOST_OPENGL_SDL_CONTEXT_FLAGS
# ifdef WITH_GPU_DEBUG
# define GHOST_OPENGL_SDL_CONTEXT_FLAGS SDL_GL_CONTEXT_DEBUG_FLAG
# else
# define GHOST_OPENGL_SDL_CONTEXT_FLAGS 0
# endif
#endif
#ifndef GHOST_OPENGL_SDL_RESET_NOTIFICATION_STRATEGY
# define GHOST_OPENGL_SDL_RESET_NOTIFICATION_STRATEGY 0
#endif
class GHOST_ContextSDL : public GHOST_Context {
public:
/**
* Constructor.
*/
GHOST_ContextSDL(const GHOST_ContextParams &context_params,
SDL_Window *window,
int contextProfileMask,
int contextMajorVersion,
int contextMinorVersion,
int contextFlags,
int contextResetNotificationStrategy);
/**
* Destructor.
*/
~GHOST_ContextSDL() override;
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override
{
return GHOST_kSuccess;
}
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
GHOST_TSuccess swapBufferRelease() override;
/**
* Activates the drawing context of this window.
* \return A boolean success indicator.
*/
GHOST_TSuccess activateDrawingContext() override;
/**
* Release the drawing context of the calling thread.
* \return A boolean success indicator.
*/
GHOST_TSuccess releaseDrawingContext() override;
/**
* Call immediately after new to initialize. If this fails then immediately delete the object.
* \return Indication as to whether initialization has succeeded.
*/
GHOST_TSuccess initializeDrawingContext() override;
/**
* Removes references to native handles from this context and then returns
* \return GHOST_kSuccess if it is OK for the parent to release the handles and
* GHOST_kFailure if releasing the handles will interfere with sharing
*/
GHOST_TSuccess releaseNativeHandles() override;
/**
* Sets the swap interval for #swapBuffers.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
GHOST_TSuccess setSwapInterval(int interval) override;
/**
* Gets the current swap interval for #swapBuffers.
* \param interval_out: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
GHOST_TSuccess getSwapInterval(int &interval_out) override;
private:
SDL_Window *window_;
SDL_Window *hidden_window_;
const int context_profile_mask_;
const int context_major_version_;
const int context_minor_version_;
const int context_flags_;
const int context_reset_notification_strategy_;
SDL_GLContext context_; /* sdl_glcontext_ */
/** The first created OpenGL context (for sharing display lists) */
static SDL_GLContext s_sharedContext;
static int s_sharedCount;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,294 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#ifndef WITH_VULKAN_BACKEND
# error "ContextVK requires WITH_VULKAN_BACKEND"
#endif
#include "GHOST_Context.hh"
#ifdef _WIN32
# include "GHOST_SystemWin32.hh"
#elif defined(__APPLE__)
# include "GHOST_SystemCocoa.hh"
#else
# ifdef WITH_GHOST_X11
# include "GHOST_SystemX11.hh"
# else
# define Display void
# define Window void *
# endif
# ifdef WITH_GHOST_WAYLAND
# include "GHOST_SystemWayland.hh"
# else
# define wl_surface void
# define wl_display void
# endif
#endif
#include <map>
#include <optional>
#include <vector>
namespace blender {
class StringRefNull;
}
#ifndef GHOST_OPENGL_VK_CONTEXT_FLAGS
/* leave as convenience define for the future */
# define GHOST_OPENGL_VK_CONTEXT_FLAGS 0
#endif
#ifndef GHOST_OPENGL_VK_RESET_NOTIFICATION_STRATEGY
# define GHOST_OPENGL_VK_RESET_NOTIFICATION_STRATEGY 0
#endif
enum GHOST_TVulkanPlatformType {
GHOST_kVulkanPlatformHeadless = 0,
#ifdef WITH_GHOST_X11
GHOST_kVulkanPlatformX11 = 1,
#endif
#ifdef WITH_GHOST_WAYLAND
GHOST_kVulkanPlatformWayland = 2,
#endif
};
struct GHOST_ContextVK_WindowInfo {
int size[2];
};
struct GHOST_FrameDiscard {
std::vector<VkSwapchainKHR> swapchains;
std::vector<VkSemaphore> semaphores;
void destroy(VkDevice vk_device);
};
struct GHOST_SwapchainImage {
/** Swap-chain image (owned by the swapchain). */
VkImage vk_image = VK_NULL_HANDLE;
/**
* Semaphore for presenting; being signaled when the swap-chain image is ready to be presented.
*/
VkSemaphore present_semaphore = VK_NULL_HANDLE;
void destroy(VkDevice vk_device);
};
struct GHOST_Frame {
/**
* Fence signaled when "previous" use of the frame has finished rendering. When signaled the
* frame can acquire a new image and the semaphores can be reused.
*/
VkFence submission_fence = VK_NULL_HANDLE;
/** Semaphore for acquiring; being signaled when the swap-chain image is ready to be updated. */
VkSemaphore acquire_semaphore = VK_NULL_HANDLE;
GHOST_FrameDiscard discard_pile;
void destroy(VkDevice vk_device);
};
class GHOST_ContextVK : public GHOST_Context {
friend class GHOST_XrGraphicsBindingVulkan;
friend class GHOST_XrGraphicsBindingVulkanD3D;
public:
/**
* Constructor.
*/
GHOST_ContextVK(const GHOST_ContextParams &context_params,
#ifdef _WIN32
HWND hwnd,
#elif defined(__APPLE__)
/* FIXME CAMetalLayer but have issue with linking. */
void *metal_layer,
#else
GHOST_TVulkanPlatformType platform,
/* X11 */
Window window,
Display *display,
/* Wayland */
wl_surface *wayland_surface,
wl_display *wayland_display,
const GHOST_ContextVK_WindowInfo *wayland_window_info,
#endif
int contextMajorVersion,
int contextMinorVersion,
const GHOST_GPUDevice &preferred_device,
const GHOST_WindowHDRInfo *hdr_info_ = nullptr);
/**
* Destructor.
*/
~GHOST_ContextVK() override;
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override;
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
GHOST_TSuccess swapBufferRelease() override;
/**
* Activates the drawing context of this window.
* \return A boolean success indicator.
*/
GHOST_TSuccess activateDrawingContext() override;
/**
* Release the drawing context of the calling thread.
* \return A boolean success indicator.
*/
GHOST_TSuccess releaseDrawingContext() override;
/**
* Call immediately after new to initialize. If this fails then immediately delete the object.
* \return Indication as to whether initialization has succeeded.
*/
GHOST_TSuccess initializeDrawingContext() override;
/**
* Removes references to native handles from this context and then returns
* \return GHOST_kSuccess if it is OK for the parent to release the handles and
* GHOST_kFailure if releasing the handles will interfere with sharing
*/
GHOST_TSuccess releaseNativeHandles() override;
/**
* Gets the Vulkan context related resource handles.
* \return A boolean success indicator.
*/
GHOST_TSuccess getVulkanHandles(GHOST_VulkanHandles &r_handles) override;
GHOST_TSuccess getVulkanSwapChainFormat(GHOST_VulkanSwapChainData *r_swap_chain_data) override;
GHOST_TSuccess setVulkanSwapBuffersCallbacks(
std::function<void(const GHOST_VulkanSwapChainData *, bool)> swap_buffer_draw_callback,
std::function<void(void)> swap_buffer_acquired_callback,
std::function<void(GHOST_VulkanOpenXRData *)> openxr_acquire_framebuffer_image_callback,
std::function<void(GHOST_VulkanOpenXRData *)> openxr_release_framebuffer_image_callback)
override;
#ifdef WITH_GHOST_WAYLAND
/**
* \brief Check if the active driver supports wayland color management.
*
* NVIDIA driver before 595 don't support wayland color management protocol as expected resulting
* in to bright output.
*/
static GHOST_TSuccess supportsWaylandColorManagement();
#endif
/**
* Sets the swap interval for `swapBuffers`.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
GHOST_TSuccess setSwapInterval(int /*interval*/) override
{
return GHOST_kFailure;
}
/**
* Gets the current swap interval for swapBuffers.
* \param interval_out: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
GHOST_TSuccess getSwapInterval(int & /*interval_out*/) override
{
return GHOST_kFailure;
};
/**
* Returns if the context is rendered upside down compared to OpenGL.
*
* Vulkan is always rendered upside down.
*/
bool isUpsideDown() const override
{
return true;
}
/**
* \brief Is the given extension name enabled on instance level?
*
* \returns false, when extension isn't enabled on instance level or when no instance exists.
* Will return true when instance exists and extension name has been enabled on the instance.
*/
static bool is_instance_extension_enabled(blender::StringRefNull extension_name);
/**
* \brief Is the given extension name enabled on device level?
*
* \returns false, when extension isn't enabled on device level or when no instance or device
* exists. Will return true when instance exists and extension name has been enabled on the
* instance.
*/
static bool is_device_extension_enabled(blender::StringRefNull extension_name);
private:
#ifdef _WIN32
HWND hwnd_;
#elif defined(__APPLE__)
/* Is CAMetalLayer* */
void *metal_layer_;
#else /* Linux */
GHOST_TVulkanPlatformType platform_;
/* X11 */
Display *display_;
Window window_;
/* Wayland */
wl_surface *wayland_surface_;
wl_display *wayland_display_;
const GHOST_ContextVK_WindowInfo *wayland_window_info_;
#endif
const int context_major_version_;
const int context_minor_version_;
const GHOST_GPUDevice preferred_device_;
/* Optional HDR info updated by window. */
const GHOST_WindowHDRInfo *hdr_info_;
/* For display only. */
VkSurfaceKHR surface_;
VkSwapchainKHR swapchain_;
std::vector<GHOST_SwapchainImage> swapchain_images_;
std::vector<GHOST_Frame> frame_data_;
uint64_t render_frame_;
uint64_t image_count_;
VkExtent2D render_extent_;
VkExtent2D render_extent_min_;
VkSurfaceFormatKHR surface_format_;
bool use_hdr_swapchain_;
std::optional<uint32_t> acquired_swapchain_image_index_;
std::function<void(const GHOST_VulkanSwapChainData *, bool)> swap_buffer_draw_callback_;
std::function<void(void)> swap_buffer_acquired_callback_;
std::function<void(GHOST_VulkanOpenXRData *)> openxr_acquire_framebuffer_image_callback_;
std::function<void(GHOST_VulkanOpenXRData *)> openxr_release_framebuffer_image_callback_;
std::vector<VkFence> fence_pile_;
std::map<VkSwapchainKHR, std::vector<VkFence>> present_fences_;
const char *getPlatformSpecificSurfaceExtension() const;
GHOST_TSuccess recreateSwapchain(bool use_hdr_swapchain);
GHOST_TSuccess initializeFrameData();
GHOST_TSuccess destroySwapchain();
VkFence getFence();
void setPresentFence(VkSwapchainKHR swapchain, VkFence fence);
void destroySwapchainPresentFences(VkSwapchainKHR swapchain);
};

View File

@@ -0,0 +1,737 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_ContextWGL class.
*/
#include "GHOST_ContextWGL.hh"
#include <tchar.h>
#include <cassert>
#include <cstdio>
#include <vector>
HGLRC GHOST_ContextWGL::s_sharedHGLRC = nullptr;
int GHOST_ContextWGL::s_sharedCount = 0;
std::list<GHOST_ContextWGL::OffscreenWindowHandle>
GHOST_ContextWGL::s_sharedOffscreenWindowHandles;
/* Some third-generation Intel video-cards are constantly bring problems */
static bool is_crappy_intel_card()
{
return strstr((const char *)glGetString(GL_VENDOR), "Intel") != nullptr;
}
GHOST_ContextWGL::GHOST_ContextWGL(const GHOST_ContextParams &context_params,
bool alphaBackground,
HWND hWnd,
HDC hDC,
int contextProfileMask,
int contextMajorVersion,
int contextMinorVersion,
int contextFlags,
int contextResetNotificationStrategy)
: GHOST_Context(context_params),
h_wnd_(hWnd),
h_DC_(hDC),
context_profile_mask_(contextProfileMask),
context_major_version_(contextMajorVersion),
context_minor_version_(contextMinorVersion),
context_flags_(contextFlags),
alpha_background_(alphaBackground),
context_reset_notification_strategy_(contextResetNotificationStrategy),
h_GLRC_(nullptr),
#ifndef NDEBUG
dummy_vendor_(nullptr),
dummy_renderer_(nullptr),
dummy_version_(nullptr),
#endif
offscreen_window_handle_(nullptr)
{
if (h_wnd_ == nullptr) {
/* For offscreen context, OpenGL needs a dummy window to create a context on windows.
* Create or reuse an existing one. */
for (OffscreenWindowHandle &handle : s_sharedOffscreenWindowHandles) {
if (!handle.used) {
offscreen_window_handle_ = &handle;
break;
}
}
if (offscreen_window_handle_ == nullptr) {
offscreen_window_handle_ = &s_sharedOffscreenWindowHandles.emplace_back();
}
offscreen_window_handle_->used = true;
h_wnd_ = offscreen_window_handle_->h_wnd;
h_DC_ = offscreen_window_handle_->h_DC;
}
assert(h_DC_ != nullptr);
}
GHOST_ContextWGL::~GHOST_ContextWGL()
{
if (h_GLRC_ != nullptr) {
if (h_GLRC_ == ::wglGetCurrentContext()) {
WIN32_CHK(::wglMakeCurrent(nullptr, nullptr));
}
if (h_GLRC_ != s_sharedHGLRC || s_sharedCount == 1) {
assert(s_sharedCount > 0);
s_sharedCount--;
if (s_sharedCount == 0) {
s_sharedHGLRC = nullptr;
}
WIN32_CHK(::wglDeleteContext(h_GLRC_));
}
}
releaseNativeHandles();
#ifndef NDEBUG
if (dummy_renderer_) {
free((void *)dummy_renderer_);
free((void *)dummy_vendor_);
free((void *)dummy_version_);
}
#endif
}
GHOST_TSuccess GHOST_ContextWGL::swapBufferRelease()
{
return WIN32_CHK(::SwapBuffers(h_DC_)) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextWGL::setSwapInterval(int interval)
{
if (epoxy_has_wgl_extension(h_DC_, "WGL_EXT_swap_control")) {
return WIN32_CHK(::wglSwapIntervalEXT(interval)) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
}
else {
return GHOST_kFailure;
}
}
GHOST_TSuccess GHOST_ContextWGL::getSwapInterval(int &interval_out)
{
if (epoxy_has_wgl_extension(h_DC_, "WGL_EXT_swap_control")) {
interval_out = ::wglGetSwapIntervalEXT();
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
GHOST_TSuccess GHOST_ContextWGL::activateDrawingContext()
{
if (WIN32_CHK(::wglMakeCurrent(h_DC_, h_GLRC_))) {
active_context_ = this;
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
GHOST_TSuccess GHOST_ContextWGL::releaseDrawingContext()
{
/* Calling wglMakeCurrent(nullptr, nullptr) without an active context returns an error,
* so we always pass the device context handle. */
if (WIN32_CHK(::wglMakeCurrent(h_DC_, nullptr))) {
active_context_ = nullptr;
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
/* Ron Fosner's code for weighting pixel formats and forcing software.
* See http://www.opengl.org/resources/faq/technical/weight.cpp
*/
static int weight_pixel_format(PIXELFORMATDESCRIPTOR &pfd, PIXELFORMATDESCRIPTOR &preferredPFD)
{
int weight = 0;
/* assume desktop color depth is 32 bits per pixel */
/* cull unusable pixel formats */
/* if no formats can be found, can we determine why it was rejected? */
if (!(pfd.dwFlags & PFD_SUPPORT_OPENGL) || !(pfd.dwFlags & PFD_DRAW_TO_WINDOW) ||
!(pfd.dwFlags & PFD_DOUBLEBUFFER) || /* Blender _needs_ this. */
!(pfd.iPixelType == PFD_TYPE_RGBA) ||
(pfd.cColorBits > 32) || /* 64 bit formats disable AERO. */
(pfd.dwFlags & PFD_GENERIC_FORMAT)) /* No software renderers. */
{
return 0;
}
weight = 1; /* it's usable */
weight += pfd.cColorBits - 8;
if (preferredPFD.cAlphaBits > 0 && pfd.cAlphaBits > 0) {
weight++;
}
return weight;
}
/*
* A modification of Ron Fosner's replacement for ChoosePixelFormat
* returns 0 on error, else returns the pixel format number to be used
*/
static int choose_pixel_format_legacy(HDC hDC, PIXELFORMATDESCRIPTOR &preferredPFD)
{
int iPixelFormat = 0;
int weight = 0;
int iStereoPixelFormat = 0;
int stereoWeight = 0;
/* choose a pixel format using the useless Windows function in case we come up empty handed */
int iLastResortPixelFormat = ::ChoosePixelFormat(hDC, &preferredPFD);
WIN32_CHK(iLastResortPixelFormat != 0);
int lastPFD = ::DescribePixelFormat(hDC, 1, sizeof(PIXELFORMATDESCRIPTOR), nullptr);
WIN32_CHK(lastPFD != 0);
for (int i = 1; i <= lastPFD; i++) {
PIXELFORMATDESCRIPTOR pfd;
int check = ::DescribePixelFormat(hDC, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
WIN32_CHK(check == lastPFD);
int w = weight_pixel_format(pfd, preferredPFD);
if (w > weight) {
weight = w;
iPixelFormat = i;
}
if (w > stereoWeight && (preferredPFD.dwFlags & pfd.dwFlags & PFD_STEREO)) {
stereoWeight = w;
iStereoPixelFormat = i;
}
}
/* choose any available stereo format over a non-stereo format */
if (iStereoPixelFormat != 0) {
iPixelFormat = iStereoPixelFormat;
}
if (iPixelFormat == 0) {
fprintf(stderr, "Warning! Using result of ChoosePixelFormat.\n");
iPixelFormat = iLastResortPixelFormat;
}
return iPixelFormat;
}
/**
* Clone a window for the purpose of creating a temporary context to initialize WGL extensions.
* There is no generic way to clone the lpParam parameter,
* so the caller is responsible for cloning it themselves.
*/
static HWND clone_window(HWND hWnd, LPVOID lpParam)
{
int count;
SetLastError(NO_ERROR);
DWORD dwExStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
WIN32_CHK(GetLastError() == NO_ERROR);
/* The maximum length for WNDCLASSEXW.lpszClassName is 256. */
WCHAR lpClassName[257] = L"";
count = GetClassNameW(hWnd, lpClassName, sizeof(lpClassName) / sizeof(WCHAR));
WIN32_CHK(count != 0);
const int titleLength = GetWindowTextLengthW(hWnd);
WIN32_CHK(titleLength != 0 || GetLastError() == NO_ERROR);
/* Allocate space for characters + terminating null. */
std::wstring WindowName(static_cast<size_t>(titleLength) + 1, L'\0');
count = GetWindowTextW(hWnd, WindowName.data(), static_cast<int>(WindowName.size()));
WIN32_CHK(count >= 0);
WindowName.resize(static_cast<size_t>(count));
DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
WIN32_CHK(GetLastError() == NO_ERROR);
RECT rect;
GetWindowRect(hWnd, &rect);
WIN32_CHK(GetLastError() == NO_ERROR);
HWND hWndParent = (HWND)GetWindowLongPtr(hWnd, GWLP_HWNDPARENT);
WIN32_CHK(GetLastError() == NO_ERROR);
HMENU hMenu = GetMenu(hWnd);
WIN32_CHK(GetLastError() == NO_ERROR);
HINSTANCE hInstance = (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
WIN32_CHK(GetLastError() == NO_ERROR);
HWND hwndCloned = CreateWindowExW(dwExStyle,
lpClassName,
WindowName.c_str(),
dwStyle,
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
hWndParent,
hMenu,
hInstance,
lpParam);
WIN32_CHK(hwndCloned != nullptr);
return hwndCloned;
}
static void makeAttribList(std::vector<int> &out, bool stereoVisual, bool needAlpha)
{
out.clear();
out.reserve(30);
out.push_back(WGL_SUPPORT_OPENGL_ARB);
out.push_back(GL_TRUE);
out.push_back(WGL_DRAW_TO_WINDOW_ARB);
out.push_back(GL_TRUE);
out.push_back(WGL_DOUBLE_BUFFER_ARB);
out.push_back(GL_TRUE);
out.push_back(WGL_ACCELERATION_ARB);
out.push_back(WGL_FULL_ACCELERATION_ARB);
if (stereoVisual) {
out.push_back(WGL_STEREO_ARB);
out.push_back(GL_TRUE);
}
out.push_back(WGL_PIXEL_TYPE_ARB);
out.push_back(WGL_TYPE_RGBA_ARB);
out.push_back(WGL_COLOR_BITS_ARB);
out.push_back(24);
if (needAlpha) {
out.push_back(WGL_ALPHA_BITS_ARB);
out.push_back(8);
}
out.push_back(0);
}
/* Temporary context used to create the actual context. We need ARB pixel format
* and context extensions, which are only available within a context. */
struct DummyContextWGL {
HWND dummyHWND = nullptr;
HDC dummyHDC = nullptr;
HGLRC dummyHGLRC = nullptr;
HDC prevHDC = nullptr;
HGLRC prevHGLRC = nullptr;
int dummyPixelFormat = 0;
PIXELFORMATDESCRIPTOR preferredPFD;
bool has_WGL_ARB_pixel_format = false;
bool has_WGL_ARB_create_context = false;
bool has_WGL_ARB_create_context_profile = false;
bool has_WGL_ARB_create_context_robustness = false;
DummyContextWGL(HDC hDC, HWND hWnd, bool stereoVisual, bool needAlpha)
{
preferredPFD = {
sizeof(PIXELFORMATDESCRIPTOR), /* size */
1, /* version */
(DWORD)(PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW |
PFD_DOUBLEBUFFER | /* support double-buffering */
(stereoVisual ? PFD_STEREO : 0)), /* support stereo */
PFD_TYPE_RGBA, /* color type */
(BYTE)(needAlpha ? 32 : 24), /* preferred color depth */
0,
0,
0,
0,
0,
0, /* color bits (ignored) */
(BYTE)(needAlpha ? 8 : 0), /* alpha buffer */
0, /* alpha shift (ignored) */
0, /* no accumulation buffer */
0,
0,
0,
0, /* Accumulation bits (ignored). */
0, /* depth buffer */
0, /* stencil buffer */
0, /* no auxiliary buffers */
PFD_MAIN_PLANE, /* main layer */
0, /* reserved */
0,
0,
0 /* layer, visible, and damage masks (ignored) */
};
SetLastError(NO_ERROR);
prevHDC = ::wglGetCurrentDC();
WIN32_CHK(GetLastError() == NO_ERROR);
prevHGLRC = ::wglGetCurrentContext();
WIN32_CHK(GetLastError() == NO_ERROR);
dummyPixelFormat = choose_pixel_format_legacy(hDC, preferredPFD);
if (dummyPixelFormat == 0) {
return;
}
PIXELFORMATDESCRIPTOR chosenPFD;
if (!WIN32_CHK(::DescribePixelFormat(
hDC, dummyPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &chosenPFD)))
{
return;
}
if (hWnd) {
dummyHWND = clone_window(hWnd, nullptr);
if (dummyHWND == nullptr) {
return;
}
dummyHDC = GetDC(dummyHWND);
}
if (!WIN32_CHK(dummyHDC != nullptr)) {
return;
}
if (!WIN32_CHK(::SetPixelFormat(dummyHDC, dummyPixelFormat, &chosenPFD))) {
return;
}
dummyHGLRC = ::wglCreateContext(dummyHDC);
if (!WIN32_CHK(dummyHGLRC != nullptr)) {
return;
}
if (!WIN32_CHK(::wglMakeCurrent(dummyHDC, dummyHGLRC))) {
return;
}
has_WGL_ARB_pixel_format = epoxy_has_wgl_extension(hDC, "WGL_ARB_pixel_format");
has_WGL_ARB_create_context = epoxy_has_wgl_extension(hDC, "WGL_ARB_create_context");
has_WGL_ARB_create_context_profile = epoxy_has_wgl_extension(hDC,
"WGL_ARB_create_context_profile");
has_WGL_ARB_create_context_robustness = epoxy_has_wgl_extension(
hDC, "WGL_ARB_create_context_robustness");
}
~DummyContextWGL()
{
WIN32_CHK(::wglMakeCurrent(prevHDC, prevHGLRC));
if (dummyHGLRC != nullptr) {
WIN32_CHK(::wglDeleteContext(dummyHGLRC));
}
if (dummyHWND != nullptr) {
if (dummyHDC != nullptr) {
WIN32_CHK(::ReleaseDC(dummyHWND, dummyHDC));
}
WIN32_CHK(::DestroyWindow(dummyHWND));
}
}
};
int GHOST_ContextWGL::_choose_pixel_format_arb_1(bool stereoVisual, bool needAlpha)
{
std::vector<int> iAttributes;
#define _MAX_PIXEL_FORMATS 32
int iPixelFormat = 0;
int iPixelFormats[_MAX_PIXEL_FORMATS];
makeAttribList(iAttributes, stereoVisual, needAlpha);
uint nNumFormats;
WIN32_CHK(wglChoosePixelFormatARB(
h_DC_, &(iAttributes[0]), nullptr, _MAX_PIXEL_FORMATS, iPixelFormats, &nNumFormats));
if (nNumFormats > 0) {
iPixelFormat = iPixelFormats[0];
}
// check pixel format
if (iPixelFormat != 0) {
if (needAlpha) {
int alphaBits, iQuery = WGL_ALPHA_BITS_ARB;
wglGetPixelFormatAttribivARB(h_DC_, iPixelFormat, 0, 1, &iQuery, &alphaBits);
if (alphaBits == 0) {
fprintf(stderr, "Warning! Unable to find a frame buffer with alpha channel.\n");
}
}
}
return iPixelFormat;
}
int GHOST_ContextWGL::choose_pixel_format_arb(bool stereoVisual, bool needAlpha)
{
int iPixelFormat;
iPixelFormat = _choose_pixel_format_arb_1(stereoVisual, needAlpha);
if (iPixelFormat == 0 && stereoVisual) {
fprintf(stderr, "Warning! Unable to find a stereo pixel format.\n");
iPixelFormat = _choose_pixel_format_arb_1(false, needAlpha);
context_params_.is_stereo_visual = false; /* Set context property to actual value. */
}
return iPixelFormat;
}
#ifndef NDEBUG
static void reportContextString(const char *name, const char *dummy, const char *context)
{
fprintf(stderr, "%s: %s\n", name, context);
if (dummy && strcmp(dummy, context) != 0) {
fprintf(stderr, "Warning! Dummy %s: %s\n", name, dummy);
}
}
#endif
GHOST_TSuccess GHOST_ContextWGL::initializeDrawingContext()
{
SetLastError(NO_ERROR);
HGLRC prevHGLRC = ::wglGetCurrentContext();
WIN32_CHK(GetLastError() == NO_ERROR);
HDC prevHDC = ::wglGetCurrentDC();
WIN32_CHK(GetLastError() == NO_ERROR);
{
const bool needAlpha = alpha_background_;
DummyContextWGL dummy(h_DC_, h_wnd_, context_params_.is_stereo_visual, needAlpha);
if (!dummy.has_WGL_ARB_create_context || ::GetPixelFormat(h_DC_) == 0) {
int iPixelFormat = 0;
if (dummy.has_WGL_ARB_pixel_format) {
iPixelFormat = choose_pixel_format_arb(context_params_.is_stereo_visual, needAlpha);
}
if (iPixelFormat == 0) {
iPixelFormat = choose_pixel_format_legacy(h_DC_, dummy.preferredPFD);
}
if (iPixelFormat == 0) {
goto error;
}
PIXELFORMATDESCRIPTOR chosenPFD;
int lastPFD = ::DescribePixelFormat(
h_DC_, iPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &chosenPFD);
if (!WIN32_CHK(lastPFD != 0)) {
goto error;
}
if (needAlpha && chosenPFD.cAlphaBits == 0) {
fprintf(stderr, "Warning! Unable to find a pixel format with an alpha channel.\n");
}
if (!WIN32_CHK(::SetPixelFormat(h_DC_, iPixelFormat, &chosenPFD))) {
goto error;
}
}
if (dummy.has_WGL_ARB_create_context) {
int profileBitCore = context_profile_mask_ & WGL_CONTEXT_CORE_PROFILE_BIT_ARB;
int profileBitCompat = context_profile_mask_ & WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
if (!dummy.has_WGL_ARB_create_context_profile && profileBitCore) {
fprintf(stderr, "Warning! OpenGL core profile not available.\n");
}
if (!dummy.has_WGL_ARB_create_context_profile && profileBitCompat) {
fprintf(stderr, "Warning! OpenGL compatibility profile not available.\n");
}
int profileMask = 0;
if (dummy.has_WGL_ARB_create_context_profile && profileBitCore) {
profileMask |= profileBitCore;
}
if (dummy.has_WGL_ARB_create_context_profile && profileBitCompat) {
profileMask |= profileBitCompat;
}
if (profileMask != context_profile_mask_) {
fprintf(stderr, "Warning! Ignoring untested OpenGL context profile mask bits.");
}
std::vector<int> iAttributes;
if (profileMask) {
iAttributes.push_back(WGL_CONTEXT_PROFILE_MASK_ARB);
iAttributes.push_back(profileMask);
}
if (context_major_version_ != 0) {
iAttributes.push_back(WGL_CONTEXT_MAJOR_VERSION_ARB);
iAttributes.push_back(context_major_version_);
}
if (context_minor_version_ != 0) {
iAttributes.push_back(WGL_CONTEXT_MINOR_VERSION_ARB);
iAttributes.push_back(context_minor_version_);
}
if (context_flags_ != 0) {
iAttributes.push_back(WGL_CONTEXT_FLAGS_ARB);
iAttributes.push_back(context_flags_);
}
if (context_reset_notification_strategy_ != 0) {
if (dummy.has_WGL_ARB_create_context_robustness) {
iAttributes.push_back(WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB);
iAttributes.push_back(context_reset_notification_strategy_);
}
else {
fprintf(stderr, "Warning! Cannot set the reset notification strategy.");
}
}
iAttributes.push_back(0);
h_GLRC_ = ::wglCreateContextAttribsARB(h_DC_, s_sharedHGLRC, &(iAttributes[0]));
}
}
/* Silence warnings interpreted as errors by users when trying to get
* a context with version higher than 3.3 Core. */
{
const bool silent = context_major_version_ > 3;
if (!WIN32_CHK_SILENT(h_GLRC_ != nullptr, silent)) {
goto error;
}
}
s_sharedCount++;
if (s_sharedHGLRC == nullptr) {
s_sharedHGLRC = h_GLRC_;
}
if (!WIN32_CHK(::wglMakeCurrent(h_DC_, h_GLRC_))) {
goto error;
}
{
const GHOST_TVSyncModes vsync = getVSync();
if (vsync != GHOST_kVSyncModeUnset) {
setSwapInterval(int(vsync));
}
}
if (is_crappy_intel_card()) {
/* Some Intel cards with context 4.1 or 4.2
* don't have the point sprite enabled by default.
*
* However GL_POINT_SPRITE was removed in 3.2 and is now permanently ON.
* Then use brute force. */
glEnable(GL_POINT_SPRITE);
}
initClearGL();
::SwapBuffers(h_DC_);
#ifndef NDEBUG
{
const char *vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
const char *renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
const char *version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
reportContextString("Vendor", dummy_vendor_, vendor);
reportContextString("Renderer", dummy_renderer_, renderer);
reportContextString("Version", dummy_version_, version);
fprintf(stderr, "Context Version: %d.%d\n", context_major_version_, context_minor_version_);
}
#endif
active_context_ = this;
return GHOST_kSuccess;
error:
::wglMakeCurrent(prevHDC, prevHGLRC);
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextWGL::releaseNativeHandles()
{
GHOST_TSuccess success = h_GLRC_ != s_sharedHGLRC || s_sharedCount == 1 ? GHOST_kSuccess :
GHOST_kFailure;
if (offscreen_window_handle_) {
offscreen_window_handle_->used = false;
offscreen_window_handle_ = nullptr;
}
h_wnd_ = nullptr;
h_DC_ = nullptr;
return success;
}
GHOST_ContextWGL::OffscreenWindowHandle::OffscreenWindowHandle()
{
h_wnd = CreateWindowA("STATIC",
"BlenderGLEW",
WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
0,
0,
64,
64,
nullptr,
nullptr,
GetModuleHandle(nullptr),
nullptr);
h_DC = GetDC(h_wnd);
}
GHOST_ContextWGL::OffscreenWindowHandle::~OffscreenWindowHandle()
{
if (h_DC != nullptr) {
WIN32_CHK(::ReleaseDC(h_wnd, h_DC));
}
if (h_wnd != nullptr) {
WIN32_CHK(::DestroyWindow(h_wnd));
}
}

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_Context.hh"
#include <epoxy/wgl.h>
#include <list>
#ifndef GHOST_OPENGL_WGL_RESET_NOTIFICATION_STRATEGY
# define GHOST_OPENGL_WGL_RESET_NOTIFICATION_STRATEGY 0
#endif
class GHOST_ContextWGL : public GHOST_Context {
/* XR code needs low level graphics data to send to OpenXR. */
friend class GHOST_XrGraphicsBindingOpenGL;
public:
/**
* Constructor.
*/
GHOST_ContextWGL(const GHOST_ContextParams &context_params,
bool alphaBackground,
HWND hWnd,
HDC hDC,
int contextProfileMask,
int contextMajorVersion,
int contextMinorVersion,
int contextFlags,
int contextResetNotificationStrategy);
/**
* Destructor.
*/
~GHOST_ContextWGL() override;
/** \copydoc #GHOST_IContext::swapBuffersAcquire */
GHOST_TSuccess swapBufferAcquire() override
{
return GHOST_kSuccess;
}
/**
* Swaps front and back buffers of a window.
* \return A boolean success indicator.
*/
GHOST_TSuccess swapBufferRelease() override;
/**
* Activates the drawing context of this window.
* \return A boolean success indicator.
*/
GHOST_TSuccess activateDrawingContext() override;
/**
* Release the drawing context of the calling thread.
* \return A boolean success indicator.
*/
GHOST_TSuccess releaseDrawingContext() override;
/**
* Call immediately after new to initialize. If this fails then immediately delete the object.
* \return Indication as to whether initialization has succeeded.
*/
GHOST_TSuccess initializeDrawingContext() override;
/**
* Removes references to native handles from this context and then returns
* \return GHOST_kSuccess if it is OK for the parent to release the handles and
* GHOST_kFailure if releasing the handles will interfere with sharing
*/
GHOST_TSuccess releaseNativeHandles() override;
/**
* Sets the swap interval for #swapBuffers.
* \param interval: The swap interval to use.
* \return A boolean success indicator.
*/
GHOST_TSuccess setSwapInterval(int interval) override;
/**
* Gets the current swap interval for #swapBuffers.
* \param interval_out: Variable to store the swap interval if it can be read.
* \return Whether the swap interval can be read.
*/
GHOST_TSuccess getSwapInterval(int &interval_out) override;
private:
int choose_pixel_format_arb(bool stereoVisual, bool needAlpha);
int _choose_pixel_format_arb_1(bool stereoVisual, bool needAlpha);
HWND h_wnd_;
HDC h_DC_;
const int context_profile_mask_;
const int context_major_version_;
const int context_minor_version_;
const int context_flags_;
const bool alpha_background_;
const int context_reset_notification_strategy_;
HGLRC h_GLRC_;
#ifndef NDEBUG
const char *dummy_vendor_;
const char *dummy_renderer_;
const char *dummy_version_;
#endif
static HGLRC s_sharedHGLRC;
static int s_sharedCount;
/* Offscreen window handles are cached to ensure they are created and freed on the
* main thread, as required by the Windows API. */
struct OffscreenWindowHandle {
OffscreenWindowHandle();
~OffscreenWindowHandle();
HWND h_wnd = nullptr;
HDC h_DC = nullptr;
bool used = false;
};
static std::list<OffscreenWindowHandle> s_sharedOffscreenWindowHandles;
OffscreenWindowHandle *offscreen_window_handle_;
};

View File

@@ -0,0 +1,80 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Macro's used in GHOST debug target.
*/
#pragma once
#ifdef _MSC_VER
# ifdef _DEBUG
/* Suppress STL-MSVC debug info warning. */
# pragma warning(disable : 4786)
# endif
#endif
#include <cstdio> /* For `printf()`. */
#include <iostream>
#if defined(WITH_GHOST_DEBUG)
# define GHOST_PRINT(x) \
{ \
std::cout << x; \
} \
((void)0)
# define GHOST_PRINTF(x, ...) \
{ \
printf(x, __VA_ARGS__); \
} \
((void)0)
#else
/* Expand even when `WITH_GHOST_DEBUG` is disabled to prevent expressions
* becoming invalid even when the option is disable. */
# define GHOST_PRINT(x) \
{ \
if (false) { \
std::cout << x; \
} \
} \
((void)0)
# define GHOST_PRINTF(x, ...) \
{ \
if (false) { \
printf(x, __VA_ARGS__); \
} \
} \
((void)0)
#endif /* `!defined(WITH_GHOST_DEBUG)` */
#ifndef NDEBUG
# ifdef WITH_ASSERT_ABORT
# include <cstdlib> /* For `abort()`. */
# define GHOST_ASSERT(x, info) \
{ \
if (!(x)) { \
fprintf(stderr, "GHOST_ASSERT failed: "); \
fprintf(stderr, info); \
fprintf(stderr, "\n"); \
abort(); \
} \
} \
((void)0)
/* Show the failure in non-release builds too. */
# else
# define GHOST_ASSERT(x, info) \
{ \
if (!(x)) { \
GHOST_PRINT("GHOST_ASSERT failed: "); \
GHOST_PRINT(info); \
GHOST_PRINT("\n"); \
} \
} \
((void)0)
# endif
#else /* `!defined(NDEBUG)` */
# define GHOST_ASSERT(x, info) ((void)0)
#endif /* `!defined(NDEBUG)` */

View File

@@ -0,0 +1,355 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include "GHOST_DropTargetWin32.hh"
#include "GHOST_Debug.hh"
#include <shellapi.h>
#include "utf_winfunc.hh"
#include "utfconv.hh"
#ifdef WITH_GHOST_DEBUG
/* utility */
void printLastError(void);
#endif /* WITH_GHOST_DEBUG */
GHOST_DropTargetWin32::GHOST_DropTargetWin32(GHOST_WindowWin32 *window, GHOST_SystemWin32 *system)
: window_(window), system_(system)
{
c_ref_ = 1;
h_wnd_ = window->getHWND();
dragged_object_type_ = GHOST_kDragnDropTypeUnknown;
}
GHOST_DropTargetWin32::~GHOST_DropTargetWin32() {}
/*
* IUnknown::QueryInterface
*/
HRESULT __stdcall GHOST_DropTargetWin32::QueryInterface(REFIID riid, void **ppv_obj)
{
if (!ppv_obj) {
return E_INVALIDARG;
}
*ppv_obj = nullptr;
if (riid == IID_IUnknown || riid == IID_IDropTarget) {
AddRef();
*ppv_obj = (void *)this;
return S_OK;
}
*ppv_obj = nullptr;
return E_NOINTERFACE;
}
/*
* IUnknown::AddRef
*/
ULONG __stdcall GHOST_DropTargetWin32::AddRef(void)
{
return ::InterlockedIncrement(&c_ref_);
}
/*
* IUnknown::Release
*/
ULONG __stdcall GHOST_DropTargetWin32::Release(void)
{
ULONG refs = ::InterlockedDecrement(&c_ref_);
if (refs == 0) {
delete this;
return 0;
}
else {
return refs;
}
}
/*
* Implementation of IDropTarget::DragEnter
*/
HRESULT __stdcall GHOST_DropTargetWin32::DragEnter(IDataObject *p_data_object,
DWORD /*grf_key_state*/,
POINTL pt,
DWORD *pdw_effect)
{
/* We accept all drop by default. */
window_->setAcceptDragOperation(true);
*pdw_effect = DROPEFFECT_NONE;
dragged_object_type_ = getGhostType(p_data_object);
system_->pushDragDropEvent(
GHOST_kEventDraggingEntered, dragged_object_type_, window_, pt.x, pt.y, nullptr);
return S_OK;
}
/*
* Implementation of IDropTarget::DragOver
*/
HRESULT __stdcall GHOST_DropTargetWin32::DragOver(DWORD /*grf_key_state*/,
POINTL pt,
DWORD *pdw_effect)
{
if (window_->canAcceptDragOperation()) {
*pdw_effect = allowedDropEffect(*pdw_effect);
}
else {
*pdw_effect = DROPEFFECT_NONE;
/* XXX Uncomment to test drop. Drop will not be called if `pdw_effect == DROPEFFECT_NONE`. */
// *pdw_effect = DROPEFFECT_COPY;
}
system_->pushDragDropEvent(
GHOST_kEventDraggingUpdated, dragged_object_type_, window_, pt.x, pt.y, nullptr);
return S_OK;
}
/*
* Implementation of IDropTarget::DragLeave
*/
HRESULT __stdcall GHOST_DropTargetWin32::DragLeave(void)
{
system_->pushDragDropEvent(
GHOST_kEventDraggingExited, dragged_object_type_, window_, 0, 0, nullptr);
dragged_object_type_ = GHOST_kDragnDropTypeUnknown;
return S_OK;
}
/* Implementation of IDropTarget::Drop
* This function will not be called if pdw_effect is set to DROPEFFECT_NONE in
* the implementation of IDropTarget::DragOver
*/
HRESULT __stdcall GHOST_DropTargetWin32::Drop(IDataObject *p_data_object,
DWORD /*grf_key_state*/,
POINTL pt,
DWORD *pdw_effect)
{
void *data = getGhostData(p_data_object);
if (window_->canAcceptDragOperation()) {
*pdw_effect = allowedDropEffect(*pdw_effect);
}
else {
*pdw_effect = DROPEFFECT_NONE;
}
if (data) {
system_->pushDragDropEvent(
GHOST_kEventDraggingDropDone, dragged_object_type_, window_, pt.x, pt.y, data);
}
dragged_object_type_ = GHOST_kDragnDropTypeUnknown;
return S_OK;
}
/*
* Helpers
*/
DWORD GHOST_DropTargetWin32::allowedDropEffect(DWORD dw_allowed)
{
DWORD dw_effect = DROPEFFECT_NONE;
if (dw_allowed & DROPEFFECT_COPY) {
dw_effect = DROPEFFECT_COPY;
}
return dw_effect;
}
GHOST_TDragnDropTypes GHOST_DropTargetWin32::getGhostType(IDataObject *p_data_object)
{
/* File-names. Prefer looking for the CF_HDROP data format first as certain file-manager
* applications send CF_TEXT as well. See issue #135672. */
FORMATETC fmtetc = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
if (p_data_object->QueryGetData(&fmtetc) == S_OK) {
return GHOST_kDragnDropTypeFilenames;
}
/* Text
* NOTE: Unlike the clipboard, Windows will not synthesize missing text formats for drag and
* drop operations, so we need to check for both CF_UNICODETEXT and CF_TEXT.
*/
fmtetc.cfFormat = CF_UNICODETEXT;
if (p_data_object->QueryGetData(&fmtetc) == S_OK) {
return GHOST_kDragnDropTypeString;
}
fmtetc.cfFormat = CF_TEXT;
if (p_data_object->QueryGetData(&fmtetc) == S_OK) {
return GHOST_kDragnDropTypeString;
}
return GHOST_kDragnDropTypeUnknown;
}
void *GHOST_DropTargetWin32::getGhostData(IDataObject *p_data_object)
{
GHOST_TDragnDropTypes type = getGhostType(p_data_object);
switch (type) {
case GHOST_kDragnDropTypeFilenames:
return getDropDataAsFilenames(p_data_object);
case GHOST_kDragnDropTypeString:
return getDropDataAsString(p_data_object);
case GHOST_kDragnDropTypeBitmap:
// return getDropDataAsBitmap(p_data_object);
break;
default:
#ifdef WITH_GHOST_DEBUG
::printf("\nGHOST_kDragnDropTypeUnknown");
#endif /* WITH_GHOST_DEBUG */
return nullptr;
}
return nullptr;
}
void *GHOST_DropTargetWin32::getDropDataAsFilenames(IDataObject *p_data_object)
{
GHOST_TStringArray *str_array = nullptr;
FORMATETC fmtetc = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
/* Check if data-object supplies the format we want.
* Double checking here, first in #getGhostType. */
if (p_data_object->QueryGetData(&fmtetc) == S_OK) {
STGMEDIUM stgmed;
if (p_data_object->GetData(&fmtetc, &stgmed) == S_OK) {
const HDROP hdrop = (HDROP)::GlobalLock(stgmed.hGlobal);
const uint totfiles = ::DragQueryFileW(hdrop, -1, nullptr, 0);
if (totfiles) {
str_array = (GHOST_TStringArray *)::malloc(sizeof(GHOST_TStringArray));
str_array->count = 0;
str_array->strings = (uint8_t **)::malloc(totfiles * sizeof(uint8_t *));
for (uint nfile = 0; nfile < totfiles; nfile++) {
WCHAR fpath[MAX_PATH];
if (::DragQueryFileW(hdrop, nfile, fpath, MAX_PATH) > 0) {
char *temp_path;
if (!(temp_path = alloc_utf_8_from_16(fpath, 0))) {
/* Just ignore paths that could not be converted verbatim. */
continue;
}
str_array->strings[str_array->count++] = (uint8_t *)temp_path;
}
}
}
/* Free up memory. */
::GlobalUnlock(stgmed.hGlobal);
::ReleaseStgMedium(&stgmed);
}
}
return str_array;
}
void *GHOST_DropTargetWin32::getDropDataAsString(IDataObject *p_data_object)
{
char *tmp_string;
FORMATETC fmtetc = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
STGMEDIUM stgmed;
/* Try unicode first.
* Check if data-object supplies the format we want. */
if (p_data_object->QueryGetData(&fmtetc) == S_OK) {
if (p_data_object->GetData(&fmtetc, &stgmed) == S_OK) {
LPCWSTR wstr = (LPCWSTR)::GlobalLock(stgmed.hGlobal);
tmp_string = alloc_utf_8_from_16((wchar_t *)wstr, 0);
/* Free memory. */
::GlobalUnlock(stgmed.hGlobal);
::ReleaseStgMedium(&stgmed);
#ifdef WITH_GHOST_DEBUG
if (tmp_string) {
::printf("\n<converted droped unicode string>\n%s\n</droped converted unicode string>\n",
tmp_string);
}
#endif /* WITH_GHOST_DEBUG */
return tmp_string;
}
}
fmtetc.cfFormat = CF_TEXT;
if (p_data_object->QueryGetData(&fmtetc) == S_OK) {
if (p_data_object->GetData(&fmtetc, &stgmed) == S_OK) {
char *str = (char *)::GlobalLock(stgmed.hGlobal);
int str_size = ::strlen(str) + 1;
tmp_string = (char *)::malloc(str_size);
if (tmp_string) {
::memcpy(tmp_string, str, str_size);
}
/* Free memory. */
::GlobalUnlock(stgmed.hGlobal);
::ReleaseStgMedium(&stgmed);
return tmp_string;
}
}
return nullptr;
}
int GHOST_DropTargetWin32::WideCharToANSI(LPCWSTR in, char *&out)
{
int size;
out = nullptr; /* caller should free if != nullptr */
/* Get the required size. */
size = ::WideCharToMultiByte(CP_ACP, /* System Default Codepage */
0x00000400, /* WC_NO_BEST_FIT_CHARS */
in,
-1, /* -1 null terminated, makes output null terminated too. */
nullptr,
0,
nullptr,
nullptr);
if (!size) {
#ifdef WITH_GHOST_DEBUG
::printLastError();
#endif /* WITH_GHOST_DEBUG */
return 0;
}
out = (char *)::malloc(size);
if (!out) {
::printf("\nmalloc failed!!!");
return 0;
}
size = ::WideCharToMultiByte(CP_ACP, 0x00000400, in, -1, (LPSTR)out, size, nullptr, nullptr);
if (!size) {
#ifdef WITH_GHOST_DEBUG
::printLastError();
#endif /* WITH_GHOST_DEBUG */
::free(out);
out = nullptr;
}
return size;
}
#ifdef WITH_GHOST_DEBUG
void printLastError(void)
{
LPTSTR s;
DWORD err;
err = GetLastError();
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
nullptr,
err,
0,
(LPTSTR)&s,
0,
nullptr))
{
printf("\nLastError: (%d) %s\n", int(err), s);
LocalFree(s);
}
}
#endif /* WITH_GHOST_DEBUG */

View File

@@ -0,0 +1,137 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_SystemWin32.hh"
#include "GHOST_Types.hh"
#include "GHOST_WindowWin32.hh"
#include <string.h>
class GHOST_DropTargetWin32 final : public IDropTarget {
public:
/* IUnknownd implementation.
* Enables clients to get pointers to other interfaces on a given object
* through the QueryInterface method, and manage the existence of the object
* through the AddRef and Release methods. All other COM interfaces are
* inherited, directly or indirectly, from IUnknown. Therefore, the three
* methods in IUnknown are the first entries in the VTable for every interface.
*/
HRESULT __stdcall QueryInterface(REFIID riid, void **ppv_obj);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
/* IDropTarget implementation
* + The IDropTarget interface is one of the interfaces you implement to
* provide drag-and-drop operations in your application. It contains methods
* used in any application that can be a target for data during a
* drag-and-drop operation. A drop-target application is responsible for:
*
* - Determining the effect of the drop on the target application.
* - Incorporating any valid dropped data when the drop occurs.
* - Communicating target feedback to the source so the source application
* can provide appropriate visual feedback such as setting the cursor.
* - Implementing drag scrolling.
* - Registering and revoking its application windows as drop targets.
*
* The IDropTarget interface contains methods that handle all these
* responsibilities except registering and revoking the application window
* as a drop target, for which you must call the RegisterDragDrop and the
* RevokeDragDrop functions.
*/
HRESULT __stdcall DragEnter(IDataObject *p_data_object,
DWORD grf_key_state,
POINTL pt,
DWORD *pdw_effect);
HRESULT __stdcall DragOver(DWORD grf_key_state, POINTL pt, DWORD *pdw_effect);
HRESULT __stdcall DragLeave();
HRESULT __stdcall Drop(IDataObject *p_data_object,
DWORD grf_key_state,
POINTL pt,
DWORD *pdw_effect);
/**
* Constructor
* With the modifier keys, we want to distinguish left and right keys.
* Sometimes this is not possible (Windows ME for instance). Then, we want
* events generated for both keys.
* \param window: The window to register as drop target.
* \param system: The associated system.
*/
GHOST_DropTargetWin32(GHOST_WindowWin32 *window, GHOST_SystemWin32 *system);
/**
* Destructor
* Do NOT destroy directly. Use Release() instead to make COM happy.
*/
~GHOST_DropTargetWin32();
private:
/* Internal helper functions */
/**
* Base the effect on those allowed by the drop-source.
* \param dw_allowed: Drop sources allowed drop effect.
* \return The allowed drop effect.
*/
DWORD allowedDropEffect(DWORD dw_allowed);
/**
* Query DataObject for the data types it supports.
* \param p_data_object: Pointer to the DataObject.
* \return GHOST data type.
*/
GHOST_TDragnDropTypes getGhostType(IDataObject *p_data_object);
/**
* Get data to pass in event.
* It checks the type and calls specific functions for each type.
* \param p_data_object: Pointer to the DataObject.
* \return Pointer to data.
*/
void *getGhostData(IDataObject *p_data_object);
/**
* Allocate data as file array to pass in event.
* \param p_data_object: Pointer to the DataObject.
* \return Pointer to data.
*/
void *getDropDataAsFilenames(IDataObject *p_data_object);
/**
* Allocate data as string to pass in event.
* \param p_data_object: Pointer to the DataObject.
* \return Pointer to data.
*/
void *getDropDataAsString(IDataObject *p_data_object);
/**
* Convert Unicode to ANSI, replacing uncomfortable chars with '?'.
* The ANSI codepage is the system default codepage,
* and can change from system to system.
* \param in: LPCWSTR.
* \param out: char *. Is set to nullptr on failure.
* \return 0 on failure. Else the size of the string including '\0'.
*/
int WideCharToANSI(LPCWSTR in, char *&out);
/* Private member variables */
/* COM reference count. */
LONG c_ref_;
/* Handle of the associated window. */
HWND h_wnd_;
/* The associated GHOST_WindowWin32. */
GHOST_WindowWin32 *window_;
/* The System. */
GHOST_SystemWin32 *system_;
/* Data type of the dragged object */
GHOST_TDragnDropTypes dragged_object_type_;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_DropTargetWin32")
};

View File

@@ -0,0 +1,232 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include "GHOST_DropTargetX11.hh"
#include "GHOST_Debug.hh"
#include "GHOST_PathUtils.hh"
#include "GHOST_utildefines.hh"
#include <cassert>
#include <cctype>
#include <cstdio>
#include <cstring>
bool GHOST_DropTargetX11::xdnd_initialized_ = false;
DndClass GHOST_DropTargetX11::dnd_class_;
Atom *GHOST_DropTargetX11::dnd_types_ = nullptr;
Atom *GHOST_DropTargetX11::dnd_actions_ = nullptr;
const char *GHOST_DropTargetX11::dnd_mime_types_[] = {
"url/url", "text/uri-list", "text/plain", "application/octet-stream"};
int GHOST_DropTargetX11::ref_counter_ = 0;
#define dndTypeURLID 0
#define dndTypeURIListID 1
#define dndTypePlainTextID 2
#define dndTypeOctetStreamID 3
#define dndTypeURL dnd_types_[dndTypeURLID]
#define dndTypeURIList dnd_types_[dndTypeURIListID]
#define dndTypePlainText dnd_types_[dndTypePlainTextID]
#define dndTypeOctetStream dnd_types_[dndTypeOctetStreamID]
void GHOST_DropTargetX11::Initialize()
{
Display *display = system_->getXDisplay();
int dndTypesCount = ARRAY_SIZE(dnd_mime_types_);
int counter;
xdnd_init(&dnd_class_, display);
dnd_types_ = new Atom[dndTypesCount + 1];
XInternAtoms(display, (char **)dnd_mime_types_, dndTypesCount, 0, dnd_types_);
dnd_types_[dndTypesCount] = 0;
dnd_actions_ = new Atom[8];
counter = 0;
dnd_actions_[counter++] = dnd_class_.XdndActionCopy;
dnd_actions_[counter++] = dnd_class_.XdndActionMove;
#if 0 /* Not supported yet */
dndActions[counter++] = dnd->XdndActionLink;
dndActions[counter++] = dnd->XdndActionAsk;
dndActions[counter++] = dnd->XdndActionPrivate;
dndActions[counter++] = dnd->XdndActionList;
dndActions[counter++] = dnd->XdndActionDescription;
#endif
dnd_actions_[counter++] = 0;
}
void GHOST_DropTargetX11::Uninitialize()
{
xdnd_shut(&dnd_class_);
delete[] dnd_actions_;
delete[] dnd_types_;
}
GHOST_DropTargetX11::GHOST_DropTargetX11(GHOST_WindowX11 *window, GHOST_SystemX11 *system)
: window_(window), system_(system)
{
if (!xdnd_initialized_) {
Initialize();
xdnd_initialized_ = true;
GHOST_PRINT("XDND initialized\n");
}
Window wnd = window->getXWindow();
xdnd_set_dnd_aware(&dnd_class_, wnd, nullptr);
xdnd_set_type_list(&dnd_class_, wnd, dnd_types_);
dragged_object_type_ = GHOST_kDragnDropTypeUnknown;
ref_counter_++;
}
GHOST_DropTargetX11::~GHOST_DropTargetX11()
{
ref_counter_--;
if (ref_counter_ == 0) {
Uninitialize();
xdnd_initialized_ = false;
GHOST_PRINT("XDND uninitialized\n");
}
}
char *GHOST_DropTargetX11::FileUrlDecode(const char *fileUrl)
{
if (strncmp(fileUrl, "file://", 7) == 0) {
const char *file = fileUrl + 7;
return GHOST_URL_decode_alloc(file, strlen(file));
}
return nullptr;
}
void *GHOST_DropTargetX11::getURIListGhostData(const uchar *dropBuffer, int dropBufferSize)
{
GHOST_TStringArray *strArray = nullptr;
int totPaths = 0, curLength = 0;
/* Count total number of file paths in buffer. */
for (int i = 0; i <= dropBufferSize; i++) {
if (ELEM(dropBuffer[i], 0, '\n', '\r')) {
if (curLength) {
totPaths++;
curLength = 0;
}
}
else {
curLength++;
}
}
strArray = (GHOST_TStringArray *)malloc(sizeof(GHOST_TStringArray));
strArray->count = 0;
strArray->strings = (uint8_t **)malloc(totPaths * sizeof(uint8_t *));
curLength = 0;
for (int i = 0; i <= dropBufferSize; i++) {
if (ELEM(dropBuffer[i], 0, '\n', '\r')) {
if (curLength) {
char *curPath = (char *)malloc(curLength + 1);
char *decodedPath;
strncpy(curPath, (char *)dropBuffer + i - curLength, curLength);
curPath[curLength] = 0;
decodedPath = FileUrlDecode(curPath);
if (decodedPath) {
strArray->strings[strArray->count] = (uint8_t *)decodedPath;
strArray->count++;
}
free(curPath);
curLength = 0;
}
}
else {
curLength++;
}
}
return strArray;
}
void *GHOST_DropTargetX11::getGhostData(Atom dropType, const uchar *dropBuffer, int dropBufferSize)
{
void *data = nullptr;
uchar *tmpBuffer = (uchar *)malloc(dropBufferSize + 1);
bool needsFree = true;
/* Ensure nil-terminator. */
memcpy(tmpBuffer, dropBuffer, dropBufferSize);
tmpBuffer[dropBufferSize] = 0;
if (dropType == dndTypeURIList) {
dragged_object_type_ = GHOST_kDragnDropTypeFilenames;
data = getURIListGhostData(tmpBuffer, dropBufferSize);
}
else if (dropType == dndTypeURL) {
/* need to be tested */
char *decodedPath = FileUrlDecode((const char *)tmpBuffer);
if (decodedPath) {
dragged_object_type_ = GHOST_kDragnDropTypeString;
data = decodedPath;
}
}
else if (ELEM(dropType, dndTypePlainText, dndTypeOctetStream)) {
dragged_object_type_ = GHOST_kDragnDropTypeString;
data = tmpBuffer;
needsFree = false;
}
else {
dragged_object_type_ = GHOST_kDragnDropTypeUnknown;
}
if (needsFree) {
free(tmpBuffer);
}
return data;
}
bool GHOST_DropTargetX11::GHOST_HandleClientMessage(XEvent *event)
{
Atom dropType;
uchar *dropBuffer;
int dropBufferSize, dropX, dropY;
if (xdnd_get_drop(system_->getXDisplay(),
event,
dnd_types_,
dnd_actions_,
&dropBuffer,
&dropBufferSize,
&dropType,
&dropX,
&dropY))
{
void *data = getGhostData(dropType, dropBuffer, dropBufferSize);
if (data) {
system_->pushDragDropEvent(
GHOST_kEventDraggingDropDone, dragged_object_type_, window_, dropX, dropY, data);
}
free(dropBuffer);
dragged_object_type_ = GHOST_kDragnDropTypeUnknown;
return true;
}
return false;
}

View File

@@ -0,0 +1,102 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_SystemX11.hh"
#include "GHOST_Types.hh"
#include "GHOST_WindowX11.hh"
#include "xdnd.h"
class GHOST_DropTargetX11 {
public:
/**
* Constructor
*
* \param window: The window to register as drop target.
* \param system: The associated system.
*/
GHOST_DropTargetX11(GHOST_WindowX11 *window, GHOST_SystemX11 *system);
/**
* Destructor
*/
~GHOST_DropTargetX11();
/**
* Handler of ClientMessage X11 event
*/
bool GHOST_HandleClientMessage(XEvent *event);
/**
* Get data to pass in event.
* It checks the type and calls specific functions for each type.
* \param dropType: Type of dropped entity.
* \param dropBuffer: Buffer returned from source application.
* \param dropBufferSize: Size of returned buffer.
* \return Pointer to data.
*/
void *getGhostData(Atom dropType, const unsigned char *dropBuffer, int dropBufferSize);
private:
/* Internal helper functions */
/**
* Initialize XDND and all related X atoms
*/
void Initialize();
/**
* Uninitialized XDND and all related X atoms
*/
void Uninitialize();
/**
* Get data to be passed to event from text/URI-list mime type
* \param dropBuffer: Buffer returned from source application.
* \param dropBufferSize: Size of dropped buffer.
* \return pointer to newly created GHOST data.
*/
void *getURIListGhostData(const unsigned char *dropBuffer, int dropBufferSize);
/**
* Fully decode file URL (i.e. converts `file:///a%20b/test` to `/a b/test`)
* \param fileUrl: - file path URL to be fully decoded.
* \return decoded file path (result should be free-d).
*/
char *FileUrlDecode(const char *fileUrl);
/* The associated GHOST_WindowWin32. */
GHOST_WindowX11 *window_;
/* The System. */
GHOST_SystemX11 *system_;
/* Data type of the dragged object */
GHOST_TDragnDropTypes dragged_object_type_;
/* Is drag-and-drop stuff initialized. */
static bool xdnd_initialized_;
/* Class holding internal stiff of `xdnd` library. */
static DndClass dnd_class_;
/* List of supported types to be dragged into. */
static Atom *dnd_types_;
/* List of supported drag-and-drop actions. */
static Atom *dnd_actions_;
/* List of supported MIME types to be dragged into. */
static const char *dnd_mime_types_[];
/* Counter of references to global #XDND structures. */
static int ref_counter_;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_DropTargetX11")
};

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_Event class.
*/
#pragma once
#include "GHOST_IEvent.hh"
/**
* Base class for events received the operating system.
*/
class GHOST_Event : public GHOST_IEvent {
public:
/**
* Constructor.
* \param msec: The time this event was generated.
* \param type: The type of this event.
* \param window: The generating window (or nullptr if system event).
*/
GHOST_Event(uint64_t msec, GHOST_TEventType type, GHOST_IWindow *window)
: type_(type), time_(msec), window_(window)
{
}
/** \copydoc #GHOST_IEvent::getType */
GHOST_TEventType getType() const override
{
return type_;
}
/** \copydoc #GHOST_IEvent::getTime */
uint64_t getTime() const override
{
return time_;
}
/** \copydoc #GHOST_IEvent::getWindow */
GHOST_IWindow *getWindow() const override
{
return window_;
}
/** \copydoc #GHOST_IEvent::getData */
GHOST_TEventDataPtr getData() const override
{
return data_;
}
protected:
/** Type of this event. */
GHOST_TEventType type_;
/** The time this event was generated. */
uint64_t time_;
/** Pointer to the generating window. */
GHOST_IWindow *window_;
/** Pointer to the event data. */
GHOST_TEventDataPtr data_ = nullptr;
};

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventButton class.
*/
#pragma once
#include "GHOST_Event.hh"
#include "GHOST_Window.hh"
/**
* Mouse button event.
*/
class GHOST_EventButton : public GHOST_Event {
public:
/**
* Constructor.
* \param time: The time this event was generated.
* \param type: The type of this event.
* \param window: The window of this event.
* \param button: The state of the buttons were at the time of the event.
* \param tablet: The tablet data associated with this event.
*/
GHOST_EventButton(uint64_t time,
GHOST_TEventType type,
GHOST_IWindow *window,
GHOST_TButton button,
const GHOST_TabletData &tablet)
: GHOST_Event(time, type, window), button_event_data_({button, tablet})
{
data_ = &button_event_data_;
}
protected:
/** The button event data. */
GHOST_TEventButtonData button_event_data_;
};

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventCursor class.
*/
#pragma once
#include "GHOST_Event.hh"
/**
* Cursor event.
*/
class GHOST_EventCursor : public GHOST_Event {
public:
/**
* Constructor.
* \param msec: The time this event was generated.
* \param type: The type of this event.
* \param x: The x-coordinate of the location the cursor was at the time of the event.
* \param y: The y-coordinate of the location the cursor was at the time of the event.
* \param tablet: The tablet data associated with this event.
*/
GHOST_EventCursor(uint64_t msec,
GHOST_TEventType type,
GHOST_IWindow *window,
int32_t x,
int32_t y,
const GHOST_TabletData &tablet)
: GHOST_Event(msec, type, window), cursor_event_data_({x, y, tablet})
{
data_ = &cursor_event_data_;
}
protected:
/** The x,y-coordinates of the cursor position. */
GHOST_TEventCursorData cursor_event_data_;
};

View File

@@ -0,0 +1,109 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_Event.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
/**
* Drag & drop event
*
* The dragging sequence is performed in four phases:
*
* - Start sequence (GHOST_kEventDraggingEntered) that tells
* a drag & drop operation has started.
* Already gives the object data type, and the entering mouse location
*
* - Update mouse position (GHOST_kEventDraggingUpdated) sent upon each mouse move until the
* drag & drop operation stops, to give the updated mouse position.
* Useful to highlight a potential destination, and update the status
* (through GHOST_IWindow::setAcceptDragOperation) telling if the object can be dropped at the
* current cursor position.
*
* - Abort drag & drop sequence (#GHOST_kEventDraggingExited)
* sent when the user moved the mouse outside the window.
*
* - Send the dropped data (GHOST_kEventDraggingDropDone)
*
* - Outside of the normal sequence, dropped data can be sent (GHOST_kEventDraggingDropOnIcon).
* This can happen when the user drops an object on the application icon.
* (Also used in OSX to pass the filename of the document the user doubled-clicked in the finder)
*
* Note that the mouse positions are given in Blender coordinates (y=0 at bottom)
*
* Currently supported object types:
* - UTF8 string.
* - array of strings representing filenames (#GHOST_TStringArray).
* - bitmap #ImBuf.
*/
class GHOST_EventDragnDrop : public GHOST_Event {
public:
/**
* Constructor.
* \param time: The time this event was generated.
* \param type: The type of this event.
* \param dataType: The type of the drop candidate object.
* \param window: The window where the event occurred.
* \param x: The x-coordinate of the location the cursor was at the time of the event.
* \param y: The y-coordinate of the location the cursor was at the time of the event.
* \param data: The "content" dropped in the window.
*/
GHOST_EventDragnDrop(uint64_t time,
GHOST_TEventType type,
GHOST_TDragnDropTypes dataType,
GHOST_IWindow *window,
int x,
int y,
GHOST_TDragnDropDataPtr data)
: GHOST_Event(time, type, window)
{
dragn_drop_event_data_.x = x;
dragn_drop_event_data_.y = y;
dragn_drop_event_data_.dataType = dataType;
dragn_drop_event_data_.data = data;
data_ = &dragn_drop_event_data_;
}
~GHOST_EventDragnDrop() override
{
/* Free the dropped object data. */
if (dragn_drop_event_data_.data == nullptr) {
return;
}
switch (dragn_drop_event_data_.dataType) {
case GHOST_kDragnDropTypeBitmap:
blender::IMB_freeImBuf((blender::ImBuf *)dragn_drop_event_data_.data);
break;
case GHOST_kDragnDropTypeFilenames: {
GHOST_TStringArray *strArray = (GHOST_TStringArray *)dragn_drop_event_data_.data;
int i;
for (i = 0; i < strArray->count; i++) {
free(strArray->strings[i]);
}
free(strArray->strings);
free(strArray);
break;
}
case GHOST_kDragnDropTypeString:
free(dragn_drop_event_data_.data);
break;
default:
break;
}
}
protected:
/** The x,y-coordinates of the cursor position. */
GHOST_TEventDragnDropData dragn_drop_event_data_;
};

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventKey class.
*/
#pragma once
#include <cstring>
#include "GHOST_Event.hh"
/**
* Key event.
*/
class GHOST_EventKey : public GHOST_Event {
public:
/**
* Constructor.
* \param msec: The time this event was generated.
* \param type: The type of key event.
* \param key: The key code of the key.
* \param is_repeat: Enabled for key repeat events (only for press events).
*/
GHOST_EventKey(
uint64_t msec, GHOST_TEventType type, GHOST_IWindow *window, GHOST_TKey key, bool is_repeat)
: GHOST_Event(msec, type, window)
{
key_event_data_.key = key;
key_event_data_.utf8_buf[0] = '\0';
key_event_data_.is_repeat = is_repeat;
data_ = &key_event_data_;
}
/**
* Constructor.
* \param msec: The time this event was generated.
* \param type: The type of key event.
* \param key: The key code of the key.
* \param is_repeat: Enabled for key repeat events (only for press events).
* \param utf8_buf: The text associated with this key event (only for press events).
*/
GHOST_EventKey(uint64_t msec,
GHOST_TEventType type,
GHOST_IWindow *window,
GHOST_TKey key,
bool is_repeat,
const char utf8_buf[6])
: GHOST_Event(msec, type, window)
{
key_event_data_.key = key;
if (utf8_buf) {
memcpy(key_event_data_.utf8_buf, utf8_buf, sizeof(key_event_data_.utf8_buf));
}
else {
key_event_data_.utf8_buf[0] = '\0';
}
key_event_data_.is_repeat = is_repeat;
data_ = &key_event_data_;
}
protected:
/** The key event data. */
GHOST_TEventKeyData key_event_data_;
};

View File

@@ -0,0 +1,185 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
/**
* Copyright (C) 2001 NaN Technologies B.V.
*/
#include "GHOST_EventManager.hh"
#include "GHOST_Debug.hh"
#include <algorithm>
GHOST_EventManager::GHOST_EventManager() = default;
GHOST_EventManager::~GHOST_EventManager()
{
disposeEvents();
TConsumerVector::iterator iter = consumers_.begin();
while (iter != consumers_.end()) {
GHOST_IEventConsumer *consumer = *iter;
delete consumer;
iter = consumers_.erase(iter);
}
}
uint32_t GHOST_EventManager::getNumEvents()
{
return uint32_t(events_.size());
}
uint32_t GHOST_EventManager::getNumEvents(GHOST_TEventType type)
{
uint32_t numEvents = 0;
TEventStack::iterator p;
for (p = events_.begin(); p != events_.end(); ++p) {
if ((*p)->getType() == type) {
numEvents++;
}
}
return numEvents;
}
GHOST_TSuccess GHOST_EventManager::pushEvent(std::unique_ptr<const GHOST_IEvent> event)
{
GHOST_TSuccess success;
GHOST_ASSERT(event, "invalid event");
if (events_.size() < events_.max_size()) {
events_.push_front(std::move(event));
success = GHOST_kSuccess;
}
else {
success = GHOST_kFailure;
}
return success;
}
void GHOST_EventManager::dispatchEvent(const GHOST_IEvent *event)
{
TConsumerVector::iterator iter;
for (iter = consumers_.begin(); iter != consumers_.end(); ++iter) {
(*iter)->processEvent(event);
}
}
void GHOST_EventManager::dispatchEvent()
{
std::unique_ptr<const GHOST_IEvent> event = std::move(events_.back());
events_.pop_back();
const GHOST_IEvent *event_ptr = event.get();
handled_events_.push_back(std::move(event));
dispatchEvent(event_ptr);
}
void GHOST_EventManager::dispatchEvents()
{
while (!events_.empty()) {
dispatchEvent();
}
disposeEvents();
}
GHOST_TSuccess GHOST_EventManager::addConsumer(GHOST_IEventConsumer *consumer)
{
GHOST_TSuccess success;
GHOST_ASSERT(consumer, "invalid consumer");
/* Check to see whether the consumer is already in our list. */
TConsumerVector::const_iterator iter = std::find(consumers_.begin(), consumers_.end(), consumer);
if (iter == consumers_.end()) {
/* Add the consumer. */
consumers_.push_back(consumer);
success = GHOST_kSuccess;
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_TSuccess GHOST_EventManager::removeConsumer(GHOST_IEventConsumer *consumer)
{
GHOST_TSuccess success;
GHOST_ASSERT(consumer, "invalid consumer");
/* Check to see whether the consumer is in our list. */
TConsumerVector::iterator iter = std::find(consumers_.begin(), consumers_.end(), consumer);
if (iter != consumers_.end()) {
/* Remove the consumer. */
consumers_.erase(iter);
success = GHOST_kSuccess;
}
else {
success = GHOST_kFailure;
}
return success;
}
void GHOST_EventManager::removeWindowEvents(const GHOST_IWindow *window)
{
TEventStack::iterator iter;
iter = events_.begin();
while (iter != events_.end()) {
std::unique_ptr<const GHOST_IEvent> &event = *iter;
if (event->getWindow() == window) {
GHOST_PRINT("GHOST_EventManager::removeWindowEvents(): removing event\n");
/*
* Found an event for this window, remove it.
* The iterator will become invalid.
*/
event.reset();
events_.erase(iter);
iter = events_.begin();
}
else {
++iter;
}
}
}
void GHOST_EventManager::removeTypeEvents(GHOST_TEventType type, const GHOST_IWindow *window)
{
TEventStack::iterator iter;
iter = events_.begin();
while (iter != events_.end()) {
std::unique_ptr<const GHOST_IEvent> &event = *iter;
if ((event->getType() == type) && (!window || (event->getWindow() == window))) {
GHOST_PRINT("GHOST_EventManager::removeTypeEvents(): removing event\n");
/*
* Found an event of this type for the window, remove it.
* The iterator will become invalid.
*/
event.reset();
events_.erase(iter);
iter = events_.begin();
}
else {
++iter;
}
}
}
void GHOST_EventManager::disposeEvents()
{
while (handled_events_.empty() == false) {
GHOST_ASSERT(handled_events_[0], "invalid event");
handled_events_[0].reset();
handled_events_.pop_front();
}
while (events_.empty() == false) {
GHOST_ASSERT(events_[0], "invalid event");
events_[0].reset();
events_.pop_front();
}
}

View File

@@ -0,0 +1,124 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventManager class.
*/
#pragma once
#include <deque>
#include <vector>
#include "GHOST_IEventConsumer.hh"
/**
* Manages an event stack and a list of event consumers.
* The stack works on a FIFO (First In First Out) basis.
* Events are pushed on the front of the stack and retrieved from the back.
* Ownership of the event is transferred to the event manager as soon as an event is pushed.
* Ownership of the event is transferred from the event manager as soon as an event is popped.
* Events can be dispatched to the event consumers.
*/
class GHOST_EventManager {
public:
/**
* Constructor.
*/
GHOST_EventManager();
/**
* Destructor.
*/
~GHOST_EventManager();
/**
* Returns the number of events currently on the stack.
* \return The number of events on the stack.
*/
uint32_t getNumEvents();
/**
* Returns the number of events of a certain type currently on the stack.
* \param type: The type of events to be counted.
* \return The number of events on the stack of this type.
*/
uint32_t getNumEvents(GHOST_TEventType type);
/**
* Pushes an event on the stack.
* To dispatch it, call dispatchEvent() or dispatchEvents().
* Do not delete the event!
* \param event: The event to push on the stack.
*/
GHOST_TSuccess pushEvent(std::unique_ptr<const GHOST_IEvent> event);
/**
* Dispatches the given event directly, bypassing the event stack.
*/
void dispatchEvent(const GHOST_IEvent *event);
/**
* Dispatches the event at the back of the stack.
* The event will be removed from the stack.
*/
void dispatchEvent();
/**
* Dispatches all the events on the stack.
* The event stack will be empty afterwards.
*/
void dispatchEvents();
/**
* Adds a consumer to the list of event consumers.
* \param consumer: The consumer added to the list.
* \return Indication as to whether addition has succeeded.
*/
GHOST_TSuccess addConsumer(GHOST_IEventConsumer *consumer);
/**
* Removes a consumer from the list of event consumers.
* \param consumer: The consumer removed from the list.
* \return Indication as to whether removal has succeeded.
*/
GHOST_TSuccess removeConsumer(GHOST_IEventConsumer *consumer);
/**
* Removes all events for a window from the stack.
* \param window: The window to remove events for.
*/
void removeWindowEvents(const GHOST_IWindow *window);
/**
* Removes all events of a certain type from the stack.
* The window parameter is optional.
* If non-null, the routine will remove events only associated with that window.
* \param type: The type of events to be removed.
* \param window: The window to remove the events for.
*/
void removeTypeEvents(GHOST_TEventType type, const GHOST_IWindow *window = nullptr);
protected:
/**
* Removes all events from the stack.
*/
void disposeEvents();
/** A stack with events. */
using TEventStack = std::deque<std::unique_ptr<const GHOST_IEvent>>;
/** The event stack. */
TEventStack events_;
TEventStack handled_events_;
/** A vector with event consumers. */
using TConsumerVector = std::vector<GHOST_IEventConsumer *>;
/** The list with event consumers. */
TConsumerVector consumers_;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_EventManager")
};

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#ifndef WITH_INPUT_NDOF
# error NDOF code included in non-NDOF-enabled build
#endif
#include "GHOST_Event.hh"
class GHOST_EventNDOFMotion : public GHOST_Event {
protected:
GHOST_TEventNDOFMotionData axis_data_;
public:
GHOST_EventNDOFMotion(uint64_t time, GHOST_IWindow *window)
: GHOST_Event(time, GHOST_kEventNDOFMotion, window)
{
data_ = &axis_data_;
}
};
class GHOST_EventNDOFButton : public GHOST_Event {
protected:
GHOST_TEventNDOFButtonData button_data_;
public:
GHOST_EventNDOFButton(uint64_t time, GHOST_IWindow *window)
: GHOST_Event(time, GHOST_kEventNDOFButton, window)
{
data_ = &button_data_;
}
};

View File

@@ -0,0 +1,374 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventPrinter class.
*/
#include "GHOST_EventPrinter.hh"
#include "GHOST_Debug.hh"
#include "GHOST_EventDragnDrop.hh"
#include "GHOST_EventKey.hh"
#include <iomanip>
#include <iostream>
/* For now only used with NDOF. */
#ifdef WITH_INPUT_NDOF
static const char *getButtonActionString(const GHOST_TButtonAction action)
{
switch (action) {
case GHOST_kPress:
return "Press";
case GHOST_kRelease:
return "Release";
}
return "Unknown";
}
#endif /* WITH_INPUT_NDOF */
bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event)
{
bool handled = false;
GHOST_ASSERT(event, "event==0");
if (event->getType() == GHOST_kEventWindowUpdate) {
return false;
}
GHOST_TEventDataPtr data = event->getData();
std::cout << "GHOST_EventPrinter::processEvent, time: " << int32_t(event->getTime())
<< ", type: ";
#define CASE_TYPE(ty) \
case ty: { \
std::cout << #ty; \
handled = true; \
break; \
} \
((void)0)
const GHOST_TEventType event_type = event->getType();
switch (event_type) {
case GHOST_kEventUnknown: {
std::cout << "GHOST_kEventUnknown";
handled = false;
break;
}
case GHOST_kEventCursorMove: {
const GHOST_TEventCursorData *cursorData = static_cast<const GHOST_TEventCursorData *>(data);
std::cout << "GHOST_kEventCursorMove, (x,y): (" << cursorData->x << "," << cursorData->y
<< ")";
handled = true;
break;
}
case GHOST_kEventButtonDown: {
const GHOST_TEventButtonData *buttonData = static_cast<const GHOST_TEventButtonData *>(data);
std::cout << "GHOST_kEventButtonDown, button: " << buttonData->button;
handled = true;
break;
}
case GHOST_kEventButtonUp: {
const GHOST_TEventButtonData *buttonData = static_cast<const GHOST_TEventButtonData *>(data);
std::cout << "GHOST_kEventCursorButtonUp, button: " << buttonData->button;
handled = true;
break;
}
case GHOST_kEventWheel: {
const GHOST_TEventWheelData *wheelData = static_cast<const GHOST_TEventWheelData *>(data);
std::cout << "GHOST_kEventWheel, axis: "
<< (wheelData->axis == GHOST_kEventWheelAxisVertical ? "vertical" : "horizontal")
<< ", value: " << wheelData->value;
handled = true;
break;
}
CASE_TYPE(GHOST_kEventTrackpad);
#ifdef WITH_INPUT_NDOF
case GHOST_kEventNDOFMotion: {
const GHOST_TEventNDOFMotionData *ndof_motion =
static_cast<const GHOST_TEventNDOFMotionData *>(data);
std::cout << "GHOST_kEventNDOFMotion: ";
std::cout << std::fixed << std::setprecision(2) <<
/* Translation. */
"tx=" << ndof_motion->tx << " ty=" << ndof_motion->tx << " tz=" << ndof_motion->tz <<
/* Rotation. */
"rx=" << ndof_motion->rx << " ry=" << ndof_motion->ry << " rz=" << ndof_motion->rz;
std::cout << std::fixed << std::setprecision(4) << " dt=" << ndof_motion->dt;
handled = true;
break;
}
case GHOST_kEventNDOFButton: {
const GHOST_TEventNDOFButtonData *ndof_button =
static_cast<const GHOST_TEventNDOFButtonData *>(data);
std::cout << "GHOST_kEventNDOFButton: " << getButtonActionString(ndof_button->action)
<< " button=" << ndof_button->button;
handled = true;
break;
}
#endif /* WITH_INPUT_NDOF */
case GHOST_kEventKeyDown: {
const GHOST_TEventKeyData *keyData = static_cast<const GHOST_TEventKeyData *>(data);
std::cout << "GHOST_kEventKeyDown, key: " << getKeyString(keyData->key);
handled = true;
break;
}
case GHOST_kEventKeyUp: {
const GHOST_TEventKeyData *keyData = static_cast<const GHOST_TEventKeyData *>(data);
std::cout << "GHOST_kEventKeyUp, key: " << getKeyString(keyData->key);
handled = true;
break;
}
CASE_TYPE(GHOST_kEventQuitRequest);
CASE_TYPE(GHOST_kEventWindowClose);
CASE_TYPE(GHOST_kEventWindowActivate);
CASE_TYPE(GHOST_kEventWindowDeactivate);
CASE_TYPE(GHOST_kEventWindowUpdate);
CASE_TYPE(GHOST_kEventWindowUpdateDecor);
CASE_TYPE(GHOST_kEventWindowSize);
CASE_TYPE(GHOST_kEventWindowMove);
CASE_TYPE(GHOST_kEventWindowDPIHintChanged);
case GHOST_kEventDraggingEntered: {
const GHOST_TEventDragnDropData *dragnDropData =
static_cast<const GHOST_TEventDragnDropData *>(data);
std::cout << "GHOST_kEventDraggingEntered, dragged object type : "
<< dragnDropData->dataType;
std::cout << " mouse at x=" << dragnDropData->x << " y=" << dragnDropData->y;
handled = true;
break;
}
case GHOST_kEventDraggingUpdated: {
const GHOST_TEventDragnDropData *dragnDropData =
static_cast<const GHOST_TEventDragnDropData *>(data);
std::cout << "GHOST_kEventDraggingUpdated, dragged object type : "
<< dragnDropData->dataType;
std::cout << " mouse at x=" << dragnDropData->x << " y=" << dragnDropData->y;
handled = true;
break;
}
case GHOST_kEventDraggingExited: {
const GHOST_TEventDragnDropData *dragnDropData =
static_cast<const GHOST_TEventDragnDropData *>(data);
std::cout << "GHOST_kEventDraggingExited, dragged object type : " << dragnDropData->dataType;
handled = true;
break;
}
case GHOST_kEventDraggingDropDone: {
const GHOST_TEventDragnDropData *dragnDropData =
static_cast<const GHOST_TEventDragnDropData *>(data);
std::cout << "GHOST_kEventDraggingDropDone,";
std::cout << " mouse at x=" << dragnDropData->x << " y=" << dragnDropData->y;
switch (dragnDropData->dataType) {
case GHOST_kDragnDropTypeString: {
std::cout << " type : GHOST_kDragnDropTypeString,";
std::cout << "\n String received = " << (char *)dragnDropData->data;
break;
}
case GHOST_kDragnDropTypeFilenames: {
GHOST_TStringArray *strArray = (GHOST_TStringArray *)dragnDropData->data;
int i;
std::cout << " type : GHOST_kDragnDropTypeFilenames,";
std::cout << "\n Received " << strArray->count << " filename"
<< (strArray->count > 1 ? "s:" : ":");
for (i = 0; i < strArray->count; i++) {
std::cout << "\n File[" << i << "] : " << strArray->strings[i];
}
break;
}
default: {
break;
}
}
handled = true;
break;
}
case GHOST_kEventOpenMainFile: {
if (data) {
std::cout << "GHOST_kEventOpenMainFile for path: " << static_cast<const char *>(data);
}
else {
std::cout << "GHOST_kEventOpenMainFile with no path specified!!";
}
handled = true;
break;
}
CASE_TYPE(GHOST_kEventNativeResolutionChange);
CASE_TYPE(GHOST_kEventImeCompositionStart);
CASE_TYPE(GHOST_kEventImeComposition);
CASE_TYPE(GHOST_kEventImeCompositionEnd);
}
#undef CASE_TYPE
if ((handled == false) && event_type != GHOST_kEventUnknown) {
std::cout << "not found";
}
std::cout << std::endl;
std::cout.flush();
return handled;
}
const char *GHOST_EventPrinter::getKeyString(const GHOST_TKey key) const
{
const char *tstr = nullptr;
#define CASE_KEY(k, v) \
case k: { \
tstr = v; \
break; \
}
switch (key) {
CASE_KEY(GHOST_kKeyBackSpace, "BackSpace");
CASE_KEY(GHOST_kKeyTab, "Tab");
CASE_KEY(GHOST_kKeyLinefeed, "Linefeed");
CASE_KEY(GHOST_kKeyClear, "Clear");
CASE_KEY(GHOST_kKeyEnter, "Enter");
CASE_KEY(GHOST_kKeyEsc, "Esc");
CASE_KEY(GHOST_kKeySpace, "Space");
CASE_KEY(GHOST_kKeyQuote, "Quote");
CASE_KEY(GHOST_kKeyBackslash, "\\");
CASE_KEY(GHOST_kKeyAccentGrave, "`");
CASE_KEY(GHOST_kKeyLeftShift, "LeftShift");
CASE_KEY(GHOST_kKeyRightShift, "RightShift");
CASE_KEY(GHOST_kKeyLeftControl, "LeftControl");
CASE_KEY(GHOST_kKeyRightControl, "RightControl");
CASE_KEY(GHOST_kKeyLeftAlt, "LeftAlt");
CASE_KEY(GHOST_kKeyRightAlt, "RightAlt");
CASE_KEY(GHOST_kKeyLeftOS, "LeftOS");
CASE_KEY(GHOST_kKeyRightOS, "RightOS");
CASE_KEY(GHOST_kKeyLeftHyper, "LeftHyper");
CASE_KEY(GHOST_kKeyRightHyper, "RightHyper");
CASE_KEY(GHOST_kKeyApp, "App");
CASE_KEY(GHOST_kKeyGrLess, "GrLess");
CASE_KEY(GHOST_kKeyCapsLock, "CapsLock");
CASE_KEY(GHOST_kKeyNumLock, "NumLock");
CASE_KEY(GHOST_kKeyScrollLock, "ScrollLock");
CASE_KEY(GHOST_kKeyLeftArrow, "LeftArrow");
CASE_KEY(GHOST_kKeyRightArrow, "RightArrow");
CASE_KEY(GHOST_kKeyUpArrow, "UpArrow");
CASE_KEY(GHOST_kKeyDownArrow, "DownArrow");
CASE_KEY(GHOST_kKeyPrintScreen, "PrintScreen");
CASE_KEY(GHOST_kKeyPause, "Pause");
CASE_KEY(GHOST_kKeyInsert, "Insert");
CASE_KEY(GHOST_kKeyDelete, "Delete");
CASE_KEY(GHOST_kKeyHome, "Home");
CASE_KEY(GHOST_kKeyEnd, "End");
CASE_KEY(GHOST_kKeyUpPage, "UpPage");
CASE_KEY(GHOST_kKeyDownPage, "DownPage");
CASE_KEY(GHOST_kKeyNumpadPeriod, "NumpadPeriod");
CASE_KEY(GHOST_kKeyNumpadEnter, "NumpadEnter");
CASE_KEY(GHOST_kKeyNumpadPlus, "NumpadPlus");
CASE_KEY(GHOST_kKeyNumpadMinus, "NumpadMinus");
CASE_KEY(GHOST_kKeyNumpadAsterisk, "NumpadAsterisk");
CASE_KEY(GHOST_kKeyNumpadSlash, "NumpadSlash");
CASE_KEY(GHOST_kKeyMediaPlay, "MediaPlayPause");
CASE_KEY(GHOST_kKeyMediaStop, "MediaStop");
CASE_KEY(GHOST_kKeyMediaFirst, "MediaFirst");
CASE_KEY(GHOST_kKeyMediaLast, "MediaLast");
CASE_KEY(GHOST_kKeyNumpad0, "Numpad 0");
CASE_KEY(GHOST_kKeyNumpad1, "Numpad 1");
CASE_KEY(GHOST_kKeyNumpad2, "Numpad 2");
CASE_KEY(GHOST_kKeyNumpad3, "Numpad 3");
CASE_KEY(GHOST_kKeyNumpad4, "Numpad 4");
CASE_KEY(GHOST_kKeyNumpad5, "Numpad 5");
CASE_KEY(GHOST_kKeyNumpad6, "Numpad 6");
CASE_KEY(GHOST_kKeyNumpad7, "Numpad 7");
CASE_KEY(GHOST_kKeyNumpad8, "Numpad 8");
CASE_KEY(GHOST_kKeyNumpad9, "Numpad 9");
CASE_KEY(GHOST_kKeyF1, "F1");
CASE_KEY(GHOST_kKeyF2, "F2");
CASE_KEY(GHOST_kKeyF3, "F3");
CASE_KEY(GHOST_kKeyF4, "F4");
CASE_KEY(GHOST_kKeyF5, "F5");
CASE_KEY(GHOST_kKeyF6, "F6");
CASE_KEY(GHOST_kKeyF7, "F7");
CASE_KEY(GHOST_kKeyF8, "F8");
CASE_KEY(GHOST_kKeyF9, "F9");
CASE_KEY(GHOST_kKeyF10, "F10");
CASE_KEY(GHOST_kKeyF11, "F11");
CASE_KEY(GHOST_kKeyF12, "F12");
CASE_KEY(GHOST_kKeyF13, "F13");
CASE_KEY(GHOST_kKeyF14, "F14");
CASE_KEY(GHOST_kKeyF15, "F15");
CASE_KEY(GHOST_kKeyF16, "F16");
CASE_KEY(GHOST_kKeyF17, "F17");
CASE_KEY(GHOST_kKeyF18, "F18");
CASE_KEY(GHOST_kKeyF19, "F19");
CASE_KEY(GHOST_kKeyF20, "F20");
CASE_KEY(GHOST_kKeyF21, "F21");
CASE_KEY(GHOST_kKeyF22, "F22");
CASE_KEY(GHOST_kKeyF23, "F23");
CASE_KEY(GHOST_kKeyF24, "F24");
CASE_KEY(GHOST_kKeyUnknown, "Unknown");
CASE_KEY(GHOST_kKeyComma, ",");
CASE_KEY(GHOST_kKeyMinus, "-");
CASE_KEY(GHOST_kKeyPlus, "=");
CASE_KEY(GHOST_kKeyPeriod, ".");
CASE_KEY(GHOST_kKeySlash, "/");
CASE_KEY(GHOST_kKey0, "0");
CASE_KEY(GHOST_kKey1, "1");
CASE_KEY(GHOST_kKey2, "2");
CASE_KEY(GHOST_kKey3, "3");
CASE_KEY(GHOST_kKey4, "4");
CASE_KEY(GHOST_kKey5, "5");
CASE_KEY(GHOST_kKey6, "6");
CASE_KEY(GHOST_kKey7, "7");
CASE_KEY(GHOST_kKey8, "8");
CASE_KEY(GHOST_kKey9, "9");
CASE_KEY(GHOST_kKeySemicolon, ";");
CASE_KEY(GHOST_kKeyEqual, "=");
CASE_KEY(GHOST_kKeyA, "A");
CASE_KEY(GHOST_kKeyB, "B");
CASE_KEY(GHOST_kKeyC, "C");
CASE_KEY(GHOST_kKeyD, "D");
CASE_KEY(GHOST_kKeyE, "E");
CASE_KEY(GHOST_kKeyF, "F");
CASE_KEY(GHOST_kKeyG, "G");
CASE_KEY(GHOST_kKeyH, "H");
CASE_KEY(GHOST_kKeyI, "I");
CASE_KEY(GHOST_kKeyJ, "J");
CASE_KEY(GHOST_kKeyK, "K");
CASE_KEY(GHOST_kKeyL, "L");
CASE_KEY(GHOST_kKeyM, "M");
CASE_KEY(GHOST_kKeyN, "N");
CASE_KEY(GHOST_kKeyO, "O");
CASE_KEY(GHOST_kKeyP, "P");
CASE_KEY(GHOST_kKeyQ, "Q");
CASE_KEY(GHOST_kKeyR, "R");
CASE_KEY(GHOST_kKeyS, "S");
CASE_KEY(GHOST_kKeyT, "T");
CASE_KEY(GHOST_kKeyU, "U");
CASE_KEY(GHOST_kKeyV, "V");
CASE_KEY(GHOST_kKeyW, "W");
CASE_KEY(GHOST_kKeyX, "X");
CASE_KEY(GHOST_kKeyY, "Y");
CASE_KEY(GHOST_kKeyZ, "Z");
CASE_KEY(GHOST_kKeyLeftBracket, "[");
CASE_KEY(GHOST_kKeyRightBracket, "]");
}
#undef CASE_KEY
/* Shouldn't happen (the value is not known to #GHOST_TKey). */
if (tstr == nullptr) {
tstr = "Invalid";
}
return tstr;
}

View File

@@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventPrinter class.
*/
#pragma once
#include "GHOST_IEventConsumer.hh"
/**
* An Event consumer that prints all the events to standard out.
* Really useful when debugging.
*/
class GHOST_EventPrinter : public GHOST_IEventConsumer {
public:
/**
* Prints all the events received to std out.
* \param event: The event that can be handled or not.
* \return Indication as to whether the event was handled.
*/
bool processEvent(const GHOST_IEvent *event) override;
protected:
/**
* Converts GHOST key code to a readable string.
* \param key: The GHOST key code to convert.
* \return The GHOST key code converted to a readable string.
*/
const char *getKeyString(GHOST_TKey key) const;
};

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventString class.
*/
#pragma once
#include "GHOST_Event.hh"
/**
* Generic class for events with string data
*/
class GHOST_EventString : public GHOST_Event {
public:
/**
* Constructor.
* \param msec: The time this event was generated.
* \param type: The type of this event.
* \param window: The generating window (or nullptr if system event).
* \param data_ptr: Pointer to the (un-formatted) data associated with the event.
*/
GHOST_EventString(uint64_t msec,
GHOST_TEventType type,
GHOST_IWindow *window,
GHOST_TEventDataPtr data_ptr)
: GHOST_Event(msec, type, window)
{
data_ = data_ptr;
}
~GHOST_EventString() override
{
if (data_) {
free((void *)data_);
}
}
};

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventTrackpad class.
*/
#pragma once
#include "GHOST_Event.hh"
/**
* Trackpad (scroll, magnify, rotate, ...) event.
*/
class GHOST_EventTrackpad : public GHOST_Event {
public:
/**
* Constructor.
* \param msec: The time this event was generated.
* \param window: The window of this event.
* \param subtype: The subtype of the event.
* \param x: The x-delta of the pan event.
* \param y: The y-delta of the pan event.
*/
GHOST_EventTrackpad(uint64_t msec,
GHOST_IWindow *window,
GHOST_TTrackpadEventSubTypes subtype,
int32_t x,
int32_t y,
int32_t deltaX,
int32_t deltaY,
bool isDirectionInverted)
: GHOST_Event(msec, GHOST_kEventTrackpad, window)
{
trackpad_event_data_.subtype = subtype;
trackpad_event_data_.x = x;
trackpad_event_data_.y = y;
trackpad_event_data_.deltaX = deltaX;
trackpad_event_data_.deltaY = deltaY;
trackpad_event_data_.isDirectionInverted = isDirectionInverted;
data_ = &trackpad_event_data_;
}
protected:
/** The mouse pan data */
GHOST_TEventTrackpadData trackpad_event_data_;
};

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_EventWheel class.
*/
#pragma once
#include "GHOST_Event.hh"
/**
* Mouse wheel event.
* The displacement of the mouse wheel is counted in ticks.
* A positive value means the wheel is turned away from the user.
*/
class GHOST_EventWheel : public GHOST_Event {
public:
/**
* Constructor.
* \param msec: The time this event was generated.
* \param window: The window of this event.
* \param axis: The axis of the mouse wheel.
* \param value: The displacement of the mouse wheel.
*/
GHOST_EventWheel(uint64_t msec, GHOST_IWindow *window, GHOST_TEventWheelAxis axis, int32_t value)
: GHOST_Event(msec, GHOST_kEventWheel, window)
{
wheel_event_data_.axis = axis;
wheel_event_data_.value = value;
data_ = &wheel_event_data_;
}
protected:
GHOST_TEventWheelData wheel_event_data_;
};

View File

@@ -0,0 +1,296 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
/**
* Copyright (C) 2001 NaN Technologies B.V.
*/
#include <vector>
#include "GHOST_ISystem.hh"
#include "GHOST_SystemHeadless.hh"
#if defined(WITH_GHOST_X11) && defined(WITH_GHOST_WAYLAND)
# include "GHOST_SystemWayland.hh"
# include "GHOST_SystemX11.hh"
#elif defined(WITH_GHOST_X11)
# include "GHOST_SystemX11.hh"
#elif defined(WITH_GHOST_WAYLAND)
# include "GHOST_SystemWayland.hh"
#elif defined(WITH_GHOST_SDL)
# include "GHOST_SystemSDL.hh"
#elif defined(WIN32)
# include "GHOST_SystemWin32.hh"
#elif defined(__APPLE__)
# include "GHOST_SystemCocoa.hh"
#endif
#include "CLG_log.h"
static CLG_LogRef LOG = {"ghost.system"};
GHOST_ISystem *GHOST_ISystem::system_ = nullptr;
const char *GHOST_ISystem::system_backend_id_ = nullptr;
GHOST_TBacktraceFn GHOST_ISystem::backtrace_fn_ = nullptr;
bool GHOST_ISystem::use_window_frame_ = true;
GHOST_TSuccess GHOST_ISystem::createSystem(bool verbose, [[maybe_unused]] bool background)
{
/* When GHOST fails to start, report the back-ends that were attempted.
* A Verbose argument could be supported in printing isn't always desired. */
struct GHOST_BackendInfo {
const char *id = nullptr;
/** The cause of the failure. */
std::string failure_msg;
};
std::vector<GHOST_BackendInfo> backends_attempted;
GHOST_TSuccess success;
if (!system_) {
#if defined(WITH_HEADLESS)
/* Pass. */
#elif defined(WITH_GHOST_WAYLAND)
# if defined(WITH_GHOST_WAYLAND_DYNLOAD)
const bool has_wayland_libraries = ghost_wl_dynload_libraries_init();
# else
const bool has_wayland_libraries = true;
# endif
#endif
#if defined(WITH_HEADLESS)
/* Pass. */
#elif defined(WITH_GHOST_X11) && defined(WITH_GHOST_WAYLAND)
/* Special case, try Wayland, fall back to X11. */
if (has_wayland_libraries) {
backends_attempted.push_back({"WAYLAND"});
try {
CLOG_INFO(&LOG, "Create Wayland system");
system_ = new GHOST_SystemWayland(background);
}
catch (const std::runtime_error &e) {
if (verbose) {
backends_attempted.back().failure_msg = e.what();
}
CLOG_INFO(&LOG, "Wayland system not created, falling back to X11");
delete system_;
system_ = nullptr;
# ifdef WITH_GHOST_WAYLAND_DYNLOAD
ghost_wl_dynload_libraries_exit();
# endif
}
}
else {
system_ = nullptr;
}
if (!system_) {
/* Try to fall back to X11. */
backends_attempted.push_back({"X11"});
try {
CLOG_INFO(&LOG, "Create X11 system");
system_ = new GHOST_SystemX11();
}
catch (const std::runtime_error &e) {
if (verbose) {
backends_attempted.back().failure_msg = e.what();
}
delete system_;
system_ = nullptr;
}
}
#elif defined(WITH_GHOST_X11)
backends_attempted.push_back({"X11"});
try {
CLOG_INFO(&LOG, "Create X11 system");
system_ = new GHOST_SystemX11();
}
catch (const std::runtime_error &e) {
if (verbose) {
backends_attempted.back().failure_msg = e.what();
}
delete system_;
system_ = nullptr;
}
#elif defined(WITH_GHOST_WAYLAND)
if (has_wayland_libraries) {
backends_attempted.push_back({"WAYLAND"});
try {
CLOG_INFO(&LOG, "Create Wayland system");
system_ = new GHOST_SystemWayland(background);
}
catch (const std::runtime_error &e) {
if (verbose) {
backends_attempted.back().failure_msg = e.what();
}
delete system_;
system_ = nullptr;
# ifdef WITH_GHOST_WAYLAND_DYNLOAD
ghost_wl_dynload_libraries_exit();
# endif
}
}
else {
system_ = nullptr;
}
#elif defined(WITH_GHOST_SDL)
backends_attempted.push_back({"SDL"});
try {
CLOG_INFO(&LOG, "Create SDL system");
system_ = new GHOST_SystemSDL();
}
catch (const std::runtime_error &e) {
if (verbose) {
backends_attempted.back().failure_msg = e.what();
}
delete system_;
system_ = nullptr;
}
#elif defined(WIN32)
backends_attempted.push_back({"WIN32"});
CLOG_INFO(&LOG, "Create Windows system");
system_ = new GHOST_SystemWin32();
#elif defined(__APPLE__)
backends_attempted.push_back({"COCOA"});
CLOG_INFO(&LOG, "Create Cocoa system");
system_ = new GHOST_SystemCocoa();
#endif
if (system_) {
system_backend_id_ = backends_attempted.back().id;
}
else if (verbose || CLOG_CHECK(&LOG, CLG_LEVEL_INFO)) {
bool show_messages = false;
std::string msg = "Failed to initialize display for back-end(s): [";
for (int i = 0; i < backends_attempted.size(); i++) {
const GHOST_BackendInfo &backend_item = backends_attempted[i];
if (i != 0) {
msg += ", ";
}
msg += "'" + std::string(backend_item.id) + "'";
if (!backend_item.failure_msg.empty()) {
show_messages = true;
}
}
msg += "]\n";
if (show_messages) {
for (int i = 0; i < backends_attempted.size(); i++) {
const GHOST_BackendInfo &backend_item = backends_attempted[i];
msg += " '";
msg += backend_item.id;
msg += "': ";
msg += backend_item.failure_msg.empty() ? "<unknown>" : backend_item.failure_msg.c_str();
msg += "\n";
}
}
CLOG_STR_INFO_NOCHECK(&LOG, msg.c_str());
#ifdef WITH_GHOST_WAYLAND
# ifndef WITH_GHOST_X11
/* Special case, Blender will eventually drop X11.
* show a more user friendly warning while this is in the process
* of being disabled by default. */
const char *wayland_display = getenv("WAYLAND_DISPLAY");
if (!(wayland_display && wayland_display[0])) {
const char *x11_display = getenv("DISPLAY");
if (x11_display && x11_display[0]) {
/* WAYLAND isn't available but X11 is.
* Print a message to let the users know how to enable X11.
* Since this is a non-standard option (currently - but used for "lite" builds),
* the user is likely a developer who created their own build. */
CLOG_STR_INFO_NOCHECK(
&LOG,
"Built without X11 support, enable \"WITH_GHOST_X11\" build-option to use X11.\n");
}
}
# endif /* !WITH_GHOST_X11 */
#endif /* WITH_GHOST_WAYLAND */
}
success = system_ != nullptr ? GHOST_kSuccess : GHOST_kFailure;
}
else {
success = GHOST_kFailure;
}
if (success) {
success = system_->init();
}
return success;
}
GHOST_TSuccess GHOST_ISystem::createSystemBackground()
{
GHOST_TSuccess success;
if (!system_) {
#if !defined(WITH_HEADLESS)
/* Try to create a off-screen render surface with the graphical systems. */
CLOG_INFO(&LOG, "Create background system");
success = createSystem(false, true);
if (success) {
return success;
}
/* Try to fall back to headless mode if all else fails. */
#endif
CLOG_INFO(&LOG, "Create headless system");
system_ = new GHOST_SystemHeadless();
success = system_ != nullptr ? GHOST_kSuccess : GHOST_kFailure;
}
else {
success = GHOST_kFailure;
}
if (success) {
success = system_->init();
}
return success;
}
GHOST_TSuccess GHOST_ISystem::disposeSystem()
{
CLOG_DEBUG(&LOG, "Dispose system");
GHOST_TSuccess success = GHOST_kSuccess;
if (system_) {
delete system_;
system_ = nullptr;
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_ISystem *GHOST_ISystem::getSystem()
{
return system_;
}
const char *GHOST_ISystem::getSystemBackend()
{
return system_backend_id_;
}
GHOST_TBacktraceFn GHOST_ISystem::getBacktraceFn()
{
return GHOST_ISystem::backtrace_fn_;
}
void GHOST_ISystem::setBacktraceFn(GHOST_TBacktraceFn backtrace_fn)
{
GHOST_ISystem::backtrace_fn_ = backtrace_fn;
}
bool GHOST_ISystem::getUseWindowFrame()
{
return GHOST_ISystem::use_window_frame_;
}
void GHOST_ISystem::setUseWindowFrame(bool use_window_frame)
{
GHOST_ISystem::use_window_frame_ = use_window_frame;
}

View File

@@ -0,0 +1,67 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
/**
* Copyright (C) 2001 NaN Technologies B.V.
*/
#include "GHOST_ISystemPaths.hh"
#ifdef WIN32
# include "GHOST_SystemPathsWin32.hh"
#else
# ifdef __APPLE__
# include "GHOST_SystemPathsCocoa.hh"
# else
# include "GHOST_SystemPathsUnix.hh"
# endif
#endif
GHOST_ISystemPaths *GHOST_ISystemPaths::system_paths_ = nullptr;
GHOST_TSuccess GHOST_ISystemPaths::create()
{
GHOST_TSuccess success;
if (!system_paths_) {
#ifdef WIN32
system_paths_ = new GHOST_SystemPathsWin32();
#else
# ifdef __APPLE__
system_paths_ = new GHOST_SystemPathsCocoa();
# else
system_paths_ = new GHOST_SystemPathsUnix();
# endif
#endif
success = system_paths_ != nullptr ? GHOST_kSuccess : GHOST_kFailure;
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_TSuccess GHOST_ISystemPaths::dispose()
{
GHOST_TSuccess success = GHOST_kSuccess;
if (system_paths_) {
delete system_paths_;
system_paths_ = nullptr;
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_ISystemPaths *GHOST_ISystemPaths::get()
{
if (!system_paths_) {
create();
}
return system_paths_;
}

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "GHOST_Types.hh"
#include "GHOST_Xr_openxr_includes.hh"
class GHOST_IXrGraphicsBinding {
public:
union {
#if defined(WITH_GHOST_X11)
# if defined(WITH_OPENGL_BACKEND)
XrGraphicsBindingEGLMNDX egl;
XrGraphicsBindingOpenGLXlibKHR glx;
# endif
#endif
#if defined(WIN32)
# if defined(WITH_OPENGL_BACKEND)
XrGraphicsBindingOpenGLWin32KHR wgl;
# endif
XrGraphicsBindingD3D11KHR d3d11;
#endif
#if defined(WITH_GHOST_WAYLAND)
# if defined(WITH_OPENGL_BACKEND)
XrGraphicsBindingOpenGLWaylandKHR wl;
# endif
#endif
#ifdef WITH_VULKAN_BACKEND
XrGraphicsBindingVulkanKHR vk;
#endif
#ifdef WITH_METAL_BACKEND
XrGraphicsBindingMetalKHR metal;
#endif
} oxr_binding;
virtual ~GHOST_IXrGraphicsBinding() = default;
/**
* \brief load the functions for the graphics binding.
*
* A graphic binding uses an extension. These extensions provide graphics platform specific
* functions. This function is called before #checkVersionRequirements allowing to preload the
* function pointers.
*
* \param instance: XrInstance where to retrieve the functions from.
*
* \returns true when the extensions functions were successfully loaded, return false when
* the extension functions could not be loaded or would not allow to use the binding.
*
* \note These functions should be stored as instance variable as they can differ between
* XrSessions.
*/
virtual bool loadExtensionFunctions(XrInstance instance) = 0;
/**
* Does __not__ require this object to be initialized (can be called prior to
* #initFromGhostContext). It's actually meant to be called first.
*
* \param r_requirement_info: Return argument to retrieve an informal string on the requirements.
* to be met. Useful for error/debug messages.
*/
virtual bool checkVersionRequirements(class GHOST_Context &ghost_ctx,
XrInstance instance,
XrSystemId system_id,
std::string *r_requirement_info) const = 0;
virtual void initFromGhostContext(class GHOST_Context &ghost_ctx,
XrInstance instance,
XrSystemId system_id) = 0;
virtual std::optional<int64_t> chooseSwapchainFormat(const std::vector<int64_t> &runtime_formats,
GHOST_TXrSwapchainFormat &r_format,
bool &r_is_rgb_format) const = 0;
virtual std::vector<XrSwapchainImageBaseHeader *> createSwapchainImages(
uint32_t image_count) = 0;
virtual void submitToSwapchainBegin() = 0;
virtual void submitToSwapchainImage(XrSwapchainImageBaseHeader &swapchain_image,
const GHOST_XrDrawViewInfo &draw_info) = 0;
virtual void submitToSwapchainEnd() = 0;
virtual bool needsUpsideDownDrawing(GHOST_Context &ghost_ctx) const = 0;
protected:
/* Use GHOST_XrGraphicsBindingCreateFromType! */
GHOST_IXrGraphicsBinding() = default;
};
std::unique_ptr<GHOST_IXrGraphicsBinding> GHOST_XrGraphicsBindingCreateFromType(
GHOST_TXrGraphicsBinding type, GHOST_Context &context);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,527 @@
/* SPDX-FileCopyrightText: 2010 The Chromium Authors. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#ifdef WITH_INPUT_IME
# include "GHOST_ImeWin32.hh"
# include "GHOST_WindowWin32.hh"
# include "utfconv.hh"
/* ISO_639-1 2-Letter Abbreviations. */
# define IMELANG_ENGLISH "en"
# define IMELANG_CHINESE "zh"
# define IMELANG_JAPANESE "ja"
# define IMELANG_KOREAN "ko"
GHOST_ImeWin32::GHOST_ImeWin32()
: is_composing_(false),
language_(IMELANG_ENGLISH),
conversion_modes_(IME_CMODE_ALPHANUMERIC),
sentence_mode_(IME_SMODE_NONE),
system_caret_(false),
caret_rect_(-1, -1, 0, 0),
is_first(true),
is_enable(true)
{
}
GHOST_ImeWin32::~GHOST_ImeWin32() {}
void GHOST_ImeWin32::UpdateInputLanguage()
{
/* Get the current input locale full name. */
WCHAR locale[LOCALE_NAME_MAX_LENGTH];
LCIDToLocaleName(
MAKELCID(LOWORD(::GetKeyboardLayout(0)), SORT_DEFAULT), locale, LOCALE_NAME_MAX_LENGTH, 0);
/* Get the 2-letter ISO-63901 abbreviation of the input locale name. */
WCHAR language_u16[W32_ISO639_LEN];
GetLocaleInfoEx(locale, LOCALE_SISO639LANGNAME, language_u16, W32_ISO639_LEN);
/* Store this as a UTF8 string. */
WideCharToMultiByte(
CP_UTF8, 0, language_u16, W32_ISO639_LEN, language_, W32_ISO639_LEN, nullptr, nullptr);
}
BOOL GHOST_ImeWin32::IsLanguage(const char name[W32_ISO639_LEN])
{
return (strcmp(name, language_) == 0);
}
void GHOST_ImeWin32::UpdateConversionStatus(HWND window_handle)
{
HIMC imm_context = ::ImmGetContext(window_handle);
if (imm_context) {
if (::ImmGetOpenStatus(imm_context)) {
::ImmGetConversionStatus(imm_context, &conversion_modes_, &sentence_mode_);
}
else {
conversion_modes_ = IME_CMODE_ALPHANUMERIC;
sentence_mode_ = IME_SMODE_NONE;
}
::ImmReleaseContext(window_handle, imm_context);
}
else {
conversion_modes_ = IME_CMODE_ALPHANUMERIC;
sentence_mode_ = IME_SMODE_NONE;
}
}
bool GHOST_ImeWin32::IsEnglishMode()
{
return (conversion_modes_ & IME_CMODE_NOCONVERSION) ||
!(conversion_modes_ & (IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE));
}
bool GHOST_ImeWin32::IsImeKeyEvent(char ascii, GHOST_TKey key)
{
if (!(IsEnglishMode())) {
/* In Chinese, Japanese, Korean, all alpha keys are processed by IME. */
if ((ascii >= 'A' && ascii <= 'Z') || (ascii >= 'a' && ascii <= 'z')) {
return true;
}
if (IsLanguage(IMELANG_JAPANESE) && (ascii >= ' ' && ascii <= '~')) {
return true;
}
if (IsLanguage(IMELANG_CHINESE)) {
if (ascii && strchr("!\"$'(),.:;<>?[\\]^_`/", ascii) && !(key == GHOST_kKeyNumpadPeriod)) {
return true;
}
if (conversion_modes_ & IME_CMODE_FULLSHAPE && (ascii >= '0' && ascii <= '9')) {
/* When in Full Width mode the number keys are also converted. */
return true;
}
}
}
return false;
}
void GHOST_ImeWin32::CreateImeWindow(HWND window_handle)
{
/**
* When a user disables TSF (Text Service Framework) and CUAS (Cicero
* Unaware Application Support), Chinese IMEs somehow ignore function calls
* to ::ImmSetCandidateWindow(), i.e. they do not move their candidate
* window to the position given as its parameters, and use the position
* of the current system caret instead, i.e. it uses ::GetCaretPos() to
* retrieve the position of their IME candidate window.
* Therefore, we create a temporary system caret for Chinese IMEs and use
* it during this input context.
* Since some third-party Japanese IME also uses ::GetCaretPos() to determine
* their window position, we also create a caret for Japanese IMEs.
*/
if (!system_caret_ && (IsLanguage(IMELANG_CHINESE) || IsLanguage(IMELANG_JAPANESE))) {
system_caret_ = ::CreateCaret(window_handle, nullptr, 1, 1);
}
/* Restore the positions of the IME windows. */
UpdateImeWindow(window_handle);
}
void GHOST_ImeWin32::SetImeWindowStyle(
HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam, BOOL *handled)
{
/**
* To prevent the IMM (Input Method Manager) from displaying the IME
* composition window, Update the styles of the IME windows and EXPLICITLY
* call ::DefWindowProc() here.
* NOTE(hbono): We can NEVER let WTL call ::DefWindowProc() when we update
* the styles of IME windows because the 'lparam' variable is a local one
* and all its updates disappear in returning from this function, i.e. WTL
* does not call ::DefWindowProc() with our updated 'lparam' value but call
* the function with its original value and over-writes our window styles.
*/
*handled = TRUE;
lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
::DefWindowProc(window_handle, message, wparam, lparam);
}
void GHOST_ImeWin32::DestroyImeWindow(HWND /*window_handle*/)
{
/* Destroy the system caret if we have created for this IME input context. */
if (system_caret_) {
::DestroyCaret();
system_caret_ = false;
}
}
void GHOST_ImeWin32::MoveImeWindow(HWND /*window_handle*/, HIMC imm_context)
{
int x = caret_rect_.l_;
int y = caret_rect_.t_;
const int kCaretMargin = 1;
/**
* As written in a comment in GHOST_ImeWin32::CreateImeWindow(),
* Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
* when a user disables TSF (Text Service Framework) and CUAS (Cicero
* Unaware Application Support).
* On the other hand, when a user enables TSF and CUAS, Chinese IMEs
* ignore the position of the current system caret and uses the
* parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
* parameter CFS_CANDIDATEPOS.
* Therefore, we do not only call ::ImmSetCandidateWindow() but also
* set the positions of the temporary system caret if it exists.
*/
CANDIDATEFORM candidate_position = {0, CFS_CANDIDATEPOS, {x, y}, {0, 0, 0, 0}};
::ImmSetCandidateWindow(imm_context, &candidate_position);
if (system_caret_) {
::SetCaretPos(x, y);
}
if (IsLanguage(IMELANG_KOREAN)) {
/**
* Chinese IMEs and Japanese IMEs require the upper-left corner of
* the caret to move the position of their candidate windows.
* On the other hand, Korean IMEs require the lower-left corner of the
* caret to move their candidate windows.
*/
y += kCaretMargin;
}
/**
* Japanese IMEs and Korean IMEs also use the rectangle given to
* ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
* to move their candidate windows when a user disables TSF and CUAS.
* Therefore, we also set this parameter here.
*/
CANDIDATEFORM exclude_rectangle = {
0, CFS_EXCLUDE, {x, y}, {x, y, x + caret_rect_.getWidth(), y + caret_rect_.getHeight()}};
::ImmSetCandidateWindow(imm_context, &exclude_rectangle);
}
void GHOST_ImeWin32::UpdateImeWindow(HWND window_handle)
{
/* Just move the IME window attached to the given window. */
if (caret_rect_.l_ >= 0 && caret_rect_.t_ >= 0) {
HIMC imm_context = ::ImmGetContext(window_handle);
if (imm_context) {
MoveImeWindow(window_handle, imm_context);
::ImmReleaseContext(window_handle, imm_context);
}
}
}
void GHOST_ImeWin32::CleanupComposition(HWND window_handle)
{
/**
* Notify the IMM attached to the given window to complete the ongoing
* composition, (this case happens when the given window is de-activated
* while composing a text and re-activated), and reset the composition status.
*/
if (is_composing_) {
HIMC imm_context = ::ImmGetContext(window_handle);
if (imm_context) {
::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
::ImmReleaseContext(window_handle, imm_context);
}
ResetComposition(window_handle);
}
}
void GHOST_ImeWin32::CheckFirst(HWND window_handle)
{
if (is_first) {
this->EndIME(window_handle);
is_first = false;
}
}
void GHOST_ImeWin32::ResetComposition(HWND /*window_handle*/)
{
/* Currently, just reset the composition status. */
is_composing_ = false;
}
void GHOST_ImeWin32::CompleteComposition(HWND window_handle, HIMC imm_context)
{
/**
* We have to confirm there is an ongoing composition before completing it.
* This is for preventing some IMEs from getting confused while completing an
* ongoing composition even if they do not have any ongoing compositions.
*/
if (is_composing_) {
::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
ResetComposition(window_handle);
}
}
void GHOST_ImeWin32::GetCaret(HIMC imm_context, LPARAM lparam, ImeComposition *composition)
{
/**
* This operation is optional and language-dependent because the caret
* style is dependent on the language, e.g.:
* * Korean IMEs: the caret is a blinking block,
* (It contains only one hangul character);
* * Chinese IMEs: the caret is a blinking line,
* (i.e. they do not need to retrieve the target selection);
* * Japanese IMEs: the caret is a selection (or underlined) block,
* (which can contain one or more Japanese characters).
*/
int target_start = -1;
int target_end = -1;
if (IsLanguage(IMELANG_KOREAN)) {
if (lparam & CS_NOMOVECARET) {
target_start = 0;
target_end = 1;
}
}
else if (IsLanguage(IMELANG_CHINESE)) {
int clause_size = ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, nullptr, 0);
if (clause_size) {
static std::vector<ulong> clauses;
clause_size = clause_size / sizeof(clauses[0]);
clauses.resize(clause_size);
ImmGetCompositionStringW(
imm_context, GCS_COMPCLAUSE, &clauses[0], sizeof(clauses[0]) * clause_size);
if (composition->cursor_position == composition->ime_string.size()) {
target_start = clauses[clause_size - 2];
target_end = clauses[clause_size - 1];
}
else {
for (int i = 0; i < clause_size - 1; i++) {
if (clauses[i] == composition->cursor_position) {
target_start = clauses[i];
target_end = clauses[i + 1];
break;
}
}
}
}
else {
if (composition->cursor_position != -1) {
target_start = composition->cursor_position;
target_end = composition->ime_string.size();
}
}
}
else if (IsLanguage(IMELANG_JAPANESE)) {
/**
* For Japanese IMEs, the robustest way to retrieve the caret
* is scanning the attribute of the latest composition string and
* retrieving the beginning and the end of the target clause, i.e.
* a clause being converted.
*/
if (lparam & GCS_COMPATTR) {
int attribute_size = ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, nullptr, 0);
if (attribute_size > 0) {
char *attribute_data = new char[attribute_size];
if (attribute_data) {
::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, attribute_data, attribute_size);
for (target_start = 0; target_start < attribute_size; ++target_start) {
if (IsTargetAttribute(attribute_data[target_start])) {
break;
}
}
for (target_end = target_start; target_end < attribute_size; ++target_end) {
if (!IsTargetAttribute(attribute_data[target_end])) {
break;
}
}
if (target_start == attribute_size) {
/**
* This composition clause does not contain any target clauses,
* i.e. this clauses is an input clause.
* We treat whole this clause as a target clause.
*/
target_end = target_start;
target_start = 0;
}
if (target_start != -1 && target_start < attribute_size &&
attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED)
{
composition->cursor_position = target_start;
}
}
delete[] attribute_data;
}
}
}
composition->target_start = target_start;
composition->target_end = target_end;
}
bool GHOST_ImeWin32::GetString(HIMC imm_context,
WPARAM lparam,
int type,
ImeComposition *composition)
{
bool result = false;
if (lparam & type) {
int string_size = ::ImmGetCompositionStringW(imm_context, type, nullptr, 0);
if (string_size > 0) {
int string_length = string_size / sizeof(wchar_t);
wchar_t *string_data = new wchar_t[string_length + 1];
string_data[string_length] = '\0';
if (string_data) {
/* Fill the given ImeComposition object. */
::ImmGetCompositionStringW(imm_context, type, string_data, string_size);
composition->string_type = type;
composition->ime_string = string_data;
result = true;
}
delete[] string_data;
}
}
return result;
}
bool GHOST_ImeWin32::GetResult(HWND window_handle, LPARAM lparam, ImeComposition *composition)
{
bool result = false;
HIMC imm_context = ::ImmGetContext(window_handle);
if (imm_context) {
/* Copy the result string to the ImeComposition object. */
result = GetString(imm_context, lparam, GCS_RESULTSTR, composition);
/**
* Reset all the other parameters because a result string does not
* have composition attributes.
*/
composition->cursor_position = -1;
composition->target_start = -1;
composition->target_end = -1;
::ImmReleaseContext(window_handle, imm_context);
}
return result;
}
bool GHOST_ImeWin32::GetComposition(HWND window_handle, LPARAM lparam, ImeComposition *composition)
{
bool result = false;
HIMC imm_context = ::ImmGetContext(window_handle);
if (imm_context) {
/* Copy the composition string to the ImeComposition object. */
result = GetString(imm_context, lparam, GCS_COMPSTR, composition);
/* Retrieve the cursor position in the IME composition. */
int cursor_position = ::ImmGetCompositionStringW(imm_context, GCS_CURSORPOS, nullptr, 0);
composition->cursor_position = cursor_position;
composition->target_start = -1;
composition->target_end = -1;
/* Retrieve the target selection and Update the ImeComposition object. */
GetCaret(imm_context, lparam, composition);
/* Mark that there is an ongoing composition. */
is_composing_ = true;
::ImmReleaseContext(window_handle, imm_context);
}
return result;
}
void GHOST_ImeWin32::EndIME(HWND window_handle)
{
/**
* A renderer process have moved its input focus to a password input
* when there is an ongoing composition, e.g. a user has clicked a
* mouse button and selected a password input while composing a text.
* For this case, we have to cancel the ongoing composition and
* clean up the resources attached to this object BEFORE DISABLING THE IME.
*/
if (!is_enable) {
return;
}
is_enable = false;
if (is_composing_) {
HIMC imm_context = ::ImmGetContext(window_handle);
if (imm_context) {
/* Cancel composition */
::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
::ImmNotifyIME(imm_context, NI_CLOSECANDIDATE, 0, 0);
::ImmReleaseContext(window_handle, imm_context);
}
ResetComposition(window_handle);
}
::ImmAssociateContextEx(window_handle, nullptr, 0);
eventImeData.composite.clear();
}
void GHOST_ImeWin32::BeginIME(HWND window_handle, const GHOST_Rect &caret_rect, bool complete)
{
if (is_enable && complete) {
return;
}
is_enable = true;
/**
* Load the default IME context.
* NOTE(hbono)
* IMM ignores this call if the IME context is loaded. Therefore, we do
* not have to check whether or not the IME context is loaded.
*/
::ImmAssociateContextEx(window_handle, nullptr, IACE_DEFAULT);
/* Complete the ongoing composition and move the IME windows. */
HIMC imm_context = ::ImmGetContext(window_handle);
if (imm_context) {
if (complete) {
/**
* A renderer process have moved its input focus to another edit
* control when there is an ongoing composition, e.g. a user has
* clicked a mouse button and selected another edit control while
* composing a text.
* For this case, we have to complete the ongoing composition and
* hide the IME windows BEFORE MOVING THEM.
*/
CompleteComposition(window_handle, imm_context);
}
/**
* Save the caret position, and Update the position of the IME window.
* This update is used for moving an IME window when a renderer process
* resize/moves the input caret.
*/
if (caret_rect.l_ >= 0 && caret_rect.t_ >= 0) {
caret_rect_ = caret_rect;
MoveImeWindow(window_handle, imm_context);
}
::ImmReleaseContext(window_handle, imm_context);
}
}
static void convert_utf16_to_utf8_len(std::wstring s, int &len)
{
if (len >= 0 && len <= s.size()) {
len = count_utf_8_from_16(s.substr(0, len).c_str()) - 1;
}
else {
len = -1;
}
}
static size_t updateUtf8Buf(ImeComposition &info)
{
size_t len = count_utf_8_from_16(info.ime_string.c_str());
info.utf8_buf.resize(len);
conv_utf_16_to_8(info.ime_string.c_str(), &info.utf8_buf[0], len);
convert_utf16_to_utf8_len(info.ime_string, info.cursor_position);
convert_utf16_to_utf8_len(info.ime_string, info.target_start);
convert_utf16_to_utf8_len(info.ime_string, info.target_end);
return len - 1;
}
void GHOST_ImeWin32::UpdateInfo(HWND window_handle)
{
int res = this->GetResult(window_handle, GCS_RESULTSTR, &resultInfo);
int comp = this->GetComposition(window_handle, GCS_COMPSTR | GCS_COMPATTR, &compInfo);
/* Convert wchar to UTF8. */
if (res) {
updateUtf8Buf(resultInfo);
eventImeData.result = std::string(&resultInfo.utf8_buf[0]);
}
else {
eventImeData.result = "";
}
if (comp) {
updateUtf8Buf(compInfo);
eventImeData.composite = std::string(&compInfo.utf8_buf[0]);
eventImeData.cursor_position = compInfo.cursor_position;
eventImeData.target_start = compInfo.target_start;
eventImeData.target_end = compInfo.target_end;
}
else {
eventImeData.composite = "";
eventImeData.cursor_position = -1;
eventImeData.target_start = -1;
eventImeData.target_end = -1;
}
}
#endif /* WITH_INPUT_IME */

View File

@@ -0,0 +1,357 @@
/* SPDX-FileCopyrightText: 2010 The Chromium Authors. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#ifdef WITH_INPUT_IME
# include <windows.h>
# include <string>
# include "GHOST_Event.hh"
# include "GHOST_Rect.hh"
# include <vector>
/* MSDN LOCALE_SISO639LANGNAME states maximum length of 9, including terminating null. */
# define W32_ISO639_LEN 9
class GHOST_EventIME : public GHOST_Event {
public:
/**
* Constructor.
* \param msec: The time this event was generated.
* \param type: The type of key event.
* \param customdata: custom-data, typically #GHOST_TEventImeData.
*/
GHOST_EventIME(uint64_t msec,
GHOST_TEventType type,
GHOST_IWindow *window,
const void *customdata)
: GHOST_Event(msec, type, window)
{
this->data_ = customdata;
}
};
/**
* This header file defines a struct and a class used for encapsulating IMM32
* APIs, controls IMEs attached to a window, and enables the 'on-the-spot'
* input without deep knowledge about the APIs, i.e. knowledge about the
* language-specific and IME-specific behaviors.
* The following items enumerates the simplest steps for an (window)
* application to control its IMEs with the struct and the class defined
* this file.
* 1. Add an instance of the GHOST_ImeWin32 class to its window class.
* (The GHOST_ImeWin32 class needs a window handle.)
* 2. Add messages handlers listed in the following subsections, follow the
* instructions written in each subsection, and use the GHOST_ImeWin32 class.
* 2.1. WM_IME_SETCONTEXT (0x0281)
* Call the functions listed below:
* - GHOST_ImeWin32::CreateImeWindow();
* - GHOST_ImeWin32::CleanupComposition(), and;
* - GHOST_ImeWin32::SetImeWindowStyle().
* An application MUST prevent from calling ::DefWindowProc().
* 2.2. WM_IME_STARTCOMPOSITION (0x010D)
* Call the functions listed below:
* - GHOST_ImeWin32::CreateImeWindow(), and;
* - GHOST_ImeWin32::ResetComposition().
* An application MUST prevent from calling ::DefWindowProc().
* 2.3. WM_IME_COMPOSITION (0x010F)
* Call the functions listed below:
* - GHOST_ImeWin32::UpdateImeWindow();
* - GHOST_ImeWin32::GetResult();
* - GHOST_ImeWin32::GetComposition(), and;
* - GHOST_ImeWin32::ResetComposition() (optional).
* An application MUST prevent from calling ::DefWindowProc().
* 2.4. WM_IME_ENDCOMPOSITION (0x010E)
* Call the functions listed below:
* - GHOST_ImeWin32::ResetComposition(), and;
* - GHOST_ImeWin32::DestroyImeWindow().
* An application CAN call ::DefWindowProc().
* 2.5. WM_INPUTLANGCHANGE (0x0051)
* Call the functions listed below:
* - GHOST_ImeWin32::UpdateInputLanguage().
* An application CAN call ::DefWindowProc().
*/
/* This struct represents the status of an ongoing composition. */
struct ImeComposition {
/* Represents the cursor position in the IME composition. */
int cursor_position;
/* Represents the position of the beginning of the selection */
int target_start;
/* Represents the position of the end of the selection */
int target_end;
/**
* Represents the type of the string in the 'ime_string' parameter.
* Its possible values and description are listed below:
* Value Description
* 0 The parameter is not used.
* GCS_RESULTSTR The parameter represents a result string.
* GCS_COMPSTR The parameter represents a composition string.
*/
int string_type;
/* Represents the string retrieved from IME (Input Method Editor) */
std::wstring ime_string;
std::vector<char> utf8_buf;
std::vector<unsigned char> format;
};
/**
* This class controls the IMM (Input Method Manager) through IMM32 APIs and
* enables it to retrieve the string being controlled by the IMM. (I wrote
* a note to describe the reason why I do not use 'IME' but 'IMM' below.)
* NOTE(hbono):
* Fortunately or unfortunately, TSF (Text Service Framework) and
* CUAS (Cicero Unaware Application Support) allows IMM32 APIs for
* retrieving not only the inputs from IMEs (Input Method Editors), used
* only for inputting East-Asian language texts, but also the ones from
* tablets (on Windows XP Tablet PC Edition and Windows Vista), voice
* recognizers (e.g. ViaVoice and Microsoft Office), etc.
* We can disable TSF and CUAS in Windows XP Tablet PC Edition. On the other
* hand, we can NEVER disable either TSF or CUAS in Windows Vista, i.e.
* THIS CLASS IS NOT ONLY USED ON THE INPUT CONTEXTS OF EAST-ASIAN
* LANGUAGES BUT ALSO USED ON THE INPUT CONTEXTS OF ALL LANGUAGES.
*/
class GHOST_ImeWin32 {
public:
GHOST_ImeWin32();
~GHOST_ImeWin32();
/* Retrieves whether or not there is an ongoing composition. */
bool is_composing() const
{
return is_composing_;
}
/* Retrieve the input language from Windows and store it. */
void UpdateInputLanguage();
BOOL IsLanguage(const char name[W32_ISO639_LEN]);
/* Saves the current conversion status. */
void UpdateConversionStatus(HWND window_handle);
/* Is the IME currently in conversion mode? */
bool IsEnglishMode();
/* Checks a key whether IME has to do handling. */
bool IsImeKeyEvent(char ascii, GHOST_TKey key);
/**
* Create the IME windows, and allocate required resources for them.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
*/
void CreateImeWindow(HWND window_handle);
/**
* Update the style of the IME windows.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
* * message [in] (UINT)
* * wparam [in] (WPARAM)
* * lparam [in] (LPARAM)
* Represent the windows message of the caller.
* These parameters are used for verifying if this function is called
* in a handler function for WM_IME_SETCONTEXT messages because this
* function uses ::DefWindowProc() to update the style.
* A caller just has to pass the input parameters for the handler
* function without modifications.
* * handled [out] (BOOL*)
* Returns ::DefWindowProc() is really called in this function.
* PLEASE DO NOT CALL ::DefWindowProc() IF THIS VALUE IS TRUE!
* All the window styles set in this function are over-written when
* calling ::DefWindowProc() after returning this function.
*/
void SetImeWindowStyle(
HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam, BOOL *handled);
/**
* Destroy the IME windows and all the resources attached to them.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
*/
void DestroyImeWindow(HWND window_handle);
/**
* Update the position of the IME windows.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
*/
void UpdateImeWindow(HWND window_handle);
/**
* Clean up the all resources attached to the given GHOST_ImeWin32 object, and
* reset its composition status.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
*/
void CleanupComposition(HWND window_handle);
/**
* Reset the composition status.
* Cancel the ongoing composition if it exists.
* NOTE(hbono): This method does not release the allocated resources.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
*/
void ResetComposition(HWND window_handle);
/**
* Retrieve a composition result of the ongoing composition if it exists.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
* * lparam [in] (LPARAM)
* Specifies the updated members of the ongoing composition, and must be
* the same parameter of a WM_IME_COMPOSITION message handler.
* This parameter is used for checking if the ongoing composition has
* its result string,
* * composition [out] (ImeComposition)
* Represents the struct contains the composition result.
* Return values
* * true
* The ongoing composition has a composition result.
* * false
* The ongoing composition does not have composition results.
* Remarks
* This function is designed for being called from WM_IME_COMPOSITION
* message handlers.
*/
bool GetResult(HWND window_handle, LPARAM lparam, ImeComposition *composition);
/**
* Retrieve the current composition status of the ongoing composition.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
* * lparam [in] (LPARAM)
* Specifies the updated members of the ongoing composition, and must be
* the same parameter of a WM_IME_COMPOSITION message handler.
* This parameter is used for checking if the ongoing composition has
* its result string,
* * composition [out] (ImeComposition)
* Represents the struct contains the composition status.
* Return values
* * true
* The status of the ongoing composition is updated.
* * false
* The status of the ongoing composition is not updated.
* Remarks
* This function is designed for being called from WM_IME_COMPOSITION
* message handlers.
*/
bool GetComposition(HWND window_handle, LPARAM lparam, ImeComposition *composition);
/**
* Enable the IME attached to the given window, i.e. allows user-input
* events to be dispatched to the IME.
* In Chrome, this function is used when:
* * a renderer process moves its input focus to another edit control, or;
* * a renderer process moves the position of the focused edit control.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
* * caret_rect [in] (`const gfx::Rect&`)
* Represent the rectangle of the input caret.
* This rectangle is used for controlling the positions of IME windows.
* * complete [in] (bool)
* Represents whether or not to complete the ongoing composition.
* + true
* After finishing the ongoing composition and close its IME windows,
* start another composition and display its IME windows to the given
* position.
* + false
* Just move the IME windows of the ongoing composition to the given
* position without finishing it.
*/
void BeginIME(HWND window_handle, const GHOST_Rect &caret_rect, bool complete);
/**
* Disable the IME attached to the given window, i.e. prohibits any user-input
* events from being dispatched to the IME.
* In Chrome, this function is used when:
* * a renderer process sets its input focus to a password input.
* Parameters
* * window_handle [in] (HWND)
* Represents the window handle of the caller.
*/
void EndIME(HWND window_handle);
/** Update #resultInfo and #compInfo */
void UpdateInfo(HWND window_handle);
/** Disable IME when start up. */
void CheckFirst(HWND window_handle);
ImeComposition resultInfo, compInfo;
GHOST_TEventImeData eventImeData;
protected:
/* Determines whether or not the given attribute represents a target (a.k.a. a selection). */
bool IsTargetAttribute(char attribute) const
{
return (attribute == ATTR_TARGET_CONVERTED || attribute == ATTR_TARGET_NOTCONVERTED);
}
/* Retrieve the target area. */
void GetCaret(HIMC imm_context, LPARAM lparam, ImeComposition *composition);
/* Update the position of the IME windows. */
void MoveImeWindow(HWND window_handle, HIMC imm_context);
/* Complete the ongoing composition if it exists. */
void CompleteComposition(HWND window_handle, HIMC imm_context);
/* Retrieve a string from the IMM. */
bool GetString(HIMC imm_context, WPARAM lparam, int type, ImeComposition *composition);
private:
/**
* Represents whether or not there is an ongoing composition in a browser
* process, i.e. whether or not a browser process is composing a text.
*/
bool is_composing_;
/* Abbreviated ISO 639-1 name of the input language, such as "en" for English. */
char language_[W32_ISO639_LEN];
/* Current Conversion Mode Values. Retrieved with ImmGetConversionStatus. */
DWORD conversion_modes_;
/* Current Sentence Mode. Retrieved with ImmGetConversionStatus. */
DWORD sentence_mode_;
/**
* Represents whether or not the current input context has created a system
* caret to set the position of its IME candidate window.
* * true: it creates a system caret.
* * false: it does not create a system caret.
*/
bool system_caret_;
/* The rectangle of the input caret retrieved from a renderer process. */
GHOST_Rect caret_rect_;
/* used for disable ime when start up */
bool is_first, is_enable;
};
#endif /* WITH_INPUT_IME */

View File

@@ -0,0 +1,155 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
/**
* Copyright (C) 2001 NaN Technologies B.V.
*/
#include "GHOST_ModifierKeys.hh"
#include "GHOST_Debug.hh"
GHOST_ModifierKeys::GHOST_ModifierKeys()
{
clear();
}
GHOST_ModifierKeys::~GHOST_ModifierKeys() = default;
GHOST_TKey GHOST_ModifierKeys::getModifierKeyCode(GHOST_TModifierKey mask)
{
GHOST_TKey key;
switch (mask) {
case GHOST_kModifierKeyLeftShift:
key = GHOST_kKeyLeftShift;
break;
case GHOST_kModifierKeyRightShift:
key = GHOST_kKeyRightShift;
break;
case GHOST_kModifierKeyLeftAlt:
key = GHOST_kKeyLeftAlt;
break;
case GHOST_kModifierKeyRightAlt:
key = GHOST_kKeyRightAlt;
break;
case GHOST_kModifierKeyLeftControl:
key = GHOST_kKeyLeftControl;
break;
case GHOST_kModifierKeyRightControl:
key = GHOST_kKeyRightControl;
break;
case GHOST_kModifierKeyLeftOS:
key = GHOST_kKeyLeftOS;
break;
case GHOST_kModifierKeyRightOS:
key = GHOST_kKeyRightOS;
break;
case GHOST_kModifierKeyLeftHyper:
key = GHOST_kKeyLeftHyper;
break;
case GHOST_kModifierKeyRightHyper:
key = GHOST_kKeyRightHyper;
break;
default:
/* Should not happen. */
GHOST_ASSERT(0, "Invalid key!");
key = GHOST_kKeyUnknown;
break;
}
return key;
}
bool GHOST_ModifierKeys::get(GHOST_TModifierKey mask) const
{
switch (mask) {
case GHOST_kModifierKeyLeftShift:
return left_shift_;
case GHOST_kModifierKeyRightShift:
return right_shift_;
case GHOST_kModifierKeyLeftAlt:
return left_alt_;
case GHOST_kModifierKeyRightAlt:
return right_alt_;
case GHOST_kModifierKeyLeftControl:
return left_control_;
case GHOST_kModifierKeyRightControl:
return right_control_;
case GHOST_kModifierKeyLeftOS:
return left_os_;
case GHOST_kModifierKeyRightOS:
return right_os_;
case GHOST_kModifierKeyLeftHyper:
return left_hyper_;
case GHOST_kModifierKeyRightHyper:
return right_hyper_;
default:
GHOST_ASSERT(0, "Invalid key!");
return false;
}
}
void GHOST_ModifierKeys::set(GHOST_TModifierKey mask, bool down)
{
switch (mask) {
case GHOST_kModifierKeyLeftShift:
left_shift_ = down;
break;
case GHOST_kModifierKeyRightShift:
right_shift_ = down;
break;
case GHOST_kModifierKeyLeftAlt:
left_alt_ = down;
break;
case GHOST_kModifierKeyRightAlt:
right_alt_ = down;
break;
case GHOST_kModifierKeyLeftControl:
left_control_ = down;
break;
case GHOST_kModifierKeyRightControl:
right_control_ = down;
break;
case GHOST_kModifierKeyLeftOS:
left_os_ = down;
break;
case GHOST_kModifierKeyRightOS:
right_os_ = down;
break;
case GHOST_kModifierKeyLeftHyper:
left_hyper_ = down;
break;
case GHOST_kModifierKeyRightHyper:
right_hyper_ = down;
break;
default:
GHOST_ASSERT(0, "Invalid key!");
break;
}
}
void GHOST_ModifierKeys::clear()
{
left_shift_ = false;
right_shift_ = false;
left_alt_ = false;
right_alt_ = false;
left_control_ = false;
right_control_ = false;
left_os_ = false;
right_os_ = false;
left_hyper_ = false;
right_hyper_ = false;
}
bool GHOST_ModifierKeys::equals(const GHOST_ModifierKeys &keys) const
{
return (left_shift_ == keys.left_shift_) && (right_shift_ == keys.right_shift_) &&
(left_alt_ == keys.left_alt_) && (right_alt_ == keys.right_alt_) &&
(left_control_ == keys.left_control_) && (right_control_ == keys.right_control_) &&
(left_os_ == keys.left_os_) && (right_os_ == keys.right_os_) &&
(left_hyper_ == keys.left_hyper_) && (right_hyper_ == keys.right_hyper_);
}

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_ModifierKeys struct.
*/
#pragma once
#include "GHOST_Types.hh"
/**
* Stores the state of modifier keys.
* Discriminates between left and right modifier keys.
*/
struct GHOST_ModifierKeys {
/**
* Constructor.
*/
GHOST_ModifierKeys();
~GHOST_ModifierKeys();
/**
* Returns the modifier key's key code from a modifier key mask.
* \param mask: The mask of the modifier key.
* \return The modifier key's key code.
*/
static GHOST_TKey getModifierKeyCode(GHOST_TModifierKey mask);
/**
* Returns the state of a single modifier key.
* \param mask: Key state to return.
* \return The state of the key (pressed == true).
*/
bool get(GHOST_TModifierKey mask) const;
/**
* Updates the state of a single modifier key.
* \param mask: Key state to update.
* \param down: The new state of the key.
*/
void set(GHOST_TModifierKey mask, bool down);
/**
* Sets the state of all modifier keys to up.
*/
void clear();
/**
* Determines whether to modifier key states are equal.
* \param keys: The modifier key state to compare to.
* \return Indication of equality.
*/
bool equals(const GHOST_ModifierKeys &keys) const;
/** Bit-field that stores the appropriate key state. */
uint8_t left_shift_ : 1;
/** Bit-field that stores the appropriate key state. */
uint8_t right_shift_ : 1;
/** Bit-field that stores the appropriate key state. */
uint8_t left_alt_ : 1;
/** Bit-field that stores the appropriate key state. */
uint8_t right_alt_ : 1;
/** Bit-field that stores the appropriate key state. */
uint8_t left_control_ : 1;
/** Bit-field that stores the appropriate key state. */
uint8_t right_control_ : 1;
/** Bit-field that stores the appropriate key state. */
uint8_t left_os_ : 1;
uint8_t right_os_ : 1;
uint8_t left_hyper_ : 1;
uint8_t right_hyper_ : 1;
};

View File

@@ -0,0 +1,775 @@
/* SPDX-FileCopyrightText: 2007-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GHOST_NDOFManager.hh"
#include "GHOST_Debug.hh"
#include "GHOST_EventKey.hh"
#include "GHOST_EventNDOF.hh"
#include "GHOST_Types.hh"
#include "GHOST_WindowManager.hh"
#include "GHOST_utildefines.hh"
#include "CLG_log.h"
#include <algorithm>
#include <array>
#include <climits>
#include <cmath>
#include <cstring> /* For memory functions. */
#include <map>
static CLG_LogRef LOG = {"ghost.ndof"};
/**
* 3Dconnexion keyboards and keypads use specific keys that have no standard equivalent.
* These could be supported as generic "custom" keys, see !124155 review for details.
*/
// #define USE_3DCONNEXION_NONSTANDARD_KEYS
/* -------------------------------------------------------------------- */
/** \name NDOF Enum Strings
* \{ */
/* Printable values for #GHOST_TProgress enum (keep aligned). */
static const char *ndof_progress_string[] = {
"not started",
"starting",
"in progress",
"finishing",
"finished",
};
/* Printable values for #NDOF_ButtonT enum (keep aligned) */
#define MAP_ENTRY(button) {GHOST_##button, #button}
static const std::map<GHOST_NDOF_ButtonT, const char *> ndof_button_names = {
/* Disable wrapping, it makes it difficult to read. */
/* clang-format off */
MAP_ENTRY(NDOF_BUTTON_INVALID),
MAP_ENTRY(NDOF_BUTTON_MENU),
MAP_ENTRY(NDOF_BUTTON_FIT),
MAP_ENTRY(NDOF_BUTTON_TOP),
MAP_ENTRY(NDOF_BUTTON_LEFT),
MAP_ENTRY(NDOF_BUTTON_RIGHT),
MAP_ENTRY(NDOF_BUTTON_FRONT),
MAP_ENTRY(NDOF_BUTTON_BOTTOM),
MAP_ENTRY(NDOF_BUTTON_BACK),
MAP_ENTRY(NDOF_BUTTON_ROLL_CW),
MAP_ENTRY(NDOF_BUTTON_ROLL_CCW),
MAP_ENTRY(NDOF_BUTTON_SPIN_CW),
MAP_ENTRY(NDOF_BUTTON_SPIN_CCW),
MAP_ENTRY(NDOF_BUTTON_TILT_CW),
MAP_ENTRY(NDOF_BUTTON_TILT_CCW),
MAP_ENTRY(NDOF_BUTTON_ISO1),
MAP_ENTRY(NDOF_BUTTON_ISO2),
MAP_ENTRY(NDOF_BUTTON_1),
MAP_ENTRY(NDOF_BUTTON_2),
MAP_ENTRY(NDOF_BUTTON_3),
MAP_ENTRY(NDOF_BUTTON_4),
MAP_ENTRY(NDOF_BUTTON_5),
MAP_ENTRY(NDOF_BUTTON_6),
MAP_ENTRY(NDOF_BUTTON_7),
MAP_ENTRY(NDOF_BUTTON_8),
MAP_ENTRY(NDOF_BUTTON_9),
MAP_ENTRY(NDOF_BUTTON_10),
MAP_ENTRY(NDOF_BUTTON_11),
MAP_ENTRY(NDOF_BUTTON_12),
MAP_ENTRY(NDOF_BUTTON_ESC),
MAP_ENTRY(NDOF_BUTTON_ALT),
MAP_ENTRY(NDOF_BUTTON_SHIFT),
MAP_ENTRY(NDOF_BUTTON_CTRL),
MAP_ENTRY(NDOF_BUTTON_ENTER),
MAP_ENTRY(NDOF_BUTTON_DELETE),
MAP_ENTRY(NDOF_BUTTON_TAB),
MAP_ENTRY(NDOF_BUTTON_SPACE),
MAP_ENTRY(NDOF_BUTTON_ROTATE),
MAP_ENTRY(NDOF_BUTTON_PANZOOM),
MAP_ENTRY(NDOF_BUTTON_DOMINANT),
MAP_ENTRY(NDOF_BUTTON_PLUS),
MAP_ENTRY(NDOF_BUTTON_MINUS),
MAP_ENTRY(NDOF_BUTTON_V1),
MAP_ENTRY(NDOF_BUTTON_V2),
MAP_ENTRY(NDOF_BUTTON_V3),
MAP_ENTRY(NDOF_BUTTON_SAVE_V1),
MAP_ENTRY(NDOF_BUTTON_SAVE_V2),
MAP_ENTRY(NDOF_BUTTON_SAVE_V3),
MAP_ENTRY(NDOF_BUTTON_KBP_F1),
MAP_ENTRY(NDOF_BUTTON_KBP_F2),
MAP_ENTRY(NDOF_BUTTON_KBP_F3),
MAP_ENTRY(NDOF_BUTTON_KBP_F4),
MAP_ENTRY(NDOF_BUTTON_KBP_F5),
MAP_ENTRY(NDOF_BUTTON_KBP_F6),
MAP_ENTRY(NDOF_BUTTON_KBP_F7),
MAP_ENTRY(NDOF_BUTTON_KBP_F8),
MAP_ENTRY(NDOF_BUTTON_KBP_F9),
MAP_ENTRY(NDOF_BUTTON_KBP_F10),
MAP_ENTRY(NDOF_BUTTON_KBP_F11),
MAP_ENTRY(NDOF_BUTTON_KBP_F12),
MAP_ENTRY(NDOF_BUTTON_NP_F1),
MAP_ENTRY(NDOF_BUTTON_NP_F2),
MAP_ENTRY(NDOF_BUTTON_NP_F3),
MAP_ENTRY(NDOF_BUTTON_NP_F4),
/* clang-format on */
};
#undef MAP_ENTRY
static const char *ndof_device_names[] = {
"UnknownDevice",
"SpaceNavigator",
"SpaceExplorer",
"SpacePilotPro",
"SpaceMousePro",
"SpaceMouseWireless",
"SpaceMouseProWireless",
"SpaceMouseEnterprise",
"SpacePilot",
"Spaceball5000",
"SpaceTraveler",
"Keyboard Pro",
"Numpad Pro",
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name NDOF Button Maps
* \{ */
/**
* Shared by the some 3Dconnexion hardware
* SpacePilotPro uses all of these
* SpaceMouse Pro and SpaceNavigator use only some, based on button mask.
*/
static const GHOST_NDOF_ButtonT ndof_HID_map_Shared3Dx[] = {
GHOST_NDOF_BUTTON_MENU, GHOST_NDOF_BUTTON_FIT, GHOST_NDOF_BUTTON_TOP,
GHOST_NDOF_BUTTON_LEFT, GHOST_NDOF_BUTTON_RIGHT, GHOST_NDOF_BUTTON_FRONT,
GHOST_NDOF_BUTTON_BOTTOM, GHOST_NDOF_BUTTON_BACK, GHOST_NDOF_BUTTON_ROLL_CW,
GHOST_NDOF_BUTTON_ROLL_CCW, GHOST_NDOF_BUTTON_ISO1, GHOST_NDOF_BUTTON_ISO2,
GHOST_NDOF_BUTTON_1, GHOST_NDOF_BUTTON_2, GHOST_NDOF_BUTTON_3,
GHOST_NDOF_BUTTON_4, GHOST_NDOF_BUTTON_5, GHOST_NDOF_BUTTON_6,
GHOST_NDOF_BUTTON_7, GHOST_NDOF_BUTTON_8, GHOST_NDOF_BUTTON_9,
GHOST_NDOF_BUTTON_10, GHOST_NDOF_BUTTON_ESC, GHOST_NDOF_BUTTON_ALT,
GHOST_NDOF_BUTTON_SHIFT, GHOST_NDOF_BUTTON_CTRL, GHOST_NDOF_BUTTON_ROTATE,
GHOST_NDOF_BUTTON_PANZOOM, GHOST_NDOF_BUTTON_DOMINANT, GHOST_NDOF_BUTTON_PLUS,
GHOST_NDOF_BUTTON_MINUS,
};
static const GHOST_NDOF_ButtonT ndof_HID_map_SpaceExplorer[] = {
GHOST_NDOF_BUTTON_1,
GHOST_NDOF_BUTTON_2,
GHOST_NDOF_BUTTON_TOP,
GHOST_NDOF_BUTTON_LEFT,
GHOST_NDOF_BUTTON_RIGHT,
GHOST_NDOF_BUTTON_FRONT,
GHOST_NDOF_BUTTON_ESC,
GHOST_NDOF_BUTTON_ALT,
GHOST_NDOF_BUTTON_SHIFT,
GHOST_NDOF_BUTTON_CTRL,
GHOST_NDOF_BUTTON_FIT,
GHOST_NDOF_BUTTON_MENU,
GHOST_NDOF_BUTTON_PLUS,
GHOST_NDOF_BUTTON_MINUS,
GHOST_NDOF_BUTTON_ROTATE,
};
/* This is the older SpacePilot (sans Pro). */
static const GHOST_NDOF_ButtonT ndof_HID_map_SpacePilot[] = {
GHOST_NDOF_BUTTON_1, GHOST_NDOF_BUTTON_2,
GHOST_NDOF_BUTTON_3, GHOST_NDOF_BUTTON_4,
GHOST_NDOF_BUTTON_5, GHOST_NDOF_BUTTON_6,
GHOST_NDOF_BUTTON_TOP, GHOST_NDOF_BUTTON_LEFT,
GHOST_NDOF_BUTTON_RIGHT, GHOST_NDOF_BUTTON_FRONT,
GHOST_NDOF_BUTTON_ESC, GHOST_NDOF_BUTTON_ALT,
GHOST_NDOF_BUTTON_SHIFT, GHOST_NDOF_BUTTON_CTRL,
GHOST_NDOF_BUTTON_FIT, GHOST_NDOF_BUTTON_MENU,
GHOST_NDOF_BUTTON_PLUS, GHOST_NDOF_BUTTON_MINUS,
GHOST_NDOF_BUTTON_DOMINANT, GHOST_NDOF_BUTTON_ROTATE,
GHOST_NDOF_BUTTON_INVALID /* the CONFIG button -- what does it do? */
};
static const GHOST_NDOF_ButtonT ndof_HID_map_Generic[] = {
GHOST_NDOF_BUTTON_1,
GHOST_NDOF_BUTTON_2,
GHOST_NDOF_BUTTON_3,
GHOST_NDOF_BUTTON_4,
GHOST_NDOF_BUTTON_5,
GHOST_NDOF_BUTTON_6,
GHOST_NDOF_BUTTON_7,
GHOST_NDOF_BUTTON_8,
GHOST_NDOF_BUTTON_9,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name NDOF Manager Class
* \{ */
static const int genericButtonCount = ARRAY_SIZE(ndof_HID_map_Generic);
GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System &sys)
: system_(sys),
device_type_(NDOF_UnknownDevice), /* Each platform has its own device detection code. */
hid_map_button_num_(genericButtonCount),
hid_map_button_mask_(0),
hid_map_(ndof_HID_map_Generic),
button_depressed_(0),
pressed_buttons_cache_(),
pressed_long_buttons_cache_(),
motion_time_(0),
motion_time_prev_(0),
motion_state_(GHOST_kNotStarted),
motion_event_pending_(false),
motion_dead_zone_(0.0f)
{
/* To avoid the rare situation where one triple is updated and
* the other is not, initialize them both here: */
memset(translation_, 0, sizeof(translation_));
memset(rotation_, 0, sizeof(rotation_));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name NDOF Device Setup
* \{ */
bool GHOST_NDOFManager::setDevice(ushort vendor_id, ushort product_id)
{
/* Call this function until it returns true
* it's a good idea to stop calling it after that, as it will "forget"
* whichever device it already found. */
/* Default to safe generic behavior for "unknown" devices
* unidentified devices will emit motion events like normal
* rogue buttons do nothing by default, but can be customized by the user. */
device_type_ = NDOF_UnknownDevice;
hid_map_ = ndof_HID_map_Generic;
hid_map_button_num_ = genericButtonCount;
hid_map_button_mask_ = 0;
/* "mystery device" owners can help build a HID_map for their hardware
* A few users have already contributed information about several older devices
* that I don't have access to. Thanks! */
switch (vendor_id) {
case 0x046D: /* Logitech (3Dconnexion was a subsidiary). */
switch (product_id) {
/* -- current devices -- */
case 0xC626: /* Full-size SpaceNavigator. */
case 0xC628: /* The "for Notebooks" one. */
{
device_type_ = NDOF_SpaceNavigator;
hid_map_button_num_ = 2;
hid_map_ = ndof_HID_map_Shared3Dx;
break;
}
case 0xC627: {
device_type_ = NDOF_SpaceExplorer;
hid_map_button_num_ = 15;
hid_map_ = ndof_HID_map_SpaceExplorer;
break;
}
case 0xC629: {
device_type_ = NDOF_SpacePilotPro;
hid_map_button_num_ = 31;
hid_map_ = ndof_HID_map_Shared3Dx;
break;
}
case 0xC62B: {
device_type_ = NDOF_SpaceMousePro;
hid_map_button_num_ = 27; /* 15 physical buttons, but HID codes range from 0 to 26. */
hid_map_button_mask_ = 0x07C0F137;
hid_map_ = ndof_HID_map_Shared3Dx;
break;
}
/* -- older devices -- */
case 0xC625: {
device_type_ = NDOF_SpacePilot;
hid_map_button_num_ = 21;
hid_map_ = ndof_HID_map_SpacePilot;
break;
}
case 0xC621: {
device_type_ = NDOF_Spaceball5000;
hid_map_button_num_ = 12;
break;
}
case 0xC623: {
device_type_ = NDOF_SpaceTraveler;
hid_map_button_num_ = 8;
break;
}
default: {
CLOG_INFO(&LOG, "Unknown Logitech product %04hx", product_id);
}
}
break;
case 0x256F: /* 3Dconnexion. */
switch (product_id) {
case 0xC62E: /* SpaceMouse Wireless (cabled). */
case 0xC62F: /* SpaceMouse Wireless Receiver. */
case 0xC658: /* Wireless (3DConnexion Universal Wireless Receiver in WIN32), see #82412. */
{
device_type_ = NDOF_SpaceMouseWireless;
hid_map_button_num_ = 2;
hid_map_ = ndof_HID_map_Shared3Dx;
break;
}
case 0xC631: /* SpaceMouse Pro Wireless (cabled). */
case 0xC632: /* SpaceMouse Pro Wireless Receiver. */
case 0xC638: /* SpaceMouse Pro Wireless BT (cabled), see #116393.
* 3Dconnexion docs describe this as "Wireless BT", but it is cabled. */
case 0xC652: /* Universal Receiver. */
{
device_type_ = NDOF_SpaceMouseProWireless;
hid_map_button_num_ = 27; /* 15 physical buttons, but HID codes range from 0 to 26. */
hid_map_button_mask_ = 0x07C0F137;
hid_map_ = ndof_HID_map_Shared3Dx;
break;
}
case 0xC633: /* Newer devices don't need to use button mappings. */
{
device_type_ = NDOF_SpaceMouseEnterprise;
break;
}
case 0xC664:
case 0xC668: {
device_type_ = NDOF_KeyboardPro;
break;
}
case 0xC665: {
device_type_ = NDOF_NumpadPro;
break;
}
default: {
CLOG_INFO(&LOG, "Unknown 3Dconnexion product %04hx", product_id);
}
}
break;
default:
CLOG_INFO(&LOG, "Unknown device %04hx:%04hx", vendor_id, product_id);
}
if (device_type_ != NDOF_UnknownDevice) {
CLOG_INFO(&LOG, "Using %s", ndof_device_names[device_type_]);
}
if (hid_map_button_mask_ == 0) {
hid_map_button_mask_ = int(~(UINT_MAX << hid_map_button_num_));
}
CLOG_DEBUG(&LOG, "Device %d buttons -> hex:%X", hid_map_button_num_, uint(hid_map_button_mask_));
return device_type_ != NDOF_UnknownDevice;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name NDOF Update State
* \{ */
void GHOST_NDOFManager::updateTranslation(const int t[3], uint64_t time)
{
memcpy(translation_, t, sizeof(translation_));
motion_time_ = time;
motion_event_pending_ = true;
}
void GHOST_NDOFManager::updateRotation(const int r[3], uint64_t time)
{
memcpy(rotation_, r, sizeof(rotation_));
motion_time_ = time;
motion_event_pending_ = true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name NDOF Buttons
* \{ */
static GHOST_TKey ghost_map_keyboard_from_ndof_button(const GHOST_NDOF_ButtonT button)
{
switch (button) {
case GHOST_NDOF_BUTTON_ESC: {
return GHOST_kKeyEsc;
}
case GHOST_NDOF_BUTTON_ENTER: {
return GHOST_kKeyEnter;
}
case GHOST_NDOF_BUTTON_DELETE: {
return GHOST_kKeyDelete;
}
case GHOST_NDOF_BUTTON_TAB: {
return GHOST_kKeyTab;
}
case GHOST_NDOF_BUTTON_SPACE: {
return GHOST_kKeySpace;
}
case GHOST_NDOF_BUTTON_ALT: {
return GHOST_kKeyLeftAlt;
}
case GHOST_NDOF_BUTTON_SHIFT: {
return GHOST_kKeyLeftShift;
}
case GHOST_NDOF_BUTTON_CTRL: {
return GHOST_kKeyLeftControl;
}
#ifdef USE_3DCONNEXION_NONSTANDARD_KEYS
case GHOST_NDOF_BUTTON_NP_F1:
case GHOST_NDOF_BUTTON_KBP_F1: {
return GHOST_kKeyF1;
}
case GHOST_NDOF_BUTTON_NP_F2:
case GHOST_NDOF_BUTTON_KBP_F2: {
return GHOST_kKeyF2;
}
case GHOST_NDOF_BUTTON_NP_F3:
case GHOST_NDOF_BUTTON_KBP_F3: {
return GHOST_kKeyF3;
}
case GHOST_NDOF_BUTTON_NP_F4:
case GHOST_NDOF_BUTTON_KBP_F4: {
return GHOST_kKeyF4;
}
case GHOST_NDOF_BUTTON_KBP_F5: {
return GHOST_kKeyF5;
}
case GHOST_NDOF_BUTTON_KBP_F6: {
return GHOST_kKeyF6;
}
case GHOST_NDOF_BUTTON_KBP_F7: {
return GHOST_kKeyF7;
}
case GHOST_NDOF_BUTTON_KBP_F8: {
return GHOST_kKeyF8;
}
case GHOST_NDOF_BUTTON_KBP_F9: {
return GHOST_kKeyF9;
}
case GHOST_NDOF_BUTTON_KBP_F10: {
return GHOST_kKeyF10;
}
case GHOST_NDOF_BUTTON_KBP_F11: {
return GHOST_kKeyF11;
}
case GHOST_NDOF_BUTTON_KBP_F12: {
return GHOST_kKeyF12;
}
#endif /* !USE_3DCONNEXION_NONSTANDARD_KEYS */
default: {
return GHOST_kKeyUnknown;
}
}
}
void GHOST_NDOFManager::sendButtonEvent(GHOST_NDOF_ButtonT button,
bool press,
uint64_t time,
GHOST_IWindow *window)
{
GHOST_ASSERT(button > GHOST_NDOF_BUTTON_NONE && button < GHOST_NDOF_BUTTON_USER,
"rogue button trying to escape GHOST_NDOF manager");
auto event = std::make_unique<GHOST_EventNDOFButton>(time, window);
GHOST_TEventNDOFButtonData *data = (GHOST_TEventNDOFButtonData *)event->getData();
data->action = press ? GHOST_kPress : GHOST_kRelease;
data->button = button;
system_.pushEvent(std::move(event));
}
void GHOST_NDOFManager::sendKeyEvent(GHOST_TKey key,
bool press,
uint64_t time,
GHOST_IWindow *window)
{
GHOST_TEventType type = press ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
auto event = std::make_unique<GHOST_EventKey>(time, type, window, key, false);
system_.pushEvent(std::move(event));
}
void GHOST_NDOFManager::updateButton(GHOST_NDOF_ButtonT button, bool press, uint64_t time)
{
if (button == GHOST_NDOF_BUTTON_INVALID) {
CLOG_DEBUG(
&LOG, "Update button=%d, press=%d (mapped to none, ignoring!)", int(button), int(press));
return;
}
CLOG_DEBUG(&LOG,
"Update button=%d, press=%d, name=%s",
button,
int(press),
ndof_button_names.at(button));
#ifndef USE_3DCONNEXION_NONSTANDARD_KEYS
if (((button >= GHOST_NDOF_BUTTON_KBP_F1) && (button <= GHOST_NDOF_BUTTON_KBP_F12)) ||
((button >= GHOST_NDOF_BUTTON_NP_F1) && (button <= GHOST_NDOF_BUTTON_NP_F4)))
{
return;
}
#endif /* !USE_3DCONNEXION_NONSTANDARD_KEYS */
GHOST_IWindow *window = system_.getWindowManager()->getActiveWindow();
/* Delivery will fail, so don't bother sending. */
if (window != nullptr) {
const GHOST_TKey key = ghost_map_keyboard_from_ndof_button(button);
if (key != GHOST_kKeyUnknown) {
sendKeyEvent(key, press, time, window);
}
else {
sendButtonEvent(button, press, time, window);
}
}
}
void GHOST_NDOFManager::updateButtonRAW(int button_number, bool press, uint64_t time)
{
GHOST_NDOF_ButtonT button;
/* For bit-mask devices button mapping isn't unified, therefore check the button map. */
if (std::find(bitmask_devices_.begin(), bitmask_devices_.end(), device_type_) !=
bitmask_devices_.end())
{
if (button_number >= hid_map_button_num_) {
CLOG_DEBUG(
&LOG, "Update button=%d, press=%d (out of range, ignoring!)", button_number, int(press));
return;
}
button = hid_map_[button_number];
}
else {
button = static_cast<GHOST_NDOF_ButtonT>(button_number);
}
GHOST_NDOFManager::updateButton(button, press, time);
}
void GHOST_NDOFManager::updateButtonsBitmask(int button_bits, uint64_t time)
{
/* Some devices send two data packets: bitmask and number array.
* In this case, packet has to be ignored if it came from such a device. */
if (std::find(bitmask_devices_.begin(), bitmask_devices_.end(), device_type_) ==
bitmask_devices_.end())
{
return;
}
button_bits &= hid_map_button_mask_; /* Discard any "garbage" bits. */
int diff = button_depressed_ ^ button_bits;
for (int button_number = 0; button_number < hid_map_button_num_; ++button_number) {
int mask = 1 << button_number;
if (diff & mask) {
bool press = button_bits & mask;
if (press) {
button_depressed_ |= mask; /* Set this button's bit. */
}
else {
button_depressed_ &= ~mask; /* Clear this button's bit. */
}
/* Bitmask devices don't have unified keymaps, so button numbers needs to be looked up in the
* map. */
const GHOST_NDOF_ButtonT button = hid_map_[button_number];
updateButton(button, press, time);
}
}
}
void GHOST_NDOFManager::updateButtonsArray(NDOF_Button_Array buttons,
uint64_t time,
NDOF_Button_Type type)
{
NDOF_Button_Array &cache = (type == NDOF_Button_Type::LongButton) ? pressed_long_buttons_cache_ :
pressed_buttons_cache_;
/* Find released buttons */
for (const auto &cached_button : cache) {
bool found = false;
for (const auto &button : buttons) {
if (button == cached_button) {
found = true;
break;
}
}
if (!found) {
updateButton(cached_button, false, time);
}
}
/* Find pressed buttons */
for (const auto &button : buttons) {
bool found = false;
for (const auto &cached_button : cache) {
if (button == cached_button) {
found = true;
break;
}
}
if (!found) {
updateButton(button, true, time);
}
}
cache = buttons;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name NDOF Motion
* \{ */
void GHOST_NDOFManager::setDeadZone(float dz)
{
/* Negative values don't make sense, so clamp at zero. */
dz = std::max(dz, 0.0f);
motion_dead_zone_ = dz;
/* Warn the rogue user/developer about high dead-zone, but allow it. */
CLOG_INFO(&LOG, "Dead zone set to %.2f%s", dz, (dz > 0.5f) ? " (unexpectedly high)" : "");
}
static bool atHomePosition(const GHOST_TEventNDOFMotionData *ndof)
{
#define HOME(foo) (ndof->foo == 0.0f)
return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
#undef HOME
}
static bool nearHomePosition(const GHOST_TEventNDOFMotionData *ndof, float threshold)
{
if (threshold == 0.0f) {
return atHomePosition(ndof);
}
#define HOME(foo) (fabsf(ndof->foo) < threshold)
return HOME(tx) && HOME(ty) && HOME(tz) && HOME(rx) && HOME(ry) && HOME(rz);
#undef HOME
}
bool GHOST_NDOFManager::sendMotionEvent()
{
if (!motion_event_pending_) {
if (motion_state_ != GHOST_kNotStarted) {
/* Detect window de-activation and change the `motion_state_` even when no motion is pending.
* Without this check it's possible the window is de-activated before the NDOF
* motion callbacks have run, while the `motion_state_` is active.
* In this case, activating the window again would create an event
* with a large time-delta, see: #134733. */
if (system_.getWindowManager()->getActiveWindow() == nullptr) {
/* Avoid large `dt` times when changing windows. */
motion_state_ = GHOST_kNotStarted;
}
}
return false;
}
motion_event_pending_ = false; /* Any pending motion is handled right now. */
GHOST_IWindow *window = system_.getWindowManager()->getActiveWindow();
/* Delivery will fail, so don't bother sending. */
if (window == nullptr) {
/* Avoid large `dt` times when changing windows. */
motion_state_ = GHOST_kNotStarted;
return false;
}
auto event = std::make_unique<GHOST_EventNDOFMotion>(motion_time_, window);
GHOST_TEventNDOFMotionData *data = (GHOST_TEventNDOFMotionData *)event->getData();
/* Scale axis values here to normalize them to around +/- 1
* they are scaled again for overall sensitivity in the WM based on user preferences. */
const float scale = 1.0f / 350.0f; /* 3Dconnexion devices send +/- 350 usually */
data->tx = scale * translation_[0];
data->ty = scale * translation_[1];
data->tz = scale * translation_[2];
data->rx = scale * rotation_[0];
data->ry = scale * rotation_[1];
data->rz = scale * rotation_[2];
data->dt = 0.001f * (motion_time_ - motion_time_prev_); /* In seconds. */
motion_time_prev_ = motion_time_;
bool weHaveMotion = !nearHomePosition(data, motion_dead_zone_);
/* Determine what kind of motion event to send `(Starting, InProgress, Finishing)`
* and where that leaves this NDOF manager `(NotStarted, InProgress, Finished)`. */
switch (motion_state_) {
case GHOST_kNotStarted:
case GHOST_kFinished: {
if (weHaveMotion) {
data->progress = GHOST_kStarting;
motion_state_ = GHOST_kInProgress;
/* Previous motion time will be ancient, so just make up a reasonable time delta. */
data->dt = NDOF_TIME_DELTA_STARTING;
}
else {
/* Send no event and keep current state. */
CLOG_DEBUG(&LOG, "Motion ignored");
return false;
}
break;
}
case GHOST_kInProgress: {
if (weHaveMotion) {
data->progress = GHOST_kInProgress;
/* Remain 'InProgress'. */
}
else {
data->progress = GHOST_kFinishing;
motion_state_ = GHOST_kFinished;
}
break;
}
default: {
/* Will always be one of the above. */
break;
}
}
#if 1
CLOG_DEBUG(&LOG,
"Motion sent, T=(%.2f,%.2f,%.2f), R=(%.2f,%.2f,%.2f) dt=%.3f, status=%s",
data->tx,
data->ty,
data->tz,
data->rx,
data->ry,
data->rz,
data->dt,
ndof_progress_string[data->progress]);
#else
/* Raw values, may be useful for debugging. */
CLOG_DEBUG(&LOG,
"Motion sent, T=(%d,%d,%d) R=(%d,%d,%d) status=%s",
translation_[0],
translation_[1],
translation_[2],
rotation_[0],
rotation_[1],
rotation_[2],
ndof_progress_string[data->progress]);
#endif
system_.pushEvent(std::move(event));
return true;
}
/** \} */

View File

@@ -0,0 +1,142 @@
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#ifndef WITH_INPUT_NDOF
# error NDOF code included in non-NDOF-enabled build
#endif
#include "GHOST_System.hh"
#include <array>
enum NDOF_DeviceT {
NDOF_UnknownDevice = 0,
/* Current devices. */
NDOF_SpaceNavigator,
NDOF_SpaceExplorer,
NDOF_SpacePilotPro,
NDOF_SpaceMousePro,
NDOF_SpaceMouseWireless,
NDOF_SpaceMouseProWireless,
NDOF_SpaceMouseEnterprise,
NDOF_KeyboardPro,
NDOF_NumpadPro,
/* Older devices. */
NDOF_SpacePilot,
NDOF_Spaceball5000,
NDOF_SpaceTraveler
};
/**
* The dummy time delta to use for starting events.
* 1/80th of a second (12.5ms).
*/
#define NDOF_TIME_DELTA_STARTING 0.0125f
using NDOF_Button_Array = std::array<GHOST_NDOF_ButtonT, 6>;
enum NDOF_Button_Type { ShortButton, LongButton };
class GHOST_NDOFManager {
public:
GHOST_NDOFManager(GHOST_System &);
virtual ~GHOST_NDOFManager() = default;
/**
* Whether multi-axis functionality is available (via the OS or driver)
* does not imply that a device is plugged in or being used.
*/
virtual bool available() = 0;
/**
* Each platform's device detection should call this
* use standard USB/HID identifiers.
*/
bool setDevice(unsigned short vendor_id, unsigned short product_id);
/**
* Filter out small/accidental/un-calibrated motions by
* setting up a "dead zone" around home position
* set to 0 to disable
* 0.1 is a safe and reasonable value.
*/
void setDeadZone(float);
/**
* The latest raw axis data from the device.
*
* \note axis data should be in blender view coordinates
* - +X is to the right.
* - +Y is up.
* - +Z is out of the screen.
* - for rotations, look from origin to each +axis.
* - rotations are + when CCW, - when CW.
* Each platform is responsible for getting axis data into this form
* these values should not be scaled (just shuffled or flipped).
*/
void updateTranslation(const int t[3], uint64_t time);
void updateRotation(const int r[3], uint64_t time);
/**
* The latest raw button data from the device
* use HID button encoding (not #NDOF_ButtonT).
*/
void updateButtonRAW(int button_number, bool press, uint64_t time);
/**
* Add the button event which has already been mapped to #GHOST_NDOF_ButtonT.
*/
void updateButton(GHOST_NDOF_ButtonT button, bool press, uint64_t time);
void updateButtonsBitmask(int button_bits, uint64_t time);
void updateButtonsArray(NDOF_Button_Array buttons, uint64_t time, NDOF_Button_Type type);
/* #NDOFButton events are sent immediately */
/**
* Processes and sends most recent raw data as an #NDOFMotion event
* returns whether an event was sent.
*/
bool sendMotionEvent();
protected:
GHOST_System &system_;
private:
void sendButtonEvent(GHOST_NDOF_ButtonT, bool press, uint64_t time, GHOST_IWindow *);
void sendKeyEvent(GHOST_TKey, bool press, uint64_t time, GHOST_IWindow *);
NDOF_DeviceT device_type_;
int hid_map_button_num_;
int hid_map_button_mask_;
const GHOST_NDOF_ButtonT *hid_map_;
int translation_[3];
int rotation_[3];
int button_depressed_; /* Bit field. */
NDOF_Button_Array pressed_buttons_cache_;
NDOF_Button_Array pressed_long_buttons_cache_;
uint64_t motion_time_; /* In milliseconds. */
uint64_t motion_time_prev_; /* Time of most recent motion event sent. */
GHOST_TProgress motion_state_;
bool motion_event_pending_;
float motion_dead_zone_; /* Discard motion with each component < this. */
inline static std::array<NDOF_DeviceT, 9> bitmask_devices_ = {
NDOF_SpaceNavigator,
NDOF_SpaceExplorer,
NDOF_SpacePilotPro,
NDOF_SpaceMousePro,
NDOF_SpaceMouseWireless,
NDOF_SpaceMouseProWireless,
NDOF_SpacePilot,
NDOF_Spaceball5000,
NDOF_SpaceTraveler,
};
};

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GHOST_NDOFManager.hh"
// Event capture is handled within the NDOF manager on Macintosh,
// so there's no need for SystemCocoa to look for them.
class GHOST_NDOFManagerCocoa : public GHOST_NDOFManager {
public:
GHOST_NDOFManagerCocoa(GHOST_System &);
~GHOST_NDOFManagerCocoa() override;
bool available() override;
};

View File

@@ -0,0 +1,281 @@
/* SPDX-FileCopyrightText: 2010-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GHOST_NDOFManagerCocoa.hh"
#include "GHOST_SystemCocoa.hh"
#import <Cocoa/Cocoa.h>
#include <cstdint>
#include <dlfcn.h>
#define DEBUG_NDOF_DRIVER false
#if DEBUG_NDOF_DRIVER
# include <cstdio>
#endif
/* Static callback functions need to talk to these objects: */
static GHOST_SystemCocoa *ghost_system = nullptr;
static GHOST_NDOFManager *ndof_manager = nullptr;
static uint16_t clientID = 0;
static bool driver_loaded = false;
/* 3DxMacCore version >= minimal_version is considered "new".
* It was firstly introduced in 3DxWare v10.8.4 r3716 and can process
* (not yet documented in SDK manual) kConnexionCmdAppEvent events. */
static NSString *new_driver_minimal_version = @"1.3.4.473";
/* Replicate just enough of the 3Dx API for our uses, not everything the driver provides. */
#define kConnexionClientModeTakeOver 1
#define kConnexionMaskAll 0x3fff
#define kConnexionMaskAxis 0x3f00
#define kConnexionMaskNoButtons 0x0
#define kConnexionMaskAllButtons 0xffffffff
#define kConnexionCmdHandleButtons 2
#define kConnexionCmdHandleAxis 3
#define kConnexionCmdAppSpecific 10
#define kConnexionCmdAppEvent 11
#define kConnexionMsgDeviceState '3dSR'
#define kConnexionCtlGetDeviceID '3did'
#pragma pack(push, 2) /* Just this struct. */
struct ConnexionDeviceState {
uint16_t version;
uint16_t client;
uint16_t command;
int16_t param;
int32_t value;
uint64_t time;
uint8_t report[8];
uint16_t appEventPressed;
int16_t axis[6]; /* TX, TY, TZ, RX, RY, RZ. */
uint16_t address;
uint32_t buttons;
};
#pragma pack(pop)
/* Callback functions: */
typedef void (*AddedHandler)(uint32_t);
typedef void (*RemovedHandler)(uint32_t);
typedef void (*MessageHandler)(uint32_t, uint32_t msg_type, void *msg_arg);
/* Driver functions: */
typedef int16_t (*SetConnexionHandlers_ptr)(MessageHandler, AddedHandler, RemovedHandler, bool);
typedef void (*CleanupConnexionHandlers_ptr)();
typedef uint16_t (*RegisterConnexionClient_ptr)(uint32_t signature,
const char *name,
uint16_t mode,
uint32_t mask);
typedef void (*SetConnexionClientButtonMask_ptr)(uint16_t clientID, uint32_t buttonMask);
typedef void (*UnregisterConnexionClient_ptr)(uint16_t clientID);
typedef int16_t (*ConnexionClientControl_ptr)(uint16_t clientID,
uint32_t message,
int32_t param,
int32_t *result);
#define DECLARE_FUNC(name) name##_ptr name = nullptr
DECLARE_FUNC(SetConnexionHandlers);
DECLARE_FUNC(CleanupConnexionHandlers);
DECLARE_FUNC(RegisterConnexionClient);
DECLARE_FUNC(SetConnexionClientButtonMask);
DECLARE_FUNC(UnregisterConnexionClient);
DECLARE_FUNC(ConnexionClientControl);
static void *load_func(void *module, const char *func_name)
{
void *func = dlsym(module, func_name);
#if DEBUG_NDOF_DRIVER
if (func) {
printf("'%s' loaded :D\n", func_name);
}
else {
printf("<!> %s\n", dlerror());
}
#endif
return func;
}
#define LOAD_FUNC(name) name = (name##_ptr)load_func(module, #name)
static void *module; /* Handle to the whole driver. */
static bool load_driver_functions()
{
if (driver_loaded) {
return true;
}
module = dlopen("/Library/Frameworks/3DconnexionClient.framework/3DconnexionClient",
RTLD_LAZY | RTLD_LOCAL);
if (module) {
LOAD_FUNC(SetConnexionHandlers);
if (SetConnexionHandlers != nullptr) {
driver_loaded = true;
}
if (driver_loaded) {
LOAD_FUNC(CleanupConnexionHandlers);
LOAD_FUNC(RegisterConnexionClient);
LOAD_FUNC(SetConnexionClientButtonMask);
LOAD_FUNC(UnregisterConnexionClient);
LOAD_FUNC(ConnexionClientControl);
}
}
#if DEBUG_NDOF_DRIVER
else {
printf("<!> %s\n", dlerror());
}
printf("loaded: %s\n", driver_loaded ? "YES" : "NO");
#endif
return driver_loaded;
}
static void unload_driver()
{
dlclose(module);
}
static void DeviceAdded(uint32_t /*unused*/)
{
#if DEBUG_NDOF_DRIVER
printf("ndof: device added\n");
#endif
/* Determine exactly which device is plugged in. */
int32_t result;
ConnexionClientControl(clientID, kConnexionCtlGetDeviceID, 0, &result);
const int16_t vendorID = result >> 16;
const int16_t productID = result & 0xffff;
ndof_manager->setDevice(vendorID, productID);
}
static void DeviceRemoved(uint32_t /*unused*/)
{
#if DEBUG_NDOF_DRIVER
printf("ndof: device removed\n");
#endif
}
static void DeviceEvent(uint32_t /*unused*/, uint32_t msg_type, void *msg_arg)
{
if (msg_type == kConnexionMsgDeviceState) {
ConnexionDeviceState *s = (ConnexionDeviceState *)msg_arg;
/* Device state is broadcast to all clients; only react if sent to us. */
if (s->client == clientID) {
/* TODO: is s->time compatible with GHOST timestamps? if so use that instead. */
const uint64_t now = ghost_system->getMilliSeconds();
switch (s->command) {
case kConnexionCmdHandleAxis: {
/* convert to blender view coordinates. */
const int t[3] = {s->axis[0], -(s->axis[2]), s->axis[1]};
const int r[3] = {-(s->axis[3]), s->axis[5], -(s->axis[4])};
ndof_manager->updateTranslation(t, now);
ndof_manager->updateRotation(r, now);
ghost_system->notifyExternalEventProcessed();
break;
}
case kConnexionCmdHandleButtons: {
const int button_bits = s->buttons;
#ifdef DEBUG_NDOF_BUTTONS
printf("button bits: 0x%08x\n", button_bits);
#endif
ndof_manager->updateButtonsBitmask(button_bits, now);
ghost_system->notifyExternalEventProcessed();
break;
}
case kConnexionCmdAppEvent: {
const int button_number = s->value;
const bool pressed = s->appEventPressed;
#ifdef DEBUG_NDOF_BUTTONS
printf("button number: %d, pressed: %d\n", button_number, pressed);
#endif
ndof_manager->updateButton(GHOST_NDOF_ButtonT(button_number), pressed, now);
ghost_system->notifyExternalEventProcessed();
break;
}
#if DEBUG_NDOF_DRIVER
case kConnexionCmdAppSpecific:
printf("ndof: app-specific command, param = %hd, value = %d\n", s->param, s->value);
break;
default:
printf("ndof: mystery device command %d\n", s->command);
#endif
}
}
}
}
GHOST_NDOFManagerCocoa::GHOST_NDOFManagerCocoa(GHOST_System &sys) : GHOST_NDOFManager(sys)
{
if (load_driver_functions()) {
/* Give static functions something to talk to: */
ghost_system = dynamic_cast<GHOST_SystemCocoa *>(&sys);
ndof_manager = this;
const uint16_t error = SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, true);
if (error) {
#if DEBUG_NDOF_DRIVER
printf("ndof: error %d while setting up handlers\n", error);
#endif
return;
}
const NSDictionary *dictInfos =
[NSBundle bundleWithPath:@"/Library/Frameworks/3DconnexionClient.framework"]
.infoDictionary;
NSString *strVersion = [dictInfos objectForKey:(NSString *)kCFBundleVersionKey];
const auto compare = [strVersion compare:new_driver_minimal_version];
const bool has_new_driver = compare != NSOrderedAscending;
/* New driver makes use of kConnexionCmdAppEvent events, which require to have all buttons
* unmasked. Basically, this means that driver consumes all NDOF device input and then sends
* appropriate app events based on its configuration instead of forwarding raw data to the
* application. When using an older driver, the old solution with all buttons forwarded
* (masked) is preferred. */
const uint32_t client_mask = has_new_driver ? kConnexionMaskAxis : kConnexionMaskAll;
const uint32_t button_mask = has_new_driver ? kConnexionMaskNoButtons :
kConnexionMaskAllButtons;
/* Pascal string *and* a four-letter constant. How old-school. */
clientID = RegisterConnexionClient(
'blnd', "\007blender", kConnexionClientModeTakeOver, client_mask);
SetConnexionClientButtonMask(clientID, button_mask);
}
}
GHOST_NDOFManagerCocoa::~GHOST_NDOFManagerCocoa()
{
if (driver_loaded) {
UnregisterConnexionClient(clientID);
CleanupConnexionHandlers();
unload_driver();
ghost_system = nullptr;
ndof_manager = nullptr;
}
}
bool GHOST_NDOFManagerCocoa::available()
{
return driver_loaded;
}

View File

@@ -0,0 +1,161 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GHOST_NDOFManagerUnix.hh"
#include "GHOST_System.hh"
/* Logging, use `ghost.ndof.unix.*` prefix. */
#include "CLG_log.h"
#include <cstdio>
#include <cstdlib> /* For `getenv`. */
#include <spnav.h>
#include <unistd.h>
static const char *spnav_sock_path_default = "/var/run/spnav.sock";
static const char *spnav_sock_path_get()
{
/* The `SPNAV_SOCKET` environment variable convention
* comes from LIBSPNAV 1.2, see: `src/spnav.c`. */
const char *env_path = getenv("SPNAV_SOCKET");
return env_path ? env_path : spnav_sock_path_default;
}
static CLG_LogRef LOG = {"ghost.ndof"};
GHOST_NDOFManagerUnix::GHOST_NDOFManagerUnix(GHOST_System &sys)
: GHOST_NDOFManager(sys), available_(false)
{
const char *spnav_sock_path = spnav_sock_path_get();
if (access(spnav_sock_path, F_OK) != 0) {
CLOG_DEBUG(&LOG, "'spacenavd' not found at \"%s\"", spnav_sock_path);
}
else if (spnav_open() != -1) {
CLOG_DEBUG(&LOG, "'spacenavd' found at\"%s\"", spnav_sock_path);
available_ = true;
/* Only used for logging as of LIBSPNAV 1.2. */
spnav_client_name("blender");
/* determine exactly which device (if any) is plugged in */
#define MAX_LINE_LENGTH 100
/* look for USB devices with Logitech or 3Dconnexion's vendor ID */
FILE *command_output = popen("lsusb | grep '046d:\\|256f:'", "r");
if (command_output) {
char line[MAX_LINE_LENGTH] = {0};
while (fgets(line, MAX_LINE_LENGTH, command_output)) {
ushort vendor_id = 0, product_id = 0;
if (sscanf(line, "Bus %*d Device %*d: ID %hx:%hx", &vendor_id, &product_id) == 2) {
if (setDevice(vendor_id, product_id)) {
break; /* stop looking once the first 3D mouse is found */
}
}
}
pclose(command_output);
}
}
}
GHOST_NDOFManagerUnix::~GHOST_NDOFManagerUnix()
{
if (available_) {
spnav_close();
}
}
bool GHOST_NDOFManagerUnix::available()
{
return available_;
}
/**
* Workaround for a problem where we don't enter the #GHOST_kFinished state,
* this causes any proceeding event to have a very high `dt` (time delta),
* many seconds for eg, causing the view to jump.
*
* this workaround expects continuous events, if we miss a motion event,
* immediately send a dummy event with no motion to ensure the finished state is reached.
*/
#define USE_FINISH_GLITCH_WORKAROUND
/* TODO: make this available on all platforms */
#ifdef USE_FINISH_GLITCH_WORKAROUND
static bool motion_test_prev = false;
static uint64_t motion_test_prev_time = 0;
/* NOTE(ideasman42): It's important not to generate zero-motion event immediately
* because the SPNAV API may not have had a chance to generate events since the last
* call to this function.
* In my tests values between 50-100ms work well, opt for the larger value
* to avoid false positives:
* - Releasing the device can cause subtle wobble generating start/finish events.
* - Subtle motion can *sometimes* cause start/finish events too.
*
* Furthermore, waiting a 10/th of a second for the "finish" event is fast enough
* that any motion occurring in a shorter time-span can be considered "continuous". */
# define MOTION_TEST_IDLE_MS 100
#endif /* USE_FINISH_GLITCH_WORKAROUND */
bool GHOST_NDOFManagerUnix::processEvents()
{
bool anyProcessed = false;
if (available_) {
spnav_event e;
#ifdef USE_FINISH_GLITCH_WORKAROUND
bool motion_test = false;
#endif
while (spnav_poll_event(&e)) {
switch (e.type) {
case SPNAV_EVENT_MOTION: {
/* convert to blender view coords */
uint64_t now = system_.getMilliSeconds();
const int t[3] = {int(e.motion.x), int(e.motion.y), int(-e.motion.z)};
const int r[3] = {int(-e.motion.rx), int(-e.motion.ry), int(e.motion.rz)};
updateTranslation(t, now);
updateRotation(r, now);
#ifdef USE_FINISH_GLITCH_WORKAROUND
motion_test = true;
motion_test_prev_time = now;
#endif
break;
}
case SPNAV_EVENT_BUTTON:
uint64_t now = system_.getMilliSeconds();
updateButtonRAW(e.button.bnum, e.button.press, now);
break;
}
anyProcessed = true;
}
#ifdef USE_FINISH_GLITCH_WORKAROUND
if (motion_test_prev == true && motion_test == false) {
const uint64_t now = system_.getMilliSeconds();
GHOST_ASSERT(motion_test_prev_time <= now, "Invalid time offset");
if ((now - motion_test_prev_time) < MOTION_TEST_IDLE_MS) {
/* Re-run this check next time `processEvents` is called. */
motion_test = true;
}
else {
const int v[3] = {0, 0, 0};
updateTranslation(v, now);
updateRotation(v, now);
anyProcessed = true;
}
}
motion_test_prev = motion_test;
#endif /* USE_FINISH_GLITCH_WORKAROUND */
}
return anyProcessed;
}
#undef USE_FINISH_GLITCH_WORKAROUND

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GHOST_NDOFManager.hh"
/**
* Event capture is handled within the NDOF manager on Linux,
* so there's no need for SystemX11 to look for them.
*/
class GHOST_NDOFManagerUnix : public GHOST_NDOFManager {
public:
GHOST_NDOFManagerUnix(GHOST_System &);
~GHOST_NDOFManagerUnix() override;
bool available() override;
bool processEvents();
private:
bool available_;
};

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GHOST_NDOFManagerWin32.hh"
GHOST_NDOFManagerWin32::GHOST_NDOFManagerWin32(GHOST_System &sys) : GHOST_NDOFManager(sys)
{
/* pass */
}
/* Whether multi-axis functionality is available (via the OS or driver)
* does not imply that a device is plugged in or being used. */
bool GHOST_NDOFManagerWin32::available()
{
/* Always available since RawInput is built into Windows. */
return true;
}

View File

@@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GHOST_NDOFManager.hh"
class GHOST_NDOFManagerWin32 : public GHOST_NDOFManager {
public:
GHOST_NDOFManagerWin32(GHOST_System &);
bool available() override;
};

View File

@@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2010 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include <cassert>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "GHOST_Debug.hh"
#include "GHOST_PathUtils.hh"
#include "GHOST_Types.hh"
/* Based on: https://stackoverflow.com/a/2766963/432509 */
enum DecodeState_e {
/** Searching for an ampersand to convert. */
STATE_SEARCH = 0,
/** Convert the two proceeding characters from hex. */
STATE_CONVERTING
};
void GHOST_URL_decode(char *buf_dst, int buf_dst_size, const char *buf_src, const int buf_src_len)
{
GHOST_ASSERT(strnlen(buf_src, buf_src_len) == buf_src_len, "Incorrect length");
DecodeState_e state = STATE_SEARCH;
uint ascii_character;
memset(buf_dst, 0, buf_dst_size);
for (uint i = 0; i < buf_src_len; i++) {
switch (state) {
case STATE_SEARCH: {
if (buf_src[i] != '%') {
strncat(buf_dst, &buf_src[i], 1);
assert(int(strlen(buf_dst)) < buf_dst_size);
break;
}
/* We are now converting. */
state = STATE_CONVERTING;
break;
}
case STATE_CONVERTING: {
/* Create a buffer to hold the hex. For example, if `%20`,
* this buffer would hold 20 (in ASCII). */
char temp_num_buf[3];
/* Conversion complete (i.e. don't convert again next iteration). */
state = STATE_SEARCH;
/* Ensure both characters are hexadecimal. */
bool both_digits = true;
for (int j = 0; j < 2; j++) {
/* `isxdigit` serves to early null terminate the string too. */
const char hex_char = buf_src[i + j];
if (!isxdigit(hex_char)) {
both_digits = false;
break;
}
temp_num_buf[j] = hex_char;
}
if (!both_digits) {
break;
}
/* Convert two hexadecimal characters into one character. */
temp_num_buf[2] = '\0';
sscanf(temp_num_buf, "%x", &ascii_character);
/* Ensure we aren't going to overflow. */
assert(int(strlen(buf_dst)) < buf_dst_size);
/* Concatenate this character onto the output. */
strncat(buf_dst, (char *)&ascii_character, 1);
/* Skip the next character. */
i++;
break;
}
}
}
}
char *GHOST_URL_decode_alloc(const char *buf_src, const int buf_src_len)
{
/* Assume one character of encoded URL can be expanded to 4 chars max. */
const size_t decoded_size_max = 4 * buf_src_len + 1;
char *buf_dst = (char *)malloc(decoded_size_max);
GHOST_URL_decode(buf_dst, decoded_size_max, buf_src, buf_src_len);
const size_t decoded_size = strlen(buf_dst) + 1;
if (decoded_size != decoded_size_max) {
char *buf_dst_trim = (char *)malloc(decoded_size);
memcpy(buf_dst_trim, buf_dst, decoded_size);
free(buf_dst);
buf_dst = buf_dst_trim;
}
return buf_dst;
}

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2010 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
/**
* Decode URL (i.e. converts `file:///a%20b/test` to `file:///a b/test`)
*
* \param buf_dst: Buffer for decoded URL.
* \param buf_dst_size: Size of output buffer.
* \param buf_src: Input encoded buffer to be decoded.
* \param buf_src_len: The length of `buf_src` to use.
*/
void GHOST_URL_decode(char *buf_dst, int buf_dst_size, const char *buf_src, int buf_src_len);
/**
* A version of #GHOST_URL_decode that allocates the string & returns it.
*
* \param buf_src: Input encoded buffer to be decoded.
* \return The decoded output buffer.
*/
char *GHOST_URL_decode_alloc(const char *buf_src, int buf_src_len);

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include "GHOST_Rect.hh"
void GHOST_Rect::inset(int32_t i)
{
if (i > 0) {
/* Grow the rectangle. */
l_ -= i;
r_ += i;
t_ -= i;
b_ += i;
}
else if (i < 0) {
/* Shrink the rectangle, check for insets larger than half the size. */
int32_t i2 = i * 2;
if (getWidth() > i2) {
l_ += i;
r_ -= i;
}
else {
l_ = l_ + ((r_ - l_) / 2);
r_ = l_;
}
if (getHeight() > i2) {
t_ += i;
b_ -= i;
}
else {
t_ = t_ + ((b_ - t_) / 2);
b_ = t_;
}
}
}
GHOST_TVisibility GHOST_Rect::getVisibility(GHOST_Rect &r) const
{
bool lt = isInside(r.l_, r.t_);
bool rt = isInside(r.r_, r.t_);
bool lb = isInside(r.l_, r.b_);
bool rb = isInside(r.r_, r.b_);
GHOST_TVisibility v;
if (lt && rt && lb && rb) {
/* All points inside, rectangle is inside this. */
v = GHOST_kFullyVisible;
}
else if (!(lt || rt || lb || rb)) {
/* None of the points inside.
* Check to see whether the rectangle is larger than this one. */
if ((r.l_ < l_) && (r.t_ < t_) && (r.r_ > r_) && (r.b_ > b_)) {
v = GHOST_kPartiallyVisible;
}
else {
v = GHOST_kNotVisible;
}
}
else {
/* Some of the points inside, rectangle is partially inside. */
v = GHOST_kPartiallyVisible;
}
return v;
}
void GHOST_Rect::setCenter(int32_t cx, int32_t cy)
{
int32_t offset = cx - (l_ + (r_ - l_) / 2);
l_ += offset;
r_ += offset;
offset = cy - (t_ + (b_ - t_) / 2);
t_ += offset;
b_ += offset;
}
void GHOST_Rect::setCenter(int32_t cx, int32_t cy, int32_t w, int32_t h)
{
long w_2, h_2;
w_2 = w >> 1;
h_2 = h >> 1;
l_ = cx - w_2;
t_ = cy - h_2;
r_ = l_ + w;
b_ = t_ + h;
}
bool GHOST_Rect::clip(GHOST_Rect &r) const
{
bool clipped = false;
if (r.l_ < l_) {
r.l_ = l_;
clipped = true;
}
if (r.t_ < t_) {
r.t_ = t_;
clipped = true;
}
if (r.r_ > r_) {
r.r_ = r_;
clipped = true;
}
if (r.b_ > b_) {
r.b_ = b_;
clipped = true;
}
return clipped;
}

View File

@@ -0,0 +1,370 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include "GHOST_System.hh"
#include "GHOST_EventManager.hh"
#include "GHOST_TimerManager.hh"
#include "GHOST_TimerTask.hh"
#include "GHOST_WindowManager.hh"
#ifdef WITH_INPUT_NDOF
# include "GHOST_NDOFManager.hh"
#endif
GHOST_System::GHOST_System()
: native_pixel_(false),
window_focus_(true),
auto_focus_(true),
window_csd_params_({nullptr}),
window_csd_layout_({0}),
timer_manager_(nullptr),
window_manager_(nullptr),
event_manager_(nullptr),
#ifdef WITH_INPUT_NDOF
ndof_manager_(nullptr),
#endif
multitouch_gestures_(true),
tablet_api_(GHOST_kTabletAutomatic),
is_debug_enabled_(false)
{
}
GHOST_System::~GHOST_System()
{
exit();
}
GHOST_TSuccess GHOST_System::hasClipboardImage() const
{
return GHOST_kFailure;
}
uint *GHOST_System::getClipboardImage(int * /*r_width*/, int * /*r_height*/) const
{
return nullptr;
}
GHOST_TSuccess GHOST_System::putClipboardImage(uint * /*rgba*/,
int /*width*/,
int /*height*/) const
{
return GHOST_kFailure;
}
void GHOST_System::setWindowCSD(const GHOST_CSD_Params &params)
{
window_csd_params_ = params;
}
const GHOST_CSD_Params &GHOST_System::getWindowCSD() const
{
return window_csd_params_;
}
const GHOST_CSD_Layout &GHOST_System::getWindowCSD_Layout() const
{
return window_csd_layout_;
}
void GHOST_System::setWindowCSD_Layout(const GHOST_CSD_Layout &layout)
{
window_csd_layout_ = layout;
}
void GHOST_System::setIconGenerator(const GHOST_IconGenerator *icon_generator)
{
icon_generator_ = icon_generator;
}
GHOST_ITimerTask *GHOST_System::installTimer(uint64_t delay,
uint64_t interval,
GHOST_TimerProcPtr timer_proc,
GHOST_TUserDataPtr user_data)
{
uint64_t millis = getMilliSeconds();
GHOST_TimerTask *timer = new GHOST_TimerTask(millis + delay, interval, timer_proc, user_data);
if (timer) {
if (timer_manager_->addTimer(timer) == GHOST_kSuccess) {
/* Check to see whether we need to fire the timer right away. */
timer_manager_->fireTimers(millis);
}
else {
delete timer;
timer = nullptr;
}
}
return timer;
}
GHOST_TSuccess GHOST_System::removeTimer(GHOST_ITimerTask *timerTask)
{
GHOST_TSuccess success = GHOST_kFailure;
if (timerTask) {
success = timer_manager_->removeTimer((GHOST_TimerTask *)timerTask);
}
return success;
}
GHOST_TSuccess GHOST_System::disposeWindow(GHOST_IWindow *window)
{
GHOST_TSuccess success;
/*
* Remove all pending events for the window.
*/
if (window_manager_->getWindowFound(window)) {
event_manager_->removeWindowEvents(window);
success = window_manager_->removeWindow(window);
if (success) {
delete window;
}
}
else {
success = GHOST_kFailure;
}
return success;
}
bool GHOST_System::validWindow(GHOST_IWindow *window)
{
return window_manager_->getWindowFound(window);
}
GHOST_IWindow *GHOST_System::getWindowUnderCursor(int32_t x, int32_t y)
{
/* TODO: This solution should follow the order of the activated windows (Z-order).
* It is imperfect but usable in most cases. Ideally each platform should provide
* a custom version of this function that properly considers z-order. */
std::vector<GHOST_IWindow *> windows = window_manager_->getWindows();
std::vector<GHOST_IWindow *>::reverse_iterator iwindow_iter;
/* Search through the windows in reverse order because in most cases
* the window that is on top was created after those that are below it. */
for (iwindow_iter = windows.rbegin(); iwindow_iter != windows.rend(); ++iwindow_iter) {
GHOST_IWindow *win = *iwindow_iter;
if (win->getState() == GHOST_kWindowStateMinimized) {
continue;
}
GHOST_Rect bounds;
win->getClientBounds(bounds);
if (bounds.isInside(x, y)) {
return win;
}
}
return nullptr;
}
void GHOST_System::dispatchEvents()
{
#ifdef WITH_INPUT_NDOF
/* NDOF Motion event is sent only once per dispatch, so do it now: */
if (ndof_manager_) {
ndof_manager_->sendMotionEvent();
}
#endif
if (event_manager_) {
event_manager_->dispatchEvents();
}
timer_manager_->fireTimers(getMilliSeconds());
}
GHOST_TSuccess GHOST_System::addEventConsumer(GHOST_IEventConsumer *consumer)
{
GHOST_TSuccess success;
if (event_manager_) {
success = event_manager_->addConsumer(consumer);
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_TSuccess GHOST_System::removeEventConsumer(GHOST_IEventConsumer *consumer)
{
GHOST_TSuccess success;
if (event_manager_) {
success = event_manager_->removeConsumer(consumer);
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_TSuccess GHOST_System::pushEvent(std::unique_ptr<const GHOST_IEvent> event)
{
GHOST_TSuccess success;
if (event_manager_) {
success = event_manager_->pushEvent(std::move(event));
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_TSuccess GHOST_System::getCursorPositionClientRelative(const GHOST_IWindow *window,
int32_t &x,
int32_t &y) const
{
/* Sub-classes that can implement this directly should do so. */
int32_t screen_x, screen_y;
GHOST_TSuccess success = getCursorPosition(screen_x, screen_y);
if (success == GHOST_kSuccess) {
window->screenToClient(screen_x, screen_y, x, y);
}
return success;
}
GHOST_TSuccess GHOST_System::setCursorPositionClientRelative(GHOST_IWindow *window,
int32_t x,
int32_t y)
{
/* Sub-classes that can implement this directly should do so. */
int32_t screen_x, screen_y;
window->clientToScreen(x, y, screen_x, screen_y);
return setCursorPosition(screen_x, screen_y);
}
uint32_t GHOST_System::getCursorPreferredLogicalSize() const
{
return uint32_t(24);
}
GHOST_TSuccess GHOST_System::getModifierKeyState(GHOST_TModifierKey mask, bool &is_down) const
{
is_down = false;
GHOST_ModifierKeys keys;
/* Get the state of all modifier keys. */
GHOST_TSuccess success = getModifierKeys(keys);
if (success) {
/* Isolate the state of the key requested. */
is_down = keys.get(mask);
}
return success;
}
GHOST_TSuccess GHOST_System::getButtonState(GHOST_TButton mask, bool &is_down) const
{
is_down = false;
GHOST_Buttons buttons;
/* Get the state of all mouse buttons. */
GHOST_TSuccess success = getButtons(buttons);
if (success) {
/* Isolate the state of the mouse button requested. */
is_down = buttons.get(mask);
}
return success;
}
void GHOST_System::setMultitouchGestures(const bool use)
{
multitouch_gestures_ = use;
}
void GHOST_System::setTabletAPI(GHOST_TTabletAPI api)
{
tablet_api_ = api;
}
GHOST_TTabletAPI GHOST_System::getTabletAPI()
{
return tablet_api_;
}
GHOST_TSuccess GHOST_System::getPixelAtCursor(float /*r_color*/[3]) const
{
return GHOST_kFailure;
}
#ifdef WITH_INPUT_NDOF
void GHOST_System::setNDOFDeadZone(float deadzone)
{
if (this->ndof_manager_) {
this->ndof_manager_->setDeadZone(deadzone);
}
}
#endif
GHOST_TSuccess GHOST_System::init()
{
timer_manager_ = new GHOST_TimerManager();
window_manager_ = new GHOST_WindowManager();
event_manager_ = new GHOST_EventManager();
#ifdef WITH_GHOST_DEBUG
if (event_manager_) {
event_printer_ = new GHOST_EventPrinter();
event_manager_->addConsumer(event_printer_);
}
#endif /* WITH_GHOST_DEBUG */
if (timer_manager_ && window_manager_ && event_manager_) {
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_System::exit()
{
/** WARNING: exit() may run more than once, since it may need to be called from a derived class
* destructor. Take it into account when modifying this function. */
delete window_manager_;
window_manager_ = nullptr;
delete timer_manager_;
timer_manager_ = nullptr;
delete event_manager_;
event_manager_ = nullptr;
#ifdef WITH_INPUT_NDOF
delete ndof_manager_;
ndof_manager_ = nullptr;
#endif
return GHOST_kSuccess;
}
bool GHOST_System::useNativePixel()
{
native_pixel_ = true;
return true;
}
void GHOST_System::useWindowFocus(const bool use_focus)
{
window_focus_ = use_focus;
}
void GHOST_System::setAutoFocus(const bool auto_focus)
{
auto_focus_ = auto_focus;
}
void GHOST_System::initDebug(GHOST_Debug debug)
{
is_debug_enabled_ = debug.flags & GHOST_kDebugDefault;
}
bool GHOST_System::isDebugEnabled()
{
return is_debug_enabled_;
}

View File

@@ -0,0 +1,326 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_System class.
*/
#pragma once
#include "GHOST_ISystem.hh"
#include "GHOST_Buttons.hh"
#include "GHOST_Debug.hh"
#include "GHOST_EventManager.hh"
#include "GHOST_ModifierKeys.hh"
#ifdef WITH_GHOST_DEBUG
# include "GHOST_EventPrinter.hh"
#endif // WITH_GHOST_DEBUG
class GHOST_Event;
class GHOST_TimerManager;
class GHOST_Window;
class GHOST_WindowManager;
#ifdef WITH_INPUT_NDOF
class GHOST_NDOFManager;
#endif
/**
* Implementation of platform independent functionality of the GHOST_ISystem
* interface.
* GHOST_System is an abstract class because not all methods of GHOST_ISystem
* are implemented.
* \see GHOST_ISystem.
*/
class GHOST_System : public GHOST_ISystem {
protected:
/**
* Constructor.
* Protected default constructor to force use of static createSystem member.
*/
GHOST_System();
/**
* Destructor.
* Protected default constructor to force use of static dispose member.
*/
~GHOST_System() override;
public:
/***************************************************************************************
* Time(r) functionality
***************************************************************************************/
/** \copydoc #GHOST_ISystem::installTimer */
GHOST_ITimerTask *installTimer(uint64_t delay,
uint64_t interval,
GHOST_TimerProcPtr timer_proc,
GHOST_TUserDataPtr user_data = nullptr) override;
/** \copydoc #GHOST_ISystem::removeTimer */
GHOST_TSuccess removeTimer(GHOST_ITimerTask *timerTask) override;
/***************************************************************************************
* Display/window management functionality
***************************************************************************************/
/** \copydoc #GHOST_ISystem::disposeWindow */
GHOST_TSuccess disposeWindow(GHOST_IWindow *window) override;
/** \copydoc #GHOST_ISystem::createOffscreenContext */
GHOST_IContext *createOffscreenContext(GHOST_GPUSettings gpu_settings) override = 0;
/** \copydoc #GHOST_ISystem::validWindow */
bool validWindow(GHOST_IWindow *window) override;
/** \copydoc #GHOST_ISystem::useNativePixel */
bool useNativePixel() override;
bool native_pixel_;
/** \copydoc #GHOST_ISystem::useWindowFocus */
void useWindowFocus(const bool use_focus) override;
bool window_focus_;
/** \copydoc #GHOST_ISystem::setAutoFocus */
void setAutoFocus(const bool auto_focus) override;
bool auto_focus_;
/** \copydoc #GHOST_ISystem::getWindowUnderCursor */
GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y) override;
GHOST_CSD_Params window_csd_params_;
GHOST_CSD_Layout window_csd_layout_;
/***************************************************************************************
* Event management functionality
***************************************************************************************/
/**
* Inherited from GHOST_ISystem but left pure virtual
*
* <pre>
* virtual bool processEvents(bool waitForEvent) = 0;
* </pre>
*/
/** \copydoc #GHOST_ISystem::dispatchEvents */
void dispatchEvents() override;
/** \copydoc #GHOST_ISystem::addEventConsumer */
GHOST_TSuccess addEventConsumer(GHOST_IEventConsumer *consumer) override;
/** \copydoc #GHOST_ISystem::removeEventConsumer */
GHOST_TSuccess removeEventConsumer(GHOST_IEventConsumer *consumer) override;
/***************************************************************************************
* Cursor management functionality
***************************************************************************************/
/* Client relative functions use a default implementation
* that converts from screen-coordinates to client coordinates.
* Implementations may override. */
/** \copydoc #GHOST_ISystem::getCursorPositionClientRelative */
GHOST_TSuccess getCursorPositionClientRelative(const GHOST_IWindow *window,
int32_t &x,
int32_t &y) const override;
/** \copydoc #GHOST_ISystem::setCursorPositionClientRelative */
GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window,
int32_t x,
int32_t y) override;
/** \copydoc #GHOST_ISystem::getCursorPreferredLogicalSize */
uint32_t getCursorPreferredLogicalSize() const override;
/**
* Inherited from GHOST_ISystem but left pure virtual
* <pre>
* GHOST_TSuccess getCursorPosition(int32_t& x, int32_t& y) const = 0;
* GHOST_TSuccess setCursorPosition(int32_t x, int32_t y)
* </pre>
*/
/***************************************************************************************
* Access to mouse button and keyboard states.
***************************************************************************************/
/** \copydoc #GHOST_ISystem::getModifierKeyState */
GHOST_TSuccess getModifierKeyState(GHOST_TModifierKey mask, bool &is_down) const override;
/** \copydoc #GHOST_ISystem::getButtonState */
GHOST_TSuccess getButtonState(GHOST_TButton mask, bool &is_down) const override;
/** \copydoc #GHOST_ISystem::setMultitouchGestures */
void setMultitouchGestures(const bool use) override;
/** \copydoc #GHOST_ISystem::setTabletAPI */
void setTabletAPI(GHOST_TTabletAPI api) override;
GHOST_TTabletAPI getTabletAPI();
/** \copydoc #GHOST_ISystem::getPixelAtCursor */
GHOST_TSuccess getPixelAtCursor(float r_color[3]) const override;
#ifdef WITH_INPUT_NDOF
/***************************************************************************************
* Access to 3D mouse.
***************************************************************************************/
/** \copydoc #GHOST_ISystem::setNDOFDeadZone */
void setNDOFDeadZone(float deadzone) override;
#endif
/***************************************************************************************
* Access to the Clipboard
***************************************************************************************/
/** \copydoc #GHOST_ISystem::getClipboard */
char *getClipboard(bool selection) const override = 0;
/** \copydoc #GHOST_ISystem::putClipboard */
void putClipboard(const char *buffer, bool selection) const override = 0;
/** \copydoc #GHOST_ISystem::hasClipboardImage */
GHOST_TSuccess hasClipboardImage() const override;
/** \copydoc #GHOST_ISystem::getClipboardImage */
uint *getClipboardImage(int *r_width, int *r_height) const override;
/** \copydoc #GHOST_ISystem::putClipboardImage */
GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const override;
/***************************************************************************************
* Window "Client Side Decorations" (CSD)
***************************************************************************************/
void setWindowCSD(const GHOST_CSD_Params &params) override;
const GHOST_CSD_Params &getWindowCSD() const;
void setWindowCSD_Layout(const GHOST_CSD_Layout &csd_layout);
const GHOST_CSD_Layout &getWindowCSD_Layout() const override;
/***************************************************************************************
* Window Icon Generator.
***************************************************************************************/
/** \copydoc #GHOST_ISystem::setIconGenerator */
void setIconGenerator(const GHOST_IconGenerator *icon_generator) override;
const GHOST_IconGenerator *icon_generator_ = nullptr;
/** \copydoc #GHOST_ISystem::showMessageBox */
GHOST_TSuccess showMessageBox(const char * /*title*/,
const char * /*message*/,
const char * /*help_label*/,
const char * /*continue_label*/,
const char * /*link*/,
GHOST_DialogOptions /*dialog_options*/) const override
{
return GHOST_kFailure;
};
/***************************************************************************************
* Other (internal) functionality.
***************************************************************************************/
/**
* Pushes an event on the stack.
* To dispatch it, call dispatchEvent() or dispatchEvents().
* Do not delete the event!
* \param event: The event to push on the stack.
*/
GHOST_TSuccess pushEvent(std::unique_ptr<const GHOST_IEvent> event);
/**
* \return The timer manager.
*/
inline GHOST_TimerManager *getTimerManager() const;
/**
* \return A pointer to our event manager.
*/
inline GHOST_EventManager *getEventManager() const;
/**
* \return A pointer to our window manager.
*/
inline GHOST_WindowManager *getWindowManager() const;
#ifdef WITH_INPUT_NDOF
/**
* \return A pointer to our n-degree of freedom manager.
*/
inline GHOST_NDOFManager *getNDOFManager() const;
#endif
/**
* Returns the state of all modifier keys.
* \param keys: The state of all modifier keys (true == pressed).
* \return Indication of success.
*/
virtual GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const = 0;
/**
* Returns the state of the mouse buttons (outside the message queue).
* \param buttons: The state of the buttons.
* \return Indication of success.
*/
virtual GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const = 0;
/***************************************************************************************
* Debugging
***************************************************************************************/
/** \copydoc #GHOST_ISystem::initDebug */
void initDebug(GHOST_Debug debug) override;
/** \copydoc #GHOST_ISystem::isDebugEnabled */
bool isDebugEnabled() override;
protected:
/** \copydoc #GHOST_ISystem::init */
GHOST_TSuccess init() override;
/** \copydoc #GHOST_ISystem::exit */
GHOST_TSuccess exit() override;
/** The timer manager. */
GHOST_TimerManager *timer_manager_;
/** The window manager. */
GHOST_WindowManager *window_manager_;
/** The event manager. */
GHOST_EventManager *event_manager_;
#ifdef WITH_INPUT_NDOF
/** The N-degree of freedom device manager */
GHOST_NDOFManager *ndof_manager_;
#endif
#ifdef WITH_GHOST_DEBUG
/** Prints all the events. */
GHOST_EventPrinter *event_printer_;
#endif // WITH_GHOST_DEBUG
/** Use multi-touch gestures. */
bool multitouch_gestures_;
/** Which tablet API to use. */
GHOST_TTabletAPI tablet_api_;
bool is_debug_enabled_;
};
inline GHOST_TimerManager *GHOST_System::getTimerManager() const
{
return timer_manager_;
}
inline GHOST_EventManager *GHOST_System::getEventManager() const
{
return event_manager_;
}
inline GHOST_WindowManager *GHOST_System::getWindowManager() const
{
return window_manager_;
}
#ifdef WITH_INPUT_NDOF
inline GHOST_NDOFManager *GHOST_System::getNDOFManager() const
{
return ndof_manager_;
}
#endif

View File

@@ -0,0 +1,363 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_SystemCocoa class.
*/
#pragma once
#ifndef __APPLE__
# error Apple OSX only!
#endif // __APPLE__
// #define __CARBONSOUND__
#include "GHOST_System.hh"
class GHOST_EventCursor;
class GHOST_EventKey;
class GHOST_EventWindow;
class GHOST_WindowCocoa;
class GHOST_SystemCocoa : public GHOST_System {
public:
/**
* Constructor.
*/
GHOST_SystemCocoa();
/**
* Destructor.
*/
~GHOST_SystemCocoa() override;
/***************************************************************************************
* Time(r) functionality.
***************************************************************************************/
/**
* Returns the system time.
* Returns the number of milliseconds since the start of the system process.
* Based on ANSI clock() routine.
* \return The number of milliseconds.
*/
uint64_t getMilliSeconds() const override;
/***************************************************************************************
* Display/window management functionality.
***************************************************************************************/
/**
* Returns the number of displays on this system.
* \return The number of displays.
*/
uint8_t getNumDisplays() const override;
/**
* Returns the dimensions of the main display on this system.
* \return The dimension of the main display.
*/
void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override;
/** Returns the combine dimensions of all monitors.
* \return The dimension of the workspace.
*/
void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const override;
/**
* Create a new window.
* The new window is added to the list of windows managed.
* Never explicitly delete the window, use #disposeWindow() instead.
* \param title: The name of the window.
* (displayed in the title bar of the window if the OS supports it).
* \param left: The coordinate of the left edge of the window.
* \param top: The coordinate of the top edge of the window.
* \param width: The width the window.
* \param height: The height the window.
* \param state: The state of the window when opened.
* \param gpu_settings: Misc GPU settings.
* \param exclusive: Use to show the window on top and ignore others (used full-screen).
* \param parent_window: Parent (embedder) window.
* \return The new window (or 0 if creation failed).
*/
GHOST_IWindow *createWindow(const char *title,
int32_t left,
int32_t top,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
const bool exclusive = false,
const bool is_dialog = false,
const GHOST_IWindow *parent_window = nullptr) override;
/**
* Create a new off-screen context.
* Never explicitly delete the context, use #disposeContext() instead.
* \return The new context (or 0 if creation failed).
*/
GHOST_IContext *createOffscreenContext(GHOST_GPUSettings gpu_settings) override;
/**
* Dispose of a context.
* \param context: Pointer to the context to be disposed.
* \return Indication of success.
*/
GHOST_TSuccess disposeContext(GHOST_IContext *context) override;
/**
* Get the Window under the cursor.
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return The window under the cursor or nullptr if none.
*/
GHOST_IWindow *getWindowUnderCursor(int32_t x, int32_t y) override;
/***************************************************************************************
* Event management functionality.
***************************************************************************************/
/**
* Gets events from the system and stores them in the queue.
* \param waitForEvent: Flag to wait for an event (or return immediately).
* \return Indication of the presence of events.
*/
bool processEvents(bool waitForEvent) override;
/**
* Handle User request to quit, from Menu bar Quit, and Command+Q
* Display alert panel if changes performed since last save
*/
void handleQuitRequest();
/**
* Handle Cocoa openFile event
* Display confirmation request panel if changes performed since last save
*/
bool handleOpenDocumentRequest(void *filepathStr);
/**
* Handles a drag & drop destination event. Called by GHOST_WindowCocoa window subclass.
* \param eventType: The type of drag & drop event.
* \param draggedObjectType: The type object concerned.
* (currently array of file names, string, TIFF image).
* \param window: The window on which the event occurred.
* \param mouseX: x mouse coordinate (in cocoa base window coordinates).
* \param mouseY: y mouse coordinate.
* \return Indication whether the event was handled.
*/
GHOST_TSuccess handleDraggingEvent(GHOST_TEventType eventType,
GHOST_TDragnDropTypes draggedObjectType,
GHOST_WindowCocoa *window,
int mouseX,
int mouseY,
void *data);
/***************************************************************************************
* Cursor management functionality.
***************************************************************************************/
/**
* Returns the current location of the cursor (location in screen coordinates)
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override;
/**
* \return the size of the cursor in logical pixels (before Hi-DPI scaling is applied).
*
* \warning On macOS the logical cursor size is *always* applied on top of hardware cursors,
* this should only be used for software-cursor display.
*/
uint32_t getCursorPreferredLogicalSize() const override;
/**
* Updates the location of the cursor (location in screen coordinates).
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override;
/**
* Get the color of the pixel at the current mouse cursor location
* \param r_color: returned sRGB float colors
* \return Success value (true == successful and supported by platform)
*/
GHOST_TSuccess getPixelAtCursor(float r_color[3]) const override;
/***************************************************************************************
* Access to mouse button and keyboard states.
***************************************************************************************/
/**
* Returns the state of all modifier keys.
* \param keys: The state of all modifier keys (true == pressed).
* \return Indication of success.
*/
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override;
/**
* Returns the state of the mouse buttons (outside the message queue).
* \param buttons: The state of the buttons.
* \return Indication of success.
*/
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override;
GHOST_TCapabilityFlag getCapabilities() const override;
/***************************************************************************************
* Clipboard get / set.
***************************************************************************************/
/**
* Returns Clipboard data
* \param selection: Indicate which buffer to return.
* \return Returns the selected buffer
*/
char *getClipboard(bool selection) const override;
/**
* Puts buffer to system clipboard
* \param buffer: The buffer to be copied.
* \param selection: Indicates which buffer to copy too, only used on X11.
*/
void putClipboard(const char *buffer, bool selection) const override;
/**
* Returns GHOST_kSuccess if the clipboard contains an image.
*/
GHOST_TSuccess hasClipboardImage() const override;
/**
* Get image data from the Clipboard
* \param r_width: the returned image width in pixels.
* \param r_height: the returned image height in pixels.
* \return pointer uint array in RGBA byte order. Caller must free.
*/
uint *getClipboardImage(int *r_width, int *r_height) const override;
/**
* Put image data to the Clipboard
* \param rgba: uint array in RGBA byte order.
* \param width: the image width in pixels.
* \param height: the image height in pixels.
*/
GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const override;
/**
* Handles a window event. Called by GHOST_WindowCocoa window delegate
* \param eventType: The type of window event.
* \param window: The window on which the event occurred.
* \return Indication whether the event was handled.
*/
GHOST_TSuccess handleWindowEvent(GHOST_TEventType eventType, GHOST_WindowCocoa *window);
/**
* Handles the Cocoa event telling the application has become active (again)
* \return Indication whether the event was handled.
*/
GHOST_TSuccess handleApplicationBecomeActiveEvent();
/**
* \return True if any dialog window is open.
*/
bool hasDialogWindow();
/**
* External objects should call this when they send an event outside processEvents.
*/
void notifyExternalEventProcessed();
/**
* \see GHOST_ISystem
*/
bool setConsoleWindowState(GHOST_TConsoleWindowState /*action*/) override
{
return false;
}
/**
* Handles a tablet event.
* \param eventPtr: An #NSEvent pointer (cast to void* to enable compilation in standard C++).
* \param eventType: The type of the event.
* It needs to be passed separately as it can be either directly in the event type,
* or as a sub-type if combined with a mouse button event.
* \return Indication whether the event was handled.
*/
GHOST_TSuccess handleTabletEvent(void *eventPtr, short eventType);
bool handleTabletEvent(void *eventPtr);
/**
* Handles a mouse event.
* \param eventPtr: An #NSEvent pointer (cast to `void *` to enable compilation in standard C++).
* \return Indication whether the event was handled.
*/
GHOST_TSuccess handleMouseEvent(void *eventPtr);
/**
* Handles a key event.
* \param eventPtr: An #NSEvent pointer (cast to `void *` to enable compilation in standard C++).
* \return Indication whether the event was handled.
*/
GHOST_TSuccess handleKeyEvent(void *eventPtr);
/**
* Show a system message box
* \param title: The title of the message box.
* \param message: The message to display.
* \param help_label: Help button label.
* \param continue_label: Continue button label.
* \param link: An optional hyperlink.
* \param dialog_options: Options how to display the message.
*/
GHOST_TSuccess showMessageBox(const char *title,
const char *message,
const char *help_label,
const char *continue_label,
const char *link,
GHOST_DialogOptions dialog_options) const override;
protected:
/**
* Initializes the system.
* For now, it just registers the window class (WNDCLASS).
* \return A success value.
*/
GHOST_TSuccess init() override;
/**
* Performs the actual cursor position update (location in screen coordinates).
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
GHOST_TSuccess setMouseCursorPosition(int32_t x, int32_t y);
/** Event has been processed directly by Cocoa (or NDOF manager)
* and has sent a ghost event to be dispatched */
bool outside_loop_event_processed_;
/** Raised window is not yet known by the window manager,
* so delay application become active event handling */
bool need_delayed_application_become_active_event_processing_;
/** State of the modifiers. */
uint32_t modifier_mask_;
/** Ignores window size messages (when window is dragged). */
bool ignore_window_sized_messages_;
/** Temporarily ignore momentum scroll events */
bool ignore_momentum_scroll_;
/** Is the scroll wheel event generated by a multi-touch trackpad or mouse? */
bool multi_touch_scroll_;
/** To prevent multiple warp, we store the time of the last warp event
* and ignore mouse moved events generated before that. */
double last_warp_timestamp_;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,232 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_SystemHeadless class.
*/
#pragma once
#include "../GHOST_Types.hh"
#include "GHOST_System.hh"
#include "GHOST_WindowNULL.hh"
#if defined(WITH_OPENGL_BACKEND) && defined(__linux__)
# include "GHOST_ContextEGL.hh"
#endif
#ifdef WITH_VULKAN_BACKEND
# include "GHOST_ContextVK.hh"
#endif
#include "GHOST_ContextNone.hh"
class GHOST_WindowNULL;
class GHOST_SystemHeadless : public GHOST_System {
public:
GHOST_SystemHeadless() : GHOST_System()
{ /* nop */
}
~GHOST_SystemHeadless() override = default;
bool processEvents(bool /*waitForEvent*/) override
{
return false;
}
bool setConsoleWindowState(GHOST_TConsoleWindowState /*action*/) override
{
return false;
}
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys & /*keys*/) const override
{
return GHOST_kSuccess;
}
GHOST_TSuccess getButtons(GHOST_Buttons & /*buttons*/) const override
{
return GHOST_kSuccess;
}
GHOST_TCapabilityFlag getCapabilities() const override
{
return GHOST_TCapabilityFlag(
GHOST_CAPABILITY_FLAG_ALL &
/* No windowing functionality supported.
* In most cases this value doesn't matter for the headless backend.
*
* Nevertheless, don't advertise support.
*
* NOTE: order the following flags as they they're declared in the source. */
~(
/* Wrap. */
GHOST_kCapabilityWindowPosition |
/* Wrap. */
GHOST_kCapabilityCursorWarp |
/* Wrap. */
GHOST_kCapabilityClipboardPrimary |
/* Wrap. */
GHOST_kCapabilityClipboardImage |
/* Wrap. */
GHOST_kCapabilityDesktopSample |
/* Wrap. */
GHOST_kCapabilityInputIME |
/* Wrap. */
GHOST_kCapabilityWindowDecorationStyles |
/* Wrap. */
GHOST_kCapabilityKeyboardHyperKey |
/* Wrap. */
GHOST_kCapabilityCursorRGBA |
/* Wrap. */
GHOST_kCapabilityCursorGenerator |
/* Wrap. */
GHOST_kCapabilityMultiMonitorPlacement |
/* Wrap. */
GHOST_kCapabilityWindowPath)
);
}
char *getClipboard(bool /*selection*/) const override
{
return nullptr;
}
void putClipboard(const char * /*buffer*/, bool /*selection*/) const override
{ /* nop */
}
uint64_t getMilliSeconds() const override
{
return 0;
}
uint8_t getNumDisplays() const override
{
return uint8_t(1);
}
GHOST_TSuccess getCursorPosition(int32_t & /*x*/, int32_t & /*y*/) const override
{
return GHOST_kFailure;
}
GHOST_TSuccess setCursorPosition(int32_t /*x*/, int32_t /*y*/) override
{
return GHOST_kFailure;
}
void getMainDisplayDimensions(uint32_t & /*width*/, uint32_t & /*height*/) const override
{ /* nop */
}
void getAllDisplayDimensions(uint32_t & /*width*/, uint32_t & /*height*/) const override
{ /* nop */
}
GHOST_IContext *createOffscreenContext(GHOST_GPUSettings gpu_settings) override
{
const GHOST_ContextParams context_params_offscreen =
GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS_OFFSCREEN(gpu_settings);
/* This may not be used depending on the build configuration. */
(void)context_params_offscreen;
switch (gpu_settings.context_type) {
#ifdef WITH_VULKAN_BACKEND
case GHOST_kDrawingContextTypeVulkan: {
# ifdef _WIN32
GHOST_Context *context = new GHOST_ContextVK(
context_params_offscreen, (HWND)0, 1, 2, gpu_settings.preferred_device);
# elif defined(__APPLE__)
GHOST_Context *context = new GHOST_ContextVK(
context_params_offscreen, nullptr, 1, 2, gpu_settings.preferred_device);
# else
GHOST_Context *context = new GHOST_ContextVK(context_params_offscreen,
GHOST_kVulkanPlatformHeadless,
0,
0,
nullptr,
nullptr,
nullptr,
1,
2,
gpu_settings.preferred_device);
# endif
if (context->initializeDrawingContext()) {
return context;
}
delete context;
return nullptr;
}
#endif
#if defined(WITH_OPENGL_BACKEND) && defined(__linux__)
case GHOST_kDrawingContextTypeOpenGL: {
GHOST_Context *context;
for (int minor = 6; minor >= 3; --minor) {
context = new GHOST_ContextEGL((GHOST_System *)this,
context_params_offscreen,
EGLNativeWindowType(0),
EGLNativeDisplayType(EGL_DEFAULT_DISPLAY),
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
4,
minor,
GHOST_OPENGL_EGL_CONTEXT_FLAGS,
GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY,
EGL_OPENGL_API);
if (context->initializeDrawingContext()) {
return context;
}
delete context;
context = nullptr;
}
return context;
}
#endif
default:
/* Unsupported backend. */
return nullptr;
}
return nullptr;
}
GHOST_TSuccess disposeContext(GHOST_IContext *context) override
{
delete context;
return GHOST_kSuccess;
}
GHOST_TSuccess init() override
{
GHOST_TSuccess success = GHOST_System::init();
if (success) {
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_IWindow *createWindow(const char *title,
int32_t left,
int32_t top,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
const bool /*exclusive*/,
const bool /*is_dialog*/,
const GHOST_IWindow *parent_window) override
{
const GHOST_ContextParams context_params = GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS(
gpu_settings);
return new GHOST_WindowNULL(title,
left,
top,
width,
height,
state,
parent_window,
gpu_settings.context_type,
context_params);
}
GHOST_IWindow *getWindowUnderCursor(int32_t /*x*/, int32_t /*y*/) override
{
return nullptr;
}
};

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include "GHOST_ISystemPaths.hh"
class GHOST_SystemPaths : public GHOST_ISystemPaths {
protected:
/**
* Constructor.
* Protected default constructor to force use of static createSystem member.
*/
GHOST_SystemPaths() {}
/**
* Destructor.
* Protected default constructor to force use of static dispose member.
*/
~GHOST_SystemPaths() override = default;
public:
/** \copydoc #GHOST_SystemPaths::getSystemDir */
const char *getSystemDir(int version, const char *versionstr) const override = 0;
/** \copydoc #GHOST_SystemPaths::getUserDir */
const char *getUserDir(int version, const char *versionstr) const override = 0;
/** \copydoc #GHOST_SystemPaths::getBinaryDir */
const char *getBinaryDir() const override = 0;
/** \copydoc #GHOST_SystemPaths::addToSystemRecentFiles */
void addToSystemRecentFiles(const char *filepath) const override = 0;
};

View File

@@ -0,0 +1,62 @@
/* SPDX-FileCopyrightText: 2010 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#ifndef __APPLE__
# error Apple OSX only!
#endif // __APPLE__
#include <optional>
#include <string>
#include "GHOST_SystemPaths.hh"
class GHOST_SystemPathsCocoa : public GHOST_SystemPaths {
public:
/**
* Constructor.
*/
GHOST_SystemPathsCocoa() = default;
/**
* Destructor.
*/
~GHOST_SystemPathsCocoa() override = default;
/**
* Determine the base directory in which shared resources are located. It will first try to use
* "unpack and run" path, then look for properly installed path, including versioning.
* \return Unsigned char string pointing to system directory (eg `/usr/share/blender/`).
*/
const char *getSystemDir(int version, const char *versionstr) const override;
/**
* Determine the base directory in which user configuration is stored, including versioning.
* If needed, it will create the base directory.
* \return Unsigned char string pointing to user directory (eg `~/.blender/`).
*/
const char *getUserDir(int version, const char *versionstr) const override;
/**
* Determine a special ("well known") and easy to reach user directory.
* \return If successfull, a string containing the user directory path (eg `~/Documents/`).
*/
std::optional<std::string> getUserSpecialDir(GHOST_TUserSpecialDirTypes type) const override;
/**
* Determine the directory of the current binary.
* \return Unsigned char string pointing to the binary directory.
*/
const char *getBinaryDir() const override;
/**
* Add the file to the operating system most recently used files
*/
void addToSystemRecentFiles(const char *filepath) const override;
};

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2010 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#import <AppKit/NSDocumentController.h>
#import <Foundation/Foundation.h>
#include <optional>
#include <string>
#include "GHOST_Debug.hh"
#include "GHOST_SystemPathsCocoa.hh"
/* --------------------------------------------------------------------
* Base directories retrieval.
*/
static const char *GetApplicationSupportDir(const char *versionstr,
const NSSearchPathDomainMask mask,
char *tempPath,
const std::size_t len_tempPath)
{
@autoreleasepool {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, mask, YES);
if (paths.count == 0) {
return nullptr;
}
NSString *basePath = [paths objectAtIndex:0];
snprintf(tempPath,
len_tempPath,
"%s/Blender/%s",
[basePath cStringUsingEncoding:NSASCIIStringEncoding],
versionstr);
}
return tempPath;
}
const char *GHOST_SystemPathsCocoa::getSystemDir(int /* version */, const char *versionstr) const
{
static char tempPath[512] = "";
return GetApplicationSupportDir(versionstr, NSLocalDomainMask, tempPath, sizeof(tempPath));
}
const char *GHOST_SystemPathsCocoa::getUserDir(int /* version */, const char *versionstr) const
{
static char tempPath[512] = "";
return GetApplicationSupportDir(versionstr, NSUserDomainMask, tempPath, sizeof(tempPath));
}
std::optional<std::string> GHOST_SystemPathsCocoa::getUserSpecialDir(
GHOST_TUserSpecialDirTypes type) const
{
char tempPath[512] = "";
@autoreleasepool {
NSSearchPathDirectory ns_directory;
switch (type) {
case GHOST_kUserSpecialDirDesktop:
ns_directory = NSDesktopDirectory;
break;
case GHOST_kUserSpecialDirDocuments:
ns_directory = NSDocumentDirectory;
break;
case GHOST_kUserSpecialDirDownloads:
ns_directory = NSDownloadsDirectory;
break;
case GHOST_kUserSpecialDirMusic:
ns_directory = NSMusicDirectory;
break;
case GHOST_kUserSpecialDirPictures:
ns_directory = NSPicturesDirectory;
break;
case GHOST_kUserSpecialDirVideos:
ns_directory = NSMoviesDirectory;
break;
case GHOST_kUserSpecialDirCaches:
ns_directory = NSCachesDirectory;
break;
default:
GHOST_ASSERT(
false,
"GHOST_SystemPathsCocoa::getUserSpecialDir(): Invalid enum value for type parameter");
return std::nullopt;
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(ns_directory, NSUserDomainMask, YES);
if (paths.count == 0) {
return std::nullopt;
}
NSString *basePath = [paths objectAtIndex:0];
const char *basePath_cstr = [basePath cStringUsingEncoding:NSASCIIStringEncoding];
int basePath_len = strlen(basePath_cstr);
basePath_len = MIN(basePath_len, sizeof(tempPath) - 1);
memcpy(tempPath, basePath_cstr, basePath_len);
tempPath[basePath_len] = '\0';
}
if (!tempPath[0]) {
return std::nullopt;
}
return tempPath;
}
const char *GHOST_SystemPathsCocoa::getBinaryDir() const
{
static char tempPath[512] = "";
@autoreleasepool {
NSString *basePath = [[NSBundle mainBundle] bundlePath];
if (basePath == nil) {
return nullptr;
}
const char *basePath_cstr = [basePath cStringUsingEncoding:NSASCIIStringEncoding];
int basePath_len = strlen(basePath_cstr);
basePath_len = MIN(basePath_len, sizeof(tempPath) - 1);
memcpy(tempPath, basePath_cstr, basePath_len);
tempPath[basePath_len] = '\0';
}
return tempPath;
}
void GHOST_SystemPathsCocoa::addToSystemRecentFiles(const char *filepath) const
{
@autoreleasepool {
NSURL *file_url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:filepath]];
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:file_url];
}
}

View File

@@ -0,0 +1,197 @@
/* SPDX-FileCopyrightText: 2010 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include <optional>
#include <sstream>
#include "GHOST_SystemPathsUnix.hh"
#include "GHOST_Debug.hh"
/* For timing. */
#include <sys/time.h>
#include <unistd.h>
#include <pwd.h> /* For get home without use `getenv()`. */
#include <string>
using std::string;
#ifdef PREFIX
static const char *static_path = PREFIX "/share";
#else
static const char *static_path = nullptr;
#endif
#if defined(PREFIX) && defined(BLENDER_INSTALL_LIBDIR)
static const char *static_libs_path = PREFIX "/" BLENDER_INSTALL_LIBDIR;
#else
static const char *static_libs_path = nullptr;
#endif
GHOST_SystemPathsUnix::GHOST_SystemPathsUnix() = default;
GHOST_SystemPathsUnix::~GHOST_SystemPathsUnix() = default;
const char *GHOST_SystemPathsUnix::getSystemDir(int /*version*/, const char *versionstr) const
{
/* no prefix assumes a portable build which only uses bundled scripts */
if (static_path) {
static string system_path = string(static_path) + "/blender/" + versionstr;
return system_path.c_str();
}
return nullptr;
}
const char *GHOST_SystemPathsUnix::getSystemLibsDir(int /*version*/, const char *versionstr) const
{
if (static_libs_path) {
static string system_libs_path = string(static_libs_path) + "/blender/" + versionstr;
return system_libs_path.c_str();
}
return nullptr;
}
/**
* See docstring & code-comments for #BLI_dir_home which matches this functionality.
*/
static const char *home_dir_get()
{
const char *home_dir = getenv("HOME");
if (home_dir == nullptr) {
if (const passwd *pwuser = getpwuid(getuid())) {
home_dir = pwuser->pw_dir;
}
}
return home_dir;
}
const char *GHOST_SystemPathsUnix::getUserDir(int version, const char *versionstr) const
{
static string user_path;
static int last_version = 0;
/* in blender 2.64, we migrate to XDG. to ensure the copy previous settings
* operator works we give a different path depending on the requested version */
if (version < 264) {
if (user_path.empty() || last_version != version) {
const char *home = home_dir_get();
last_version = version;
if (home) {
user_path = string(home) + "/.blender/" + versionstr;
}
else {
return nullptr;
}
}
return user_path.c_str();
}
if (user_path.empty() || last_version != version) {
const char *home = getenv("XDG_CONFIG_HOME");
last_version = version;
if (home) {
user_path = string(home) + "/blender/" + versionstr;
}
else {
home = home_dir_get();
if (home) {
user_path = string(home) + "/.config/blender/" + versionstr;
}
else {
return nullptr;
}
}
}
return user_path.c_str();
}
std::optional<std::string> GHOST_SystemPathsUnix::getUserSpecialDir(
GHOST_TUserSpecialDirTypes type) const
{
const char *type_str;
switch (type) {
case GHOST_kUserSpecialDirDesktop:
type_str = "DESKTOP";
break;
case GHOST_kUserSpecialDirDocuments:
type_str = "DOCUMENTS";
break;
case GHOST_kUserSpecialDirDownloads:
type_str = "DOWNLOAD";
break;
case GHOST_kUserSpecialDirMusic:
type_str = "MUSIC";
break;
case GHOST_kUserSpecialDirPictures:
type_str = "PICTURES";
break;
case GHOST_kUserSpecialDirVideos:
type_str = "VIDEOS";
break;
case GHOST_kUserSpecialDirCaches: {
const char *cache_dir = getenv("XDG_CACHE_HOME");
if (cache_dir) {
return cache_dir;
}
/* If `XDG_CACHE_HOME` is not set, then `$HOME/.cache is used`. */
const char *home_dir = home_dir_get();
if (home_dir == nullptr) {
return std::nullopt;
}
return string(home_dir) + "/.cache";
}
default:
GHOST_ASSERT(
false,
"GHOST_SystemPathsUnix::getUserSpecialDir(): Invalid enum value for type parameter");
return std::nullopt;
}
/* Pipe `stderr` to `/dev/null` to avoid error prints. We will fail gracefully still. */
string command = string("xdg-user-dir ") + type_str + " 2> /dev/null";
FILE *fstream = popen(command.c_str(), "r");
if (fstream == nullptr) {
return std::nullopt;
}
std::stringstream path_stream;
while (!feof(fstream)) {
char c = fgetc(fstream);
/* `xdg-user-dir` ends the path with '\n'. */
if (c == '\n') {
break;
}
path_stream << c;
}
if (pclose(fstream) == -1) {
perror("GHOST_SystemPathsUnix::getUserSpecialDir failed at pclose()");
return std::nullopt;
}
std::string path = path_stream.str();
return path[0] ? std::optional(path) : std::nullopt;
}
const char *GHOST_SystemPathsUnix::getBinaryDir() const
{
return nullptr;
}
void GHOST_SystemPathsUnix::addToSystemRecentFiles(const char * /*filename*/) const
{
/* TODO: implement for X11 */
}

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2010 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include <optional>
#include <string>
#include "../GHOST_Types.hh"
#include "GHOST_SystemPaths.hh"
class GHOST_SystemPathsUnix : public GHOST_SystemPaths {
public:
/**
* Constructor
* this class should only be instantiated by GHOST_ISystem.
*/
GHOST_SystemPathsUnix();
/**
* Destructor.
*/
~GHOST_SystemPathsUnix() override;
/**
* Determine the base directory in which shared resources are located. It will first try to use
* "unpack and run" path, then look for properly installed path, including versioning.
* \return Unsigned char string pointing to system directory (eg `/usr/share/blender/`).
*/
const char *getSystemDir(int version, const char *versionstr) const override;
/**
* Determine the base directory for architecture-dependent shared libraries, mirroring
* #getSystemDir under the install lib tree (eg `/usr/lib/blender/`).
* \return Unsigned char string pointing to the system libraries directory or null
* when the system-libraries path is not used.
*/
const char *getSystemLibsDir(int version, const char *versionstr) const override;
/**
* Determine the base directory in which user configuration is stored, including versioning.
* If needed, it will create the base directory.
* \return Unsigned char string pointing to user directory (eg `~/.config/.blender/`).
*/
const char *getUserDir(int version, const char *versionstr) const override;
/**
* Determine a special ("well known") and easy to reach user directory.
* \return If successfull, a string containing the user directory path (eg `~/Documents/`).
*/
std::optional<std::string> getUserSpecialDir(GHOST_TUserSpecialDirTypes type) const override;
/**
* Determine the directory of the current binary.
* \return Unsigned char string pointing to the binary directory.
*/
const char *getBinaryDir() const override;
/**
* Add the file to the operating system most recently used files
*/
void addToSystemRecentFiles(const char *filepath) const override;
};

View File

@@ -0,0 +1,152 @@
/* SPDX-FileCopyrightText: 2011 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include "GHOST_SystemPathsWin32.hh"
#include "GHOST_Debug.hh"
#ifndef _WIN32_IE
# define _WIN32_IE 0x0501
#endif
#include "utfconv.hh"
#include <shlobj.h>
GHOST_SystemPathsWin32::GHOST_SystemPathsWin32() {}
GHOST_SystemPathsWin32::~GHOST_SystemPathsWin32() {}
const char *GHOST_SystemPathsWin32::getSystemDir(int, const char *versionstr) const
{
const char *system_dir = nullptr;
/* 1 UTF16 might translate into 3 UTF8. 2 UTF16 translates into 4 UTF8. */
static char knownpath[MAX_PATH * 3 + 128] = {0};
PWSTR knownpath_16 = nullptr;
HRESULT hResult = SHGetKnownFolderPath(
FOLDERID_ProgramData, KF_FLAG_DEFAULT, nullptr, &knownpath_16);
if (hResult == S_OK) {
conv_utf_16_to_8(knownpath_16, knownpath, MAX_PATH * 3);
strcat(knownpath, "\\Blender Foundation\\Blender\\");
strcat(knownpath, versionstr);
system_dir = knownpath;
}
CoTaskMemFree(knownpath_16);
return system_dir;
}
const char *GHOST_SystemPathsWin32::getUserDir(int, const char *versionstr) const
{
const char *user_dir = nullptr;
static char knownpath[MAX_PATH * 3 + 128] = {0};
PWSTR knownpath_16 = nullptr;
HRESULT hResult = SHGetKnownFolderPath(
FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &knownpath_16);
if (hResult == S_OK) {
conv_utf_16_to_8(knownpath_16, knownpath, MAX_PATH * 3);
strcat(knownpath, "\\Blender Foundation\\Blender\\");
strcat(knownpath, versionstr);
user_dir = knownpath;
}
CoTaskMemFree(knownpath_16);
return user_dir;
}
std::optional<std::string> GHOST_SystemPathsWin32::getUserSpecialDir(
GHOST_TUserSpecialDirTypes type) const
{
const char *special_dir = nullptr;
GUID folderid;
switch (type) {
case GHOST_kUserSpecialDirDesktop:
folderid = FOLDERID_Desktop;
break;
case GHOST_kUserSpecialDirDocuments:
folderid = FOLDERID_Documents;
break;
case GHOST_kUserSpecialDirDownloads:
folderid = FOLDERID_Downloads;
break;
case GHOST_kUserSpecialDirMusic:
folderid = FOLDERID_Music;
break;
case GHOST_kUserSpecialDirPictures:
folderid = FOLDERID_Pictures;
break;
case GHOST_kUserSpecialDirVideos:
folderid = FOLDERID_Videos;
break;
case GHOST_kUserSpecialDirCaches:
folderid = FOLDERID_LocalAppData;
break;
default:
GHOST_ASSERT(
false,
"GHOST_SystemPathsWin32::getUserSpecialDir(): Invalid enum value for type parameter");
return std::nullopt;
}
static char knownpath[MAX_PATH * 3] = {0};
PWSTR knownpath_16 = nullptr;
HRESULT hResult = SHGetKnownFolderPath(folderid, KF_FLAG_DEFAULT, nullptr, &knownpath_16);
if (hResult == S_OK) {
conv_utf_16_to_8(knownpath_16, knownpath, MAX_PATH * 3);
special_dir = knownpath;
}
if ((special_dir == nullptr) || (special_dir[0] == '\0')) {
return std::nullopt;
}
CoTaskMemFree(knownpath_16);
return special_dir;
}
const char *GHOST_SystemPathsWin32::getBinaryDir() const
{
static char fullname[MAX_PATH * 3] = {0};
wchar_t fullname_16[MAX_PATH * 3];
if (GetModuleFileNameW(0, fullname_16, MAX_PATH)) {
conv_utf_16_to_8(fullname_16, fullname, MAX_PATH * 3);
return fullname;
}
return nullptr;
}
void GHOST_SystemPathsWin32::addToSystemRecentFiles(const char *filepath) const
{
UTF16_ENCODE(filepath);
UTF16_ENCODE(BLENDER_WIN_APPID);
SHARDAPPIDINFO info;
IShellItem *shell_item;
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (!SUCCEEDED(hr)) {
return;
}
hr = SHCreateItemFromParsingName(filepath_16, nullptr, IID_PPV_ARGS(&shell_item));
if (SUCCEEDED(hr)) {
info.psi = shell_item;
info.pszAppID = BLENDER_WIN_APPID_16;
SHAddToRecentDocs(SHARD_APPIDINFO, &info);
}
CoUninitialize();
UTF16_UN_ENCODE(BLENDER_WIN_APPID);
UTF16_UN_ENCODE(filepath);
}

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#include <optional>
#include <string>
#ifndef WIN32
# error WIN32 only!
#endif // WIN32
#include <windows.h>
#include "GHOST_SystemPaths.hh"
/**
* WIN32 Implementation of GHOST_SystemPaths class.
* \see GHOST_SystemPaths.
*/
class GHOST_SystemPathsWin32 : public GHOST_SystemPaths {
public:
/**
* Constructor.
*/
GHOST_SystemPathsWin32();
/**
* Destructor.
*/
~GHOST_SystemPathsWin32() override;
/**
* Determine the base directory in which shared resources are located. It will first try to use
* "unpack and run" path, then look for properly installed path, including versioning.
* \return Unsigned char string pointing to system directory (eg `/usr/share/`).
*/
const char *getSystemDir(int version, const char *versionstr) const override;
/**
* Determine the base directory in which user configuration is stored, including versioning.
* If needed, it will create the base directory.
* \return Unsigned char string pointing to user directory (eg `~/`).
*/
const char *getUserDir(int version, const char *versionstr) const override;
/**
* Determine a special ("well known") and easy to reach user directory.
* \return If successfull, a string containing the user directory path (eg `~/Documents/`).
*/
std::optional<std::string> getUserSpecialDir(GHOST_TUserSpecialDirTypes type) const override;
/**
* Determine the directory of the current binary.
* \return Unsigned char string pointing to the binary directory.
*/
const char *getBinaryDir() const override;
/**
* Add the file to the operating system most recently used files
*/
void addToSystemRecentFiles(const char *filepath) const override;
};

View File

@@ -0,0 +1,855 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include <cassert>
#include <cstring>
#include <stdexcept>
#include "GHOST_ContextSDL.hh"
#include "GHOST_SystemSDL.hh"
#include "GHOST_WindowSDL.hh"
#include "GHOST_WindowManager.hh"
#include "GHOST_EventButton.hh"
#include "GHOST_EventCursor.hh"
#include "GHOST_EventKey.hh"
#include "GHOST_EventWheel.hh"
GHOST_SystemSDL::GHOST_SystemSDL() : GHOST_System()
{
if (!SDL_Init(SDL_INIT_VIDEO)) {
throw std::runtime_error(SDL_GetError());
}
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
}
GHOST_SystemSDL::~GHOST_SystemSDL()
{
SDL_Quit();
}
GHOST_IWindow *GHOST_SystemSDL::createWindow(const char *title,
int32_t left,
int32_t top,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
const bool exclusive,
const bool /*is_dialog*/,
const GHOST_IWindow *parent_window)
{
GHOST_WindowSDL *window = nullptr;
const GHOST_ContextParams context_params = GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS(gpu_settings);
window = new GHOST_WindowSDL(this,
title,
left,
top,
width,
height,
state,
gpu_settings.context_type,
context_params,
exclusive,
parent_window);
if (window) {
if (GHOST_kWindowStateFullScreen == state) {
SDL_Window *sdl_win = window->getSDLWindow();
SDL_SetWindowFullscreenMode(sdl_win, nullptr);
SDL_ShowWindow(sdl_win);
SDL_SetWindowFullscreen(sdl_win, true);
}
if (window->getValid()) {
window_manager_->addWindow(window);
window_manager_->setActiveWindow(window);
pushEvent(std::make_unique<GHOST_Event>(getMilliSeconds(), GHOST_kEventWindowSize, window));
}
else {
delete window;
window = nullptr;
}
}
return window;
}
GHOST_TSuccess GHOST_SystemSDL::init()
{
GHOST_TSuccess success = GHOST_System::init();
if (success) {
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
/**
* Returns the dimensions of the main display on this system.
* \return The dimension of the main display.
*/
void GHOST_SystemSDL::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const
{
SDL_DisplayID display_id = SDL_GetPrimaryDisplay();
const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(display_id);
if (mode == nullptr) {
return;
}
width = mode->w;
height = mode->h;
}
void GHOST_SystemSDL::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
{
SDL_DisplayID display_id = SDL_GetPrimaryDisplay();
const SDL_DisplayMode *mode = SDL_GetCurrentDisplayMode(display_id);
if (mode == nullptr) {
return;
}
width = mode->w;
height = mode->h;
}
uint8_t GHOST_SystemSDL::getNumDisplays() const
{
int count = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&count);
SDL_free(displays);
return uint8_t(count);
}
GHOST_IContext *GHOST_SystemSDL::createOffscreenContext(GHOST_GPUSettings gpu_settings)
{
const GHOST_ContextParams context_params_offscreen =
GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS_OFFSCREEN(gpu_settings);
switch (gpu_settings.context_type) {
#ifdef WITH_OPENGL_BACKEND
case GHOST_kDrawingContextTypeOpenGL: {
for (int minor = 6; minor >= 3; --minor) {
GHOST_Context *context = new GHOST_ContextSDL(
context_params_offscreen,
nullptr,
0, /* Profile bit. */
4,
minor,
GHOST_OPENGL_SDL_CONTEXT_FLAGS,
GHOST_OPENGL_SDL_RESET_NOTIFICATION_STRATEGY);
if (context->initializeDrawingContext()) {
return context;
}
delete context;
}
return nullptr;
}
#endif
default:
/* Unsupported backend. */
return nullptr;
}
}
GHOST_TSuccess GHOST_SystemSDL::disposeContext(GHOST_IContext *context)
{
delete context;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_SystemSDL::getModifierKeys(GHOST_ModifierKeys &keys) const
{
SDL_Keymod mod = SDL_GetModState();
keys.set(GHOST_kModifierKeyLeftShift, (mod & SDL_KMOD_LSHIFT) != 0);
keys.set(GHOST_kModifierKeyRightShift, (mod & SDL_KMOD_RSHIFT) != 0);
keys.set(GHOST_kModifierKeyLeftControl, (mod & SDL_KMOD_LCTRL) != 0);
keys.set(GHOST_kModifierKeyRightControl, (mod & SDL_KMOD_RCTRL) != 0);
keys.set(GHOST_kModifierKeyLeftAlt, (mod & SDL_KMOD_LALT) != 0);
keys.set(GHOST_kModifierKeyRightAlt, (mod & SDL_KMOD_RALT) != 0);
keys.set(GHOST_kModifierKeyLeftOS, (mod & SDL_KMOD_LGUI) != 0);
keys.set(GHOST_kModifierKeyRightOS, (mod & SDL_KMOD_RGUI) != 0);
return GHOST_kSuccess;
}
#define GXMAP(k, x, y) \
case x: \
k = y; \
break
static GHOST_TKey convertSDLKey(SDL_Scancode key)
{
GHOST_TKey type;
if ((key >= SDL_SCANCODE_A) && (key <= SDL_SCANCODE_Z)) {
type = GHOST_TKey(key - SDL_SCANCODE_A + int(GHOST_kKeyA));
}
else if ((key >= SDL_SCANCODE_1) && (key <= SDL_SCANCODE_0)) {
type = (key == SDL_SCANCODE_0) ? GHOST_kKey0 :
GHOST_TKey(key - SDL_SCANCODE_1 + int(GHOST_kKey1));
}
else if ((key >= SDL_SCANCODE_F1) && (key <= SDL_SCANCODE_F12)) {
type = GHOST_TKey(key - SDL_SCANCODE_F1 + int(GHOST_kKeyF1));
}
else if ((key >= SDL_SCANCODE_F13) && (key <= SDL_SCANCODE_F24)) {
type = GHOST_TKey(key - SDL_SCANCODE_F13 + int(GHOST_kKeyF13));
}
else {
switch (key) {
GXMAP(type, SDL_SCANCODE_BACKSPACE, GHOST_kKeyBackSpace);
GXMAP(type, SDL_SCANCODE_TAB, GHOST_kKeyTab);
GXMAP(type, SDL_SCANCODE_RETURN, GHOST_kKeyEnter);
GXMAP(type, SDL_SCANCODE_ESCAPE, GHOST_kKeyEsc);
GXMAP(type, SDL_SCANCODE_SPACE, GHOST_kKeySpace);
GXMAP(type, SDL_SCANCODE_SEMICOLON, GHOST_kKeySemicolon);
GXMAP(type, SDL_SCANCODE_PERIOD, GHOST_kKeyPeriod);
GXMAP(type, SDL_SCANCODE_COMMA, GHOST_kKeyComma);
GXMAP(type, SDL_SCANCODE_APOSTROPHE, GHOST_kKeyQuote);
GXMAP(type, SDL_SCANCODE_GRAVE, GHOST_kKeyAccentGrave);
GXMAP(type, SDL_SCANCODE_MINUS, GHOST_kKeyMinus);
GXMAP(type, SDL_SCANCODE_EQUALS, GHOST_kKeyEqual);
GXMAP(type, SDL_SCANCODE_SLASH, GHOST_kKeySlash);
GXMAP(type, SDL_SCANCODE_BACKSLASH, GHOST_kKeyBackslash);
GXMAP(type, SDL_SCANCODE_KP_EQUALS, GHOST_kKeyEqual);
GXMAP(type, SDL_SCANCODE_LEFTBRACKET, GHOST_kKeyLeftBracket);
GXMAP(type, SDL_SCANCODE_RIGHTBRACKET, GHOST_kKeyRightBracket);
GXMAP(type, SDL_SCANCODE_PAUSE, GHOST_kKeyPause);
GXMAP(type, SDL_SCANCODE_LSHIFT, GHOST_kKeyLeftShift);
GXMAP(type, SDL_SCANCODE_RSHIFT, GHOST_kKeyRightShift);
GXMAP(type, SDL_SCANCODE_LCTRL, GHOST_kKeyLeftControl);
GXMAP(type, SDL_SCANCODE_RCTRL, GHOST_kKeyRightControl);
GXMAP(type, SDL_SCANCODE_LALT, GHOST_kKeyLeftAlt);
GXMAP(type, SDL_SCANCODE_RALT, GHOST_kKeyRightAlt);
GXMAP(type, SDL_SCANCODE_LGUI, GHOST_kKeyLeftOS);
GXMAP(type, SDL_SCANCODE_RGUI, GHOST_kKeyRightOS);
GXMAP(type, SDL_SCANCODE_APPLICATION, GHOST_kKeyApp);
GXMAP(type, SDL_SCANCODE_INSERT, GHOST_kKeyInsert);
GXMAP(type, SDL_SCANCODE_DELETE, GHOST_kKeyDelete);
GXMAP(type, SDL_SCANCODE_HOME, GHOST_kKeyHome);
GXMAP(type, SDL_SCANCODE_END, GHOST_kKeyEnd);
GXMAP(type, SDL_SCANCODE_PAGEUP, GHOST_kKeyUpPage);
GXMAP(type, SDL_SCANCODE_PAGEDOWN, GHOST_kKeyDownPage);
GXMAP(type, SDL_SCANCODE_LEFT, GHOST_kKeyLeftArrow);
GXMAP(type, SDL_SCANCODE_RIGHT, GHOST_kKeyRightArrow);
GXMAP(type, SDL_SCANCODE_UP, GHOST_kKeyUpArrow);
GXMAP(type, SDL_SCANCODE_DOWN, GHOST_kKeyDownArrow);
GXMAP(type, SDL_SCANCODE_CAPSLOCK, GHOST_kKeyCapsLock);
GXMAP(type, SDL_SCANCODE_SCROLLLOCK, GHOST_kKeyScrollLock);
GXMAP(type, SDL_SCANCODE_NUMLOCKCLEAR, GHOST_kKeyNumLock);
GXMAP(type, SDL_SCANCODE_PRINTSCREEN, GHOST_kKeyPrintScreen);
/* keypad events */
/* NOTE: SDL defines a bunch of key-pad identifiers that aren't supported by GHOST,
* such as #SDL_SCANCODE_KP_PERCENT, #SDL_SCANCODE_KP_XOR. */
GXMAP(type, SDL_SCANCODE_KP_0, GHOST_kKeyNumpad0);
GXMAP(type, SDL_SCANCODE_KP_1, GHOST_kKeyNumpad1);
GXMAP(type, SDL_SCANCODE_KP_2, GHOST_kKeyNumpad2);
GXMAP(type, SDL_SCANCODE_KP_3, GHOST_kKeyNumpad3);
GXMAP(type, SDL_SCANCODE_KP_4, GHOST_kKeyNumpad4);
GXMAP(type, SDL_SCANCODE_KP_5, GHOST_kKeyNumpad5);
GXMAP(type, SDL_SCANCODE_KP_6, GHOST_kKeyNumpad6);
GXMAP(type, SDL_SCANCODE_KP_7, GHOST_kKeyNumpad7);
GXMAP(type, SDL_SCANCODE_KP_8, GHOST_kKeyNumpad8);
GXMAP(type, SDL_SCANCODE_KP_9, GHOST_kKeyNumpad9);
GXMAP(type, SDL_SCANCODE_KP_PERIOD, GHOST_kKeyNumpadPeriod);
GXMAP(type, SDL_SCANCODE_KP_ENTER, GHOST_kKeyNumpadEnter);
GXMAP(type, SDL_SCANCODE_KP_PLUS, GHOST_kKeyNumpadPlus);
GXMAP(type, SDL_SCANCODE_KP_MINUS, GHOST_kKeyNumpadMinus);
GXMAP(type, SDL_SCANCODE_KP_MULTIPLY, GHOST_kKeyNumpadAsterisk);
GXMAP(type, SDL_SCANCODE_KP_DIVIDE, GHOST_kKeyNumpadSlash);
/* Media keys in some keyboards and laptops with XFree86/XORG. */
GXMAP(type, SDL_SCANCODE_MEDIA_PLAY, GHOST_kKeyMediaPlay);
GXMAP(type, SDL_SCANCODE_MEDIA_STOP, GHOST_kKeyMediaStop);
GXMAP(type, SDL_SCANCODE_MEDIA_PREVIOUS_TRACK, GHOST_kKeyMediaFirst);
GXMAP(type, SDL_SCANCODE_MEDIA_NEXT_TRACK, GHOST_kKeyMediaLast);
/* International Keys. */
/* This key has multiple purposes,
* however the only GHOST key that uses the scan-code is GrLess. */
GXMAP(type, SDL_SCANCODE_NONUSBACKSLASH, GHOST_kKeyGrLess);
default:
printf("Unknown\n");
type = GHOST_kKeyUnknown;
break;
}
}
return type;
}
#undef GXMAP
static char convert_keyboard_event_to_ascii(const SDL_KeyboardEvent &sdl_sub_evt)
{
SDL_Keycode sym = sdl_sub_evt.key;
if (sym > 127) {
switch (sym) {
case SDLK_KP_DIVIDE:
sym = '/';
break;
case SDLK_KP_MULTIPLY:
sym = '*';
break;
case SDLK_KP_MINUS:
sym = '-';
break;
case SDLK_KP_PLUS:
sym = '+';
break;
case SDLK_KP_1:
sym = '1';
break;
case SDLK_KP_2:
sym = '2';
break;
case SDLK_KP_3:
sym = '3';
break;
case SDLK_KP_4:
sym = '4';
break;
case SDLK_KP_5:
sym = '5';
break;
case SDLK_KP_6:
sym = '6';
break;
case SDLK_KP_7:
sym = '7';
break;
case SDLK_KP_8:
sym = '8';
break;
case SDLK_KP_9:
sym = '9';
break;
case SDLK_KP_0:
sym = '0';
break;
case SDLK_KP_PERIOD:
sym = '.';
break;
default:
sym = 0;
break;
}
}
else {
if (sdl_sub_evt.mod & (SDL_KMOD_LSHIFT | SDL_KMOD_RSHIFT)) {
/* Weak US keyboard assumptions. */
if (sym >= 'a' && sym <= ('a' + 32)) {
sym -= 32;
}
else {
switch (sym) {
case '`':
sym = '~';
break;
case '1':
sym = '!';
break;
case '2':
sym = '@';
break;
case '3':
sym = '#';
break;
case '4':
sym = '$';
break;
case '5':
sym = '%';
break;
case '6':
sym = '^';
break;
case '7':
sym = '&';
break;
case '8':
sym = '*';
break;
case '9':
sym = '(';
break;
case '0':
sym = ')';
break;
case '-':
sym = '_';
break;
case '=':
sym = '+';
break;
case '[':
sym = '{';
break;
case ']':
sym = '}';
break;
case '\\':
sym = '|';
break;
case ';':
sym = ':';
break;
case '\'':
sym = '"';
break;
case ',':
sym = '<';
break;
case '.':
sym = '>';
break;
case '/':
sym = '?';
break;
default:
break;
}
}
}
}
return char(sym);
}
/**
* Events don't always have valid windows,
* but GHOST needs a window _always_. Fall back to the GL window.
*/
static SDL_Window *SDL_GetWindowFromID_fallback(SDL_WindowID id)
{
SDL_Window *sdl_win = SDL_GetWindowFromID(id);
if (sdl_win == nullptr) {
sdl_win = SDL_GL_GetCurrentWindow();
}
return sdl_win;
}
void GHOST_SystemSDL::processEvent(SDL_Event *sdl_event)
{
std::unique_ptr<GHOST_Event> g_event = nullptr;
switch (sdl_event->type) {
case SDL_EVENT_WINDOW_EXPOSED:
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_MOVED:
case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED: {
const SDL_WindowEvent &sdl_sub_evt = sdl_event->window;
const uint64_t event_ms = SDL_NS_TO_MS(sdl_sub_evt.timestamp);
GHOST_WindowSDL *window = findGhostWindow(
SDL_GetWindowFromID_fallback(sdl_sub_evt.windowID));
/* Can be nullptr on close window. */
switch (sdl_event->type) {
case SDL_EVENT_WINDOW_EXPOSED:
g_event = std::make_unique<GHOST_Event>(event_ms, GHOST_kEventWindowUpdate, window);
break;
case SDL_EVENT_WINDOW_RESIZED:
g_event = std::make_unique<GHOST_Event>(event_ms, GHOST_kEventWindowSize, window);
break;
case SDL_EVENT_WINDOW_MOVED:
g_event = std::make_unique<GHOST_Event>(event_ms, GHOST_kEventWindowMove, window);
break;
case SDL_EVENT_WINDOW_FOCUS_GAINED:
g_event = std::make_unique<GHOST_Event>(event_ms, GHOST_kEventWindowActivate, window);
break;
case SDL_EVENT_WINDOW_FOCUS_LOST:
g_event = std::make_unique<GHOST_Event>(event_ms, GHOST_kEventWindowDeactivate, window);
break;
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
g_event = std::make_unique<GHOST_Event>(event_ms, GHOST_kEventWindowClose, window);
break;
}
break;
}
case SDL_EVENT_QUIT: {
const SDL_QuitEvent &sdl_sub_evt = sdl_event->quit;
const uint64_t event_ms = SDL_NS_TO_MS(sdl_sub_evt.timestamp);
GHOST_IWindow *window = window_manager_->getActiveWindow();
g_event = std::make_unique<GHOST_Event>(event_ms, GHOST_kEventQuitRequest, window);
break;
}
case SDL_EVENT_MOUSE_MOTION: {
const SDL_MouseMotionEvent &sdl_sub_evt = sdl_event->motion;
const uint64_t event_ms = SDL_NS_TO_MS(sdl_sub_evt.timestamp);
SDL_Window *sdl_win = SDL_GetWindowFromID_fallback(sdl_sub_evt.windowID);
GHOST_WindowSDL *window = findGhostWindow(sdl_win);
assert(window != nullptr);
int x_win, y_win;
SDL_GetWindowPosition(sdl_win, &x_win, &y_win);
int32_t x_root = int32_t(sdl_sub_evt.x) + x_win;
int32_t y_root = int32_t(sdl_sub_evt.y) + y_win;
#if 0
if (window->getCursorGrabMode() != GHOST_kGrabDisable &&
window->getCursorGrabMode() != GHOST_kGrabNormal)
{
int32_t x_new = x_root;
int32_t y_new = y_root;
int32_t x_accum, y_accum;
GHOST_Rect bounds;
/* fallback to window bounds */
if (window->getCursorGrabBounds(bounds) == GHOST_kFailure) {
window->getClientBounds(bounds);
}
/* Could also clamp to screen bounds wrap with a window outside the view will
* fail at the moment. Use offset of 8 in case the window is at screen bounds. */
bounds.wrapPoint(x_new, y_new, 8, window->getCursorGrabAxis());
window->getCursorGrabAccum(x_accum, y_accum);
/* Can't use #setCursorPosition because the mouse may have no focus! */
if (x_new != x_root || y_new != y_root) {
if (1 /* `xme.time > last_warp_` */) {
/* when wrapping we don't need to add an event because the
* #setCursorPosition call will cause a new event after */
SDL_WarpMouseInWindow(sdl_win, x_new - x_win, y_new - y_win); /* wrap */
window->setCursorGrabAccum(x_accum + (x_root - x_new), y_accum + (y_root - y_new));
// last_warp_ = lastEventTime(xme.time);
}
else {
// setCursorPosition(x_new, y_new); /* wrap but don't accumulate */
SDL_WarpMouseInWindow(sdl_win, x_new - x_win, y_new - y_win);
}
g_event = std::make_unique<GHOST_EventCursor>(
event_ms, GHOST_kEventCursorMove, window, x_new, y_new, GHOST_TABLET_DATA_NONE);
}
else {
g_event = std::make_unique<GHOST_EventCursor>(event_ms,
GHOST_kEventCursorMove,
window,
x_root + x_accum,
y_root + y_accum,
GHOST_TABLET_DATA_NONE);
}
}
else
#endif
{
g_event = std::make_unique<GHOST_EventCursor>(
event_ms, GHOST_kEventCursorMove, window, x_root, y_root, GHOST_TABLET_DATA_NONE);
}
break;
}
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
const SDL_MouseButtonEvent &sdl_sub_evt = sdl_event->button;
const uint64_t event_ms = SDL_NS_TO_MS(sdl_sub_evt.timestamp);
GHOST_TButton gbmask = GHOST_kButtonMaskLeft;
GHOST_TEventType type = sdl_sub_evt.down ? GHOST_kEventButtonDown : GHOST_kEventButtonUp;
GHOST_WindowSDL *window = findGhostWindow(
SDL_GetWindowFromID_fallback(sdl_sub_evt.windowID));
assert(window != nullptr);
/* process rest of normal mouse buttons */
if (sdl_sub_evt.button == SDL_BUTTON_LEFT) {
gbmask = GHOST_kButtonMaskLeft;
}
else if (sdl_sub_evt.button == SDL_BUTTON_MIDDLE) {
gbmask = GHOST_kButtonMaskMiddle;
}
else if (sdl_sub_evt.button == SDL_BUTTON_RIGHT) {
gbmask = GHOST_kButtonMaskRight;
/* these buttons are untested! */
}
else if (sdl_sub_evt.button == SDL_BUTTON_X1) {
gbmask = GHOST_kButtonMaskButton4;
}
else if (sdl_sub_evt.button == SDL_BUTTON_X2) {
gbmask = GHOST_kButtonMaskButton5;
}
else {
break;
}
g_event = std::make_unique<GHOST_EventButton>(
event_ms, type, window, gbmask, GHOST_TABLET_DATA_NONE);
break;
}
case SDL_EVENT_MOUSE_WHEEL: {
const SDL_MouseWheelEvent &sdl_sub_evt = sdl_event->wheel;
const uint64_t event_ms = SDL_NS_TO_MS(sdl_sub_evt.timestamp);
GHOST_WindowSDL *window = findGhostWindow(
SDL_GetWindowFromID_fallback(sdl_sub_evt.windowID));
assert(window != nullptr);
if (sdl_sub_evt.x != 0.0f) {
g_event = std::make_unique<GHOST_EventWheel>(
event_ms, window, GHOST_kEventWheelAxisHorizontal, int(sdl_sub_evt.x));
}
else if (sdl_sub_evt.y != 0.0f) {
g_event = std::make_unique<GHOST_EventWheel>(
event_ms, window, GHOST_kEventWheelAxisVertical, int(sdl_sub_evt.y));
}
break;
}
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP: {
const SDL_KeyboardEvent &sdl_sub_evt = sdl_event->key;
const uint64_t event_ms = SDL_NS_TO_MS(sdl_sub_evt.timestamp);
GHOST_TEventType type = sdl_sub_evt.down ? GHOST_kEventKeyDown : GHOST_kEventKeyUp;
const bool is_repeat = sdl_sub_evt.repeat != 0;
GHOST_WindowSDL *window = findGhostWindow(
SDL_GetWindowFromID_fallback(sdl_sub_evt.windowID));
assert(window != nullptr);
GHOST_TKey gkey = convertSDLKey(sdl_sub_evt.scancode);
/* NOTE: the `sdl_sub_evt.key` is truncated,
* for unicode support ghost has to be modified. */
/* TODO(@ideasman42): support full unicode, SDL supports this but it needs to be
* explicitly enabled via #SDL_StartTextInput which GHOST would have to wrap. */
char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
if (type == GHOST_kEventKeyDown) {
utf8_buf[0] = convert_keyboard_event_to_ascii(sdl_sub_evt);
}
g_event = std::make_unique<GHOST_EventKey>(
event_ms, type, window, gkey, is_repeat, utf8_buf);
break;
}
}
if (g_event) {
switch (g_event->getType()) {
case GHOST_kEventWindowActivate: {
window_manager_->setActiveWindow(g_event->getWindow());
break;
}
case GHOST_kEventWindowDeactivate: {
window_manager_->setWindowInactive(g_event->getWindow());
break;
}
default: {
break;
}
}
pushEvent(std::move(g_event));
}
}
GHOST_TSuccess GHOST_SystemSDL::getCursorPosition(int32_t &x, int32_t &y) const
{
int x_win, y_win;
SDL_Window *win = SDL_GetMouseFocus();
SDL_GetWindowPosition(win, &x_win, &y_win);
float xf, yf;
SDL_GetMouseState(&xf, &yf);
x = int32_t(xf) + x_win;
y = int32_t(yf) + y_win;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_SystemSDL::setCursorPosition(int32_t x, int32_t y)
{
int x_win, y_win;
SDL_Window *win = SDL_GetMouseFocus();
SDL_GetWindowPosition(win, &x_win, &y_win);
SDL_WarpMouseInWindow(win, x - x_win, y - y_win);
return GHOST_kSuccess;
}
bool GHOST_SystemSDL::generateWindowExposeEvents()
{
std::vector<GHOST_WindowSDL *>::iterator w_start = dirty_windows_.begin();
std::vector<GHOST_WindowSDL *>::const_iterator w_end = dirty_windows_.end();
bool anyProcessed = false;
for (; w_start != w_end; ++w_start) {
/* The caller doesn't have a time-stamp. */
const uint64_t event_ms = getMilliSeconds();
auto g_event = std::make_unique<GHOST_Event>(event_ms, GHOST_kEventWindowUpdate, *w_start);
(*w_start)->validate();
if (g_event) {
// printf("Expose events pushed\n");
pushEvent(std::move(g_event));
anyProcessed = true;
}
}
dirty_windows_.clear();
return anyProcessed;
}
bool GHOST_SystemSDL::processEvents(bool waitForEvent)
{
/* Get all the current events - translate them into
* ghost events and call base class #pushEvent() method. */
bool anyProcessed = false;
do {
GHOST_TimerManager *timerMgr = getTimerManager();
if (waitForEvent && dirty_windows_.empty() && !SDL_HasEvents(SDL_EVENT_FIRST, SDL_EVENT_LAST))
{
uint64_t next = timerMgr->nextFireTime();
if (next == GHOST_kFireTimeNever) {
SDL_WaitEventTimeout(nullptr, -1);
// SleepTillEvent(display_, -1);
}
else {
int64_t maxSleep = next - getMilliSeconds();
if (maxSleep >= 0) {
SDL_WaitEventTimeout(nullptr, next - getMilliSeconds());
// SleepTillEvent(display_, next - getMilliSeconds()); /* X11. */
}
}
}
if (timerMgr->fireTimers(getMilliSeconds())) {
anyProcessed = true;
}
SDL_Event sdl_event;
while (SDL_PollEvent(&sdl_event)) {
processEvent(&sdl_event);
anyProcessed = true;
}
if (generateWindowExposeEvents()) {
anyProcessed = true;
}
} while (waitForEvent && !anyProcessed);
return anyProcessed;
}
GHOST_WindowSDL *GHOST_SystemSDL::findGhostWindow(SDL_Window *sdl_win)
{
if (sdl_win == nullptr) {
return nullptr;
}
/* It is not entirely safe to do this as the back-pointer may point
* to a window that has recently been removed.
* We should always check the window manager's list of windows
* and only process events on these windows. */
const std::vector<GHOST_IWindow *> &win_vec = window_manager_->getWindows();
std::vector<GHOST_IWindow *>::const_iterator win_it = win_vec.begin();
std::vector<GHOST_IWindow *>::const_iterator win_end = win_vec.end();
for (; win_it != win_end; ++win_it) {
GHOST_WindowSDL *window = static_cast<GHOST_WindowSDL *>(*win_it);
if (window->getSDLWindow() == sdl_win) {
return window;
}
}
return nullptr;
}
void GHOST_SystemSDL::addDirtyWindow(GHOST_WindowSDL *bad_wind)
{
GHOST_ASSERT((bad_wind != nullptr), "addDirtyWindow() nullptr ptr trapped (window)");
dirty_windows_.push_back(bad_wind);
}
GHOST_TSuccess GHOST_SystemSDL::getButtons(GHOST_Buttons &buttons) const
{
SDL_MouseButtonFlags state = SDL_GetMouseState(nullptr, nullptr);
buttons.set(GHOST_kButtonMaskLeft, (state & SDL_BUTTON_LMASK) != 0);
buttons.set(GHOST_kButtonMaskMiddle, (state & SDL_BUTTON_MMASK) != 0);
buttons.set(GHOST_kButtonMaskRight, (state & SDL_BUTTON_RMASK) != 0);
return GHOST_kSuccess;
}
GHOST_TCapabilityFlag GHOST_SystemSDL::getCapabilities() const
{
return GHOST_TCapabilityFlag(
GHOST_CAPABILITY_FLAG_ALL &
/* NOTE: order the following flags as they they're declared in the source. */
~(
/* This SDL back-end has not yet implemented image copy/paste. */
GHOST_kCapabilityClipboardImage |
/* This SDL back-end has not yet implemented color sampling the desktop. */
GHOST_kCapabilityDesktopSample |
/* No support yet for IME input methods. */
GHOST_kCapabilityInputIME |
/* No support for window decoration styles. */
GHOST_kCapabilityWindowDecorationStyles |
/* No support for precisely placing windows on multiple monitors. */
GHOST_kCapabilityMultiMonitorPlacement |
/* No support for a Hyper modifier key. */
GHOST_kCapabilityKeyboardHyperKey |
/* No support yet for RGBA mouse cursors. */
GHOST_kCapabilityCursorRGBA |
/* No support yet for dynamic cursor generation. */
GHOST_kCapabilityCursorGenerator |
/* No support for window path meta-data. */
GHOST_kCapabilityWindowPath));
}
char *GHOST_SystemSDL::getClipboard(bool selection) const
{
/* The clipboard must be freed with `SDL_free`, copy for the return value. */
char *sdl_text = selection ? SDL_GetPrimarySelectionText() : SDL_GetClipboardText();
if (sdl_text == nullptr) {
return nullptr;
}
char *result = strdup(sdl_text);
SDL_free(sdl_text);
return result;
}
void GHOST_SystemSDL::putClipboard(const char *buffer, bool selection) const
{
if (selection) {
SDL_SetPrimarySelectionText(buffer);
}
else {
SDL_SetClipboardText(buffer);
}
}
uint64_t GHOST_SystemSDL::getMilliSeconds() const
{
return SDL_GetTicks();
}

View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_SystemSDL class.
*/
#pragma once
#include "../GHOST_Types.hh"
#include "GHOST_Event.hh"
#include "GHOST_System.hh"
#include "GHOST_TimerManager.hh"
#include "GHOST_WindowSDL.hh"
#include <SDL3/SDL.h>
class GHOST_WindowSDL;
class GHOST_SystemSDL : public GHOST_System {
public:
void addDirtyWindow(GHOST_WindowSDL *bad_wind);
GHOST_SystemSDL();
~GHOST_SystemSDL();
bool processEvents(bool waitForEvent) override;
bool setConsoleWindowState(GHOST_TConsoleWindowState /*action*/) override
{
return false;
}
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override;
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override;
GHOST_TCapabilityFlag getCapabilities() const override;
char *getClipboard(bool selection) const override;
void putClipboard(const char *buffer, bool selection) const override;
uint64_t getMilliSeconds() const override;
uint8_t getNumDisplays() const override;
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override;
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override;
void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const override;
void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override;
GHOST_IContext *createOffscreenContext(GHOST_GPUSettings gpu_settings) override;
GHOST_TSuccess disposeContext(GHOST_IContext *context) override;
private:
GHOST_TSuccess init() override;
GHOST_IWindow *createWindow(const char *title,
int32_t left,
int32_t top,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
const bool exclusive = false,
const bool is_dialog = false,
const GHOST_IWindow *parent_window = nullptr) override;
/* SDL specific */
GHOST_WindowSDL *findGhostWindow(SDL_Window *sdl_win);
bool generateWindowExposeEvents();
void processEvent(SDL_Event *sdl_event);
/** The vector of windows that need to be updated. */
std::vector<GHOST_WindowSDL *> dirty_windows_;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,383 @@
/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_SystemWayland class.
*/
#pragma once
#include "../GHOST_Types.hh"
#include "GHOST_System.hh"
#include "GHOST_WindowWayland.hh"
#ifdef WITH_GHOST_WAYLAND_DYNLOAD
# include <wayland_dynload_client.h>
#endif
#include <wayland-client.h>
#include <mutex>
#include <span>
#include <string>
#ifdef USE_EVENT_BACKGROUND_THREAD
# include <atomic>
# include <thread>
#endif
class GHOST_WindowWayland;
bool ghost_wl_display_report_error_if_set(wl_display *display);
bool ghost_wl_output_own(const struct wl_output *wl_output);
void ghost_wl_output_tag(struct wl_output *wl_output);
struct GWL_Output *ghost_wl_output_user_data(struct wl_output *wl_output);
/**
* Enter/exit handlers may be called with a null window surface (when the window just closed),
* so add a version of the function that checks this.
*
* All of the functions could in fact however paranoid null checks make the expected
* state difficult to reason about, so only use this in cases the surface may be null.
*/
bool ghost_wl_surface_own_with_null_check(const struct wl_surface *wl_surface);
bool ghost_wl_surface_own(const struct wl_surface *wl_surface);
void ghost_wl_surface_tag(struct wl_surface *wl_surface);
GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *wl_surface);
bool ghost_wl_surface_own_cursor_pointer(const struct wl_surface *wl_surface);
void ghost_wl_surface_tag_cursor_pointer(struct wl_surface *wl_surface);
bool ghost_wl_surface_own_cursor_tablet(const struct wl_surface *wl_surface);
void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *wl_surface);
/* Scaling to: translates from WAYLAND into GHOST (viewport local) coordinates.
* Scaling from: performs the reverse translation.
*
* Scaling "to" is used to map WAYLAND location cursor coordinates to GHOST coordinates.
* Scaling "from" is used to clamp cursor coordinates in WAYLAND local coordinates. */
struct GWL_WindowScaleParams;
wl_fixed_t gwl_window_scale_wl_fixed_to(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value);
wl_fixed_t gwl_window_scale_wl_fixed_from(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value);
/* Avoid this where possible as scaling integers often incurs rounding errors.
* Scale #wl_fixed_t where possible.
*
* In general scale by large values where this is less likely to be a problem. */
int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, int value);
int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int value);
/**
* Scale a logical buffer size to physical pixels, returning an integer buffer scale.
* The buffer scale is rounded up so `result / *r_buffer_scale == logical_size`.
*/
int gwl_window_scale_buffer_size_to(const GWL_WindowScaleParams &scale_params,
int logical_size,
int *r_buffer_scale);
#define FRACTIONAL_DENOMINATOR 120
/**
* The current desktop (Gnome, KDE etc..).
*
* \note Use this as a last resort, ideally wayland integration would *not* depend on this.
*/
enum class GWL_CurrentDesktopType {
Other = 0,
Gnome,
KDE,
};
#ifdef WITH_GHOST_WAYLAND_DYNLOAD
/**
* Return true when all required WAYLAND libraries are present.
*/
bool ghost_wl_dynload_libraries_init();
void ghost_wl_dynload_libraries_exit();
#endif
struct GWL_Output {
/** Wayland core types. */
struct {
wl_output *output = nullptr;
} wl;
/** XDG native types. */
struct {
struct zxdg_output_v1 *output = nullptr;
} xdg;
GHOST_SystemWayland *system = nullptr;
/**
* Dimensions in pixels.
*
* \note Rotation (from the `transform` flag) has *not* been applied.
* So a vertical monitor will still have a larger width.
*/
int32_t size_native[2] = {0, 0};
/** Dimensions in millimeter. */
int32_t size_mm[2] = {0, 0};
/**
* Dimensions in logical points.
*
* \note A 2x Hi-DPI monitor with a `size_native` of 1600x1200
* would have a `size_logical` of 800x600.
*
* \note Rotation (from the `transform` flag) *has* been applied.
*/
int32_t size_logical[2] = {0, 0};
bool has_size_logical = false;
/** Monitor position in pixels. */
int32_t position_logical[2] = {0, 0};
bool has_position_logical = false;
int transform = 0;
int scale = 1;
/**
* The integer `scale` value should be used in almost all cases,
* as this is what is used for most API calls.
* Only use fractional scaling to calculate the DPI.
*
* \note Use the same scale as #wp_fractional_scale_manager_v1
* (avoid floating point arithmetic in general).
*/
int scale_fractional = (1 * FRACTIONAL_DENOMINATOR);
bool has_scale_fractional = false;
std::string make;
std::string model;
};
class GHOST_SystemWayland : public GHOST_System {
public:
GHOST_SystemWayland(bool background);
GHOST_SystemWayland() : GHOST_SystemWayland(true) {};
~GHOST_SystemWayland() override;
GHOST_TSuccess init() override;
bool processEvents(bool waitForEvent) override;
bool setConsoleWindowState(GHOST_TConsoleWindowState action) override;
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override;
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override;
char *getClipboard(bool selection) const override;
void putClipboard(const char *buffer, bool selection) const override;
/**
* Returns GHOST_kSuccess if the clipboard contains an image.
*/
GHOST_TSuccess hasClipboardImage() const override;
/**
* Get image data from the Clipboard
* \param r_width: the returned image width in pixels.
* \param r_height: the returned image height in pixels.
* \return pointer uint array in RGBA byte order. Caller must free.
*/
uint *getClipboardImage(int *r_width, int *r_height) const override;
/**
* Put image data to the Clipboard
* \param rgba: uint array in RGBA byte order.
* \param width: the image width in pixels.
* \param height: the image height in pixels.
*/
GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const override;
uint8_t getNumDisplays() const override;
uint64_t getMilliSeconds() const override;
GHOST_TSuccess getCursorPositionClientRelative(const GHOST_IWindow *window,
int32_t &x,
int32_t &y) const override;
GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window,
int32_t x,
int32_t y) override;
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override;
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override;
uint32_t getCursorPreferredLogicalSize() const override;
void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override;
void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const override;
GHOST_IContext *createOffscreenContext(GHOST_GPUSettings gpu_settings) override;
GHOST_TSuccess disposeContext(GHOST_IContext *context) override;
GHOST_IWindow *createWindow(const char *title,
int32_t left,
int32_t top,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
const bool exclusive,
const bool is_dialog,
const GHOST_IWindow *parent_window) override;
GHOST_TCapabilityFlag getCapabilities() const override;
void setMultitouchGestures(const bool use) override;
/* WAYLAND utility functions (share window/system logic). */
GHOST_TSuccess cursor_shape_set(GHOST_TStandardCursor shape);
GHOST_TSuccess cursor_shape_check(GHOST_TStandardCursor cursor_shape);
GHOST_TSuccess cursor_shape_custom_set(const GHOST_CursorGenerator &cg);
GHOST_TSuccess cursor_bitmap_get(GHOST_CursorBitmapRef *bitmap);
GHOST_TSuccess cursor_visibility_set(bool visible);
bool cursor_grab_use_software_display_get(const GHOST_TGrabCursorMode mode);
/**
* Return a separate WAYLAND local timer manager to #GHOST_System::getTimerManager
* Manipulation & access must lock with #GHOST_WaylandSystem::server_mutex.
*
* See #GWL_Display::key_repeat_timer_manager docstring for details on why this is needed.
*/
GHOST_TimerManager *key_repeat_timer_manager();
void xdg_toplevel_icon_update(GHOST_WindowWayland *window, struct xdg_toplevel *toplevel);
/* WAYLAND direct-data access. */
struct wl_display *wl_display_get();
struct wl_compositor *wl_compositor_get();
struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_manager_get();
struct xdg_activation_v1 *xdg_activation_manager_get();
struct zwp_pointer_gestures_v1 *wp_pointer_gestures_get();
struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_get();
struct wp_viewporter *wp_viewporter_get();
struct wp_color_manager_v1 *wp_color_manager_get();
struct wl_event_queue *wp_color_manager_queue_get();
bool supports_color_manager_feature_windows_scrgb() const;
bool supports_color_manager_extended_srgb_linear() const;
struct xdg_wm_base *xdg_decor_shell_get();
struct zxdg_decoration_manager_v1 *xdg_decor_manager_get();
/* End `xdg_decor`. */
const std::span<GWL_Output *const> outputs_get() const;
struct wl_shm *wl_shm_get() const;
void ime_begin(const GHOST_WindowWayland *win,
int32_t x,
int32_t y,
int32_t w,
int32_t h,
bool completed) const;
void ime_end(const GHOST_WindowWayland *win) const;
bool use_window_frame_get() const;
bool use_window_frame_csd_get() const;
#ifdef WITH_GHOST_CSD
const GHOST_CSD_Layout &csd_layout_base_get() const;
#endif
static const char *xdg_app_id_get();
/* WAYLAND utility functions. */
/**
* Use this function instead of #GHOST_System::getMilliSeconds,
* passing in the time-stamp from WAYLAND input to get the event
* time-stamp with an offset applied to make it compatible with `getMilliSeconds`.
*/
uint64_t ms_from_input_time(const uint32_t timestamp_as_uint);
/**
* Push an event, with support for calling from a thread.
* NOTE: only needed for `USE_EVENT_BACKGROUND_THREAD`.
*/
GHOST_TSuccess pushEvent_maybe_pending(std::unique_ptr<const GHOST_IEvent> event);
/** Set this seat to be active. */
void seat_active_set(const struct GWL_Seat *seat);
struct wl_seat *wl_seat_active_get_with_input_serial(uint32_t &serial);
/**
* Clear all references to this output.
*
* \note The compositor should have already called the `wl_surface_listener.leave` callback,
* however some compositors may not (see #103586).
* So remove references to the output before it's destroyed to avoid crashing.
*
* \return true when any references were removed.
*/
bool output_unref(struct wl_output *wl_output);
void output_scale_update(GWL_Output *output);
/**
* Clear all references to this surface to prevent accessing nullptr pointers.
*
* \return true when any references were removed.
*/
bool window_surface_unref(const wl_surface *wl_surface);
bool window_cursor_grab_set(const GHOST_TGrabCursorMode mode,
const GHOST_TGrabCursorMode mode_current,
int32_t init_grab_xy[2],
const GHOST_Rect *wrap_bounds,
GHOST_TAxisFlag wrap_axis,
wl_surface *wl_surface,
const struct GWL_WindowScaleParams &scale_params);
#ifdef USE_EVENT_BACKGROUND_THREAD
/* NOTE: allocate mutex so `const` functions can lock the mutex. */
/** Lock to prevent #wl_display_dispatch / #wl_display_roundtrip / #wl_display_flush
* from running at the same time. */
std::mutex *server_mutex = nullptr;
/**
* Threads must lock this before manipulating #GWL_Display::ghost_timer_manager.
*
* \note Using a separate lock to `server_mutex` is necessary because the
* server lock is already held when calling `ghost_wl_display_event_pump`.
* If manipulating the timer used the `server_mutex`, event pump can indirectly
* handle key up/down events which would lock `server_mutex` causing a dead-lock.
*/
std::mutex *timer_mutex = nullptr;
std::thread::id main_thread_id;
std::atomic<bool> has_pending_actions_for_window = false;
#endif
private:
/**
* Support freeing the internal data separately from the destructor
* so it can be called when WAYLAND isn't running (immediately before raising an exception).
*/
void display_destroy_and_free_all();
struct GWL_Display *display_;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,513 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_SystemWin32 class.
*/
#pragma once
#ifndef WIN32
# error WIN32 only!
#endif /* WIN32 */
#include <ole2.h> /* For drag-n-drop. */
#include <windows.h>
#include "GHOST_System.hh"
class GHOST_EventButton;
class GHOST_EventCursor;
class GHOST_EventKey;
class GHOST_EventWheel;
class GHOST_EventWindow;
class GHOST_EventDragnDrop;
class GHOST_ContextD3D;
class GHOST_WindowWin32;
/**
* WIN32 Implementation of GHOST_System class.
* \see GHOST_System.
*/
class GHOST_SystemWin32 : public GHOST_System {
public:
/**
* Constructor.
*/
GHOST_SystemWin32();
/**
* Destructor.
*/
~GHOST_SystemWin32() override;
/***************************************************************************************
** Time(r) functionality
***************************************************************************************/
/**
* This method converts performance counter measurements into milliseconds since the start of the
* Blender process.
* \return The number of milliseconds since the start of the Blender process.
*/
uint64_t performanceCounterToMillis(__int64 perf_ticks) const;
/**
* Returns the system time.
* Returns the number of milliseconds since the start of the Blender process.
* This overloaded method uses the high frequency timer if available.
* \return The number of milliseconds.
*/
uint64_t getMilliSeconds() const override;
/***************************************************************************************
** Display/window management functionality
***************************************************************************************/
/**
* Returns the number of displays on this system.
* \return The number of displays.
*/
uint8_t getNumDisplays() const override;
/**
* Returns the dimensions of the main display on this system.
* \return The dimension of the main display.
*/
void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override;
/**
* Returns the dimensions of all displays on this system.
* \return The dimension of the main display.
*/
void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const override;
/**
* Create a new window.
* The new window is added to the list of windows managed.
* Never explicitly delete the window, use #disposeWindow() instead.
* \param title: The name of the window.
* (displayed in the title bar of the window if the OS supports it).
* \param left: The coordinate of the left edge of the window.
* \param top: The coordinate of the top edge of the window.
* \param width: The width the window.
* \param height: The height the window.
* \param state: The state of the window when opened.
* \param gpu_settings: Misc GPU settings.
* \param exclusive: Use to show the window on top and ignore others (used full-screen).
* \param parent_window: Parent window.
* \return The new window (or 0 if creation failed).
*/
GHOST_IWindow *createWindow(const char *title,
int32_t left,
int32_t top,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
const bool exclusive = false,
const bool is_dialog = false,
const GHOST_IWindow *parent_window = nullptr) override;
/**
* Create a new off-screen context.
* Never explicitly delete the window, use #disposeContext() instead.
* \return The new context (or 0 if creation failed).
*/
GHOST_IContext *createOffscreenContext(GHOST_GPUSettings gpu_settings) override;
/**
* Dispose of a context.
* \param context: Pointer to the context to be disposed.
* \return Indication of success.
*/
GHOST_TSuccess disposeContext(GHOST_IContext *context) override;
/**
* Create a new off-screen DirectX context.
* Never explicitly delete the context, use #disposeContext() instead.
* This is for GHOST internal, Win32 specific use, so it can be called statically.
*
* \return The new context (or 0 if creation failed).
*/
static GHOST_ContextD3D *createOffscreenContextD3D();
/**
* Dispose of a DirectX context.
* This is for GHOST internal, Win32 specific use, so it can be called statically.
* \param context: Pointer to the context to be disposed.
* \return Indication of success.
*/
static GHOST_TSuccess disposeContextD3D(GHOST_ContextD3D *context);
/**
* Get the Window under the mouse cursor. Location obtained from the OS.
* \return The window under the cursor or nullptr if none.
*/
GHOST_IWindow *getWindowUnderCursor(int32_t /*x*/, int32_t /*y*/) override;
/***************************************************************************************
** Event management functionality
***************************************************************************************/
/**
* Gets events from the system and stores them in the queue.
* \param waitForEvent: Flag to wait for an event (or return immediately).
* \return Indication of the presence of events.
*/
bool processEvents(bool waitForEvent) override;
/***************************************************************************************
** Cursor management functionality
***************************************************************************************/
/**
* Returns the current location of the cursor (location in screen coordinates)
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override;
/**
* Updates the location of the cursor (location in screen coordinates).
* \param x: The x-coordinate of the cursor.
* \param y: The y-coordinate of the cursor.
* \return Indication of success.
*/
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override;
/**
* Get the color of the pixel at the current mouse cursor location
* \param r_color: returned sRGB float colors
* \return Success value (true == successful and supported by platform)
*/
GHOST_TSuccess getPixelAtCursor(float r_color[3]) const override;
uint32_t getCursorPreferredLogicalSize() const override;
/***************************************************************************************
** Access to mouse button and keyboard states.
***************************************************************************************/
/**
* Returns the state of all modifier keys.
* \param keys: The state of all modifier keys (true == pressed).
* \return Indication of success.
*/
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override;
/**
* Returns the state of the mouse buttons (outside the message queue).
* \param buttons: The state of the buttons.
* \return Indication of success.
*/
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override;
GHOST_TCapabilityFlag getCapabilities() const override;
/**
* Returns unsigned char from CUT_BUFFER0
* \param selection: Used by X11 only.
* \return Returns the Clipboard.
*/
char *getClipboard(bool selection) const override;
/**
* Puts buffer to system clipboard.
* \param selection: Used by X11 only.
* \return No return.
*/
void putClipboard(const char *buffer, bool selection) const override;
/**
* Returns GHOST_kSuccess if the clipboard contains an image.
*/
GHOST_TSuccess hasClipboardImage() const override;
/**
* Get image data from the Clipboard
* \param r_width: the returned image width in pixels.
* \param r_height: the returned image height in pixels.
* \return pointer uint array in RGBA byte order. Caller must free.
*/
uint *getClipboardImage(int *r_width, int *r_height) const override;
/**
* Put image data to the Clipboard
* \param rgba: uint array in RGBA byte order.
* \param width: the image width in pixels.
* \param height: the image height in pixels.
*/
GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const override;
/**
* Show a system message box
* \param title: The title of the message box.
* \param message: The message to display.
* \param help_label: Help button label.
* \param continue_label: Continue button label.
* \param link: An optional hyperlink.
* \param dialog_options: Options how to display the message.
*/
GHOST_TSuccess showMessageBox(const char *title,
const char *message,
const char *help_label,
const char *continue_label,
const char *link,
GHOST_DialogOptions dialog_options) const override;
/**
* Creates a drag & drop event and pushes it immediately onto the event queue.
* Called by #GHOST_DropTargetWin32 class.
* \param eventType: The type of drag & drop event
* \param draggedObjectType: The type object concerned
* (currently array of file names, string, ?bitmap)
* \param window: The window on which the event occurred
* \param mouseX: x mouse coordinate (in window coordinates)
* \param mouseY: y mouse coordinate
* \return Indication whether the event was handled.
*/
static GHOST_TSuccess pushDragDropEvent(GHOST_TEventType eventType,
GHOST_TDragnDropTypes draggedObjectType,
GHOST_WindowWin32 *window,
int mouseX,
int mouseY,
void *data);
/***************************************************************************************
** Modify tablet API
***************************************************************************************/
/**
* Set which tablet API to use.
* \param api: Enum indicating which API to use.
*/
void setTabletAPI(GHOST_TTabletAPI api) override;
/***************************************************************************************
** Debug Info
***************************************************************************************/
/**
* Specify which debug messages are to be shown.
* \param debug: Flag for systems to debug.
*/
void initDebug(GHOST_Debug debug) override;
protected:
/**
* Initializes the system.
* For now, it just registers the window class (WNDCLASS).
* \return A success value.
*/
GHOST_TSuccess init() override;
/**
* Closes the system down.
* \return A success value.
*/
GHOST_TSuccess exit() override;
/**
* Converts raw WIN32 key codes from the `wndproc` to GHOST keys.
* \param vKey: The virtual key from #hardKey.
* \param ScanCode: The ScanCode of pressed key (similar to PS/2 Set 1).
* \param extend: Flag if key is not primly (left or right).
* \return The GHOST key (GHOST_kKeyUnknown if no match).
*/
GHOST_TKey convertKey(short vKey, short ScanCode, short extend) const;
/**
* Catches raw WIN32 key codes from WM_INPUT in the `wndproc`.
* \param raw: RawInput structure with detailed info about the key event.
* \param r_key_down: Set true when the key is pressed, otherwise false.
* \return The GHOST key (GHOST_kKeyUnknown if no match).
*/
GHOST_TKey hardKey(RAWINPUT const &raw, bool *r_key_down);
/**
* Creates mouse button event.
* \param type: The type of event to create.
* \param window: The window receiving the event (the active window).
* \param mask: The button mask of this event.
* \return The event created.
*/
static std::unique_ptr<GHOST_EventButton> processButtonEvent(GHOST_TEventType type,
GHOST_WindowWin32 *window,
GHOST_TButton mask);
/**
* Creates tablet events from Wintab events.
* \param window: The window receiving the event (the active window).
*/
static void processWintabEvent(GHOST_WindowWin32 *window);
/**
* Creates tablet events from pointer events.
* \param type: The type of pointer event.
* \param window: The window receiving the event (the active window).
* \param wParam: The wParam from the `wndproc`.
* \param lParam: The lParam from the `wndproc`.
* \param eventhandled: True if the method handled the event.
*/
static void processPointerEvent(
UINT type, GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam, bool &eventhandled);
/**
* Creates cursor event.
* \param window: The window receiving the event (the active window).
* \return The event created.
*/
static std::unique_ptr<GHOST_EventCursor> processCursorEvent(GHOST_WindowWin32 *window,
const int32_t screen_co[2]);
/**
* Handles a vertical mouse wheel event.
* \param window: The window receiving the event (the active window).
* \param wParam: The wParam from the `wndproc`.
* \param lParam: The lParam from the `wndproc`.
*/
static void processWheelEventVertical(GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam);
/**
* Handles a horizontal mouse wheel event.
* \param window: The window receiving the event (the active window).
* \param wParam: The wParam from the `wndproc`.
* \param lParam: The lParam from the `wndproc`.
*/
static void processWheelEventHorizontal(GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam);
/**
* Creates a key event and updates the key data stored locally (modifier_keys_).
* In most cases this is a straightforward conversion of key codes.
* For the modifier keys however, we want to distinguish left and right keys.
* \param window: The window receiving the event (the active window).
* \param raw: RawInput structure with detailed info about the key event.
*/
static std::unique_ptr<GHOST_EventKey> processKeyEvent(GHOST_WindowWin32 *window,
RAWINPUT const &raw);
/**
* Process special keys `VK_OEM_*`, to see if current key layout
* gives us anything special, like `!` on French AZERTY.
* \param vKey: The virtual key from #hardKey.
* \param scanCode: The ScanCode of pressed key (similar to PS/2 Set 1).
*/
GHOST_TKey processSpecialKey(short vKey, short scanCode) const;
/**
* Creates a window size event.
* \param window: The window receiving the event (the active window).
* \return The event created.
*/
static std::unique_ptr<GHOST_Event> processWindowSizeEvent(GHOST_WindowWin32 *window);
/**
* Creates a window event.
* \param type: The type of event to create.
* \param window: The window receiving the event (the active window).
* \return The event created.
*/
static std::unique_ptr<GHOST_Event> processWindowEvent(GHOST_TEventType type,
GHOST_WindowWin32 *window);
#ifdef WITH_INPUT_IME
/**
* Creates a IME event.
* \param type: The type of event to create.
* \param window: The window receiving the event (the active window).
* \param data: IME data.
* \return The event created.
*/
static std::unique_ptr<GHOST_Event> processImeEvent(GHOST_TEventType type,
GHOST_WindowWin32 *window,
const GHOST_TEventImeData *data);
#endif /* WITH_INPUT_IME */
/**
* Handles minimum window size.
* \param minmax: The MINMAXINFO structure.
*/
static void processMinMaxInfo(MINMAXINFO *minmax);
#ifdef WITH_INPUT_NDOF
/**
* Handles Motion and Button events from a SpaceNavigator or related device.
* Instead of returning an event object, this function communicates directly
* with the GHOST_NDOFManager.
* \param raw: RawInput structure with detailed info about the NDOF event.
* \return Whether an event was generated and sent.
*/
bool processNDOF(RAWINPUT const &raw);
#endif
/**
* Drives Direct Manipulation update.
*/
void driveTrackpad();
/**
* Creates trackpad events for the active window.
*/
void processTrackpad();
/**
* Check current key layout for AltGr
*/
inline void handleKeyboardChange();
/**
* Windows call back routine for our window class.
*/
static LRESULT WINAPI s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
/**
* Set the Console State
* \param action: console state
* \return current status (1 -visible, 0 - hidden)
*/
bool setConsoleWindowState(GHOST_TConsoleWindowState action) override;
/** State variable set at initialization. */
bool has_performance_counter_;
/** High frequency timer variable. */
__int64 freq_;
/** AltGr on current keyboard layout. */
bool has_alt_gr_;
/** Language identifier. */
WORD lang_id_;
/** Stores keyboard layout. */
HKL keylayout_;
/** Console status. */
bool console_status_;
/** Wheel delta accumulators. */
int wheel_delta_accum_vertical_;
int wheel_delta_accum_horizontal_;
};
inline void GHOST_SystemWin32::handleKeyboardChange()
{
keylayout_ = GetKeyboardLayout(0); /* Get keylayout for current thread. */
int i;
SHORT s;
/* Save the language identifier. */
lang_id_ = LOWORD(keylayout_);
for (has_alt_gr_ = false, i = 32; i < 256; ++i) {
s = VkKeyScanEx((char)i, keylayout_);
/* `s == -1` means no key that translates passed char code high byte contains shift state.
* bit 2 Control pressed, bit 4 `Alt` pressed if both are pressed,
* we have `AltGr` key-combination on key-layout. */
if (s != -1 && (s & 0x600) == 0x600) {
has_alt_gr_ = true;
break;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,394 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_SystemX11 class.
*/
#pragma once
#include <X11/XKBlib.h> /* Allow detectable auto-repeat. */
#include <X11/Xlib.h>
#include "../GHOST_Types.hh"
#include "GHOST_System.hh"
/* For tablets. */
#ifdef WITH_X11_XINPUT
# include <X11/extensions/XInput.h>
/* Disable XINPUT warp, currently not implemented by XORG for multi-head display.
* (see comment in XSERVER `Xi/xiwarppointer.c` -> `FIXME: panoramix stuff is missing` ~ v1.13.4)
* If this is supported we can add back XINPUT for warping (fixing #48901).
* For now disable (see #50383). */
// # define USE_X11_XINPUT_WARP
#endif
#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
# define GHOST_X11_RES_NAME "Blender" /* res_name */
# define GHOST_X11_RES_CLASS "Blender" /* res_class */
#endif
/* generic error handlers */
int GHOST_X11_ApplicationErrorHandler(Display *display, XErrorEvent *event);
int GHOST_X11_ApplicationIOErrorHandler(Display *display);
#define GHOST_X11_ERROR_HANDLERS_OVERRIDE(var) \
struct { \
XErrorHandler handler; \
XIOErrorHandler handler_io; \
} var = { \
XSetErrorHandler(GHOST_X11_ApplicationErrorHandler), \
XSetIOErrorHandler(GHOST_X11_ApplicationIOErrorHandler), \
}
#define GHOST_X11_ERROR_HANDLERS_RESTORE(var) \
{ \
(void)XSetErrorHandler(var.handler); \
(void)XSetIOErrorHandler(var.handler_io); \
} \
((void)0)
class GHOST_WindowX11;
/**
* X11 Implementation of GHOST_System class.
* \see GHOST_System.
*/
class GHOST_SystemX11 : public GHOST_System {
public:
/**
* Constructor
* this class should only be instantiated by GHOST_ISystem.
*/
GHOST_SystemX11();
/**
* Destructor.
*/
~GHOST_SystemX11() override;
GHOST_TSuccess init() override;
/**
* \section Interface Inherited from GHOST_ISystem
*/
/**
* Returns the system time.
* Returns the number of milliseconds since the start of the system process.
* \return The number of milliseconds.
*/
uint64_t getMilliSeconds() const override;
/**
* Returns the number of displays on this system.
* \return The number of displays.
*/
uint8_t getNumDisplays() const override;
/**
* Returns the dimensions of the main display on this system.
* \return The dimension of the main display.
*/
void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override;
/**
* Returns the dimensions of all displays on this system.
* \return The dimension of the main display.
*/
void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const override;
/**
* Create a new window.
* The new window is added to the list of windows managed.
* Never explicitly delete the window, use #disposeWindow() instead.
* \param title: The name of the window.
* (displayed in the title bar of the window if the OS supports it).
* \param left: The coordinate of the left edge of the window.
* \param top: The coordinate of the top edge of the window.
* \param width: The width the window.
* \param height: The height the window.
* \param state: The state of the window when opened.
* \param gpu_settings: Misc GPU settings.
* \param exclusive: Use to show the window on top and ignore others (used full-screen).
* \param parent_window: Parent (embedder) window.
* \return The new window (or 0 if creation failed).
*/
GHOST_IWindow *createWindow(const char *title,
int32_t left,
int32_t top,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
const bool exclusive = false,
const bool is_dialog = false,
const GHOST_IWindow *parent_window = nullptr) override;
/**
* Create a new off-screen context.
* Never explicitly delete the context, use #disposeContext() instead.
* \return The new context (or 0 if creation failed).
*/
GHOST_IContext *createOffscreenContext(GHOST_GPUSettings gpu_settings) override;
/**
* Dispose of a context.
* \param context: Pointer to the context to be disposed.
* \return Indication of success.
*/
GHOST_TSuccess disposeContext(GHOST_IContext *context) override;
/**
* Retrieves events from the system and stores them in the queue.
* \param waitForEvent: Flag to wait for an event (or return immediately).
* \return Indication of the presence of events.
*/
bool processEvents(bool waitForEvent) override;
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override;
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override;
/**
* Get the color of the pixel at the current mouse cursor location
* \param r_color: returned sRGB float colors
* \return Success value (true == successful and supported by platform)
*/
GHOST_TSuccess getPixelAtCursor(float r_color[3]) const override;
/**
* Returns the state of all modifier keys.
* \param keys: The state of all modifier keys (true == pressed).
* \return Indication of success.
*/
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override;
/**
* Returns the state of the mouse buttons (outside the message queue).
* \param buttons: The state of the buttons.
* \return Indication of success.
*/
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override;
GHOST_TCapabilityFlag getCapabilities() const override;
/**
* Flag a window as dirty. This will
* generate a GHOST window update event on a call to processEvents()
*/
void addDirtyWindow(GHOST_WindowX11 *bad_wind);
/**
* return a pointer to the X11 display structure
*/
Display *getXDisplay()
{
return display_;
}
#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
XIM getX11_XIM()
{
return xim_;
}
#endif
/**
* Use this function instead of #GHOST_System::getMilliSeconds,
* passing in the time-stamp from X to input to get the event
* time-stamp with an offset applied to make it compatible with `getMilliSeconds`.
*/
uint64_t ms_from_input_time(const Time timestamp) const;
/** Helped function for get data from the clipboard. */
void getClipboard_xcout(const XEvent *evt,
Atom sel,
Atom target,
unsigned char **txt,
unsigned long *len,
unsigned int *context) const;
/**
* Returns unsigned char from CUT_BUFFER0
* \param selection: Get selection, X11 only feature.
* \return Returns the Clipboard indicated by Flag.
*/
char *getClipboard(bool selection) const override;
/**
* Puts buffer to system clipboard
* \param buffer: The buffer to copy to the clipboard.
* \param selection: Set the selection into the clipboard, X11 only feature.
*/
void putClipboard(const char *buffer, bool selection) const override;
/**
* Show a system message box
* \param title: The title of the message box.
* \param message: The message to display.
* \param help_label: Help button label.
* \param continue_label: Continue button label.
* \param link: An optional hyperlink.
* \param dialog_options: Options how to display the message.
*/
GHOST_TSuccess showMessageBox(const char *title,
const char *message,
const char *help_label,
const char *continue_label,
const char *link,
GHOST_DialogOptions dialog_options) const override;
#ifdef WITH_XDND
/**
* Creates a drag & drop event and pushes it immediately onto the event queue.
* Called by #GHOST_DropTargetX11 class.
* \param eventType: The type of drag & drop event.
* \param draggedObjectType: The type object concerned.
* (currently array of file names, string, ?bitmap)
* \param window: The window on which the event occurred.
* \param mouseX: x mouse coordinate (in window coordinates).
* \param mouseY: y mouse coordinate.
* \return Indication whether the event was handled.
*/
static GHOST_TSuccess pushDragDropEvent(GHOST_TEventType eventType,
GHOST_TDragnDropTypes draggedObjectType,
GHOST_IWindow *window,
int mouseX,
int mouseY,
void *data);
#endif
/**
* \see GHOST_ISystem
*/
bool setConsoleWindowState(GHOST_TConsoleWindowState /*action*/) override
{
return 0;
}
#ifdef WITH_X11_XINPUT
typedef struct GHOST_TabletX11 {
GHOST_TTabletMode mode;
XDevice *Device;
XID ID;
int MotionEvent;
int ProxInEvent;
int ProxOutEvent;
int PressEvent;
int PressureLevels;
int XtiltLevels, YtiltLevels;
} GHOST_TabletX11;
std::vector<GHOST_TabletX11> &GetXTablets()
{
return xtablets_;
}
#endif // WITH_X11_XINPUT
struct {
/**
* Atom used for ICCCM, WM-spec and Motif.
* We only need get this atom at the start, it's relative
* to the display not the window and are public for every
* window that need it.
*/
Atom WM_STATE;
Atom WM_CHANGE_STATE;
Atom _NET_WM_STATE;
Atom _NET_WM_STATE_MAXIMIZED_HORZ;
Atom _NET_WM_STATE_MAXIMIZED_VERT;
Atom _NET_WM_STATE_FULLSCREEN;
Atom _MOTIF_WM_HINTS;
Atom WM_TAKE_FOCUS;
Atom WM_PROTOCOLS;
Atom WM_DELETE_WINDOW;
/* Atoms for Selection, copy & paste. */
Atom TARGETS;
Atom STRING;
Atom C_STRING;
Atom COMPOUND_TEXT;
Atom TEXT;
Atom CLIPBOARD;
Atom PRIMARY;
Atom XCLIP_OUT;
Atom INCR;
Atom UTF8_STRING;
#ifdef WITH_X11_XINPUT
Atom TABLET;
#endif
} atom_;
#ifdef WITH_X11_XINPUT
XExtensionVersion xinput_version_;
#endif
private:
Display *display_;
/** Use for scan-code look-ups. */
XkbDescRec *xkb_descr_;
#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
XIM xim_;
#endif
#ifdef WITH_X11_XINPUT
/* Tablet devices */
std::vector<GHOST_TabletX11> xtablets_;
#endif
/** The vector of windows that need to be updated. */
std::vector<GHOST_WindowX11 *> dirty_windows_;
/** A vector of keyboard key masks. */
char keyboard_vector_[32];
/**
* To prevent multiple warp, we store the time of the last warp event
* and stop accumulating all events generated before that.
*/
Time last_warp_x_;
Time last_warp_y_;
/* Detect auto-repeat glitch. */
unsigned int last_release_keycode_;
Time last_release_time_;
#ifdef WITH_X11_XINPUT
/** Last key press or release, to apply to XIM generated events. */
Time last_key_time_;
#endif
uint keycode_last_repeat_key_;
/**
* Return the ghost window associated with the
* X11 window xwind
*/
#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
bool openX11_IM();
#endif
#ifdef WITH_X11_XINPUT
void clearXInputDevices();
void refreshXInputDevices();
#endif
GHOST_WindowX11 *findGhostWindow(Window xwind) const;
void processEvent(XEvent *xe);
Time lastEventTime(Time default_time);
bool generateWindowExposeEvents();
};

View File

@@ -0,0 +1,116 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#ifndef WIN32
# error WIN32 only!
#endif // WIN32
#include <shlobj.h>
#include <windows.h>
// ITaskbarList, ITaskbarList2 and ITaskbarList3 might be missing, present here in that case.
// Note, ITaskbarList3 is supported only since Windows 7, though. Check for that is done in
// GHOST_WindowWin32
#ifndef __ITaskbarList_INTERFACE_DEFINED__
# define __ITaskbarList_INTERFACE_DEFINED__
extern "C" {
const GUID CLSID_TaskbarList = {
0x56FDF344, 0xFD6D, 0x11D0, {0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90}};
const GUID IID_ITaskbarList = {
0x56FDF342, 0xFD6D, 0x11D0, {0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90}};
}
class ITaskbarList : public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE HrInit(void) = 0;
virtual HRESULT STDMETHODCALLTYPE AddTab(HWND hwnd) = 0;
virtual HRESULT STDMETHODCALLTYPE DeleteTab(HWND hwnd) = 0;
virtual HRESULT STDMETHODCALLTYPE ActivateTab(HWND hwnd) = 0;
virtual HRESULT STDMETHODCALLTYPE SetActiveAlt(HWND hwnd) = 0;
};
#endif /* ITaskbarList */
#ifndef __ITaskbarList2_INTERFACE_DEFINED__
# define __ITaskbarList2_INTERFACE_DEFINED__
extern "C" {
const GUID IID_ITaskbarList2 = {
0x602D4995, 0xB13A, 0x429b, {0xA6, 0x6E, 0x19, 0x35, 0xE4, 0x4F, 0x43, 0x17}};
}
class ITaskbarList2 : public ITaskbarList {
public:
virtual HRESULT STDMETHODCALLTYPE MarkFullscreenWindow(HWND hwnd, BOOL fFullscreen) = 0;
};
#endif /* ITaskbarList2 */
#ifndef __ITaskbarList3_INTERFACE_DEFINED__
# define __ITaskbarList3_INTERFACE_DEFINED__
typedef enum THUMBBUTTONFLAGS {
THBF_ENABLED = 0,
THBF_DISABLED = 0x1,
THBF_DISMISSONCLICK = 0x2,
THBF_NOBACKGROUND = 0x4,
THBF_HIDDEN = 0x8,
THBF_NONINTERACTIVE = 0x10,
} THUMBBUTTONFLAGS;
typedef enum THUMBBUTTONMASK {
THB_BITMAP = 0x1,
THB_ICON = 0x2,
THB_TOOLTIP = 0x4,
THB_FLAGS = 0x8,
} THUMBBUTTONMASK;
typedef struct THUMBBUTTON {
THUMBBUTTONMASK dwMask;
UINT iId;
UINT iBitmap;
HICON hIcon;
WCHAR szTip[260];
THUMBBUTTONFLAGS dwFlags;
} THUMBBUTTON;
typedef enum TBPFLAG {
TBPF_NOPROGRESS = 0,
TBPF_INDETERMINATE = 0x1,
TBPF_NORMAL = 0x2,
TBPF_ERROR = 0x4,
TBPF_PAUSED = 0x8,
} TBPFLAG;
# define THBN_CLICKED 0x1800
extern "C" {
const GUID IID_ITaskList3 = {
0xEA1AFB91, 0x9E28, 0x4B86, {0x90, 0xE9, 0x9E, 0x9F, 0x8A, 0x5E, 0xEF, 0xAF}};
}
class ITaskbarList3 : public ITaskbarList2 {
public:
virtual HRESULT STDMETHODCALLTYPE SetProgressValue(HWND hwnd,
ULONGLONG ullCompleted,
ULONGLONG ullTotal) = 0;
virtual HRESULT STDMETHODCALLTYPE SetProgressState(HWND hwnd, TBPFLAG tbpFlags) = 0;
virtual HRESULT STDMETHODCALLTYPE RegisterTab(HWND hwndTab, HWND hwndMDI) = 0;
virtual HRESULT STDMETHODCALLTYPE UnregisterTab(HWND hwndTab) = 0;
virtual HRESULT STDMETHODCALLTYPE SetTabOrder(HWND hwndTab, HWND hwndInsertBefore) = 0;
virtual HRESULT STDMETHODCALLTYPE SetTabActive(HWND hwndTab, HWND hwndMDI, DWORD dwReserved) = 0;
virtual HRESULT STDMETHODCALLTYPE ThumbBarAddButtons(HWND hwnd,
UINT cButtons,
THUMBBUTTON *pButton) = 0;
virtual HRESULT STDMETHODCALLTYPE ThumbBarUpdateButtons(HWND hwnd,
UINT cButtons,
THUMBBUTTON *pButton) = 0;
virtual HRESULT STDMETHODCALLTYPE ThumbBarSetImageList(HWND hwnd, HIMAGELIST himl) = 0;
virtual HRESULT STDMETHODCALLTYPE SetOverlayIcon(HWND hwnd,
HICON hIcon,
LPCWSTR pszDescription) = 0;
virtual HRESULT STDMETHODCALLTYPE SetThumbnailTooltip(HWND hwnd, LPCWSTR pszTip) = 0;
virtual HRESULT STDMETHODCALLTYPE SetThumbnailClip(HWND hwnd, RECT *prcClip) = 0;
};
#endif /* ITaskbarList3 */

View File

@@ -0,0 +1,123 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
/**
* Copyright (C) 2001 NaN Technologies B.V.
*/
#include "GHOST_TimerManager.hh"
#include <algorithm>
#include "GHOST_TimerTask.hh"
GHOST_TimerManager::GHOST_TimerManager() = default;
GHOST_TimerManager::~GHOST_TimerManager()
{
disposeTimers();
}
uint32_t GHOST_TimerManager::getNumTimers()
{
return uint32_t(timers_.size());
}
bool GHOST_TimerManager::getTimerFound(GHOST_TimerTask *timer)
{
TTimerVector::const_iterator iter = std::find(timers_.begin(), timers_.end(), timer);
return iter != timers_.end();
}
GHOST_TSuccess GHOST_TimerManager::addTimer(GHOST_TimerTask *timer)
{
GHOST_TSuccess success;
if (!getTimerFound(timer)) {
/* Add the timer task. */
timers_.push_back(timer);
success = GHOST_kSuccess;
}
else {
success = GHOST_kFailure;
}
return success;
}
GHOST_TSuccess GHOST_TimerManager::removeTimer(GHOST_TimerTask *timer)
{
GHOST_TSuccess success;
TTimerVector::iterator iter = std::find(timers_.begin(), timers_.end(), timer);
if (iter != timers_.end()) {
/* Remove the timer task. */
timers_.erase(iter);
delete timer;
success = GHOST_kSuccess;
}
else {
success = GHOST_kFailure;
}
return success;
}
uint64_t GHOST_TimerManager::nextFireTime()
{
uint64_t smallest = GHOST_kFireTimeNever;
TTimerVector::iterator iter;
for (iter = timers_.begin(); iter != timers_.end(); ++iter) {
const uint64_t next = (*iter)->getNext();
smallest = std::min(next, smallest);
}
return smallest;
}
bool GHOST_TimerManager::fireTimers(uint64_t time)
{
TTimerVector::iterator iter;
bool anyProcessed = false;
for (iter = timers_.begin(); iter != timers_.end(); ++iter) {
if (fireTimer(time, *iter)) {
anyProcessed = true;
}
}
return anyProcessed;
}
bool GHOST_TimerManager::fireTimer(uint64_t time, GHOST_TimerTask *task)
{
uint64_t next = task->getNext();
/* Check if the timer should be fired. */
if (time > next) {
/* Fire the timer. */
GHOST_TimerProcPtr timer_proc = task->getTimerProc();
uint64_t start = task->getStart();
timer_proc(task, time - start);
/* Update the time at which we will fire it again. */
uint64_t interval = task->getInterval();
uint64_t numCalls = (next - start) / interval;
numCalls++;
next = start + numCalls * interval;
task->setNext(next);
return true;
}
return false;
}
void GHOST_TimerManager::disposeTimers()
{
while (timers_.empty() == false) {
delete timers_[0];
timers_.erase(timers_.begin());
}
}

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_TimerManager class.
*/
#pragma once
#include <vector>
#include "GHOST_Types.hh"
class GHOST_TimerTask;
/**
* Manages a list of timer tasks.
* Timer tasks added are owned by the manager.
* Don't delete timer task objects.
*/
class GHOST_TimerManager {
public:
/**
* Constructor.
*/
GHOST_TimerManager();
/**
* Destructor.
*/
~GHOST_TimerManager();
/**
* Returns the number of timer tasks.
* \return The number of events on the stack.
*/
uint32_t getNumTimers();
/**
* Returns whether this timer task ins in our list.
* \return Indication of presence.
*/
bool getTimerFound(GHOST_TimerTask *timer);
/**
* Adds a timer task to the list.
* It is only added when it not already present in the list.
* \param timer: The timer task added to the list.
* \return Indication as to whether addition has succeeded.
*/
GHOST_TSuccess addTimer(GHOST_TimerTask *timer);
/**
* Removes a timer task from the list.
* It is only removed when it is found in the list.
* \param timer: The timer task to be removed from the list.
* \return Indication as to whether removal has succeeded.
*/
GHOST_TSuccess removeTimer(GHOST_TimerTask *timer);
/**
* Finds the soonest time the next timer would fire.
* \return The soonest time the next timer would fire,
* or GHOST_kFireTimeNever if no timers exist.
*/
uint64_t nextFireTime();
/**
* Checks all timer tasks to see if they are expired and fires them if needed.
* \param time: The current time.
* \return True if any timers were fired.
*/
bool fireTimers(uint64_t time);
/**
* Checks this timer task to see if they are expired and fires them if needed.
* \param time: The current time.
* \param task: The timer task to check and optionally fire.
* \return True if the timer fired.
*/
bool fireTimer(uint64_t time, GHOST_TimerTask *task);
protected:
/**
* Deletes all timers.
*/
void disposeTimers();
using TTimerVector = std::vector<GHOST_TimerTask *>;
/** The list with event consumers. */
TTimerVector timers_;
MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_TimerManager")
};

View File

@@ -0,0 +1,152 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST_TimerTask class.
*/
#pragma once
#include "GHOST_ITimerTask.hh"
/**
* Implementation of a timer task.
*/
class GHOST_TimerTask : public GHOST_ITimerTask {
public:
/**
* Constructor.
* \param start: The timer start time.
* \param interval: The interval between calls to the #timer_proc.
* \param timer_proc: The callback invoked when the interval expires.
* \param user_data: The timer user data.
*/
GHOST_TimerTask(uint64_t start,
uint64_t interval,
GHOST_TimerProcPtr timer_proc,
GHOST_TUserDataPtr user_data = nullptr)
: start_(start),
interval_(interval),
next_(start),
timer_proc_(timer_proc),
user_data_(user_data)
{
}
/**
* Returns the timer start time.
* \return The timer start time.
*/
uint64_t getStart() const
{
return start_;
}
/**
* Changes the timer start time.
* \param start: The timer start time.
*/
void setStart(uint64_t start)
{
start_ = start;
}
/**
* Returns the timer interval.
* \return The timer interval.
*/
uint64_t getInterval() const
{
return interval_;
}
/**
* Changes the timer interval.
* \param interval: The timer interval.
*/
void setInterval(uint64_t interval)
{
interval_ = interval;
}
/**
* Returns the time the timer_proc will be called.
* \return The time the timer_proc will be called.
*/
uint64_t getNext() const
{
return next_;
}
/**
* Changes the time the timer_proc will be called.
* \param next: The time the timer_proc will be called.
*/
void setNext(uint64_t next)
{
next_ = next;
}
/** \copydoc #GHOST_ITimerTask::getTimerProc */
GHOST_TimerProcPtr getTimerProc() const override
{
return timer_proc_;
}
/** \copydoc #GHOST_ITimerTask::setTimerProc */
void setTimerProc(const GHOST_TimerProcPtr timer_proc) override
{
timer_proc_ = timer_proc;
}
/** \copydoc #GHOST_ITimerTask::getUserData */
GHOST_TUserDataPtr getUserData() const override
{
return user_data_;
}
/** \copydoc #GHOST_ITimerTask::setUserData */
void setUserData(const GHOST_TUserDataPtr user_data) override
{
user_data_ = user_data;
}
/**
* Returns the auxiliary storage room.
* \return The auxiliary storage room.
*/
uint32_t getAuxData() const
{
return aux_data_;
}
/**
* Changes the auxiliary storage room.
* \param auxData: The auxiliary storage room.
*/
void setAuxData(uint32_t auxData)
{
aux_data_ = auxData;
}
protected:
/** The time the timer task was started. */
uint64_t start_;
/** The interval between calls. */
uint64_t interval_;
/** The time the timer_proc will be called. */
uint64_t next_;
/** The callback invoked when the timer expires. */
GHOST_TimerProcPtr timer_proc_;
/** The timer task user data. */
GHOST_TUserDataPtr user_data_;
/** Auxiliary storage room. */
uint32_t aux_data_ = 0;
};

View File

@@ -0,0 +1,353 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include <cmath>
#include "GHOST_Debug.hh"
#include "GHOST_TrackpadWin32.hh"
GHOST_DirectManipulationHelper::GHOST_DirectManipulationHelper(
HWND hWnd,
Microsoft::WRL::ComPtr<IDirectManipulationManager> directManipulationManager,
Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> directManipulationUpdateManager,
Microsoft::WRL::ComPtr<IDirectManipulationViewport> directManipulationViewport,
Microsoft::WRL::ComPtr<GHOST_DirectManipulationViewportEventHandler>
directManipulationEventHandler,
DWORD directManipulationViewportHandlerCookie,
bool isScrollDirectionInverted)
: h_wnd_(hWnd),
scroll_direction_reg_key_(nullptr),
scroll_direction_change_event_(nullptr),
direct_manipulation_manager_(directManipulationManager),
direct_manipulation_update_manager_(directManipulationUpdateManager),
direct_manipulation_viewport_(directManipulationViewport),
direct_manipulation_event_handler_(directManipulationEventHandler),
direct_manipulation_viewport_handler_cookie_(directManipulationViewportHandlerCookie),
is_scroll_direction_inverted_(isScrollDirectionInverted)
{
}
GHOST_DirectManipulationHelper *GHOST_DirectManipulationHelper::create(HWND hWnd, uint16_t dpi)
{
#define DM_CHECK_RESULT_AND_EXIT_EARLY(hr, failMessage) \
{ \
if (!SUCCEEDED(hr)) { \
GHOST_PRINT(failMessage); \
return nullptr; \
} \
}
Microsoft::WRL::ComPtr<IDirectManipulationManager> directManipulationManager;
HRESULT hr = ::CoCreateInstance(CLSID_DirectManipulationManager,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&directManipulationManager));
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "DirectManipulationManager create failed\n");
/* Since we want to use fake viewport, we need to send fake updates to UpdateManager. */
Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> directManipulationUpdateManager;
hr = directManipulationManager->GetUpdateManager(IID_PPV_ARGS(&directManipulationUpdateManager));
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Get UpdateManager failed\n");
Microsoft::WRL::ComPtr<IDirectManipulationViewport> directManipulationViewport;
hr = directManipulationManager->CreateViewport(
nullptr, hWnd, IID_PPV_ARGS(&directManipulationViewport));
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport create failed\n");
DIRECTMANIPULATION_CONFIGURATION configuration =
DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |
DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA |
DIRECTMANIPULATION_CONFIGURATION_SCALING;
hr = directManipulationViewport->ActivateConfiguration(configuration);
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport set ActivateConfiguration failed\n");
/* Since we are using fake viewport and only want to use Direct Manipulation for touchpad, we
* need to use MANUALUPDATE option. */
hr = directManipulationViewport->SetViewportOptions(
DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport set ViewportOptions failed\n");
/* We receive Direct Manipulation transform updates in IDirectManipulationViewportEventHandler
* callbacks. */
Microsoft::WRL::ComPtr<GHOST_DirectManipulationViewportEventHandler>
directManipulationEventHandler =
Microsoft::WRL::Make<GHOST_DirectManipulationViewportEventHandler>(dpi);
DWORD directManipulationViewportHandlerCookie;
directManipulationViewport->AddEventHandler(
hWnd, directManipulationEventHandler.Get(), &directManipulationViewportHandlerCookie);
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport add EventHandler failed\n");
/* Set default rect for viewport before activating. */
RECT rect = {0, 0, 10000, 10000};
hr = directManipulationViewport->SetViewportRect(&rect);
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport set rect failed\n");
hr = directManipulationManager->Activate(hWnd);
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "DirectManipulationManager activate failed\n");
hr = directManipulationViewport->Enable();
DM_CHECK_RESULT_AND_EXIT_EARLY(hr, "Viewport enable failed\n");
directManipulationEventHandler->resetViewport(directManipulationViewport.Get());
bool isScrollDirectionInverted = getScrollDirectionFromReg();
auto instance = new GHOST_DirectManipulationHelper(hWnd,
directManipulationManager,
directManipulationUpdateManager,
directManipulationViewport,
directManipulationEventHandler,
directManipulationViewportHandlerCookie,
isScrollDirectionInverted);
instance->registerScrollDirectionChangeListener();
return instance;
#undef DM_CHECK_RESULT_AND_EXIT_EARLY
}
bool GHOST_DirectManipulationHelper::getScrollDirectionFromReg()
{
DWORD scrollDirectionRegValue = 0;
DWORD pcbData = sizeof(scrollDirectionRegValue);
HRESULT hr = HRESULT_FROM_WIN32(
RegGetValueW(HKEY_CURRENT_USER,
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PrecisionTouchPad\\",
L"ScrollDirection",
RRF_RT_REG_DWORD,
nullptr,
&scrollDirectionRegValue,
&pcbData));
if (!SUCCEEDED(hr)) {
GHOST_PRINT("Failed to get scroll direction from registry\n");
/* This is the default value for scroll direction, not a failure/success indicator. */
return true;
}
/* Return false for natural scrolling direction (upward motion results in content scrolling
* downward), and true for reversed direction. */
return scrollDirectionRegValue == 0;
}
void GHOST_DirectManipulationHelper::registerScrollDirectionChangeListener()
{
if (!scroll_direction_reg_key_) {
HRESULT hr = HRESULT_FROM_WIN32(
RegOpenKeyExW(HKEY_CURRENT_USER,
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PrecisionTouchPad\\",
0,
KEY_NOTIFY,
&scroll_direction_reg_key_));
if (!SUCCEEDED(hr)) {
GHOST_PRINT("Failed to open scroll direction registry key\n");
return;
}
}
if (!scroll_direction_change_event_) {
scroll_direction_change_event_ = CreateEventW(nullptr, true, false, nullptr);
}
else {
ResetEvent(scroll_direction_change_event_);
}
HRESULT hr = HRESULT_FROM_WIN32(RegNotifyChangeKeyValue(scroll_direction_reg_key_,
true,
REG_NOTIFY_CHANGE_LAST_SET,
scroll_direction_change_event_,
true));
if (!SUCCEEDED(hr)) {
GHOST_PRINT("Failed to register scroll direction change listener\n");
return;
}
}
void GHOST_DirectManipulationHelper::onPointerHitTest(UINT32 pointerId)
{
[[maybe_unused]] HRESULT hr = direct_manipulation_viewport_->SetContact(pointerId);
GHOST_ASSERT(SUCCEEDED(hr), "Viewport set contact failed\n");
if (WaitForSingleObject(scroll_direction_change_event_, 0) == WAIT_OBJECT_0) {
is_scroll_direction_inverted_ = getScrollDirectionFromReg();
registerScrollDirectionChangeListener();
}
}
void GHOST_DirectManipulationHelper::update()
{
if (direct_manipulation_event_handler_->dm_status == DIRECTMANIPULATION_RUNNING ||
direct_manipulation_event_handler_->dm_status == DIRECTMANIPULATION_INERTIA)
{
[[maybe_unused]] HRESULT hr = direct_manipulation_update_manager_->Update(nullptr);
GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationUpdateManager update failed\n");
}
}
void GHOST_DirectManipulationHelper::setDPI(uint16_t dpi)
{
direct_manipulation_event_handler_->dpi = dpi;
}
GHOST_TTrackpadInfo GHOST_DirectManipulationHelper::getTrackpadInfo()
{
GHOST_TTrackpadInfo result = direct_manipulation_event_handler_->accumulated_values;
result.isScrollDirectionInverted = is_scroll_direction_inverted_;
direct_manipulation_event_handler_->accumulated_values = {0, 0, 0};
return result;
}
GHOST_DirectManipulationHelper::~GHOST_DirectManipulationHelper()
{
[[maybe_unused]] HRESULT hr;
hr = direct_manipulation_viewport_->Stop();
GHOST_ASSERT(SUCCEEDED(hr), "Viewport stop failed\n");
hr = direct_manipulation_viewport_->RemoveEventHandler(
direct_manipulation_viewport_handler_cookie_);
GHOST_ASSERT(SUCCEEDED(hr), "Viewport remove event handler failed\n");
hr = direct_manipulation_viewport_->Abandon();
GHOST_ASSERT(SUCCEEDED(hr), "Viewport abandon failed\n");
hr = direct_manipulation_manager_->Deactivate(h_wnd_);
GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationManager deactivate failed\n");
if (scroll_direction_change_event_) {
CloseHandle(scroll_direction_change_event_);
scroll_direction_change_event_ = nullptr;
}
if (scroll_direction_reg_key_) {
RegCloseKey(scroll_direction_reg_key_);
scroll_direction_reg_key_ = nullptr;
}
}
GHOST_DirectManipulationViewportEventHandler::GHOST_DirectManipulationViewportEventHandler(
uint16_t dpi)
: accumulated_values({0, 0, 0}), dpi(dpi), dm_status(DIRECTMANIPULATION_BUILDING)
{
}
void GHOST_DirectManipulationViewportEventHandler::resetViewport(
IDirectManipulationViewport *viewport)
{
if (gesture_state != GESTURE_NONE) {
[[maybe_unused]] HRESULT hr = viewport->ZoomToRect(0.0f, 0.0f, 10000.0f, 10000.0f, FALSE);
GHOST_ASSERT(SUCCEEDED(hr), "Viewport reset failed\n");
}
gesture_state = GESTURE_NONE;
last_scale = PINCH_SCALE_FACTOR;
last_x = 0.0f;
last_y = 0.0f;
}
HRESULT GHOST_DirectManipulationViewportEventHandler::OnViewportStatusChanged(
IDirectManipulationViewport *viewport,
DIRECTMANIPULATION_STATUS current,
DIRECTMANIPULATION_STATUS previous)
{
dm_status = current;
if (current == previous) {
return S_OK;
}
if (previous == DIRECTMANIPULATION_ENABLED || current == DIRECTMANIPULATION_READY ||
(previous == DIRECTMANIPULATION_INERTIA && current != DIRECTMANIPULATION_INERTIA))
{
resetViewport(viewport);
}
return S_OK;
}
HRESULT GHOST_DirectManipulationViewportEventHandler::OnViewportUpdated(
IDirectManipulationViewport * /*viewport*/)
{
/* Nothing to do here. */
return S_OK;
}
HRESULT GHOST_DirectManipulationViewportEventHandler::OnContentUpdated(
IDirectManipulationViewport * /*viewport*/, IDirectManipulationContent *content)
{
float transform[6];
HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform));
GHOST_ASSERT(SUCCEEDED(hr), "DirectManipulationContent get transform failed\n");
const float device_scale_factor = dpi / 96.0f;
const float scale = transform[0] * PINCH_SCALE_FACTOR;
const float x = transform[4] / device_scale_factor;
const float y = transform[5] / device_scale_factor;
const float EPS = 3e-5;
/* Ignore repeating or incorrect input. */
if ((fabs(scale - last_scale) <= EPS && fabs(x - last_x) <= EPS && fabs(y - last_y) <= EPS) ||
scale == 0.0f)
{
GHOST_PRINT("Ignoring touchpad input\n");
return hr;
}
/* Assume that every gesture is a pan in the beginning.
* If it's a pinch, the gesture will be changed below. */
if (gesture_state == GESTURE_NONE) {
gesture_state = GESTURE_PAN;
}
/* DM doesn't always immediately recognize pinch gestures,
* so allow transition from pan to pinch. */
if (gesture_state == GESTURE_PAN) {
if (fabs(scale - PINCH_SCALE_FACTOR) > EPS) {
gesture_state = GESTURE_PINCH;
}
}
/* This state machine is used here because:
* 1. Pinch and pan gestures must be differentiated and cannot be processed at the same time
* because XY transform values become nonsensical during pinch gesture.
* 2. GHOST requires delta values for events while DM provides transformation matrix of the
* current gesture.
* 3. GHOST events accept integer values while DM values are non-integer.
* Truncated fractional parts are accumulated and accounted for in following updates.
*/
switch (gesture_state) {
case GESTURE_PINCH: {
int32_t dscale = roundf(scale - last_scale);
last_scale += dscale;
accumulated_values.scale += dscale;
break;
}
case GESTURE_PAN: {
int32_t dx = roundf(x - last_x);
int32_t dy = roundf(y - last_y);
last_x += dx;
last_y += dy;
accumulated_values.x += dx;
accumulated_values.y += dy;
break;
}
case GESTURE_NONE:
break;
}
return hr;
}

View File

@@ -0,0 +1,140 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
* Declaration of GHOST DirectManipulation classes.
*/
#pragma once
#ifndef WIN32
# error WIN32 only!
#endif // WIN32
#include "GHOST_Types.hh"
#include <directmanipulation.h>
#include <wrl.h>
#define PINCH_SCALE_FACTOR 125.0f
typedef struct {
int32_t x, y, scale;
bool isScrollDirectionInverted;
} GHOST_TTrackpadInfo;
class GHOST_DirectManipulationHelper;
class GHOST_DirectManipulationViewportEventHandler
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::ClassicCom>,
Microsoft::WRL::Implements<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::ClassicCom>,
Microsoft::WRL::FtmBase,
IDirectManipulationViewportEventHandler>> {
public:
GHOST_DirectManipulationViewportEventHandler(uint16_t dpi);
/**
* Resets viewport and tracked touchpad state.
*/
void resetViewport(IDirectManipulationViewport *viewport);
/** DirectManipulation callbacks. */
HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(IDirectManipulationViewport *viewport,
DIRECTMANIPULATION_STATUS current,
DIRECTMANIPULATION_STATUS previous) override;
HRESULT STDMETHODCALLTYPE OnViewportUpdated(IDirectManipulationViewport *viewport) override;
HRESULT STDMETHODCALLTYPE OnContentUpdated(IDirectManipulationViewport *viewport,
IDirectManipulationContent *content) override;
private:
enum { GESTURE_NONE, GESTURE_PAN, GESTURE_PINCH } gesture_state;
int32_t last_x, last_y, last_scale;
GHOST_TTrackpadInfo accumulated_values;
uint16_t dpi;
DIRECTMANIPULATION_STATUS dm_status;
friend class GHOST_DirectManipulationHelper;
};
class GHOST_DirectManipulationHelper {
public:
/**
* Creates a GHOST_DirectManipulationHelper for the provided window.
* \param hWnd: The window receiving DirectManipulation events.
* \param dpi: The current DPI.
* \return Pointer to the new GHOST_DirectManipulationHelper if created, nullptr if there was an
* error.
*/
static GHOST_DirectManipulationHelper *create(HWND hWnd, uint16_t dpi);
~GHOST_DirectManipulationHelper();
/**
* Drives the DirectManipulation context.
* DirectManipulation's intended use is to tie user input into DirectComposition's compositor
* scaling and translating. We are not using DirectComposition and therefore must drive
* DirectManipulation manually.
*/
void update();
/**
* Sets pointer in contact with the DirectManipulation context.
* \param pointerId: ID of the pointer in contact.
*/
void onPointerHitTest(UINT32 pointerId);
/**
* Updates DPI information for touchpad scaling.
* \param dpi: The new DPI.
*/
void setDPI(uint16_t dpi);
/**
* Retrieves trackpad input.
* \return The accumulated trackpad translation and scale since last call.
*/
GHOST_TTrackpadInfo getTrackpadInfo();
private:
GHOST_DirectManipulationHelper(
HWND hWnd,
Microsoft::WRL::ComPtr<IDirectManipulationManager> directManipulationManager,
Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> directManipulationUpdateManager,
Microsoft::WRL::ComPtr<IDirectManipulationViewport> directManipulationViewport,
Microsoft::WRL::ComPtr<GHOST_DirectManipulationViewportEventHandler>
directManipulationEventHandler,
DWORD directManipulationViewportHandlerCookie,
bool isScrollDirectionInverted);
/**
* Retrieves the scroll direction from the registry.
* \return True if scroll direction is inverted.
*/
static bool getScrollDirectionFromReg();
/**
* Registers listener for registry scroll direction entry changes.
*/
void registerScrollDirectionChangeListener();
HWND h_wnd_;
HKEY scroll_direction_reg_key_;
HANDLE scroll_direction_change_event_;
Microsoft::WRL::ComPtr<IDirectManipulationManager> direct_manipulation_manager_;
Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> direct_manipulation_update_manager_;
Microsoft::WRL::ComPtr<IDirectManipulationViewport> direct_manipulation_viewport_;
Microsoft::WRL::ComPtr<GHOST_DirectManipulationViewportEventHandler>
direct_manipulation_event_handler_;
DWORD direct_manipulation_viewport_handler_cookie_;
bool is_scroll_direction_inverted_;
};

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \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_);
}
}
};

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#pragma once
#undef wl_array_for_each
/**
* This macro causes a warning for C++ code, define our own.
* See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/34
*/
#define WL_ARRAY_FOR_EACH(pos, array) \
for (pos = (decltype(pos))((array)->data); \
reinterpret_cast<const char *>(pos) < \
(reinterpret_cast<const char *>((array)->data) + (array)->size); \
(pos)++)

View File

@@ -0,0 +1,293 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
/**
* Copyright (C) 2001 NaN Technologies B.V.
*/
#include "GHOST_Window.hh"
#include "GHOST_ContextNone.hh"
#include "GHOST_utildefines.hh"
#include <cassert>
GHOST_Window::GHOST_Window(uint32_t width,
uint32_t height,
GHOST_TWindowState state,
const GHOST_ContextParams &context_params,
const bool /*exclusive*/)
: drawing_context_type_(GHOST_kDrawingContextTypeNone),
user_data_(nullptr),
cursor_visible_(true),
cursor_grab_(GHOST_kGrabDisable),
cursor_grab_axis_(GHOST_kAxisNone),
cursor_grab_init_pos_{0, 0},
cursor_grab_accum_pos_{0, 0},
cursor_shape_(GHOST_kStandardCursorDefault),
progress_bar_visible_(false),
can_accept_drag_operation_(false),
is_unsaved_changes_(false),
window_decoration_style_flags_(GHOST_kDecorationNone),
window_decoration_style_settings_(),
want_context_params_(context_params),
native_pixel_size_(1.0f),
context_(nullptr)
{
const GHOST_ContextParams context_params_none = GHOST_CONTEXT_PARAMS_NONE;
context_ = new GHOST_ContextNone(context_params_none);
full_screen_ = state == GHOST_kWindowStateFullScreen;
if (full_screen_) {
full_screen_width_ = width;
full_screen_height_ = height;
}
}
GHOST_Window::~GHOST_Window()
{
delete context_;
}
void *GHOST_Window::getOSWindow() const
{
return nullptr;
}
GHOST_TWindowDecorationStyleFlags GHOST_Window::getWindowDecorationStyleFlags()
{
return window_decoration_style_flags_;
}
void GHOST_Window::setWindowDecorationStyleFlags(GHOST_TWindowDecorationStyleFlags style_flags)
{
window_decoration_style_flags_ = style_flags;
}
void GHOST_Window::setWindowDecorationStyleSettings(
GHOST_WindowDecorationStyleSettings decoration_settings)
{
window_decoration_style_settings_ = decoration_settings;
}
GHOST_TSuccess GHOST_Window::setDrawingContextType(GHOST_TDrawingContextType type)
{
if (type != drawing_context_type_) {
delete context_;
context_ = nullptr;
if (type != GHOST_kDrawingContextTypeNone) {
context_ = newDrawingContext(type);
}
if (context_ != nullptr) {
drawing_context_type_ = type;
}
else {
context_ = new GHOST_ContextNone(want_context_params_);
drawing_context_type_ = GHOST_kDrawingContextTypeNone;
}
return (type == drawing_context_type_) ? GHOST_kSuccess : GHOST_kFailure;
}
return GHOST_kSuccess;
}
GHOST_IContext *GHOST_Window::getDrawingContext()
{
return context_;
}
GHOST_TSuccess GHOST_Window::swapBufferAcquire()
{
return context_->swapBufferAcquire();
}
GHOST_TSuccess GHOST_Window::swapBufferRelease()
{
return context_->swapBufferRelease();
}
GHOST_TSuccess GHOST_Window::setSwapInterval(int interval)
{
return context_->setSwapInterval(interval);
}
GHOST_TSuccess GHOST_Window::getSwapInterval(int &interval_out)
{
return context_->getSwapInterval(interval_out);
}
GHOST_Context *GHOST_Window::getContext()
{
return context_;
}
uint GHOST_Window::getDefaultFramebuffer()
{
return (context_) ? context_->getDefaultFramebuffer() : 0;
}
#ifdef WITH_VULKAN_BACKEND
GHOST_TSuccess GHOST_Window::getVulkanSwapChainFormat(GHOST_VulkanSwapChainData *r_swap_chain_data)
{
return context_->getVulkanSwapChainFormat(r_swap_chain_data);
}
#endif
GHOST_TSuccess GHOST_Window::activateDrawingContext()
{
return context_->activateDrawingContext();
}
GHOST_TSuccess GHOST_Window::updateDrawingContext()
{
return context_->updateDrawingContext();
}
GHOST_TSuccess GHOST_Window::releaseNativeHandles()
{
return context_->releaseNativeHandles();
}
GHOST_TSuccess GHOST_Window::setCursorVisibility(bool visible)
{
if (setWindowCursorVisibility(visible)) {
cursor_visible_ = visible;
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_Window::setCursorGrab(GHOST_TGrabCursorMode mode,
GHOST_TAxisFlag wrap_axis,
GHOST_Rect *bounds,
int32_t mouse_ungrab_xy[2])
{
if (cursor_grab_ == mode) {
return GHOST_kSuccess;
}
/* Override with new location. */
if (mouse_ungrab_xy) {
assert(mode == GHOST_kGrabDisable);
cursor_grab_init_pos_[0] = mouse_ungrab_xy[0];
cursor_grab_init_pos_[1] = mouse_ungrab_xy[1];
}
if (setWindowCursorGrab(mode)) {
if (mode == GHOST_kGrabDisable) {
cursor_grab_bounds_.l_ = cursor_grab_bounds_.r_ = -1;
}
else if (bounds) {
cursor_grab_bounds_ = *bounds;
}
else { /* if bounds not defined, use window */
getClientBounds(cursor_grab_bounds_);
}
cursor_grab_ = mode;
cursor_grab_axis_ = wrap_axis;
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_Window::getCursorGrabBounds(GHOST_Rect &bounds) const
{
if (!ELEM(cursor_grab_, GHOST_kGrabWrap, GHOST_kGrabHide)) {
return GHOST_kFailure;
}
bounds = cursor_grab_bounds_;
return (bounds.l_ == -1 && bounds.r_ == -1) ? GHOST_kFailure : GHOST_kSuccess;
}
void GHOST_Window::getCursorGrabState(GHOST_TGrabCursorMode &mode,
GHOST_TAxisFlag &wrap_axis,
GHOST_Rect &bounds,
bool &use_software_cursor)
{
mode = cursor_grab_;
if (cursor_grab_ == GHOST_kGrabWrap) {
bounds = cursor_grab_bounds_;
wrap_axis = cursor_grab_axis_;
}
else {
bounds.l_ = -1;
bounds.r_ = -1;
bounds.t_ = -1;
bounds.b_ = -1;
wrap_axis = GHOST_kAxisNone;
}
use_software_cursor = (cursor_grab_ != GHOST_kGrabDisable) ? getCursorGrabUseSoftwareDisplay() :
false;
}
bool GHOST_Window::getCursorGrabUseSoftwareDisplay()
{
/* Sub-classes may override, by default don't use software cursor. */
return false;
}
GHOST_TSuccess GHOST_Window::setCursorShape(GHOST_TStandardCursor cursor_shape)
{
if (setWindowCursorShape(cursor_shape)) {
cursor_shape_ = cursor_shape;
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_Window::setCustomCursorShape(const uint8_t *bitmap,
const uint8_t *mask,
const int size[2],
const int hot_spot[2],
bool can_invert_color)
{
if (setWindowCustomCursorShape(bitmap, mask, size, hot_spot, can_invert_color)) {
cursor_shape_ = GHOST_kStandardCursorCustom;
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_Window::setCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator)
{
if (setWindowCustomCursorGenerator(cursor_generator)) {
cursor_shape_ = GHOST_kStandardCursorCustom;
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_Window::getCursorBitmap(GHOST_CursorBitmapRef * /*bitmap*/)
{
/* Sub-classes may override. */
return GHOST_kFailure;
}
void GHOST_Window::setAcceptDragOperation(bool can_accept)
{
can_accept_drag_operation_ = can_accept;
}
bool GHOST_Window::canAcceptDragOperation() const
{
return can_accept_drag_operation_;
}
GHOST_TSuccess GHOST_Window::setModifiedState(bool is_unsaved_changes)
{
is_unsaved_changes_ = is_unsaved_changes;
return GHOST_kSuccess;
}
bool GHOST_Window::getModifiedState()
{
return is_unsaved_changes_;
}

Some files were not shown because too many files have changed in this diff Show More