diff --git a/vstgui/cmake/modules/FindWayland.cmake b/vstgui/cmake/modules/FindWayland.cmake new file mode 100644 index 000000000..0870a04b9 --- /dev/null +++ b/vstgui/cmake/modules/FindWayland.cmake @@ -0,0 +1,120 @@ +# This file is part of VSTGUI. It is subject to the license terms +# in the LICENSE file found in the top-level directory of this +# distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +# Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +include_guard (GLOBAL) + +find_package (PkgConfig) +pkg_check_modules (PKG_WAYLAND QUIET wayland-client) + +set (WAYLAND_DEFINITIONS ${PKG_WAYLAND_CFLAGS}) + +# Find wayland libraries / includes +# When cross-compiling, these libraries and headers need to be found for the target architecture. +find_path (WAYLAND_CLIENT_INCLUDE_DIR NAMES wayland-client.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS} ONLY_CMAKE_FIND_ROOT_PATH) +mark_as_advanced (WAYLAND_CLIENT_INCLUDE_DIR) + +if ("client" IN_LIST Wayland_FIND_COMPONENTS) + find_library (WAYLAND_CLIENT_LIBRARIES NAMES wayland-client HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) + find_library (WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) +endif () + +if ("egl" IN_LIST Wayland_FIND_COMPONENTS) + find_library (WAYLAND_EGL_LIBRARIES NAMES wayland-egl HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) +endif () + +if ("server" IN_LIST Wayland_FIND_COMPONENTS) + find_library (WAYLAND_SERVER_LIBRARIES NAMES wayland-server HINTS ${PKG_WAYLAND_LIBRARY_DIRS}) +endif () + +if ("protocols" IN_LIST Wayland_FIND_COMPONENTS) + + # Find wayland-scanner + find_program (WAYLAND_SCANNER NAMES "wayland-scanner") + if (NOT WAYLAND_SCANNER) + find_program (WAYLAND_SCANNER NAMES wayland-scanner) + endif () + mark_as_advanced (WAYLAND_SCANNER) + + # Generate extra protocol headers + + find_path (XDG_DIR NAMES "stable/xdg-shell/xdg-shell.xml" HINTS "/usr/share/wayland-protocols") + mark_as_advanced (XDG_DIR) + + set (protocols + "stable/xdg-shell/xdg-shell.xml" + #"staging/xdg-activation/xdg-activation-v1.xml" + #"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" + #"unstable/xdg-foreign/xdg-foreign-unstable-v1.xml" + #"unstable/xdg-foreign/xdg-foreign-unstable-v2.xml" + #"unstable/text-input/text-input-unstable-v3.xml" + ) + + set (WAYLAND_PROTOCOLS_DIR "${CMAKE_BINARY_DIR}/wayland-protocols" CACHE PATH "Directory for generated wayland protocol headers and source files") + file (MAKE_DIRECTORY ${WAYLAND_PROTOCOLS_DIR}) + + foreach (protocol ${protocols}) + + get_filename_component (protocol_name "${protocol}" NAME_WLE) + + # Generate client header + set (header "${WAYLAND_PROTOCOLS_DIR}/${protocol_name}-client-protocol.h") + if (NOT EXISTS "${XDG_DIR}/${protocol}") + message (WARNING "Unknown Wayland protocol: ${protocol_name}") + file (WRITE "${header}" "") + continue () + endif () + + add_custom_command (OUTPUT ${header} + COMMAND /bin/sh -c "${WAYLAND_SCANNER} client-header < ${XDG_DIR}/${protocol} > \"${header}\"" + DEPENDS ${XDG_DIR}/${protocol} + VERBATIM USES_TERMINAL + ) + list (APPEND protocol_headers ${header}) + + # Generate server header + set (header "${WAYLAND_PROTOCOLS_DIR}/${protocol_name}-server-protocol.h") + + add_custom_command (OUTPUT ${header} + COMMAND /bin/sh -c "${WAYLAND_SCANNER} server-header < ${XDG_DIR}/${protocol} > \"${header}\"" + DEPENDS ${XDG_DIR}/${protocol} + VERBATIM USES_TERMINAL + ) + list (APPEND protocol_headers ${header}) + + # Generate source file + set (sourcefile "${WAYLAND_PROTOCOLS_DIR}/${protocol_name}-protocol.c") + add_custom_command (OUTPUT ${sourcefile} + COMMAND /bin/sh -c "${WAYLAND_SCANNER} private-code < ${XDG_DIR}/${protocol} > \"${sourcefile}\"" + DEPENDS ${XDG_DIR}/${protocol} + VERBATIM USES_TERMINAL + ) + list (APPEND protocol_source_files "${sourcefile}") + endforeach () + + add_library (wayland_protocols OBJECT ${protocol_headers} ${protocol_source_files} ${CMAKE_CURRENT_LIST_FILE}) + + set_target_properties (wayland_protocols PROPERTIES + USE_FOLDERS ON + FOLDER libs + ) + +endif () + +# Set result variables +set (WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_CURSOR_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES}) + +set (WAYLAND_INCLUDE_DIRS ${WAYLAND_CLIENT_INCLUDE_DIR} ${WAYLAND_PROTOCOLS_DIR}) +list (REMOVE_DUPLICATES WAYLAND_INCLUDE_DIRS) + +if (TARGET wayland_protocols) + list (APPEND WAYLAND_LIBRARIES wayland_protocols) + target_include_directories (wayland_protocols PUBLIC ${WAYLAND_INCLUDE_DIRS}) +endif () + +include (FindPackageHandleStandardArgs) +find_package_handle_standard_args (Wayland + FOUND_VAR WAYLAND_FOUND + REQUIRED_VARS WAYLAND_LIBRARIES WAYLAND_INCLUDE_DIRS +) diff --git a/vstgui/lib/CMakeLists.txt b/vstgui/lib/CMakeLists.txt index 79e4fb44d..c88026827 100644 --- a/vstgui/lib/CMakeLists.txt +++ b/vstgui/lib/CMakeLists.txt @@ -181,6 +181,7 @@ set(${target}_common_sources platform/platform_macos.h platform/platform_win32.h platform/platform_x11.h + platform/platform_wayland.h platform/std_unorderedmap.h platform/common/fileresourceinputstream.cpp platform/common/fileresourceinputstream.h @@ -318,6 +319,12 @@ set(${target}_linux_sources platform/linux/x11timer.h platform/linux/x11utils.cpp platform/linux/x11utils.h + platform/linux/waylandframe.cpp + platform/linux/waylandframe.h + platform/linux/waylandplatform.cpp + platform/linux/waylandplatform.h + platform/linux/waylandutils.cpp + platform/linux/waylandutils.h platform/linux/linuxfactory.cpp platform/linux/linuxfactory.h ) @@ -336,11 +343,17 @@ endif() # Linux if(LINUX) set(${target}_sources ${${target}_common_sources} ${${target}_linux_sources}) + + find_package (Wayland REQUIRED COMPONENTS client protocols) endif() ########################################################################################## add_library(${target} STATIC ${${target}_sources}) +find_path (presonusinterfaces_dir NAMES ipslviewrendering.h HINTS ${SDK_ROOT}/presonus) + +target_include_directories (${target} PRIVATE ${presonusinterfaces_dir} ${SDK_ROOT}) + target_compile_definitions(${target} ${VSTGUI_COMPILE_DEFINITIONS}) vstgui_set_cxx_version(${target} 17) vstgui_source_group_by_folder(${target}) @@ -352,7 +365,7 @@ if(LINUX) target_include_directories(${target} PRIVATE ${CAIRO_INCLUDE_DIRS}) target_include_directories(${target} PRIVATE ${PANGO_INCLUDE_DIRS}) target_include_directories(${target} PRIVATE ${FONTCONFIG_INCLUDE_DIRS}) - target_link_libraries(${target} PRIVATE ${LINUX_LIBRARIES}) + target_link_libraries(${target} PRIVATE ${LINUX_LIBRARIES} ${WAYLAND_LIBRARIES}) endif() if(CMAKE_HOST_APPLE) diff --git a/vstgui/lib/cframe.cpp b/vstgui/lib/cframe.cpp index e809e95ee..d1cfb9d2a 100644 --- a/vstgui/lib/cframe.cpp +++ b/vstgui/lib/cframe.cpp @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// This file is part of VSTGUI. It is subject to the license terms // in the LICENSE file found in the top-level directory of this // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE @@ -73,7 +73,7 @@ struct CFrame::Impl CView* focusView {nullptr}; CView* activeFocusView {nullptr}; CollectInvalidRects* collectInvalidRects {nullptr}; - + ViewList mouseViews; ModalViewSessionStack modalViewSessionStack; DispatchList windowActiveStateChangeViews; @@ -121,9 +121,9 @@ struct CFrame::Impl // CFrame Implementation //----------------------------------------------------------------------------- /*! @class CFrame -It creates a platform dependend view object. +It creates a platform dependend view object. -On Mac OS X it is a HIView or NSView.\n +On Mac OS X it is a HIView or NSView.\n On Windows it's a WS_CHILD Window. */ @@ -156,7 +156,7 @@ void CFrame::beforeDelete () { DebugPrint ("Warning: Scale Factor Changed Listeners are not cleaned up correctly.\n If you register a change listener you must also unregister it !\n"); } - + if (!pImpl->mouseObservers.empty ()) { DebugPrint ("Warning: Mouse Observers are not cleaned up correctly.\n If you register a mouse oberver you must also unregister it !\n"); @@ -175,10 +175,10 @@ void CFrame::beforeDelete () } setViewFlag (kIsAttached, false); - + delete pImpl; pImpl = nullptr; - + CViewContainer::beforeDelete (); } @@ -203,7 +203,7 @@ void CFrame::close () //----------------------------------------------------------------------------- bool CFrame::open (void* systemWin, PlatformType systemWindowType, IPlatformFrameConfig* config) { - if (!systemWin || isAttached ()) + if ((!systemWin && systemWindowType != PlatformType::kWaylandSurface) || isAttached ()) return false; pImpl->platformFrame = getPlatformFactory ().createFrame (this, getViewSize (), systemWin, @@ -216,7 +216,7 @@ bool CFrame::open (void* systemWin, PlatformType systemWindowType, IPlatformFram CollectInvalidRects cir (this); attached (this); - + setParentView (nullptr); invalid (); @@ -236,7 +236,7 @@ bool CFrame::attached (CView* parent) for (const auto& pV : getChildren ()) pV->attached (this); - + return true; } return false; @@ -685,7 +685,7 @@ void CFrame::dispatchMouseUpEvent (MouseUpEvent& event) { auto transformedMousePosition = event.mousePosition; getTransform ().inverse ().transform (transformedMousePosition); - + auto f = finally ([this] () { setMouseDownView (nullptr); }); callMouseObserverOtherMouseEvent (event); @@ -950,7 +950,7 @@ bool CFrame::setModalView (CView* pView) endLegacyModalViewSession (); else pImpl->legacyModalViewSessionID = beginModalViewSession (pView); - + return true; } @@ -1226,11 +1226,11 @@ void CFrame::setFocusView (CView *pView) } if (pImpl->focusView && pImpl->focusView->wantsFocus ()) pImpl->focusView->takeFocus (); - + pImpl->focusViewObservers.forEach ([&] (IFocusViewObserver* observer) { observer->onFocusViewChanged (this, pImpl->focusView, pOldFocusView); }); - + recursion = false; } diff --git a/vstgui/lib/platform/iplatformframecallback.h b/vstgui/lib/platform/iplatformframecallback.h index 8b6c0737d..7b10e33ad 100644 --- a/vstgui/lib/platform/iplatformframecallback.h +++ b/vstgui/lib/platform/iplatformframecallback.h @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// This file is part of VSTGUI. It is subject to the license terms // in the LICENSE file found in the top-level directory of this // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE @@ -20,6 +20,7 @@ enum class PlatformType : int32_t { kHWNDTopLevel, // Windows HWDN Top Level (non child) kX11EmbedWindowID, // X11 XID kGdkWindow, // GdkWindow + kWaylandSurface, // Wayland Surface kDefaultNative = -1 }; @@ -30,11 +31,8 @@ enum class PlatformType : int32_t { class IPlatformFrameCallback { public: - virtual ~IPlatformFrameCallback () = default; + virtual bool platformDrawRect (CDrawContext* context, const CRect& rect) = 0; - virtual void platformDrawRects (const PlatformGraphicsDeviceContextPtr& context, - double scaleFactor, const std::vector& rects) = 0; - virtual void platformOnEvent (Event& event) = 0; virtual DragOperation platformOnDragEnter (DragEventData data) = 0; @@ -44,7 +42,7 @@ class IPlatformFrameCallback virtual void platformOnActivate (bool state) = 0; virtual void platformOnWindowActivate (bool state) = 0; - + virtual void platformScaleFactorChanged (double newScaleFactor) = 0; #if VSTGUI_TOUCH_EVENT_HANDLING diff --git a/vstgui/lib/platform/linux/linuxfactory.cpp b/vstgui/lib/platform/linux/linuxfactory.cpp index 9bbae8e40..6d51d5026 100644 --- a/vstgui/lib/platform/linux/linuxfactory.cpp +++ b/vstgui/lib/platform/linux/linuxfactory.cpp @@ -7,6 +7,7 @@ #include "cairogradient.h" #include "cairographicscontext.h" #include "x11frame.h" +#include "waylandframe.h" #include "../iplatformframecallback.h" #include "../common/fileresourceinputstream.h" #include "../iplatformresourceinputstream.h" @@ -95,6 +96,10 @@ PlatformFramePtr LinuxFactory::createFrame (IPlatformFrameCallback* frame, const auto x11Parent = reinterpret_cast (parent); return makeOwned (frame, size, x11Parent, config); } + else if (parentType == PlatformType::kWaylandSurface) + { + return makeOwned (frame, size, config); + } return nullptr; } diff --git a/vstgui/lib/platform/linux/waylandframe.cpp b/vstgui/lib/platform/linux/waylandframe.cpp new file mode 100644 index 000000000..17d904a78 --- /dev/null +++ b/vstgui/lib/platform/linux/waylandframe.cpp @@ -0,0 +1,400 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +// Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +#include "waylandframe.h" +//#include "waylanddragging.h" +#include "waylandutils.h" +#include "../../cbuttonstate.h" +#include "../../cframe.h" +#include "../../crect.h" +#include "../../dragging.h" +#include "../../vstkeycode.h" +#include "../../cinvalidrectlist.h" +#include "../iplatformopenglview.h" +#include "../iplatformviewlayer.h" +#include "../iplatformtextedit.h" +#include "../iplatformoptionmenu.h" +#include "../common/fileresourceinputstream.h" +#include "../common/generictextedit.h" +#include "../common/genericoptionmenu.h" +#include "cairobitmap.h" +#include "cairocontext.h" +#include "waylandplatform.h" +#include +#include +#include +#include + +#ifdef None +#undef None +#endif + +#include "../../events.h" + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Wayland { + +//------------------------------------------------------------------------ +struct RedrawTimerHandler +: ITimerHandler +, NonAtomicReferenceCounted +{ + using RedrawCallback = std::function; + + RedrawTimerHandler (uint64_t delay, RedrawCallback&& redrawCallback) + : redrawCallback (std::move (redrawCallback)) + { + RunLoop::instance ().get ()->registerTimer (delay, this); + } + ~RedrawTimerHandler () noexcept { RunLoop::instance ().get ()->unregisterTimer (this); } + + void onTimer () override + { + SharedPointer Self (this); + Self->redrawCallback (); + } + + RedrawCallback redrawCallback; +}; + +//------------------------------------------------------------------------ +struct DrawHandler +{ + DrawHandler (ChildWindow& window) + : childWindow (window), + windowSurface (nullptr) + { + onSizeChanged (window.getSize ()); + } + + void onSizeChanged (const CPoint& size) + { + drawContext = nullptr; + windowSurface.reset (); + + void* buffer = childWindow.getBuffer (); + if(buffer == nullptr) + return; + + auto s = cairo_image_surface_create_for_data (static_cast (buffer), + CAIRO_FORMAT_ARGB32, + childWindow.getSize ().x, childWindow.getSize ().y, + childWindow.getBufferStride ()); + if(cairo_surface_status (s) != CAIRO_STATUS_SUCCESS) + return; + + windowSurface.assign (s); + RunLoop::instance ().setDevice (cairo_surface_get_device (windowSurface)); + CRect r; + r.setSize (size); + drawContext = makeOwned (r, windowSurface); + } + + template + bool draw (const RectList& dirtyRects, Proc proc) + { + if(drawContext == nullptr) + onSizeChanged (childWindow.getSize ()); + + if(drawContext == nullptr) + return false; + + CRect copyRect; + drawContext->beginDraw (); + for (auto rect : dirtyRects) + { + drawContext->setClipRect (rect); + drawContext->saveGlobalState (); + proc (drawContext, rect); + drawContext->restoreGlobalState (); + if (copyRect.isEmpty ()) + copyRect = rect; + else + copyRect.unite (rect); + } + drawContext->endDraw (); + cairo_surface_flush (windowSurface); + + childWindow.commit (copyRect); + + return true; + } + +private: + ChildWindow& childWindow; + Cairo::SurfaceHandle windowSurface; + SharedPointer drawContext; +}; + +//------------------------------------------------------------------------ +struct Frame::Impl +{ + using RectList = CInvalidRectList; + + ChildWindow window; + DrawHandler drawHandler; + // TODO: DoubleClickDetector doubleClickDetector; + IPlatformFrameCallback* frame; + std::unique_ptr genericOptionMenuTheme; + SharedPointer redrawTimer; + RectList dirtyRects; + CCursorType currentCursor {kCursorDefault}; + uint32_t pointerGrabed {0}; + // TODO: WaylandDragAndDropHandler dndHandler; + + //------------------------------------------------------------------------ + Impl (IWaylandFrame* waylandFrame, CPoint size, IPlatformFrameCallback* frame) + : window (waylandFrame, size), drawHandler (window), frame (frame)//, dndHandler (&window, frame) + { + window.setFrame (frame); + } + + //------------------------------------------------------------------------ + ~Impl () noexcept + {} + + //------------------------------------------------------------------------ + void setSize (const CRect& size) + { + window.setSize (size); + drawHandler.onSizeChanged (size.getSize ()); + dirtyRects.clear (); + dirtyRects.add (size); + } + + //------------------------------------------------------------------------ + void setCursor (CCursorType cursor) + { + if (currentCursor == cursor) + return; + currentCursor = cursor; + setCursorInternal (cursor); + } + + //------------------------------------------------------------------------ + void setCursorInternal (CCursorType cursor) + { + // TODO + } + + //------------------------------------------------------------------------ + void redraw () + { + if (drawHandler.draw (dirtyRects, [&] (CDrawContext* context, const CRect& rect) { + frame->platformDrawRect (context, rect); + })) + dirtyRects.clear (); + } + + //------------------------------------------------------------------------ + void invalidRect (CRect r) + { + dirtyRects.add (r); + if (redrawTimer) + return; + redrawTimer = makeOwned (16, [this] () { + if (dirtyRects.data ().empty ()) + return; + redraw (); + }); + } +}; + +//------------------------------------------------------------------------ +Frame::Frame (IPlatformFrameCallback* frame, const CRect& size, + IPlatformFrameConfig* config) +: IPlatformFrame (frame) +{ + auto cfg = dynamic_cast (config); + if (cfg && cfg->runLoop) + { + RunLoop::init (cfg->runLoop, cfg->waylandHost); + } + + impl = std::unique_ptr (new Impl (cfg ? cfg->waylandFrame : nullptr, {size.getWidth (), size.getHeight ()}, frame)); + + frame->platformOnActivate (true); +} + +//------------------------------------------------------------------------ +Frame::~Frame () +{ + impl.reset (); + RunLoop::exit (); +} + +//------------------------------------------------------------------------ +void Frame::optionMenuPopupStarted () +{} + +//------------------------------------------------------------------------ +void Frame::optionMenuPopupStopped () +{} + +//------------------------------------------------------------------------ +bool Frame::getGlobalPosition (CPoint& pos) const +{ + return false; +} + +//------------------------------------------------------------------------ +bool Frame::setSize (const CRect& newSize) +{ + vstgui_assert (impl); + impl->setSize (newSize); + return true; +} + +//------------------------------------------------------------------------ +bool Frame::getSize (CRect& size) const +{ + size.setSize (impl->window.getSize ()); + return true; +} + +//------------------------------------------------------------------------ +bool Frame::getCurrentMousePosition (CPoint& mousePosition) const +{ + // TODO + return false; +} + +//------------------------------------------------------------------------ +bool Frame::getCurrentMouseButtons (CButtonState& buttons) const +{ + // TODO + return false; +} + +//------------------------------------------------------------------------ +bool Frame::getCurrentModifiers (Modifiers& modifiers) const +{ + // TODO + return false; +} + +//------------------------------------------------------------------------ +bool Frame::setMouseCursor (CCursorType type) +{ + impl->setCursor (type); + return true; +} + +//------------------------------------------------------------------------ +bool Frame::invalidRect (const CRect& rect) +{ + impl->invalidRect (rect); + return true; +} + +//------------------------------------------------------------------------ +bool Frame::scrollRect (const CRect& src, const CPoint& distance) +{ + (void)src; + (void)distance; + return false; +} + +//------------------------------------------------------------------------ +bool Frame::showTooltip (const CRect& rect, const char* utf8Text) +{ +#warning TODO: Implementation + return false; +} + +//------------------------------------------------------------------------ +bool Frame::hideTooltip () +{ +#warning TODO: Implementation + return false; +} + +//------------------------------------------------------------------------ +void* Frame::getPlatformRepresentation () const +{ + // TODO + return nullptr; + //return reinterpret_cast (getX11WindowID ()); +} + +//------------------------------------------------------------------------ +SharedPointer Frame::createPlatformTextEdit (IPlatformTextEditCallback* textEdit) +{ + return makeOwned (textEdit); +} + +//------------------------------------------------------------------------ +SharedPointer Frame::createPlatformOptionMenu () +{ + auto cFrame = dynamic_cast (frame); + GenericOptionMenuTheme theme; + if (impl->genericOptionMenuTheme) + theme = *impl->genericOptionMenuTheme.get (); + auto optionMenu = + makeOwned (cFrame, MouseEventButtonState (MouseButton::Left), theme); + optionMenu->setListener (this); + return optionMenu; +} + +#if VSTGUI_OPENGL_SUPPORT +//------------------------------------------------------------------------ +SharedPointer Frame::createPlatformOpenGLView () +{ +#warning TODO: Implementation + return nullptr; +} +#endif + +//------------------------------------------------------------------------ +SharedPointer Frame::createPlatformViewLayer ( + IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer) +{ + // optional + return nullptr; +} + +#if VSTGUI_ENABLE_DEPRECATED_METHODS +//------------------------------------------------------------------------ +DragResult Frame::doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap) +{ + return kDragError; +} +#endif + +//------------------------------------------------------------------------ +bool Frame::doDrag (const DragDescription& dragDescription, + const SharedPointer& callback) +{ + return false; +} + +//------------------------------------------------------------------------ +PlatformType Frame::getPlatformType () const +{ + return PlatformType::kWaylandSurface; +} + +//------------------------------------------------------------------------ +Optional Frame::convertCurrentKeyEventToText () +{ + // TODO: return RunLoop::instance ().convertCurrentKeyEventToText (); + return Optional (); +} + +//------------------------------------------------------------------------ +bool Frame::setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme) +{ + if (theme) + impl->genericOptionMenuTheme = + std::unique_ptr (new GenericOptionMenuTheme (*theme)); + else + impl->genericOptionMenuTheme = nullptr; + return true; +} + +//------------------------------------------------------------------------ +} // Wayland +} // VSTGUI diff --git a/vstgui/lib/platform/linux/waylandframe.h b/vstgui/lib/platform/linux/waylandframe.h new file mode 100644 index 000000000..07de99656 --- /dev/null +++ b/vstgui/lib/platform/linux/waylandframe.h @@ -0,0 +1,72 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +// Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +#pragma once + +#include "../../crect.h" +#include "../iplatformframe.h" +#include "../iplatformresourceinputstream.h" +#include "../platform_wayland.h" +#include "../common/genericoptionmenu.h" +#include +#include + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Wayland { + +//------------------------------------------------------------------------ +class Frame +: public IPlatformFrame +, public IGenericOptionMenuListener +{ +public: + Frame (IPlatformFrameCallback* frame, const CRect& size, + IPlatformFrameConfig* config); + ~Frame (); + +private: + bool getGlobalPosition (CPoint& pos) const override; + bool setSize (const CRect& newSize) override; + bool getSize (CRect& size) const override; + bool getCurrentMousePosition (CPoint& mousePosition) const override; + bool getCurrentMouseButtons (CButtonState& buttons) const override; + bool getCurrentModifiers (Modifiers& modifiers) const override; + bool setMouseCursor (CCursorType type) override; + bool invalidRect (const CRect& rect) override; + bool scrollRect (const CRect& src, const CPoint& distance) override; + bool showTooltip (const CRect& rect, const char* utf8Text) override; + bool hideTooltip () override; + void* getPlatformRepresentation () const override; + SharedPointer + createPlatformTextEdit (IPlatformTextEditCallback* textEdit) override; + SharedPointer createPlatformOptionMenu () override; +#if VSTGUI_OPENGL_SUPPORT + SharedPointer createPlatformOpenGLView () override; +#endif + SharedPointer createPlatformViewLayer ( + IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer) override; +#if VSTGUI_ENABLE_DEPRECATED_METHODS + DragResult doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap) override; +#endif + bool doDrag (const DragDescription& dragDescription, + const SharedPointer& callback) override; + + PlatformType getPlatformType () const override; + void onFrameClosed () override {} + Optional convertCurrentKeyEventToText () override; + bool setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme = nullptr) override; + + void optionMenuPopupStarted () override; + void optionMenuPopupStopped () override; + +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +} // Wayland +} // VSTGUI diff --git a/vstgui/lib/platform/linux/waylandplatform.cpp b/vstgui/lib/platform/linux/waylandplatform.cpp new file mode 100644 index 000000000..d52a41f02 --- /dev/null +++ b/vstgui/lib/platform/linux/waylandplatform.cpp @@ -0,0 +1,357 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +// Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +#include "waylandplatform.h" +#include "../../cfileselector.h" +#include "../../cframe.h" +#include "../../cstring.h" +#include "../../events.h" +#include "waylandframe.h" +//#include "x11dragging.h" +#include "cairobitmap.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdg-shell-client-protocol.h" + +//------------------------------------------------------------------------ +namespace VSTGUI { + +//------------------------------------------------------------------------ +namespace Wayland { + +//------------------------------------------------------------------------ +namespace { + +using VirtMap = std::unordered_map; +const VirtMap keyMap = {{XKB_KEY_BackSpace, VirtualKey::Back}, + {XKB_KEY_Tab, VirtualKey::Tab}, + {XKB_KEY_Clear, VirtualKey::Clear}, + {XKB_KEY_Return, VirtualKey::Return}, + {XKB_KEY_Pause, VirtualKey::Pause}, + {XKB_KEY_Escape, VirtualKey::Escape}, + {XKB_KEY_space, VirtualKey::Space}, + {XKB_KEY_End, VirtualKey::End}, + {XKB_KEY_Home, VirtualKey::Home}, + + {XKB_KEY_Left, VirtualKey::Left}, + {XKB_KEY_Up, VirtualKey::Up}, + {XKB_KEY_Right, VirtualKey::Right}, + {XKB_KEY_Down, VirtualKey::Down}, + {XKB_KEY_Page_Up, VirtualKey::PageUp}, + {XKB_KEY_Page_Down, VirtualKey::PageDown}, + + {XKB_KEY_Select, VirtualKey::Select}, + {XKB_KEY_Print, VirtualKey::Print}, + {XKB_KEY_KP_Enter, VirtualKey::Enter}, + {XKB_KEY_Insert, VirtualKey::Insert}, + {XKB_KEY_Delete, VirtualKey::Delete}, + {XKB_KEY_Help, VirtualKey::Help}, + // Numpads ??? + {XKB_KEY_KP_Multiply, VirtualKey::Multiply}, + {XKB_KEY_KP_Add, VirtualKey::Add}, + {XKB_KEY_KP_Separator, VirtualKey::Separator}, + {XKB_KEY_KP_Subtract, VirtualKey::Subtract}, + {XKB_KEY_KP_Decimal, VirtualKey::Decimal}, + {XKB_KEY_KP_Divide, VirtualKey::Divide}, + {XKB_KEY_F1, VirtualKey::F1}, + {XKB_KEY_F2, VirtualKey::F2}, + {XKB_KEY_F3, VirtualKey::F3}, + {XKB_KEY_F4, VirtualKey::F4}, + {XKB_KEY_F5, VirtualKey::F5}, + {XKB_KEY_F6, VirtualKey::F6}, + {XKB_KEY_F7, VirtualKey::F7}, + {XKB_KEY_F8, VirtualKey::F8}, + {XKB_KEY_F9, VirtualKey::F9}, + {XKB_KEY_F10, VirtualKey::F10}, + {XKB_KEY_F11, VirtualKey::F11}, + {XKB_KEY_F12, VirtualKey::F12}, + {XKB_KEY_Num_Lock, VirtualKey::NumLock}, + {XKB_KEY_Scroll_Lock, VirtualKey::Scroll}, // correct ? +#if 0 + {XKB_KEY_Shift_L, VirtualKey::SHIFT}, + {XKB_KEY_Shift_R, VirtualKey::SHIFT}, + {XKB_KEY_Control_L, VirtualKey::CONTROL}, + {XKB_KEY_Control_R, VirtualKey::CONTROL}, + {XKB_KEY_Alt_L, VirtualKey::ALT}, + {XKB_KEY_Alt_R, VirtualKey::ALT}, +#endif + {XKB_KEY_VoidSymbol, VirtualKey::None}}; +const VirtMap shiftKeyMap = {{XKB_KEY_KP_Page_Up, VirtualKey::PageUp}, + {XKB_KEY_KP_Page_Down, VirtualKey::PageDown}, + {XKB_KEY_KP_Home, VirtualKey::Home}, + {XKB_KEY_KP_End, VirtualKey::End}}; + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +struct RunLoop::Impl: wl_registry_listener, xdg_wm_base_listener, wl_seat_listener, IEventHandler +{ + SharedPointer runLoop; + SharedPointer waylandHost; + std::atomic useCount {0}; + cairo_device_t* device {nullptr}; + wl_display* display {nullptr}; + wl_registry* registry {nullptr}; + wl_compositor* compositor {nullptr}; + wl_subcompositor* subCompositor {nullptr}; + xdg_wm_base* windowManager {nullptr}; + wl_shm* sharedMemory {nullptr}; + wl_seat* seat {nullptr}; + uint32_t seatCapabilities {0}; + bool inDispatch {false}; + + Impl () + { + global = onGlobal; + global_remove = onGlobalRemoved; + ping = onPing; + capabilities = onSeatCapabilities; + name = onSeatName; + } + + void init (const SharedPointer& inRunLoop, const SharedPointer& inWaylandHost) + { + if (++useCount != 1) + return; + + runLoop = inRunLoop; + waylandHost = inWaylandHost; + + if(waylandHost == nullptr) + return; + + display = waylandHost->openWaylandConnection (); + if(display == nullptr) + return; + + runLoop->registerEventHandler (wl_display_get_fd (display), this); + + registry = wl_display_get_registry (display); + wl_registry_add_listener (registry, this, this); + + flush (); + } + + void exit () + { + if (--useCount != 0) + return; + + cairo_device_finish (device); + cairo_device_destroy (device); + device = nullptr; + + runLoop->unregisterEventHandler (this); + runLoop = nullptr; + + if(seat) + wl_seat_destroy (seat); + if(sharedMemory) + wl_shm_destroy (sharedMemory); + if(windowManager) + xdg_wm_base_destroy (windowManager); + if(subCompositor) + wl_subcompositor_destroy (subCompositor); + if(compositor) + wl_compositor_destroy (compositor); + if(registry) + wl_registry_destroy (registry); + + flush (); + + if(waylandHost && display) + waylandHost->closeWaylandConnection (display); + waylandHost = nullptr; + + display = nullptr; + registry = nullptr; + compositor = nullptr; + subCompositor = nullptr; + windowManager = nullptr; + sharedMemory = nullptr; + seat = nullptr; + } + + void flush () + { + if(display && !inDispatch) + wl_display_flush (display); + } + + // wl_registry_listener + static void onGlobal (void* data, wl_registry* registry, uint32_t name, const char* _interfaceName, uint32_t version) + { + Impl* This = static_cast (data); + + UTF8String interfaceName (_interfaceName); + + // Compositor + if(interfaceName == wl_compositor_interface.name && This->compositor == nullptr) + This->compositor = static_cast (wl_registry_bind (registry, name, &wl_compositor_interface, min (5, version))); + + // Subcompositor + else if(interfaceName == wl_subcompositor_interface.name && This->subCompositor == nullptr) + This->subCompositor = static_cast (wl_registry_bind (registry, name, &wl_subcompositor_interface, min (1, version))); + + // Window Manager + else if(interfaceName == xdg_wm_base_interface.name && This->windowManager == nullptr) + { + This->windowManager = static_cast (wl_registry_bind (registry, name, &xdg_wm_base_interface, min (6, version))); + if(This->windowManager != nullptr) + xdg_wm_base_add_listener (This->windowManager, This, This); + } + + // Shared Memory + else if(interfaceName == wl_shm_interface.name && This->sharedMemory == nullptr) + This->sharedMemory = static_cast (wl_registry_bind (registry, name, &wl_shm_interface, min (1, version))); + + // Seat + else if(interfaceName == wl_seat_interface.name && This->seat == nullptr) + { + This->seat = static_cast (wl_registry_bind (registry, name, &wl_seat_interface, min (8, version))); + if(This->seat != nullptr) + wl_seat_add_listener (This->seat, This, This); + } + } + + static void onGlobalRemoved (void* data, wl_registry* registry, uint32_t name) + { + // TODO + } + + // xdg_wm_base_listener + static void onPing (void* data, xdg_wm_base* windowManager, uint32_t serial) + { + xdg_wm_base_pong (windowManager, serial); + } + + // wl_seat_listener + static void onSeatCapabilities (void* data, wl_seat* seat, uint32_t capabilities) + { + Impl* This = static_cast (data); + if(seat == This->seat) + This->seatCapabilities = capabilities; + } + + static void onSeatName (void* data, wl_seat* seat, const char* name) + {} + + // IEventHandler + void onEvent () final + { + inDispatch = true; + if(wl_display_prepare_read (display) == 0) + wl_display_read_events (display); + wl_display_dispatch_pending (display); + inDispatch = false; + flush (); + } +}; + +//------------------------------------------------------------------------ +RunLoop& RunLoop::instance () +{ + static RunLoop gInstance; + return gInstance; +} + +//------------------------------------------------------------------------ +void RunLoop::init (const SharedPointer& runLoop, const SharedPointer& waylandHost) +{ + instance ().impl->init (runLoop, waylandHost); +} + +//------------------------------------------------------------------------ +void RunLoop::exit () +{ + instance ().impl->exit (); +} + +//------------------------------------------------------------------------ +void RunLoop::flush () +{ + instance ().impl->flush (); +} + +//------------------------------------------------------------------------ +const SharedPointer RunLoop::get () +{ + return instance ().impl->runLoop; +} + +//------------------------------------------------------------------------ +wl_display* RunLoop::getDisplay () +{ + return instance ().impl->display; +} + +//------------------------------------------------------------------------ +wl_compositor* RunLoop::getCompositor () +{ + return instance ().impl->compositor; +} + +//------------------------------------------------------------------------ +wl_subcompositor* RunLoop::getSubCompositor () +{ + return instance ().impl->subCompositor; +} + +//------------------------------------------------------------------------ +xdg_wm_base* RunLoop::getWindowManager () +{ + return instance ().impl->windowManager; +} + +//------------------------------------------------------------------------ +wl_shm* RunLoop::getSharedMemory () +{ + return instance ().impl->sharedMemory; +} + +//------------------------------------------------------------------------ +wl_seat* RunLoop::getSeat () +{ + return (instance ().impl->seatCapabilities != 0) ? instance ().impl->seat : nullptr; +} + +//------------------------------------------------------------------------ +bool RunLoop::hasPointerInput () +{ + return (instance ().impl->seatCapabilities & WL_SEAT_CAPABILITY_POINTER) != 0; +} + +//------------------------------------------------------------------------ +RunLoop::RunLoop () +{ + impl = std::unique_ptr (new Impl); +} + +//------------------------------------------------------------------------ +RunLoop::~RunLoop () noexcept = default; + +//------------------------------------------------------------------------ +void RunLoop::setDevice (cairo_device_t* device) +{ + if (impl->device != device) + { + cairo_device_destroy (impl->device); + impl->device = cairo_device_reference (device); + } +} + +//------------------------------------------------------------------------ +} // Wayland +} // VSTGUI diff --git a/vstgui/lib/platform/linux/waylandplatform.h b/vstgui/lib/platform/linux/waylandplatform.h new file mode 100644 index 000000000..394373466 --- /dev/null +++ b/vstgui/lib/platform/linux/waylandplatform.h @@ -0,0 +1,59 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +// Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +#pragma once + +#include "../../vstguifwd.h" +#include "waylandframe.h" +#include +#include +#include + +struct xdg_wm_base; +struct wl_compositor; +struct wl_subcompositor; +struct wl_shm; +struct wl_seat; + +//------------------------------------------------------------------------ +namespace VSTGUI { + +//------------------------------------------------------------------------ +namespace Wayland { + +class Frame; +class Timer; + +//------------------------------------------------------------------------ +struct RunLoop +{ + static void init (const SharedPointer& runLoop, const SharedPointer& waylandHost); + static void exit (); + static const SharedPointer get (); + + static void flush (); + + static wl_display* getDisplay (); + static wl_compositor* getCompositor (); + static wl_subcompositor* getSubCompositor (); + static xdg_wm_base* getWindowManager (); + static wl_shm* getSharedMemory (); + static wl_seat* getSeat (); + static bool hasPointerInput (); + + void setDevice (cairo_device_t* device); + static RunLoop& instance (); + +private: + RunLoop (); + ~RunLoop () noexcept; + + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +} // Wayland +} // VSTGUI diff --git a/vstgui/lib/platform/linux/waylandutils.cpp b/vstgui/lib/platform/linux/waylandutils.cpp new file mode 100644 index 000000000..f60a9537f --- /dev/null +++ b/vstgui/lib/platform/linux/waylandutils.cpp @@ -0,0 +1,588 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +// Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +#include "waylandutils.h" +#include "../../cstring.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace VSTGUI { +namespace Wayland { + +class InputHandler: public wl_pointer_listener +{ +public: + static InputHandler& instance () + { + static InputHandler inputHandler; + return inputHandler; + } + + void addWindow (ChildWindow* window); + void removeWindow (ChildWindow* window); + + // wl_pointer_listener + static void onPointerEnter (void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface, wl_fixed_t x, wl_fixed_t y); + static void onPointerLeave (void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface); + static void onPointerMotion (void* data, wl_pointer* pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y); + static void onPointerButton (void* data, wl_pointer* pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); + static void onPointerAxis (void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value); + static void onPointerAxisSource (void* data, wl_pointer* pointer, uint32_t axisSource); + static void onPointerAxisStop (void* data, wl_pointer* pointer, uint32_t time, uint32_t axis); + static void onPointerAxisDiscrete (void* data, wl_pointer* pointer, uint32_t axis, int32_t discrete); + static void onPointerAxis120 (void* data, wl_pointer* pointer, uint32_t axis, int32_t discrete); + static void onPointerFrame (void* data, wl_pointer* pointer); + +private: + struct PointerEvent + { + uint32_t eventMask {0}; + wl_fixed_t x {0}; + wl_fixed_t y {0}; + uint32_t button {0}; + uint32_t state {0}; + uint32_t time {0}; + uint32_t serial {0}; + struct Axis + { + wl_fixed_t value {0}; + int32_t discrete {0}; + bool valid {false}; + } axes[2]; + uint32_t axisSource {0}; + uint32_t buttonState {0}; + wl_surface* focus {nullptr}; + wl_surface* previousFocus {nullptr}; + }; + + enum PointerEventMask + { + kPointerEnter = 1 << 0, + kPointerLeave = 1 << 1, + kPointerMotion = 1 << 2, + kPointerButton = 1 << 3, + kPointerAxis = 1 << 4, + kPointerAxisSource = 1 << 5, + kPointerAxisStop = 1 << 6, + kPointerAxisDiscrete = 1 << 7, + }; + + wl_pointer* pointer; + PointerEvent pointerEvent; + bool initialized; + std::vector windows; + + InputHandler (); + void initialize (); + void terminate (); + void dispatch (const PointerEvent& event); +}; + +} // namespace Wayland +} // namespace VSTGUI + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Wayland { + +//------------------------------------------------------------------------ +ChildWindow::ChildWindow (IWaylandFrame* waylandFrame, CPoint size) +: size (size), + waylandFrame (shared (waylandFrame)), + data (MAP_FAILED), + buffer (nullptr), + pool (nullptr), + surface (nullptr), + subSurface (nullptr), + allocatedSize (0), + byteSize (0), + fd (-1) +{} + +//------------------------------------------------------------------------ +ChildWindow::~ChildWindow () noexcept +{ + destroyBuffer (); + terminate (); +} + +//------------------------------------------------------------------------ +void ChildWindow::setFrame (IPlatformFrameCallback* frame) +{ + frameCallback = frame; +} + +//------------------------------------------------------------------------ +IPlatformFrameCallback* ChildWindow::getFrame () const +{ + return frameCallback; +} + +//------------------------------------------------------------------------ +void ChildWindow::commit (const CRect& rect) +{ + if(surface == nullptr) + return; + + wl_surface_damage_buffer (surface, rect.left, rect.top, rect.getWidth (), rect.getHeight ()); + wl_surface_attach (surface, buffer, 0, 0); + wl_surface_commit (surface); + + RunLoop::flush (); +} + +//------------------------------------------------------------------------ +void ChildWindow::setSize (const CRect& rect) +{ + if(size != rect.getSize ()) + { + if(buffer != nullptr) + wl_buffer_destroy (buffer); + buffer = nullptr; + data = MAP_FAILED; + } + size = rect.getSize (); +} + +//------------------------------------------------------------------------ +const CPoint& ChildWindow::getSize () const +{ + return size; +} + +//------------------------------------------------------------------------ +void* ChildWindow::getBuffer () const +{ + if(buffer == nullptr) + const_cast (this)->createBuffer (); + return (data != MAP_FAILED) ? data : nullptr; +} + +//------------------------------------------------------------------------ +int ChildWindow::getBufferStride () const +{ + return cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, size.x); +} + +//------------------------------------------------------------------------ +wl_surface* ChildWindow::getSurface () const +{ + return surface; +} + +//------------------------------------------------------------------------ +void ChildWindow::createBuffer () +{ + if(!initialized) + initialize (); + if(!initialized) + return; + + wl_shm* shm = RunLoop::getSharedMemory (); + if(shm == nullptr) + return; + + int stride = getBufferStride (); + int byteSize = stride * size.y; + + if(byteSize > allocatedSize) + { + int newSize = byteSize * 1.5; + + if(pool) + wl_shm_pool_destroy (pool); + pool = nullptr; + + if(fd >= 0) + ::close (fd); + fd = -1; + + if(data != MAP_FAILED) + { + ::munmap (data, allocatedSize); + data = MAP_FAILED; + } + + for(int i = 0; i < 100 && fd < 0; i++) + { + UTF8String name = "/vstgui_wl_buffer-" + std::to_string (::rand ()); + fd = ::shm_open (name, O_RDWR | O_CREAT | O_EXCL, 0600); + if(fd >= 0) + { + ::shm_unlink (name); + break; + } + } + + int result = 0; + do + { + result = ::ftruncate (fd, newSize); + } while (result < 0 && errno == EINTR); + + if(result < 0) + { + ::close (fd); + return; + } + + data = ::mmap (NULL, newSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if(data == MAP_FAILED) + { + ::close (fd); + return; + } + + allocatedSize = newSize; + + pool = wl_shm_create_pool (shm, fd, allocatedSize); + } + + buffer = wl_shm_pool_create_buffer (pool, 0, size.x, size.y, stride, WL_SHM_FORMAT_ARGB8888); + if(surface && buffer) + wl_surface_attach (surface, buffer, 0, 0); + + // TODO add a buffer listener, check if a buffer has been released before rendering into the same buffer again + // use multiple buffers as a swapchain +} + +//------------------------------------------------------------------------ +void ChildWindow::destroyBuffer () +{ + if(buffer) + wl_buffer_destroy (buffer); + buffer = nullptr; + + if(pool) + wl_shm_pool_destroy (pool); + pool = nullptr; + + if(data != MAP_FAILED) + ::munmap (data, byteSize); + data = MAP_FAILED; +} + +//------------------------------------------------------------------------ +void ChildWindow::initialize () +{ + wl_display* display = RunLoop::getDisplay (); + wl_compositor* compositor = RunLoop::getCompositor (); + wl_subcompositor* subCompositor = RunLoop::getSubCompositor (); + wl_seat* seat = RunLoop::getSeat (); + + // FIXME: better wait for all required globals to be available before creating any ChildWindow's + if(display == nullptr || compositor == nullptr || subCompositor == nullptr || seat == nullptr) + return; + + wl_surface* parent = waylandFrame->getWaylandSurface (display); + if(parent == nullptr) + return; + + surface = wl_compositor_create_surface (compositor); + if(surface == nullptr) + return; + + subSurface = wl_subcompositor_get_subsurface (subCompositor, surface, parent); + if(subSurface == nullptr) + { + wl_surface_destroy (surface); + surface = nullptr; + return; + } + + wl_subsurface_set_desync (subSurface); + + InputHandler::instance ().addWindow (this); + + initialized = true; +} + +//------------------------------------------------------------------------ +void ChildWindow::terminate () +{ + if(!initialized) + return; + + InputHandler::instance ().removeWindow (this); + + if(subSurface) + wl_subsurface_destroy (subSurface); + subSurface = nullptr; + + if(surface) + wl_surface_destroy (surface); + surface = nullptr; + + RunLoop::flush (); + + initialized = false; +} + +//------------------------------------------------------------------------ +InputHandler::InputHandler () +: pointer (nullptr), + initialized (false) +{ + enter = onPointerEnter; + leave = onPointerLeave; + motion = onPointerMotion; + button = onPointerButton; + axis = onPointerAxis; + axis_source = onPointerAxisSource; + axis_stop = onPointerAxisStop; + axis_discrete = onPointerAxisDiscrete; +#ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION + axis_value120 = onPointerAxis120; +#endif + frame = onPointerFrame; +} + +//------------------------------------------------------------------------ +void InputHandler::initialize () +{ + wl_seat* seat = RunLoop::getSeat (); + if(seat == nullptr) + return; + + // FIXME: this property might change when seat capabilities change + if(RunLoop::hasPointerInput ()) + { + pointer = wl_seat_get_pointer (seat); + if(pointer) + wl_pointer_add_listener (pointer, this, this); + } + + initialized = true; +} + +//------------------------------------------------------------------------ +void InputHandler::terminate () +{ + if(pointer) + wl_pointer_destroy (pointer); + pointer = nullptr; + + initialized = false; +} + +//------------------------------------------------------------------------ +void InputHandler::addWindow (ChildWindow* window) +{ + windows.push_back (window); + if(!initialized) + initialize (); +} + +//------------------------------------------------------------------------ +void InputHandler::removeWindow (ChildWindow* window) +{ + windows.erase (std::remove (windows.begin (), windows.end (), window)); + if(windows.empty ()) + terminate (); +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerEnter (void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface, wl_fixed_t x, wl_fixed_t y) +{ + InputHandler* This = static_cast (data); + + This->pointerEvent.eventMask |= kPointerEnter; + This->pointerEvent.x = x; + This->pointerEvent.y = y; + This->pointerEvent.focus = surface; + + // TODO: remember the serial +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerLeave (void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface) +{ + InputHandler* This = static_cast (data); + + This->pointerEvent.eventMask |= kPointerLeave; + This->pointerEvent.previousFocus = surface; + This->pointerEvent.focus = nullptr; + This->pointerEvent.buttonState = 0; +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerMotion (void* data, wl_pointer* pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + InputHandler* This = static_cast (data); + + This->pointerEvent.eventMask |= kPointerMotion; + This->pointerEvent.time = time; + This->pointerEvent.x = x; + This->pointerEvent.y = y; +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerButton (void* data, wl_pointer* pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) +{ + InputHandler* This = static_cast (data); + + This->pointerEvent.eventMask |= kPointerButton; + This->pointerEvent.time = time; + This->pointerEvent.button = button; + This->pointerEvent.state = state; + + MouseButton flag = MouseButton::None; + switch(button) + { + case BTN_LEFT: + flag = MouseButton::Left; + break; + case BTN_MIDDLE: + flag = MouseButton::Middle; + break; + case BTN_RIGHT: + flag = MouseButton::Right; + break; + } + if(state == WL_POINTER_BUTTON_STATE_PRESSED) + { + This->pointerEvent.serial = serial; + This->pointerEvent.buttonState |= (uint32_t)flag; + } + else + This->pointerEvent.buttonState &= ~(uint32_t)flag; +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerAxis (void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) +{ + InputHandler* This = static_cast (data); + + if(axis >= 2) + return; + + This->pointerEvent.eventMask |= kPointerAxis; + This->pointerEvent.time = time; + This->pointerEvent.axes[axis].value += value; + This->pointerEvent.axes[axis].valid = true; +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerAxisSource (void* data, wl_pointer* pointer, uint32_t axisSource) +{ + InputHandler* This = static_cast (data); + + This->pointerEvent.eventMask |= kPointerAxisSource; + This->pointerEvent.axisSource = axisSource; +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerAxisStop (void* data, wl_pointer* pointer, uint32_t time, uint32_t axis) +{ + InputHandler* This = static_cast (data); + + This->pointerEvent.eventMask |= kPointerAxisStop; + This->pointerEvent.time = time; + This->pointerEvent.axes[axis].valid = true; +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerAxisDiscrete (void* data, wl_pointer* pointer, uint32_t axis, int32_t discrete) +{ + InputHandler* This = static_cast (data); + + onPointerAxis120 (data, pointer, axis, discrete * 120); +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerAxis120 (void* data, wl_pointer* pointer, uint32_t axis, int32_t discrete) +{ + InputHandler* This = static_cast (data); + + if(axis >= 2) + return; + + This->pointerEvent.eventMask |= kPointerAxisDiscrete; + This->pointerEvent.axes[axis].discrete += discrete; + This->pointerEvent.axes[axis].valid = true; +} + +//------------------------------------------------------------------------ +void InputHandler::onPointerFrame (void* data, wl_pointer* pointer) +{ + // TODO: maybe collect events first and dispatch them later to avoid delays inside Wayland dispatch + // maybe move input handling to a separate queue + // see https://gitlab.freedesktop.org/wayland/wayland/-/issues/159 + // https://bugreports.qt.io/browse/QTBUG-66997 + // https://bugs.kde.org/show_bug.cgi?id=392376 + // https://gitlab.gnome.org/GNOME/mutter/-/issues/2234 + + InputHandler* This = static_cast (data); + + PointerEvent event (This->pointerEvent); + + This->pointerEvent.eventMask = 0; + This->pointerEvent.previousFocus = nullptr; + for(int i = 0; i < 2; i++) + { + This->pointerEvent.axes[i].valid = false; + This->pointerEvent.axes[i].value = 0; + This->pointerEvent.axes[i].discrete = 0; + } + This->pointerEvent.time = 0; + + This->dispatch (event); +} + +//------------------------------------------------------------------------ +void InputHandler::dispatch (const PointerEvent& event) +{ + CPoint position (wl_fixed_to_int (event.x), wl_fixed_to_int (event.y)); + + for(ChildWindow* window : windows) + { + IPlatformFrameCallback* frame = window->getFrame (); + if(frame == nullptr) + continue; + + // TODO: wheel, axis, enter, leave, ... + + // FIXME: when dragging / manipulating a control, we might also need to forward mouse move events to a surface that is not currently in focus + if((event.eventMask & InputHandler::kPointerButton) && (window->getSurface () == event.focus || window->getSurface () == event.previousFocus)) + { + if(event.state == WL_POINTER_BUTTON_STATE_RELEASED) + { + MouseUpEvent upEvent; + upEvent.mousePosition = position; + upEvent.buttonState.add ((MouseButton)event.buttonState); + upEvent.timestamp = event.time; + frame->platformOnEvent (upEvent); + } + else + { + MouseDownEvent downEvent; + downEvent.mousePosition = position; + downEvent.buttonState.add ((MouseButton)event.buttonState); + downEvent.timestamp = event.time; + frame->platformOnEvent (downEvent); + } + } + + if((event.eventMask & InputHandler::kPointerMotion) && event.focus == window->getSurface ()) + { + MouseMoveEvent moveEvent; + moveEvent.mousePosition = position; + moveEvent.buttonState.add ((MouseButton)event.buttonState); + moveEvent.timestamp = event.time; + frame->platformOnEvent (moveEvent); + } + } +} + +//------------------------------------------------------------------------ +} // Wayland +} // VSTGUI diff --git a/vstgui/lib/platform/linux/waylandutils.h b/vstgui/lib/platform/linux/waylandutils.h new file mode 100644 index 000000000..1edb1d55c --- /dev/null +++ b/vstgui/lib/platform/linux/waylandutils.h @@ -0,0 +1,62 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +// Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +#pragma once + +#include "waylandplatform.h" + +struct wl_subsurface; +struct wl_buffer; +struct wl_shm_pool; + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Wayland { + +//------------------------------------------------------------------------ +class ChildWindow +{ +public: + ChildWindow (IWaylandFrame* waylandFrame, CPoint size); + ~ChildWindow () noexcept; + + void setFrame (IPlatformFrameCallback* frame); + IPlatformFrameCallback* getFrame () const; + + void setSize (const CRect& rect); + const CPoint& getSize () const; + + void* getBuffer () const; + int getBufferStride () const; + + wl_surface* getSurface () const; + + void commit (const CRect& rect); + +private: + SharedPointer waylandFrame; + IPlatformFrameCallback* frameCallback; + bool initialized; + CPoint size; + void* data; + + wl_surface* surface; + wl_subsurface* subSurface; + wl_buffer* buffer; + wl_shm_pool* pool; + int allocatedSize; + int byteSize; + int fd; + + void initialize (); + void terminate (); + void createBuffer (); + void destroyBuffer (); +}; + +//------------------------------------------------------------------------ + +} // Wayland +} // VSTGUI diff --git a/vstgui/lib/platform/linux/x11timer.cpp b/vstgui/lib/platform/linux/x11timer.cpp index 246b56441..d6873262f 100644 --- a/vstgui/lib/platform/linux/x11timer.cpp +++ b/vstgui/lib/platform/linux/x11timer.cpp @@ -4,6 +4,7 @@ #include "x11timer.h" #include "x11platform.h" +#include "waylandplatform.h" //------------------------------------------------------------------------ namespace VSTGUI { @@ -25,6 +26,8 @@ Timer::~Timer () noexcept bool Timer::start (uint32_t periodMs) { auto runLoop = RunLoop::get (); + if(runLoop == nullptr) + runLoop = Wayland::RunLoop::get (); vstgui_assert (runLoop, "Timer only works of run loop was set"); if (!runLoop) return false; @@ -35,6 +38,8 @@ bool Timer::start (uint32_t periodMs) bool Timer::stop () { auto runLoop = RunLoop::get (); + if(runLoop == nullptr) + runLoop = Wayland::RunLoop::get (); vstgui_assert (runLoop, "Timer only works of run loop was set"); if (!runLoop) return false; diff --git a/vstgui/lib/platform/platform_wayland.h b/vstgui/lib/platform/platform_wayland.h new file mode 100644 index 000000000..5906f9146 --- /dev/null +++ b/vstgui/lib/platform/platform_wayland.h @@ -0,0 +1,52 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +// Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +#pragma once + +#include "iplatformframe.h" +#include "platform_x11.h" + +struct wl_surface; +struct wl_display; +struct xdg_surface; +struct xdg_toplevel; + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Wayland { + +using X11::IEventHandler; +using X11::ITimerHandler; +using X11::IRunLoop; + +//------------------------------------------------------------------------ +class IWaylandHost : public virtual IReference +{ +public: + virtual wl_display* openWaylandConnection () = 0; + virtual bool closeWaylandConnection (wl_display* display) = 0; +}; + +//------------------------------------------------------------------------ +class IWaylandFrame : public virtual IReference +{ +public: + virtual wl_surface* getWaylandSurface (wl_display* display) = 0; + virtual xdg_surface* getParentSurface (CRect& parentSize, wl_display* display) = 0; + virtual xdg_toplevel* getParentToplevel (wl_display* display) = 0; +}; + +//------------------------------------------------------------------------ +class FrameConfig : public IPlatformFrameConfig +{ +public: + SharedPointer runLoop; + SharedPointer waylandHost; + SharedPointer waylandFrame; +}; + +//------------------------------------------------------------------------ +} // Wayland +} // VSTGUI diff --git a/vstgui/plugin-bindings/psliids.cpp b/vstgui/plugin-bindings/psliids.cpp new file mode 100644 index 000000000..d4950b9fe --- /dev/null +++ b/vstgui/plugin-bindings/psliids.cpp @@ -0,0 +1,13 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE +// Originally written and contributed to VSTGUI by PreSonus Software Ltd. + +#include "presonus/ipslwaylandframe.h" + +namespace Presonus { + +DEF_CLASS_IID (IWaylandHost) +DEF_CLASS_IID (IWaylandFrame) + +} // namespace Presonus diff --git a/vstgui/plugin-bindings/vst3editor.cpp b/vstgui/plugin-bindings/vst3editor.cpp index 0f9de0cdf..cfe3b1355 100644 --- a/vstgui/plugin-bindings/vst3editor.cpp +++ b/vstgui/plugin-bindings/vst3editor.cpp @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// This file is part of VSTGUI. It is subject to the license terms // in the LICENSE file found in the top-level directory of this // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE @@ -25,7 +25,9 @@ #if LINUX #include "../lib/platform/linux/x11frame.h" +#include "../lib/platform/linux/waylandframe.h" #include "pluginterfaces/gui/iplugview.h" +#include "presonus/ipslwaylandframe.h" #endif #if defined(kVstVersionMajor) && defined(kVstVersionMinor) @@ -148,7 +150,7 @@ class ParameterChangeListener : public Steinberg::FObject else updateControlValue (value); } - + void removeControl (CControl* control) { for (const auto& c : controls) @@ -161,12 +163,12 @@ class ParameterChangeListener : public Steinberg::FObject } } } - + bool containsControl (CControl* control) { return std::find (controls.begin (), controls.end (), control) != controls.end (); } - + void PLUGIN_API update (FUnknown* changedUnknown, Steinberg::int32 message) override { if (message == IDependent::kChanged && parameter) @@ -175,7 +177,7 @@ class ParameterChangeListener : public Steinberg::FObject } } - Steinberg::Vst::ParamID getParameterID () + Steinberg::Vst::ParamID getParameterID () { if (parameter) return parameter->getInfo ().id; @@ -184,13 +186,13 @@ class ParameterChangeListener : public Steinberg::FObject return static_cast (control->getTag ()); return 0xFFFFFFFF; } - + void beginEdit () { if (parameter) editController->beginEdit (getParameterID ()); } - + void endEdit () { if (parameter) @@ -350,7 +352,7 @@ class ParameterChangeListener : public Steinberg::FObject } Steinberg::Vst::EditController* editController; Steinberg::Vst::Parameter* parameter; - + using ControlList = std::list; ControlList controls; }; @@ -552,7 +554,7 @@ bool VST3Editor::setEditorSizeConstrains (const CPoint& newMinimumSize, const CP if (newSize != currentSize) requestResize (CPoint (newSize.getWidth (), newSize.getHeight ())); } - + return true; } return false; @@ -783,7 +785,7 @@ class ContextMenuTarget : public Steinberg::FObject, public Steinberg::Vst::ICon item->execute (); return Steinberg::kResultTrue; } - + OBJ_METHODS(ContextMenuTarget, Steinberg::FObject) FUNKNOWN_METHODS(Steinberg::Vst::IContextMenuTarget, Steinberg::FObject) protected: @@ -1175,6 +1177,65 @@ class RunLoop : public X11::IRunLoop, public AtomicReferenceCounted TimerHandlers timerHandlers; Steinberg::FUnknownPtr runLoop; }; + +class WaylandHost : public Wayland::IWaylandHost, public AtomicReferenceCounted +{ +public: + virtual wl_display* openWaylandConnection () final + { + if(!waylandHost) + return nullptr; + + return waylandHost->openWaylandConnection (); + } + + virtual bool closeWaylandConnection (wl_display* display) final + { + if(!waylandHost) + return false; + + return waylandHost->closeWaylandConnection (display) == Steinberg::kResultOk; + } + + WaylandHost (Steinberg::FUnknown* waylandHost) : waylandHost (waylandHost) {} +private: + Steinberg::FUnknownPtr waylandHost; +}; + +class WaylandFrame : public Wayland::IWaylandFrame, public AtomicReferenceCounted +{ +public: + wl_surface* getWaylandSurface (wl_display* display) final + { + if(!waylandFrame) + return nullptr; + + return waylandFrame->getWaylandSurface (display); + } + + xdg_surface* getParentSurface (CRect& parentSize, wl_display* display) final + { + if(!waylandFrame) + return nullptr; + + Steinberg::ViewRect viewRect; + xdg_surface* surface = waylandFrame->getParentSurface (viewRect, display); + parentSize = CRect (viewRect.left, viewRect.top, viewRect.right, viewRect.bottom); + return surface; + } + + xdg_toplevel* getParentToplevel (wl_display* display) final + { + if(!waylandFrame) + return nullptr; + + return waylandFrame->getParentToplevel (display); + } + + WaylandFrame (Steinberg::FUnknown* waylandFrame) : waylandFrame (waylandFrame) {} +private: + Steinberg::FUnknownPtr waylandFrame; +}; #endif #define kFrameEnableFocusDrawingAttr "frame-enable-focus-drawing" @@ -1247,13 +1308,36 @@ bool PLUGIN_API VST3Editor::open (void* parent, const PlatformType& type) IPlatformFrameConfig* config = nullptr; #if LINUX - X11::FrameConfig x11config; - x11config.runLoop = owned (new RunLoop (plugFrame)); - config = &x11config; + if(type == PlatformType::kWaylandSurface) + { + Presonus::IWaylandHost* waylandHost = nullptr; + Steinberg::FUnknownPtr hostApp (getController ()->getHostContext ()); + if(hostApp) + { + Steinberg::TUID iid; + Presonus::IWaylandHost::iid.toTUID (iid); + hostApp->createInstance (iid, iid, (void**)&waylandHost); + } + + Wayland::FrameConfig* waylandConfig = new Wayland::FrameConfig; + waylandConfig->runLoop = owned (new RunLoop (plugFrame)); + waylandConfig->waylandHost = owned (new WaylandHost (waylandHost)); + waylandConfig->waylandFrame = owned (new WaylandFrame (plugFrame)); + config = waylandConfig; + } + else + { + X11::FrameConfig* x11config = new X11::FrameConfig; + x11config->runLoop = owned (new RunLoop (plugFrame)); + config = x11config; + } #endif getFrame ()->open (parent, type, config); + delete config; + config = nullptr; + if (delegate) delegate->didOpen (this); @@ -1854,7 +1938,7 @@ bool VST3Editor::enableEditing (bool state) description->setFilePath (filePath->c_str ()); } } - + getFrame ()->setTransform (CGraphicsTransform ()); nonEditRect = getFrame ()->getViewSize (); description->setController (this); @@ -1876,7 +1960,7 @@ bool VST3Editor::enableEditing (bool state) getFrame ()->setFocusColor (focusColor); getFrame ()->setFocusDrawingEnabled (true); getFrame ()->setFocusWidth (1); - + COptionMenu* fileMenu = editController->getMenuController ()->getFileMenu (); if (fileMenu) {