diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index e7c9fdf8..788983ac 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -20,8 +20,6 @@ #include -#include "xdnd-proxy.h" - using namespace std::literals::chrono_literals; // A catchable alternative to `assert()`. Normally all of our `assert(!error)` @@ -204,6 +202,7 @@ Editor::Editor(MainContext& main_context, std::optional> timer_proc) : use_xembed(config.editor_xembed), x11_connection(xcb_connect(nullptr, nullptr), xcb_disconnect), + dnd_proxy_handle(WineXdndProxy::init_proxy()), client_area(get_maximum_screen_dimensions(*x11_connection)), // Create a window without any decoratiosn for easy embedding. The // combination of `WS_EX_TOOLWINDOW` and `WS_POPUP` causes the window to diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index cb2d0436..339ad803 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -31,6 +31,7 @@ #include "../common/configuration.h" #include "utils.h" +#include "xdnd-proxy.h" /** * The maximum number of Win32 messages to handle per message loop. This is @@ -223,8 +224,17 @@ class Editor { */ void do_xembed() const; + /** + * Every editor window gets its own X11 connection. + */ std::shared_ptr x11_connection; + /** + * A handle for our Wine->X11 drag-and-drop proxy. We only have one of these + * per process, and it gets freed again when the last handle gets dropped. + */ + WineXdndProxy::Handle dnd_proxy_handle; + /** * The Wine window's client area, or the maximum size of that window. This * will be set to a size that's large enough to be able to enter full screen diff --git a/src/wine-host/xdnd-proxy.cpp b/src/wine-host/xdnd-proxy.cpp index 4336adc4..c32f67d7 100644 --- a/src/wine-host/xdnd-proxy.cpp +++ b/src/wine-host/xdnd-proxy.cpp @@ -16,6 +16,8 @@ #include "xdnd-proxy.h" +#include + // FIXME: Remove #include @@ -145,12 +147,34 @@ WineXdndProxy::WineXdndProxy() WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS), UnhookWinEvent) {} -WineXdndProxy& WineXdndProxy::init_proxy() { - static std::unique_ptr instance; - if (!instance) { - // Protected constructors, hooray! - instance.reset(new WineXdndProxy{}); +/** + * The number of handles to our Wine->X11 drag-and-drop proxy object. To prevent + * running out of X11 connections when opening and closing a lot of plugin + * editors in a project, we'll free this again after the last editor in this + * process gets closed. + */ +static std::atomic_size_t instance_reference_count = 0; + +WineXdndProxy::Handle::Handle(WineXdndProxy& proxy) : proxy(proxy) {} + +WineXdndProxy::Handle::~Handle() noexcept { + if (instance_reference_count.fetch_sub(1) == 1) { + delete &proxy; + } +} + +WineXdndProxy::Handle WineXdndProxy::init_proxy() { + // We're doing a bit of a hybrid between a COM-style reference counted smart + // pointer and a singleton here because we need to ensure that there's only + // one proxy per process, but we want to free up the X11 connection when + // it's not needed anymore. Because of that this pointer may point to + // deallocated memory, so the reference count should be leading here. Oh and + // explained elsewhere, we won't even bother making this thread safe because + // it can only be called from the GUI thread anyways. + static WineXdndProxy* instance = nullptr; + if (instance_reference_count.fetch_add(1) == 0) { + instance = new WineXdndProxy{}; } - return *instance; + return Handle(*instance); } diff --git a/src/wine-host/xdnd-proxy.h b/src/wine-host/xdnd-proxy.h index d4ffbaab..ffbc3e0e 100644 --- a/src/wine-host/xdnd-proxy.h +++ b/src/wine-host/xdnd-proxy.h @@ -36,6 +36,35 @@ class WineXdndProxy { WineXdndProxy(); public: + /** + * A sort of smart pointer for `WineXdndProxy`, similar to how the COM/VST3 + * pointers work. We want to unregister the hooks and drop the X11 + * connection when the last editor closes in a plugin group. This is not + * strictly necessary, but there's an open X11 client limit and otherwise + * opening and closing a bunch of editors would get you very close to that + * limit. + */ + class Handle { + protected: + /** + * Before calling this, the reference count should be increased by one + * in `WineXdndProxy::init_proxy()`. + */ + Handle(WineXdndProxy& proxy); + + public: + /** + * Reduces the reference count by one, and frees `proxy` if this was the + * last handle. + */ + ~Handle() noexcept; + + private: + WineXdndProxy& proxy; + + friend WineXdndProxy; + }; + /** * Initialize the Wine->X11 drag-and-drop proxy. Calling this will hook into * Wine's OLE drag and drop system by listening for the creation of special @@ -47,8 +76,15 @@ class WineXdndProxy { * once from every plugin host instance. Because the actual data is stored * in a COM object, we can only handle drag-and-drop coming form this * process. + * + * This is sort of a singleton but not quite, as the `WineXdndProxy` is only + * alive for as long as there are open editors in this process. This is done + * to avoid opening too many X11 connections. + * + * @note This function, like everything other GUI realted, should be called + * from the main thread that's running the Win32 message loop. */ - static WineXdndProxy& init_proxy(); + static WineXdndProxy::Handle init_proxy(); private: std::unique_ptr x11_connection;