Files
FlaxEngine/Source/Engine/Platform/SDL/SDLPlatform.Linux.cpp

1791 lines
63 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#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 "Engine/Engine/Time.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Platform/Base/DragDropHelper.h"
#include "Engine/Platform/Unix/UnixFile.h"
#include <SDL3/SDL_video.h>
#include <SDL3/SDL_system.h>
#include <SDL3/SDL_hints.h>
#include <errno.h>
#include <wayland/xdg-toplevel-drag-v1.h>
#include <wayland/xdg-shell.h>
// 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<wl_surface*, SDLWindow*> 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<void*> 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<String> Files;
SDLWindow* Window;
Type GetType() const override
{
return Type::Files;
}
String GetAsText() const override
{
return String::Empty;
}
void GetAsFiles(Array<String>* 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<String>* 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<SDLWindow*>(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<IGuiData*>(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<SDLWindow*>(data);
//LOG(Info, "WaylandDataSource_Cancelled");
IGuiData* inputData = static_cast<IGuiData*>(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<SDLWindow*>(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<SDLWindow*>(data);
//LOG(Info, "WaylandDataSource_DnDFinished");
IGuiData* inputData = static_cast<IGuiData*>(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<SDLWindow*>(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;
SDLWindow* dragSourceWindow;
Float2 dragOffset = Float2::Zero;
int64 dragOver = 0;
int64 waitFlag = 0;
// [ThreadPoolTask]
bool Run() override
{
bool dragWindow = data == String("notawindow");
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, dragWindow ? dragSourceWindow : 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 (dragWindow)
{
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();
auto _mainwindow = dragSourceWindow->GetSDLWindow();
//if (!window->IsVisible())
// _window = mainwindow->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(_mainwindow), 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 (dragWindow)
{
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 scaledOffset = dragOffset / 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 && dragWindow)
{
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 scaledOffset = dragOffset / 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, Window* dragSourceWindow, Float2 dragOffset)
{
// 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.
// Show()?
{
if (!_visible)
{
if (_showAfterFirstPaint)
{
if (RenderTask)
RenderTask->Enabled = true;
}
else
SDL_ShowWindow(_window);
}
WindowBase::Show();
}
//while (true)
{
const double time = Platform::GetTimeSeconds();
// Update game logic
if (Time::OnBeginUpdate(time))
{
Engine::OnUpdate();
Engine::OnLateUpdate();
Time::OnEndUpdate();
}
SDLPlatform::Tick();
Engine::OnDraw();
Platform::Sleep(1);
}
waylandDraggingActive = true;
auto task = New<WaylandDragDropJob>();
task->data = data;
task->window = this;
task->dragSourceWindow = dragSourceWindow; // Needs to be the parent window when dragging a tab to window
task->dragOver = 0;
task->dragOffset = dragOffset;
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();//Scripting::Update(); // 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);
}
}
// The mouse up event was ignored earlier, release the button now
Input::Mouse->OnMouseUp(Platform::GetMousePosition(), MouseButton::Left, this);
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<X11::Atom, FixedAllocation<3>> 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<X11::Window>(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<X11::Atom> 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<const unsigned char*>(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<byte>& data)
{
}
void SDLClipboard::SetFiles(const Array<String>& 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<byte> SDLClipboard::GetRawData()
{
return Array<byte>();
}
Array<String> SDLClipboard::GetFiles()
{
return Array<String>();
}
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<X11::Atom, FixedAllocation<2>> 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<X11::Atom>(4); // XA_ATOM
xAtomClipboard = X11::XInternAtom(xDisplay, "CLIPBOARD", 0);
xAtomPrimary = static_cast<X11::Atom>(1); // XA_PRIMARY
xAtomTargets = X11::XInternAtom(xDisplay, "TARGETS", 0);
xAtomText = X11::XInternAtom(xDisplay, "TEXT", 0);
xAtomString = static_cast<X11::Atom>(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::GetXDisplay()
{
return xDisplay;
}
void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
{
base::SetHighDpiAwarenessEnabled(enable);
}
bool SDLPlatform::UsesWindows()
{
return false;
}
bool SDLPlatform::UsesWayland()
{
if (xDisplay == nullptr && WaylandDisplay == nullptr)
{
// In case the X11 display pointer has not been updated yet
return strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0;
}
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()
{
if (xDisplay == nullptr && WaylandDisplay == nullptr)
{
// In case the X11 display pointer has not been updated yet
return strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0;
}
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, Math::Min(1U, version));
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