mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
7a2febfffb
This is needed for Tracktion Waveform and other JUCE based hosts.
310 lines
11 KiB
C++
310 lines
11 KiB
C++
// yabridge: a Wine VST bridge
|
|
// Copyright (C) 2020-2021 Robbert van der Helm
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#pragma once
|
|
|
|
#include <memory>
|
|
|
|
#include "boost-fix.h"
|
|
|
|
// Use the native version of xcb
|
|
#pragma push_macro("_WIN32")
|
|
#undef _WIN32
|
|
#include <xcb/xcb.h>
|
|
#pragma pop_macro("_WIN32")
|
|
|
|
#include <windows.h>
|
|
#include <boost/container/small_vector.hpp>
|
|
|
|
#include "utils.h"
|
|
|
|
/**
|
|
* A RAII wrapper for creating an X11 window.
|
|
*/
|
|
class X11Window {
|
|
public:
|
|
/**
|
|
* Create the window.
|
|
*
|
|
* @param x11_connection The X11 connection to use for creating this window.
|
|
* Events sent to the window will be received here.
|
|
* @param create_window_fn A function receiving the X11 connection and the
|
|
* window ID that should call `xcb_create_window()`. The function doesn't
|
|
* need to call `xcb_flush()`.
|
|
*/
|
|
template <std::invocable<std::shared_ptr<xcb_connection_t>, xcb_window_t> F>
|
|
X11Window(std::shared_ptr<xcb_connection_t> x11_connection,
|
|
F&& create_window_fn)
|
|
: x11_connection(x11_connection),
|
|
window(xcb_generate_id(x11_connection.get())) {
|
|
create_window_fn(x11_connection, window);
|
|
xcb_flush(x11_connection.get());
|
|
}
|
|
|
|
/**
|
|
* Destroy the window again when this object gets dropped.
|
|
*/
|
|
~X11Window() noexcept;
|
|
|
|
X11Window(const X11Window&) noexcept = delete;
|
|
X11Window& operator=(const X11Window&) noexcept = delete;
|
|
|
|
X11Window(X11Window&&) noexcept;
|
|
X11Window& operator=(X11Window&&) noexcept;
|
|
|
|
private:
|
|
std::shared_ptr<xcb_connection_t> x11_connection;
|
|
|
|
public:
|
|
xcb_window_t window;
|
|
|
|
private:
|
|
bool is_moved = false;
|
|
};
|
|
|
|
/**
|
|
* A simple wrapper that registers a WinEvents hook to listen for new windows
|
|
* being created, and handles XDND client messages to achieve the behaviour
|
|
* described in `WineXdndProxy::get_handle()`.
|
|
*/
|
|
class WineXdndProxy {
|
|
protected:
|
|
/**
|
|
* Initialize the proxy and register all hooks.
|
|
*/
|
|
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::get_handle()`.
|
|
*/
|
|
Handle(WineXdndProxy* proxy);
|
|
|
|
public:
|
|
/**
|
|
* Reduces the reference count by one, and frees `proxy` if this was the
|
|
* last handle.
|
|
*/
|
|
~Handle() noexcept;
|
|
|
|
Handle(const Handle&) noexcept;
|
|
Handle& operator=(const Handle&) noexcept = default;
|
|
|
|
Handle(Handle&&) noexcept;
|
|
Handle& operator=(Handle&&) noexcept = default;
|
|
|
|
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
|
|
* tracker windows created by the Wine server. When a drag and drop
|
|
* operation is started, we will initiate the XDND protocol with the files
|
|
* referenced by that tracker window. This will allow us to drag files from
|
|
* Wine windows to X11 applications, something that's normally not possible.
|
|
* Calling this function more than once doesn't have any effect, but this
|
|
* should still be called at least 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::Handle get_handle();
|
|
|
|
/**
|
|
* Initiate the XDDN protocol by taking ownership of the `XdndSelection`
|
|
* selection and setting up the event listeners.
|
|
*/
|
|
void begin_xdnd(const boost::container::small_vector_base<
|
|
boost::filesystem::path>& file_paths,
|
|
HWND tracker_window);
|
|
|
|
/**
|
|
* Release ownership of the selection stop listening for X11 events.
|
|
*/
|
|
void end_xdnd();
|
|
|
|
private:
|
|
/**
|
|
* From another thread, constantly poll the mouse position until the left
|
|
* mouse button gets released, and then perform the drop if the mouse cursor
|
|
* was last positioned over an XDND aware window. This is a workaround for
|
|
* us not being able to grab the mouse cursor since Wine is already doing
|
|
* that.
|
|
*/
|
|
void run_xdnd_loop();
|
|
|
|
/**
|
|
* Find the first XDND aware X11 window at the current mouse cursor,
|
|
* starting at `window` and iteratively descending into its children until
|
|
* we reach the bottommost child where the mouse cursor is in. This respects
|
|
* `XdndProxy`. If no XdndAware window was found, then this will contain the
|
|
* deepest query so we still have access to the pointer coordinates. That
|
|
* means you will still need to check `is_xdnd_aware(result->child)` after
|
|
* the fact.
|
|
*
|
|
* This will return a null pointer if an X11 error was thrown.
|
|
*/
|
|
std::unique_ptr<xcb_query_pointer_reply_t>
|
|
query_xdnd_aware_window_at_pointer(xcb_window_t window) const noexcept;
|
|
|
|
/**
|
|
* Check whether a window is XDND-aware, respecting `XdndProxy`. This will
|
|
* return the supported XDND version. In theory we could just assume that
|
|
* everything supports version 5 of the spec since that came out in 20020,
|
|
* but for some reason JUCE only supports version 3 from 1998.
|
|
*/
|
|
std::optional<uint8_t> is_xdnd_aware(xcb_window_t window) const noexcept;
|
|
|
|
/**
|
|
* Return the XDND proxy window for `window` as specified in the `XdndProxy`
|
|
* property. Returns a nullopt if `window` doesn't have that property set.
|
|
*/
|
|
std::optional<xcb_window_t> get_xdnd_proxy(
|
|
xcb_window_t window) const noexcept;
|
|
|
|
/**
|
|
* Send an XDND message to a window, respecting `XdndProxy` (i.e. window
|
|
* should always be the window under the cursor). This does not include a
|
|
* flush. See the spec for more information:
|
|
*
|
|
* https://www.freedesktop.org/wiki/Specifications/XDND/#clientmessages
|
|
*/
|
|
void send_xdnd_message(xcb_window_t window,
|
|
xcb_atom_t message_type,
|
|
uint32_t data1,
|
|
uint32_t data2,
|
|
uint32_t data3,
|
|
uint32_t data4) const noexcept;
|
|
|
|
/**
|
|
* Handle any incoming `SelectionRequest` events.
|
|
*
|
|
* When the window we're dragging over wants to inspect the dragged content,
|
|
* it will call `ConvertSelection()` which sends us a `SelelectionRequest`.
|
|
* We should write the data in the requested format the property the
|
|
* specified on their window, and then send them a `SelectionNotify` to
|
|
* indicate that we're done. Since we only provide a single unique format,
|
|
* we have already converted the file list to `text/uri-list` format.
|
|
*
|
|
* This does included the necessary flushes.
|
|
*/
|
|
void handle_convert_selection(const xcb_selection_request_event_t& event);
|
|
|
|
/**
|
|
* We need a dedicated X11 connection for our proxy because we can have
|
|
* multiple open editors in a single process (e.g. when using VST3 plugins
|
|
* or plugin groups), and client messages are sent to the X11 connection
|
|
* that created the window. So we cannot just reuse the connection from the
|
|
* editor.
|
|
*/
|
|
std::shared_ptr<xcb_connection_t> x11_connection;
|
|
|
|
/**
|
|
* We need an unmapped 1x1 proxy window to send and receive client messages
|
|
* for the XDND protocol.
|
|
*/
|
|
X11Window proxy_window;
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
|
std::unique_ptr<std::remove_pointer_t<HWINEVENTHOOK>,
|
|
std::decay_t<decltype(&UnhookWinEvent)>>
|
|
hook_handle;
|
|
#pragma GCC diagnostic pop
|
|
|
|
/**
|
|
* MT-PowerDrumkit for some reason initializes a drag-and-drop operation,
|
|
* cancels it, and then immediately starts a new one. We need to make sure
|
|
* that we only handle a single drag-and-drop operation at a time.
|
|
*/
|
|
std::atomic_bool drag_active = false;
|
|
|
|
/**
|
|
* The files that are currently being dragged, stored as in `text/uri-list`
|
|
* format (i.e. a list of URIs, each ending with a line feed)
|
|
*/
|
|
std::string dragged_files_uri_list;
|
|
|
|
/**
|
|
* Wine's tracker window for tracking the drag-and-drop operation. When the
|
|
* XDND operation succeeds, we make sure to close this window to avoid the
|
|
* potential for weird race conditions where the plugin may still think
|
|
* we're doing drag-and-drop.
|
|
*/
|
|
HWND tracker_window;
|
|
|
|
/**
|
|
* We need to poll for mouse position changes from another thread, because
|
|
* when the drag-and-drop operation starts Wine will be blocking the GUI
|
|
* thread, so we cannot rely on the normal event loop.
|
|
*/
|
|
Win32Thread xdnd_handler;
|
|
|
|
/**
|
|
* The X11 root window.
|
|
*/
|
|
xcb_window_t root_window;
|
|
|
|
/**
|
|
* The X11 keycode for the escape key. We need to figure this out once when
|
|
* the first drag-and-drop operation happens.
|
|
*/
|
|
std::optional<xcb_keycode_t> escape_keycode;
|
|
|
|
// These are the atoms used for the XDND protocol, as described by
|
|
// https://www.freedesktop.org/wiki/Specifications/XDND/#atomsandproperties
|
|
xcb_atom_t xcb_xdnd_selection;
|
|
xcb_atom_t xcb_xdnd_aware_property;
|
|
xcb_atom_t xcb_xdnd_proxy_property;
|
|
xcb_atom_t xcb_xdnd_drop_message;
|
|
xcb_atom_t xcb_xdnd_enter_message;
|
|
xcb_atom_t xcb_xdnd_finished_message;
|
|
xcb_atom_t xcb_xdnd_position_message;
|
|
xcb_atom_t xcb_xdnd_status_message;
|
|
xcb_atom_t xcb_xdnd_leave_message;
|
|
|
|
// XDND specifies various actions for drag-and-drop, but since the file is
|
|
// technically still owned by the plugin we'll just stick with copies to be
|
|
// safe
|
|
xcb_atom_t xcb_xdnd_copy_action;
|
|
|
|
// Mime types for use in XDND, we'll only support dragging links since that
|
|
// is the foramt the Windows OLE drag-and-drop provides us
|
|
xcb_atom_t xcb_mime_text_uri_list;
|
|
xcb_atom_t xcb_mime_text_plain;
|
|
};
|