// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include #include "Engine/Platform/Base/DragDropHelper.h" #include "Engine/Platform/Unix/UnixFile.h" #include "wayland/xdg-toplevel-drag-v1.h" #include #include #if PLATFORM_SDL && PLATFORM_LINUX #include "Engine/Platform/Platform.h" #include "SDLWindow.h" #include "Engine/Platform/IGuiData.h" #include "Engine/Engine/Engine.h" #include "Engine/Platform/SDL/SDLClipboard.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Platform/WindowsManager.h" #include "Engine/Platform/Linux/IncludeX11.h" #include "Engine/Input/Input.h" #include "Engine/Input/Mouse.h" #include #include #include #include // Wayland void WaylandRegistryGlobal(void* data, wl_registry *registry, uint32 id, const char* interface, uint32 version); void WaylandRegistryGlobalRemove(void* data, wl_registry *registry, uint32 id); Dictionary SurfaceToWindowMap; uint32 ImplicitGrabSerial = 0; wl_display* WaylandDisplay = nullptr; wl_registry_listener WaylandRegistryListener = { WaylandRegistryGlobal, WaylandRegistryGlobalRemove }; xdg_toplevel_drag_manager_v1* DragManager = nullptr; wl_seat* WaylandSeat = nullptr; wl_data_device_manager* WaylandDataDeviceManager = nullptr; xdg_wm_base* WaylandXdgWmBase = nullptr; wl_data_device* dataDevice; bool waylandDraggingActive = false; // X11 Delegate LinuxPlatform::xEventReceived; // Missing Wayland features: // - Application icon (xdg-toplevel-icon-v1) https://github.com/libsdl-org/SDL/pull/9584 // - Color picker (xdg-desktop-portal?) https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Screenshot.html // - namespace { X11::Display* xDisplay = nullptr; X11::XIM IM = nullptr; X11::XIC IC = nullptr; X11::Atom xAtomDeleteWindow; X11::Atom xAtomXdndEnter; X11::Atom xAtomXdndPosition; X11::Atom xAtomXdndLeave; X11::Atom xAtomXdndDrop; X11::Atom xAtomXdndActionCopy; X11::Atom xAtomXdndStatus; X11::Atom xAtomXdndSelection; X11::Atom xAtomXdndFinished; X11::Atom xAtomXdndAware; X11::Atom xAtomWmState; X11::Atom xAtomWmStateHidden; X11::Atom xAtomWmStateMaxVert; X11::Atom xAtomWmStateMaxHorz; X11::Atom xAtomWmWindowOpacity; X11::Atom xAtomWmName; X11::Atom xAtomAtom; X11::Atom xAtomClipboard; X11::Atom xAtomPrimary; X11::Atom xAtomTargets; X11::Atom xAtomText; X11::Atom xAtomString; X11::Atom xAtomUTF8String; X11::Atom xAtomXselData; X11::Atom xDnDRequested = 0; X11::Window xDndSourceWindow = 0; DragDropEffect xDndResult; Float2 xDndPos; int32 xDnDVersion = 0; int32 XFixesSelectionNotifyEvent = 0; } class LinuxDropFilesData : public IGuiData { public: Array Files; SDLWindow* Window; Type GetType() const override { return Type::Files; } String GetAsText() const override { return String::Empty; } void GetAsFiles(Array* files) const override { files->Add(Files); } }; class LinuxDropTextData : public IGuiData { public: StringView Text; SDLWindow* Window; int64* dragOver; Type GetType() const override { return Type::Text; } String GetAsText() const override { return String(Text); } void GetAsFiles(Array* files) const override { } }; struct Property { unsigned char* data; int format, nitems; X11::Atom type; }; namespace Impl { StringAnsi ClipboardText; void ClipboardGetText(String& result, X11::Atom source, X11::Atom atom, X11::Window window) { X11::Window selectionOwner = X11::XGetSelectionOwner(xDisplay, source); if (selectionOwner == 0) { // No copy owner return; } if (selectionOwner == window) { // Copy/paste from self result.Set(ClipboardText.Get(), ClipboardText.Length()); return; } // Send event to get data from the owner int format; unsigned long N, size; char* data; X11::Atom target; X11::XEvent event; X11::XConvertSelection(xDisplay, xAtomClipboard, atom, xAtomXselData, window, CurrentTime); X11::XSync(xDisplay, 0); if (X11::XCheckTypedEvent(xDisplay, SelectionNotify, &event)) { if (event.xselection.selection != xAtomClipboard) return; if (event.xselection.property) { X11::XGetWindowProperty(event.xselection.display, event.xselection.requestor, event.xselection.property, 0L,(~0L), 0, AnyPropertyType, &target, &format, &size, &N,(unsigned char**)&data); if (target == xAtomUTF8String || target == xAtomString) { // Got text to paste result.Set(data , size); X11::XFree(data); } X11::XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property); } } } Property ReadProperty(X11::Display* display, X11::Window window, X11::Atom property) { X11::Atom readType = 0; int readFormat = 0; unsigned long nitems = 0; unsigned long readBytes = 0; unsigned char* result = nullptr; int bytesCount = 1024; if (property != 0) { do { if (result != nullptr) X11::XFree(result); XGetWindowProperty(display, window, property, 0, bytesCount, 0, AnyPropertyType, &readType, &readFormat, &nitems, &readBytes, &result); bytesCount *= 2; } while (readBytes != 0); } Property p = { result, readFormat, (int)nitems, readType }; return p; } static X11::Atom SelectTargetFromList(X11::Display* display, const char* targetType, X11::Atom* list, int count) { for (int i = 0; i < count; i++) { X11::Atom atom = list[i]; if (atom != 0 && StringAnsi(XGetAtomName(display, atom)) == targetType) return atom; } return 0; } static X11::Atom SelectTargetFromAtoms(X11::Display* display, const char* targetType, X11::Atom t1, X11::Atom t2, X11::Atom t3) { if (t1 != 0 && StringAnsi(XGetAtomName(display, t1)) == targetType) return t1; if (t2 != 0 && StringAnsi(XGetAtomName(display, t2)) == targetType) return t2; if (t3 != 0 && StringAnsi(XGetAtomName(display, t3)) == targetType) return t3; return 0; } static X11::Window FindAppWindow(X11::Display* display, X11::Window w) { int nprops, i = 0; X11::Atom* a; if (w == 0) return 0; a = X11::XListProperties(display, w, &nprops); for (i = 0; i < nprops; i++) { if (a[i] == xAtomXdndAware) break; } if (nprops) X11::XFree(a); if (i != nprops) return w; X11::Window child, wtmp; int tmp; unsigned int utmp; X11::XQueryPointer(display, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp); return FindAppWindow(display, child); } static Float2 GetX11MousePosition() { if (!xDisplay) return Float2::Zero; int32 x = 0, y = 0; uint32 screenCount = (uint32)X11::XScreenCount(xDisplay); for (uint32 i = 0; i < screenCount; i++) { X11::Window outRoot, outChild; int32 childX, childY; uint32 mask; if (X11::XQueryPointer(xDisplay, X11::XRootWindow(xDisplay, i), &outRoot, &outChild, &x, &y, &childX, &childY, &mask)) break; } return Float2((float)x, (float)y); } } DragDropEffect Window::DoDragDrop(const StringView& data) { if (CommandLine::Options.Headless) return DragDropEffect::None; if (SDLPlatform::UsesWayland()) return DoDragDropWayland(data); else return DoDragDropX11(data); } wl_data_source* dataSource; xdg_toplevel_drag_v1* toplevelDrag = nullptr; wl_data_offer* WaylandDataOffer = nullptr; // The last accepted offer uint32 WaylandDataOfferSerial = 0; // The last accepted serial for offer StringAnsi WaylandDataOfferMimeType; SDLWindow* DragTargetWindow = nullptr; Float2 DragTargetPosition; wl_data_offer* WaylandDataSelectionOffer = nullptr; void WaylandDataOffer_Offer(void* data, wl_data_offer* offer, const char *mime_type) { // We are being offered these types of data //LOG(Info, "WaylandDataOffer_Offer: {}", String(mime_type)); //if (WaylandDataOffer == nullptr) // return; //if (StringAnsi(mime_type) == "x.flaxengine.window.snap") // WaylandDataOfferMimeType = StringAnsi(mime_type); // wl_data_offer_accept(WaylandDataOffer, WaylandDataOfferSerial, mime_type); } void WaylandDataOffer_SourceActions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) { // //LOG(Info, "WaylandDataOffer_SourceActions: {}", source_actions); } void WaylandDataOffer_Action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) { // DnD: This action will be performed if dropped //LOG(Info, "WaylandDataOffer_Action: {}", dnd_action); } wl_data_offer_listener WaylandDataOfferListener = { WaylandDataOffer_Offer, WaylandDataOffer_SourceActions, WaylandDataOffer_Action}; void WaylandDataDevice_DataOffer(void *data, wl_data_device *wl_data_device, wl_data_offer *id) { LOG(Info, "WaylandDataDevice_DataOffer: {}", (uint64)id); /*int ret = wl_data_offer_add_listener(id, &WaylandDataOfferListener, nullptr); if (ret != 0) LOG(Error, "wl_data_offer_add_listener failed");*/ } void WaylandDataDevice_Enter(void *data, wl_data_device *wl_data_device, uint32 serial, wl_surface *surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer *id) { // DnD: The cursor entered a target surface LOG(Info, "WaylandDataDevice_Enter serial: {}, surface: {}, pos: {}x{}, id: {}", serial, (uint64)surface, wl_fixed_to_double(x), wl_fixed_to_double(y), (uint64)id); WaylandDataOffer = id; WaylandDataOfferSerial = serial; DragTargetPosition = Float2(MAX_float, MAX_float); SDLWindow* sourceWindow = (SDLWindow*)data; if (!SurfaceToWindowMap.TryGet(surface, DragTargetWindow)) DragTargetWindow = nullptr; if (DragTargetWindow != nullptr) DragTargetWindow = DragTargetWindow; if (/*SurfaceToWindowMap.TryGet(surface, DragTargetWindow) && */DragTargetWindow != sourceWindow) { // Inform that we support the following action at this given point wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); if (WaylandDataOfferMimeType == "x.flaxengine.window.snap") wl_data_offer_accept(WaylandDataOffer, WaylandDataOfferSerial, "x.flaxengine.window.snap"); } else { wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); } } void WaylandDataDevice_Leave(void *data, wl_data_device *wl_data_device) { // DnD: The cursor left the surface area // id from enter must be destroyed here LOG(Info, "WaylandDataDevice_Leave"); if (WaylandDataOffer != nullptr) wl_data_offer_destroy(WaylandDataOffer); WaylandDataOffer = nullptr; WaylandDataOfferSerial = 0; WaylandDataOfferMimeType = StringAnsi::Empty; } void WaylandDataDevice_Motion(void *data, wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) { // DnD: The cursor moves along the surface Float2 dragPosition(wl_fixed_to_double(x), wl_fixed_to_double(y)); LOG(Info, "WaylandDataDevice_Motion {},{}", (int)dragPosition.X, (int)dragPosition.Y); if (DragTargetWindow != nullptr) { Float2 mousePos = dragPosition * DragTargetWindow->GetDpiScale(); mousePos = Float2::Floor(mousePos); if (DragTargetPosition != mousePos) { //LOG(Info, "{}: {}", time, mousePos); Input::Mouse->OnMouseMove(mousePos, DragTargetWindow); DragTargetPosition = mousePos; } } //SDLWindow* targetWindow; //if (SurfaceToWindowMap.TryGet(surface, targetWindow) && targetWindow == surfaceWindow) } void WaylandDataDevice_Drop(void *data, wl_data_device *wl_data_device) { // DnD: The drop is accepted LOG(Info, "WaylandDataDevice_Drop"); /*int fds[2]; pipe(fds); wl_data_offer_receive(offer, "text/plain", fds[1]); close(fds[1]); // TODO: do something with fds[0] close(fds[0]);*/ if (WaylandDataOffer != nullptr) { wl_data_offer_finish(WaylandDataOffer); wl_data_offer_destroy(WaylandDataOffer); WaylandDataOffer = nullptr; } } void WaylandDataDevice_Selection(void *data, wl_data_device *wl_data_device, wl_data_offer *id) { // Clipboard: We can read the clipboard content /* int fds[2]; pipe(fds); wl_data_offer_receive(offer, "text/plain", fds[1]); close(fds[1]); wl_display_roundtrip(display); while (true) { char buf[1024]; ssize_t n = read(fds[0], buf, sizeof(buf)); if (n <= 0) break; //fwrite(buf, 1, n, stdout); } close(fds[0]); wl_data_offer_destroy(offer); */ LOG(Info, "WaylandDataDevice_Selection: {}", (uint64)id); if (WaylandDataSelectionOffer != nullptr) wl_data_offer_destroy(WaylandDataSelectionOffer); WaylandDataSelectionOffer = id; } wl_data_device_listener WaylandDataDeviceListener = { WaylandDataDevice_DataOffer, WaylandDataDevice_Enter, WaylandDataDevice_Leave, WaylandDataDevice_Motion, WaylandDataDevice_Drop, WaylandDataDevice_Selection }; void WaylandDataSource_Target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) { // The destination accepts the following types, or null if nothing //SDLWindow* window = static_cast(data); LOG(Info, "WaylandDataSource_Target mime: {}", String(mime_type)); } void WaylandDataSource_Send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) { // Clipboard: The other end has accepted the data? IGuiData* inputData = static_cast(data); //LOG(Info, "WaylandDataSource_Send mime: {}", String(mime_type)); if (inputData->GetType() == IGuiData::Type::Text) { UnixFile file(fd); StringAnsi text = StringAnsi(inputData->GetAsText()); file.Write(text.Get(), text.Length() * sizeof(StringAnsi::CharType)); file.Close(); //Platform::AtomicStore(((LinuxDropTextData*)inputData)->dragOver, 1); } } void WaylandDataSource_Cancelled(void* data, wl_data_source *source) { // Clipboard: other application has replaced the content in clipboard //SDLWindow* window = static_cast(data); //LOG(Info, "WaylandDataSource_Cancelled"); IGuiData* inputData = static_cast(data); Platform::AtomicStore(((LinuxDropTextData*)inputData)->dragOver, 1); wl_data_source_destroy(source); // The mouse up event was ignored earlier, release the button now SDLWindow* window = ((LinuxDropTextData*)inputData)->Window; Input::Mouse->OnMouseUp(Platform::GetMousePosition(), MouseButton::Left, window); /*if (DragTargetWindow != nullptr) { Input::Mouse->OnMouseUp(DragTargetPosition, MouseButton::Left, DragTargetWindow); } else*/ /*if (window != nullptr) { Input::Mouse->OnMouseUp(DragTargetPosition, MouseButton::Left, window); }*/ } void WaylandDataSource_DnDDropPerformed(void *data, struct wl_data_source *wl_data_source) { // The destination is being asked to begin DnD, asking confirmation with ASK actionh //SDLWindow* window = static_cast(data); //LOG(Info, "WaylandDataSource_DnDDropPerformed"); } void WaylandDataSource_DnDFinished(void *data, struct wl_data_source *wl_data_source) { // The destination has finally accepted the last given dnd_action //SDLWindow* window = static_cast(data); //LOG(Info, "WaylandDataSource_DnDFinished"); IGuiData* inputData = static_cast(data); Platform::AtomicStore(((LinuxDropTextData*)inputData)->dragOver, 1); wl_data_source_destroy(wl_data_source); // The mouse up event was ignored earlier, release the button now SDLWindow* window = ((LinuxDropTextData*)inputData)->Window; Input::Mouse->OnMouseUp(Platform::GetMousePosition(), MouseButton::Left, window); /*if (DragTargetWindow != nullptr) { Input::Mouse->OnMouseUp(DragTargetPosition, MouseButton::Left, DragTargetWindow); } else*/ /*if (window != nullptr) { Input::Mouse->OnMouseUp(DragTargetPosition, MouseButton::Left, window); }*/ } void WaylandDataSource_Action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) { // DnD: The destination may accept the given action if confirmed //SDLWindow* window = static_cast(data); LOG(Info, "WaylandDataSource_Action: {}", String(dnd_action == 0 ? "NONE" : dnd_action == 1 ? "COPY" : dnd_action == 2 ? "MOVE" : dnd_action == 4 ? "ASK" : "")); } wl_data_source_listener WaylandDataSourceListener = { WaylandDataSource_Target, WaylandDataSource_Send, WaylandDataSource_Cancelled, WaylandDataSource_DnDDropPerformed, WaylandDataSource_DnDFinished, WaylandDataSource_Action }; wl_event_queue* WaylandQueue = nullptr; wl_data_device_manager* wrappedManager = nullptr; wl_data_source* wrappedDataSource = nullptr; wl_data_device* wrappedDataDevice = nullptr; class WaylandDragDropJob : public ThreadPoolTask { public: int64 StartFlag = 0; int64 ExitFlag = 0; StringView data; SDLWindow* window; int64 dragOver = 0; int64 waitFlag = 0; // [ThreadPoolTask] bool Run() override { Scripting::GetScriptsDomain()->Dispatch(); wl_display* wrappedDisplay = WaylandDisplay;//(wl_display*)wl_proxy_create_wrapper(WaylandDisplay); //wl_proxy_set_queue((wl_proxy*)wrappedDisplay, queue); if (WaylandQueue == nullptr) { if (wrappedDataDevice != nullptr) wl_proxy_wrapper_destroy(wrappedDataDevice); if (wrappedDataSource != nullptr) wl_proxy_wrapper_destroy(wrappedDataSource); if (wrappedManager != nullptr) wl_proxy_wrapper_destroy(wrappedManager); if (dataDevice != nullptr) wl_data_device_destroy(dataDevice); // This seems to throw bogus warnings about wl_data_source still being attached to the queue if (WaylandQueue != nullptr) wl_event_queue_destroy(WaylandQueue); WaylandQueue = wl_display_create_queue(WaylandDisplay); wrappedManager = (wl_data_device_manager*)wl_proxy_create_wrapper(WaylandDataDeviceManager); wl_proxy_set_queue((wl_proxy*)wrappedManager, WaylandQueue); // //dataDevice = wl_data_device_manager_get_data_device(WaylandDataDeviceManager, WaylandSeat); //wl_data_device_add_listener(dataDevice, &WaylandDataDeviceListener, nullptr); //wl_display_roundtrip(WaylandDisplay); /*auto */dataDevice = wl_data_device_manager_get_data_device(wrappedManager, WaylandSeat); wl_data_device_add_listener(dataDevice, &WaylandDataDeviceListener, nullptr); wl_display_roundtrip(wrappedDisplay); wl_data_device_set_user_data(dataDevice, window); wrappedDataDevice = (wl_data_device*)wl_proxy_create_wrapper(dataDevice); wl_proxy_set_queue((wl_proxy*)wrappedDataDevice, WaylandQueue); } // We offer the following types of things for consumption: dataSource = wl_data_device_manager_create_data_source(wrappedManager); wrappedDataSource = (wl_data_source*)wl_proxy_create_wrapper(dataSource); wl_proxy_set_queue((wl_proxy*)wrappedDataSource, WaylandQueue); if (data == String("awindow")) { wl_data_source_offer(dataSource, "flaxengine/window"); wl_data_source_offer(dataSource, "text/plain;charset=utf-8"); // TODO: needs support for custom mime-types in SDL wl_data_source_set_actions(dataSource, wl_data_device_manager_dnd_action::WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); } else { wl_data_source_offer(dataSource, "text/plain"); wl_data_source_offer(dataSource, "text/plain;charset=utf-8"); wl_data_source_set_actions(dataSource, wl_data_device_manager_dnd_action::WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | wl_data_device_manager_dnd_action::WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); } LinuxDropTextData textData; textData.Text = *data; textData.Window = window; textData.dragOver = &dragOver; auto _window = window->GetSDLWindow(); //wl_data_source_set_user_data(wrappedDataSource, &textData); wl_data_source_add_listener(dataSource, &WaylandDataSourceListener, &textData); xdg_toplevel* toplevel = nullptr;//(xdg_toplevel*)SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, nullptr); if (toplevel == nullptr) { //Platform::AtomicStore(&StartFlag, 1); /*while (Platform::AtomicRead(&waitFlag) == 0) { }*/ //toplevel = (xdg_toplevel*)SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, nullptr); } xdg_toplevel* wrappedToplevel = nullptr; { wl_surface* origin = (wl_surface*)SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr); wl_surface* icon = nullptr; uint32 id = ImplicitGrabSerial; //id = (uint32)SDL_GetNumberProperty(SDL_GetGlobalProperties(), "wayland.serial", 0); wl_data_device_start_drag((wl_data_device*)SDL_GetPointerProperty(SDL_GetGlobalProperties(), "wayland.data_device", wrappedDataDevice), dataSource, origin, icon, id); if (data == String("awindow")) { if (toplevel != nullptr) { wrappedToplevel = (xdg_toplevel*)wl_proxy_create_wrapper(toplevel); wl_proxy_set_queue((wl_proxy*)wrappedToplevel, WaylandQueue); toplevelDrag = xdg_toplevel_drag_manager_v1_get_xdg_toplevel_drag(DragManager, dataSource); Float2 offset(100, 240); Float2 scaledOffset = offset / window->GetDpiScale(); //xdg_toplevel_drag_v1_attach(toplevelDrag, toplevel, (int32)scaledOffset.X, (int32)scaledOffset.Y); xdg_toplevel_drag_v1_attach(toplevelDrag, wrappedToplevel, (int32)scaledOffset.X, (int32)scaledOffset.Y); } } } /*wl_display_dispatch_queue(wrappedDisplay, queue); wl_display_roundtrip_queue(wrappedDisplay, queue); wl_display_flush(wrappedDisplay); wl_display_dispatch_queue(wrappedDisplay, queue); wl_display_dispatch(wrappedDisplay);*/ //wl_display_dispatch_queue_pending(wrappedDisplay, queue); /*int ret; while (ret = wl_display_prepare_read_queue(wrappedDisplay, queue), ret != 0) { if (ret == -1) LOG(Info, "err wl_display_prepare_read_queue: {}", errno); if (wl_display_dispatch_queue_pending(wrappedDisplay, queue) == -1) LOG(Warning, "err wl_display_dispatch_queue_pending: {}", errno); //else // LOG(Info, "OK wl_display_dispatch_queue_pending"); }*/ //if (wl_display_flush(wrappedDisplay) == -1) // LOG(Warning, "err wl_display_flush: {}", errno); Platform::AtomicStore(&StartFlag, 1); while (Platform::AtomicRead(&ExitFlag) == 0) { //SDLPlatform::Tick(); //Engine::OnDraw(); //wl_display_dispatch_queue(displayWrapped, queue); //wl_display_roundtrip_queue(displayWrapped, queue); //wl_display_flush(displayWrapped); //wl_display_dispatch_queue(displayWrapped, queue); //wl_display_dispatch(displayWrapped); //wl_display_dispatch_queue(wrappedDisplay, queue); //if (wl_display_flush(wrappedDisplay) == -1) // LOG(Warning, "err wl_display_flush: {}", errno); //if (wl_display_dispatch_pending(wrappedDisplay) == -1) // LOG(Warning, "err wl_display_dispatch_pending: {}", errno); if (wl_display_dispatch_queue(wrappedDisplay, WaylandQueue) == -1) LOG(Warning, "err wl_display_dispatch_queue: {}", errno); if (wl_display_roundtrip_queue(wrappedDisplay, WaylandQueue) == -1) LOG(Warning, "err wl_display_roundtrip_queue: {}", errno); if (toplevel == nullptr && data == String("awindow")) { if (Platform::AtomicRead(&waitFlag) != 0) { toplevel = (xdg_toplevel*)SDL_GetPointerProperty(SDL_GetWindowProperties(_window), SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, nullptr); if (toplevel != nullptr) { wrappedToplevel = (xdg_toplevel*)wl_proxy_create_wrapper(toplevel); wl_proxy_set_queue((wl_proxy*)wrappedToplevel, WaylandQueue); toplevelDrag = xdg_toplevel_drag_manager_v1_get_xdg_toplevel_drag(DragManager, dataSource); Float2 offset(100, 240); Float2 scaledOffset = offset / window->GetDpiScale(); //xdg_toplevel_drag_v1_attach(toplevelDrag, toplevel, (int32)scaledOffset.X, (int32)scaledOffset.Y); xdg_toplevel_drag_v1_attach(toplevelDrag, wrappedToplevel, (int32)scaledOffset.X, (int32)scaledOffset.Y); } } } //if (wl_display_dispatch_queue(wrappedDisplay, queue) == -1) // LOG(Warning, "err wl_display_dispatch_queue: {}", errno); //else // LOG(Info, "OK wl_display_dispatch_queue"); //if (wl_display_dispatch_queue_pending(wrappedDisplay, queue) == -1) // LOG(Warning, "err wl_display_dispatch_queue_pending: {}", errno); //else // LOG(Info, "OK wl_display_dispatch_queue_pending"); //wl_display_dispatch_pending(WaylandDisplay //Platform::Sleep(1); } if (wl_display_roundtrip_queue(wrappedDisplay, WaylandQueue) == -1) LOG(Warning, "err wl_display_roundtrip_queue: {}", errno); //if (wl_display_dispatch_queue(wrappedDisplay, queue) == -1) // LOG(Warning, "err wl_display_dispatch_queue: {}", errno); if (toplevelDrag != nullptr) { wl_proxy_wrapper_destroy(wrappedToplevel); xdg_toplevel_drag_v1_destroy(toplevelDrag); toplevelDrag = nullptr; } /* wl_proxy_wrapper_destroy(wrappedDataDevice); wl_proxy_wrapper_destroy(wrappedDataSource); wl_proxy_wrapper_destroy(wrappedManager); wl_data_device_destroy(dataDevice);*/ if (wrappedDataSource != nullptr) wl_proxy_wrapper_destroy(wrappedDataSource); //if (dataSource != nullptr) // wl_proxy_wrapper_destroy(dataSource); if (WaylandDataSelectionOffer != nullptr) { wl_data_offer_destroy(WaylandDataSelectionOffer); WaylandDataSelectionOffer = nullptr; } // This seems to throw bogus warnings about wl_data_source still being attached to the queue /*wl_event_queue_destroy(WaylandQueue); */ //dataDevice = nullptr; return false; } }; DragDropEffect Window::DoDragDropWayland(const StringView& data) { // For drag-and-drop, we need to setup the event queue in separate thread to avoid racing issues // while SDL is dispatching the main Wayland event queue when receiving the data offer from us. waylandDraggingActive = true; auto task = New(); task->data = data; task->window = this; task->dragOver = 0; Task::StartNew(task); while (task->GetState() == TaskState::Queued) Platform::Sleep(1); while (Platform::AtomicRead(&task->StartFlag) == 0) { Platform::Sleep(1); } Show(); //Focus(); int counter = 100; while (Platform::AtomicRead(&task->dragOver) == 0) { SDLPlatform::Tick(); Engine::OnUpdate(); // For docking updates Engine::OnDraw(); Platform::Sleep(1); if (IsVisible() && Platform::AtomicRead(&task->waitFlag) == 0) { /*if (counter > 0) counter--; else*/ Platform::AtomicStore(&task->waitFlag, 1); } } Platform::AtomicStore(&task->ExitFlag, 1); task->Wait(); waylandDraggingActive = false; return DragDropEffect::None; } DragDropEffect Window::DoDragDropX11(const StringView& data) { auto cursorWrong = X11::XCreateFontCursor(xDisplay, 54); auto cursorTransient = X11::XCreateFontCursor(xDisplay, 24); auto cursorGood = X11::XCreateFontCursor(xDisplay, 4); Array> formats; formats.Add(X11::XInternAtom(xDisplay, "text/plain", 0)); formats.Add(xAtomText); formats.Add(xAtomString); StringAnsi dataAnsi(data); LinuxDropTextData dropData; dropData.Text = data; #if !PLATFORM_SDL X11::Window mainWindow = _window; #else X11::Window mainWindow = static_cast(GetX11WindowHandle()); #endif // Make sure SDL hasn't grabbed the pointer, and force ungrab it XUngrabPointer(xDisplay, CurrentTime); SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); // Begin dragging auto screen = X11::XDefaultScreen(xDisplay); auto rootWindow = X11::XRootWindow(xDisplay, screen); if (X11::XGrabPointer(xDisplay, mainWindow, 1, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, rootWindow, cursorWrong, CurrentTime) != GrabSuccess) { SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "1"); return DragDropEffect::None; } X11::XSetSelectionOwner(xDisplay, xAtomXdndSelection, mainWindow, CurrentTime); // Process events X11::XEvent event; enum Status { Unaware, Unreceptive, CanDrop, }; int status = Unaware, previousVersion = -1; X11::Window previousWindow = 0; DragDropEffect result = DragDropEffect::None; float lastDraw = Platform::GetTimeSeconds(); float startTime = lastDraw; while (true) { X11::XNextEvent(xDisplay, &event); if (event.type == SelectionClear) break; if (event.type == SelectionRequest) { // Extract the relavent data X11::Window owner = event.xselectionrequest.owner; X11::Atom selection = event.xselectionrequest.selection; X11::Atom target = event.xselectionrequest.target; X11::Atom property = event.xselectionrequest.property; X11::Window requestor = event.xselectionrequest.requestor; X11::Time timestamp = event.xselectionrequest.time; X11::Display* disp = event.xselection.display; X11::XEvent s; s.xselection.type = SelectionNotify; s.xselection.requestor = requestor; s.xselection.selection = selection; s.xselection.target = target; s.xselection.property = 0; s.xselection.time = timestamp; if (target == xAtomTargets) { Array targets; targets.Add(target); targets.Add(X11::XInternAtom(disp, "MULTIPLE", 0)); targets.Add(formats.Get(), formats.Count()); X11::XChangeProperty(disp, requestor, property, xAtomAtom, 32, PropModeReplace, (unsigned char*)targets.Get(), targets.Count()); s.xselection.property = property; } else if (formats.Contains(target)) { s.xselection.property = property; X11::XChangeProperty(disp, requestor, property, target, 8, PropModeReplace, reinterpret_cast(dataAnsi.Get()), dataAnsi.Length()); } X11::XSendEvent(event.xselection.display, event.xselectionrequest.requestor, 1, 0, &s); } else if (event.type == MotionNotify) { // Find window under mouse auto window = Impl::FindAppWindow(xDisplay, rootWindow); int fmt, version = -1; X11::Atom atmp; unsigned long nitems, bytesLeft; unsigned char* data = nullptr; if (window == previousWindow) version = previousVersion; else if(window == 0) ; else if (X11::XGetWindowProperty(xDisplay, window, xAtomXdndAware, 0, 2, 0, AnyPropertyType, &atmp, &fmt, &nitems, &bytesLeft, &data) != Success) continue; else if (data == 0) continue; else if (fmt != 32) continue; else if (nitems != 1) continue; else version = data[0]; if (status == Unaware && version != -1) status = Unreceptive; else if(version == -1) status = Unaware; xDndPos = Float2((float)event.xmotion.x_root, (float)event.xmotion.y_root); // Update mouse grab if (status == Unaware) X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorWrong, CurrentTime); else if(status == Unreceptive) X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorTransient, CurrentTime); else X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, cursorGood, CurrentTime); if (window != previousWindow && previousVersion != -1) { // Send drag left event auto ww = WindowsManager::GetByNativePtr((void*)previousWindow); if (ww) { ww->_dragOver = false; ww->OnDragLeave(); } else { X11::XClientMessageEvent m; memset(&m, 0, sizeof(m)); m.type = ClientMessage; m.display = event.xclient.display; m.window = previousWindow; m.message_type = xAtomXdndLeave; m.format = 32; m.data.l[0] = mainWindow; m.data.l[1] = 0; m.data.l[2] = 0; m.data.l[3] = 0; m.data.l[4] = 0; X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m); X11::XFlush(xDisplay); } } if (window != previousWindow && version != -1) { // Send drag enter event auto ww = WindowsManager::GetByNativePtr((void*)window); if (ww) { xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition()); xDndResult = DragDropEffect::None; ww->OnDragEnter(&dropData, xDndPos, xDndResult); } else { X11::XClientMessageEvent m; memset(&m, 0, sizeof(m)); m.type = ClientMessage; m.display = event.xclient.display; m.window = window; m.message_type = xAtomXdndEnter; m.format = 32; m.data.l[0] = mainWindow; m.data.l[1] = Math::Min(5, version) << 24 | (formats.Count() > 3); m.data.l[2] = formats.Count() > 0 ? formats[0] : 0; m.data.l[3] = formats.Count() > 1 ? formats[1] : 0; m.data.l[4] = formats.Count() > 2 ? formats[2] : 0; X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m); X11::XFlush(xDisplay); } } if (version != -1) { // Send position event auto ww = WindowsManager::GetByNativePtr((void*)window); if (ww) { xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition()); ww->_dragOver = true; xDndResult = DragDropEffect::None; ww->OnDragOver(&dropData, xDndPos, xDndResult); status = CanDrop; } else { int x, y, tmp; unsigned int utmp; X11::Window wtmp; X11::XQueryPointer(xDisplay, window, &wtmp, &wtmp, &tmp, &tmp, &x, &y, &utmp); X11::XClientMessageEvent m; memset(&m, 0, sizeof(m)); m.type = ClientMessage; m.display = event.xclient.display; m.window = window; m.message_type = xAtomXdndPosition; m.format = 32; m.data.l[0] = mainWindow; m.data.l[1] = 0; m.data.l[2] = (x << 16) | y; m.data.l[3] = CurrentTime; m.data.l[4] = xAtomXdndActionCopy; X11::XSendEvent(xDisplay, window, 0, NoEventMask, (X11::XEvent*)&m); X11::XFlush(xDisplay); } } previousWindow = window; previousVersion = version; } else if (event.type == ClientMessage && event.xclient.message_type == xAtomXdndStatus) { if ((event.xclient.data.l[1]&1) && status != Unaware) status = CanDrop; if (!(event.xclient.data.l[1]&1) && status != Unaware) status = Unreceptive; } else if (event.type == ButtonRelease && event.xbutton.button == Button1) { if (status == CanDrop) { // Send drop event auto ww = WindowsManager::GetByNativePtr((void*)previousWindow); if (ww) { xDndPos = ww->ScreenToClient(Impl::GetX11MousePosition()); xDndResult = DragDropEffect::None; ww->OnDragDrop(&dropData, xDndPos, xDndResult); ww->Focus(); result = xDndResult; } else { X11::XClientMessageEvent m; memset(&m, 0, sizeof(m)); m.type = ClientMessage; m.display = event.xclient.display; m.window = previousWindow; m.message_type = xAtomXdndDrop; m.format = 32; m.data.l[0] = mainWindow; m.data.l[1] = 0; m.data.l[2] = CurrentTime; m.data.l[3] = 0; m.data.l[4] = 0; X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m); X11::XFlush(xDisplay); result = DragDropEffect::Copy; } } break; } // Redraw const float time = Platform::GetTimeSeconds(); if (time - lastDraw >= 1.0f / 20.0f) { lastDraw = time; Engine::OnDraw(); } // Prevent dead-loop if (time - startTime >= 10.0f) { LOG(Warning, "DoDragDrop timed out after 10 seconds."); break; } } // Drag end if (previousWindow != 0 && previousVersion != -1) { // Send drag left event auto ww = WindowsManager::GetByNativePtr((void*)previousWindow); if (ww) { ww->_dragOver = false; ww->OnDragLeave(); } else { X11::XClientMessageEvent m; memset(&m, 0, sizeof(m)); m.type = ClientMessage; m.display = event.xclient.display; m.window = previousWindow; m.message_type = xAtomXdndLeave; m.format = 32; m.data.l[0] = mainWindow; m.data.l[1] = 0; m.data.l[2] = 0; m.data.l[3] = 0; m.data.l[4] = 0; X11::XSendEvent(xDisplay, previousWindow, 0, NoEventMask, (X11::XEvent*)&m); X11::XFlush(xDisplay); } } // End grabbing X11::XChangeActivePointerGrab(xDisplay, Button1MotionMask | ButtonReleaseMask, 0, CurrentTime); XUngrabPointer(xDisplay, CurrentTime); X11::XFlush(xDisplay); SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "1"); return result; } void SDLClipboard::Clear() { SetText(StringView::Empty); } void SDLClipboard::SetText(const StringView& text) { if (CommandLine::Options.Headless) return; auto mainWindow = Engine::MainWindow; if (!mainWindow) return; if (xDisplay) { X11::Window window = (X11::Window)(mainWindow->GetX11WindowHandle()); Impl::ClipboardText.Set(text.Get(), text.Length()); X11::XSetSelectionOwner(xDisplay, xAtomClipboard, window, CurrentTime); // CLIPBOARD //X11::XSetSelectionOwner(xDisplay, xAtomPrimary, window, CurrentTime); // XA_PRIMARY X11::XFlush(xDisplay); X11::XGetSelectionOwner(xDisplay, xAtomClipboard); //X11::XGetSelectionOwner(xDisplay, xAtomPrimary); } else { LOG(Warning, "Wayland clipboard support is not implemented yet."); // TODO: Wayland } } void SDLClipboard::SetRawData(const Span& data) { } void SDLClipboard::SetFiles(const Array& files) { } String SDLClipboard::GetText() { if (CommandLine::Options.Headless) return String::Empty; String result; auto mainWindow = Engine::MainWindow; if (!mainWindow) return result; if (xDisplay) { X11::Window window = (X11::Window)mainWindow->GetX11WindowHandle(); Impl::ClipboardGetText(result, xAtomClipboard, xAtomUTF8String, window); if (result.HasChars()) return result; Impl::ClipboardGetText(result, xAtomClipboard, xAtomString, window); if (result.HasChars()) return result; Impl::ClipboardGetText(result, xAtomPrimary, xAtomUTF8String, window); if (result.HasChars()) return result; Impl::ClipboardGetText(result, xAtomPrimary, xAtomString, window); return result; } else { LOG(Warning, "Wayland clipboard is not implemented yet."); // TODO: Wayland return String::Empty; } } Array SDLClipboard::GetRawData() { return Array(); } Array SDLClipboard::GetFiles() { return Array(); } bool SDLCALL SDLPlatform::X11EventHook(void *userdata, _XEvent *xevent) { const X11::XEvent& event = *(X11::XEvent*)xevent; Window* window; // External event handling xEventReceived(xevent); if (event.type == ClientMessage) { if ((uint32)event.xclient.message_type == (uint32)xAtomXdndEnter) { // Drag&drop enter X11::Window source = event.xclient.data.l[0]; xDnDVersion = (int32)(event.xclient.data.l[1] >> 24); const char* targetTypeFiles = "text/uri-list"; if (event.xclient.data.l[1] & 1) { Property p = Impl::ReadProperty(xDisplay, source, XInternAtom(xDisplay, "XdndTypeList", 0)); xDnDRequested = Impl::SelectTargetFromList(xDisplay, targetTypeFiles, (X11::Atom*)p.data, p.nitems); X11::XFree(p.data); } else { xDnDRequested = Impl::SelectTargetFromAtoms(xDisplay, targetTypeFiles, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]); } return false; } else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndPosition) { // Drag&drop move X11::XClientMessageEvent m; memset(&m, 0, sizeof(m)); m.type = ClientMessage; m.display = event.xclient.display; m.window = event.xclient.data.l[0]; m.message_type = xAtomXdndStatus; m.format = 32; m.data.l[0] = event.xany.window; m.data.l[1] = (xDnDRequested != 0); m.data.l[2] = 0; m.data.l[3] = 0; m.data.l[4] = xAtomXdndActionCopy; X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m); X11::XFlush(xDisplay); xDndPos = Float2((float)(event.xclient.data.l[2] >> 16), (float)(event.xclient.data.l[2] & 0xffff)); window = WindowsManager::GetByNativePtr((void*)event.xany.window); if (window) { LinuxDropFilesData dropData; xDndResult = DragDropEffect::None; if (window->_dragOver) { window->OnDragOver(&dropData, xDndPos, xDndResult); } else { window->_dragOver = true; window->OnDragEnter(&dropData, xDndPos, xDndResult); } } return false; } else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndLeave) { window = WindowsManager::GetByNativePtr((void*)event.xany.window); if (window && window->_dragOver) { window->_dragOver = false; window->OnDragLeave(); } return false; } else if ((uint32)event.xclient.message_type == (uint32)xAtomXdndDrop) { auto w = event.xany.window; if (xDnDRequested != 0) { xDndSourceWindow = event.xclient.data.l[0]; if (xDnDVersion >= 1) XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, xAtomPrimary, w, event.xclient.data.l[2]); else XConvertSelection(xDisplay, xAtomXdndSelection, xDnDRequested, xAtomPrimary, w, CurrentTime); } else { X11::XClientMessageEvent m; memset(&m, 0, sizeof(m)); m.type = ClientMessage; m.display = event.xclient.display; m.window = event.xclient.data.l[0]; m.message_type = xAtomXdndFinished; m.format = 32; m.data.l[0] = w; m.data.l[1] = 0; m.data.l[2] = 0; X11::XSendEvent(xDisplay, event.xclient.data.l[0], 0, NoEventMask, (X11::XEvent*)&m); } return false; } } else if (event.type == SelectionNotify) { if (event.xselection.target == xDnDRequested) { // Drag&drop window = WindowsManager::GetByNativePtr((void*)event.xany.window); if (window) { Property p = Impl::ReadProperty(xDisplay, event.xany.window, xAtomPrimary); if (xDndResult != DragDropEffect::None) { LinuxDropFilesData dropData; const String filesList((const char*)p.data); filesList.Split('\n', dropData.Files); for (auto& e : dropData.Files) { e.Replace(TEXT("file://"), TEXT("")); e.Replace(TEXT("%20"), TEXT(" ")); e = e.TrimTrailing(); } xDndResult = DragDropEffect::None; window->OnDragDrop(&dropData, xDndPos, xDndResult); } } X11::XClientMessageEvent m; memset(&m, 0, sizeof(m)); m.type = ClientMessage; m.display = xDisplay; m.window = xDndSourceWindow; m.message_type = xAtomXdndFinished; m.format = 32; m.data.l[0] = event.xany.window; m.data.l[1] = 1; m.data.l[2] = xAtomXdndActionCopy; XSendEvent(xDisplay, xDndSourceWindow, 0, NoEventMask, (X11::XEvent*)&m); return false; } return false; } else if (event.type == SelectionRequest) { if (event.xselectionrequest.selection != xAtomClipboard) return false; const X11::XSelectionRequestEvent* xsr = &event.xselectionrequest; X11::XSelectionEvent ev = { 0 }; ev.type = SelectionNotify; ev.display = xsr->display; ev.requestor = xsr->requestor; ev.selection = xsr->selection; ev.time = xsr->time; ev.target = xsr->target; ev.property = xsr->property; int result = 0; if (ev.target == xAtomTargets) { Array> types(2); types.Add(xAtomTargets); types.Add(xAtomUTF8String); result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomAtom, 32, PropModeReplace, (unsigned char*)types.Get(), types.Count()); } else if (ev.target == xAtomString || ev.target == xAtomText) result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomString, 8, PropModeReplace, (unsigned char*)Impl::ClipboardText.Get(), Impl::ClipboardText.Length()); else if (ev.target == xAtomUTF8String) result = X11::XChangeProperty(xDisplay, ev.requestor, ev.property, xAtomUTF8String, 8, PropModeReplace, (unsigned char*)Impl::ClipboardText.Get(), Impl::ClipboardText.Length()); else ev.property = 0; if ((result & 2) == 0) X11::XSendEvent(xDisplay, ev.requestor, 0, 0, (X11::XEvent*)&ev); return false; } else if (event.type == SelectionClear) return false; else if (event.type == XFixesSelectionNotifyEvent) return false; return true; } int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event) { if (event->error_code == 5) return 0; // BadAtom (invalid Atom parameter) char buffer[256]; XGetErrorText(display, event->error_code, buffer, sizeof(buffer)); LOG(Error, "X11 Error: {0}", String(buffer)); return 0; } bool SDLPlatform::InitPlatform() { //if (LinuxPlatform::Init()) // return true; #if false if (!CommandLine::Options.Headless && strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { WaylandDisplay = (wl_display*)SDL_GetPointerProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, nullptr); wl_registry* registry = wl_display_get_registry(WaylandDisplay); wl_registry_add_listener(registry, &WaylandRegistryListener, nullptr); wl_display_roundtrip(WaylandDisplay); dataDevice = wl_data_device_manager_get_data_device(WaylandDataDeviceManager, WaylandSeat); wl_data_device_add_listener(dataDevice, &WaylandDataDeviceListener, nullptr); wl_display_roundtrip(WaylandDisplay); } #else bool waylandRequested = (!CommandLine::Options.X11 || CommandLine::Options.Wayland) && StringAnsi(SDL_GetHint(SDL_HINT_VIDEO_DRIVER)) == "wayland"; if (!CommandLine::Options.Headless && waylandRequested) { // Ignore in X11 session String waylandDisplayEnv; if (!GetEnvironmentVariable(String("WAYLAND_DISPLAY"), waylandDisplayEnv)) { WaylandDisplay = (wl_display*)SDL_GetPointerProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, nullptr); if (WaylandDisplay == nullptr) { WaylandDisplay = wl_display_connect(nullptr); SDL_SetPointerProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, WaylandDisplay); } if (WaylandDisplay != nullptr) { // We need to manage the wl_display and create the wl_data_device // before SDL so we can receive drag-and-drop related events from compositor. //SDL_SetPointerProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, WaylandDisplay); wl_registry* registry = wl_display_get_registry(WaylandDisplay); wl_registry_add_listener(registry, &WaylandRegistryListener, nullptr); wl_display_roundtrip(WaylandDisplay); /*dataDevice = wl_data_device_manager_get_data_device(WaylandDataDeviceManager, WaylandSeat); wl_data_device_add_listener(dataDevice, &WaylandDataDeviceListener, nullptr); wl_display_roundtrip(WaylandDisplay);*/ } } } #endif return false; } bool SDLPlatform::InitPlatformX11(void* display) { if (xDisplay || WaylandDisplay) return false; // The Display instance must be the same one SDL uses internally xDisplay = (X11::Display*)display; SDL_SetX11EventHook((SDL_X11EventHook)&X11EventHook, nullptr); X11::XSetErrorHandler(X11ErrorHandler); //xDisplay = X11::XOpenDisplay(nullptr); xAtomDeleteWindow = X11::XInternAtom(xDisplay, "WM_DELETE_WINDOW", 0); xAtomXdndEnter = X11::XInternAtom(xDisplay, "XdndEnter", 0); xAtomXdndPosition = X11::XInternAtom(xDisplay, "XdndPosition", 0); xAtomXdndLeave = X11::XInternAtom(xDisplay, "XdndLeave", 0); xAtomXdndDrop = X11::XInternAtom(xDisplay, "XdndDrop", 0); xAtomXdndActionCopy = X11::XInternAtom(xDisplay, "XdndActionCopy", 0); xAtomXdndStatus = X11::XInternAtom(xDisplay, "XdndStatus", 0); xAtomXdndSelection = X11::XInternAtom(xDisplay, "XdndSelection", 0); xAtomXdndFinished = X11::XInternAtom(xDisplay, "XdndFinished", 0); xAtomXdndAware = X11::XInternAtom(xDisplay, "XdndAware", 0); xAtomWmStateHidden = X11::XInternAtom(xDisplay, "_NET_WM_STATE_HIDDEN", 0); xAtomWmStateMaxHorz = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", 0); xAtomWmStateMaxVert = X11::XInternAtom(xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", 0); xAtomWmWindowOpacity = X11::XInternAtom(xDisplay, "_NET_WM_WINDOW_OPACITY", 0); xAtomWmName = X11::XInternAtom(xDisplay, "_NET_WM_NAME", 0); xAtomAtom = static_cast(4); // XA_ATOM xAtomClipboard = X11::XInternAtom(xDisplay, "CLIPBOARD", 0); xAtomPrimary = static_cast(1); // XA_PRIMARY xAtomTargets = X11::XInternAtom(xDisplay, "TARGETS", 0); xAtomText = X11::XInternAtom(xDisplay, "TEXT", 0); xAtomString = static_cast(31); // XA_STRING xAtomUTF8String = X11::XInternAtom(xDisplay, "UTF8_STRING", 1); if (xAtomUTF8String == 0) xAtomUTF8String = xAtomString; xAtomXselData = X11::XInternAtom(xDisplay, "XSEL_DATA", 0); // We need to override handling of the XFixes selection tracking events from SDL auto screen = X11::XDefaultScreen(xDisplay); auto rootWindow = X11::XRootWindow(xDisplay, screen); int eventBase = 0, errorBase = 0; if (X11::XFixesQueryExtension(xDisplay, &eventBase, &errorBase)) { XFixesSelectionNotifyEvent = eventBase + XFixesSelectionNotify; X11::XFixesSelectSelectionInput(xDisplay, rootWindow, xAtomClipboard, XFixesSetSelectionOwnerNotifyMask); X11::XFixesSelectSelectionInput(xDisplay, rootWindow, xAtomPrimary, XFixesSetSelectionOwnerNotifyMask); } return false; } void SDLPlatform::Exit() { } void* SDLPlatform::GetXDisplay() { return xDisplay; } void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable) { base::SetHighDpiAwarenessEnabled(enable); } bool SDLPlatform::UsesWayland() { return WaylandDisplay != nullptr; } bool SDLPlatform::UsesXWayland() { static bool foundWaylandDisplay = [] { String waylandDisplay; return !GetEnvironmentVariable(TEXT("WAYLAND_DISPLAY"), waylandDisplay) && waylandDisplay.Length() > 0; }(); return xDisplay != nullptr && foundWaylandDisplay; } bool SDLPlatform::UsesX11() { return xDisplay != nullptr; } void WaylandPointer_Enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { /*SDLWindow* window; if (!SurfaceToWindowMap.TryGet(surface, window)) return; LastPointerWindow = window; LastPointerPosition = Int2(surface_x, surface_y);*/ //LOG(Info, "WaylandPointerEnter serial:{}", serial); //ImplicitGrabSerial = serial; } void WaylandPointer_Leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { //LastPointerWindow = nullptr; //LOG(Info, "WaylandPointerLeave serial:{}", serial); } void WaylandPointer_Motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { //LOG(Info, "WaylandPointerMotion time:{}", time); //LastPointerPosition = Int2(surface_x, surface_y); } void WaylandPointer_Button(void* data, wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { LOG(Info, "WaylandPointerButton serial:{}, button:{}, state:{}", serial, button, state); // HACK: We store the serial for upcoming drag-and-drop action even though we are // not really performing the action during this specific button press event. // SDL receives the same event which actually starts the drag process. if (state == 1) ImplicitGrabSerial = serial; } void WaylandPointer_Axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { //LOG(Info, "WaylandPointerAxis time:{}", time); } void WaylandPointer_Frame(void *data, struct wl_pointer *wl_pointer) { //LOG(Info, "WaylandPointerFrame"); } void WaylandPointer_AxisSource(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) { //LOG(Info, "WaylandPointerAxisSource"); } void WaylandPointer_AxisStop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { //LOG(Info, "WaylandPointerAxisStop time:{}", time); } void WaylandPointer_AxisDiscrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { //LOG(Info, "WaylandPointerAxisDiscrete"); } void WaylandPointer_AxisValue120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) { //LOG(Info, "WaylandPointerAxisValue120"); } void WaylandPointer_AxisRelativeDirection(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) { //LOG(Info, "WaylandPointerAxisRelativeDirection"); } wl_pointer_listener WaylandPointerListener = { WaylandPointer_Enter, WaylandPointer_Leave, WaylandPointer_Motion, WaylandPointer_Button, WaylandPointer_Axis, WaylandPointer_Frame, WaylandPointer_AxisSource, WaylandPointer_AxisStop, WaylandPointer_AxisDiscrete, WaylandPointer_AxisValue120, WaylandPointer_AxisRelativeDirection }; wl_pointer* WaylandPointer = nullptr; void SeatCapabilities(void* data, wl_seat* seat, uint32 capabilities) { if ((capabilities & wl_seat_capability::WL_SEAT_CAPABILITY_POINTER) != 0) { WaylandPointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(WaylandPointer, &WaylandPointerListener, nullptr); } } void SeatName(void* data, wl_seat* seat, const char* name) { } wl_seat_listener SeatListener = { SeatCapabilities, SeatName }; void WaylandRegistryGlobal(void* data, wl_registry *registry, uint32 id, const char* interface, uint32 version) { StringAnsi interfaceStr(interface); //LOG(Info, "WaylandRegistryGlobal id: {}, interface: {}", id, String(interface)); if (interfaceStr == "xdg_toplevel_drag_manager_v1") DragManager = (xdg_toplevel_drag_manager_v1*)wl_registry_bind(registry, id, &xdg_toplevel_drag_manager_v1_interface, 1U); else if (interfaceStr == "wl_seat") { WaylandSeat = (wl_seat*)wl_registry_bind(registry, id, &wl_seat_interface, Math::Min(9U, version)); wl_seat_add_listener(WaylandSeat, &SeatListener, nullptr); } else if (interfaceStr == "wl_data_device_manager") WaylandDataDeviceManager = (wl_data_device_manager*)wl_registry_bind(registry, id, &wl_data_device_manager_interface, Math::Min(3U, version)); else if (interfaceStr == "xdg_wm_base") WaylandXdgWmBase = (xdg_wm_base*)wl_registry_bind(registry, id, &xdg_wm_base_interface, Math::Min(6U, version)); } void WaylandRegistryGlobalRemove(void* data, wl_registry *registry, uint32 id) { LOG(Info, "WaylandRegistryGlobalRemove id:{}", id); } #endif