mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
Add an option to force drag-and-drop under REAPER
This works around a long standing bug in REAPER itself that would prevent you from dragging files onto any plugin editor window.
This commit is contained in:
@@ -30,6 +30,12 @@ TODO: Add an updated screenshot with some fancy VST3-only plugins to the readme
|
|||||||
Wine's XEmbed implementation instead of yabridge's normal window embedding
|
Wine's XEmbed implementation instead of yabridge's normal window embedding
|
||||||
method. Some plugins have will have redrawing issues when using XEmbed or the
|
method. Some plugins have will have redrawing issues when using XEmbed or the
|
||||||
editor might not show up at all, so your mileage may very much vary.
|
editor might not show up at all, so your mileage may very much vary.
|
||||||
|
- Added a [compatibilty
|
||||||
|
option](https://github.com/robbert-vdh/yabridge#compatibility-options) to
|
||||||
|
forcefully enable drag-and-drop support under _REAPER_. REAPER's FX window
|
||||||
|
supports drag-and-drop itself, which makes it impossible to drag files onto a
|
||||||
|
plugin editor embedded there. This option strips the drag-and-drop support
|
||||||
|
from the FX window, thus allowing you to drag files onto plugin editors again.
|
||||||
- Added a frame rate
|
- Added a frame rate
|
||||||
[option](https://github.com/robbert-vdh/yabridge#compatibility-options) to
|
[option](https://github.com/robbert-vdh/yabridge#compatibility-options) to
|
||||||
change the rate at which events are being handled. This usually also controls
|
change the rate at which events are being handled. This usually also controls
|
||||||
|
|||||||
@@ -286,13 +286,14 @@ plugin._
|
|||||||
|
|
||||||
#### Compatibility options
|
#### Compatibility options
|
||||||
|
|
||||||
| Option | Values | Description |
|
| Option | Values | Description |
|
||||||
| --------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. |
|
| `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. |
|
||||||
| `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. |
|
| `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. |
|
||||||
| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't always work properly with it, but it could be useful in certain setups. You may need to use [this Wine patch](https://github.com/psycha0s/airwave/blob/master/fix-xembed-wine-windows.patch) if you're getting blank editor windows. Defaults to `false`. _This option is only availble on the master branch._ |
|
| `editor_force_dnd` | `{true,false}` | This option forcefully enables drag-and-drop support in _REAPER_. Because REAPER's FX window supports drag-and-drop itself, dragging a file onto a plugin editor will cause the drop to be intercepted by the FX window. This makes it impossible to drag files onto plugins in REAPER under normal circumstances. Setting this option to `true` will strip drag-and-drop support from the FX window, thus allowing files to be dragged onto the plugin again. Defaults to `false`. _This option is only availble on the master branch._ |
|
||||||
| `frame_rate` | `<number>` | The rate at which Win32 events are being handled and usually also the refresh rate of a plugin's editor GUI. When using plugin groups all plugins share the same event handling loop, so in those the last loaded plugin will set the refresh rate. Defaults to `60`. _This option is only available on the master branch._ |
|
| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't always work properly with it, but it could be useful in certain setups. You may need to use [this Wine patch](https://github.com/psycha0s/airwave/blob/master/fix-xembed-wine-windows.patch) if you're getting blank editor windows. Defaults to `false`. _This option is only availble on the master branch._ |
|
||||||
| `vst3_no_scaling` | `{true,false}` | Disable HiDPI scaling for VST3 plugins. Wine currently does not have proper fractional HiDPI support, so you might have to enable this option if you're using a HiDPI display. In most cases setting the font DPI in `winecfg`'s graphics tab to 192 will cause plugins to scale correctly at 200% size. Defaults to `false`. _This option is only available on the master branch._ |
|
| `frame_rate` | `<number>` | The rate at which Win32 events are being handled and usually also the refresh rate of a plugin's editor GUI. When using plugin groups all plugins share the same event handling loop, so in those the last loaded plugin will set the refresh rate. Defaults to `60`. _This option is only available on the master branch._ |
|
||||||
|
| `vst3_no_scaling` | `{true,false}` | Disable HiDPI scaling for VST3 plugins. Wine currently does not have proper fractional HiDPI support, so you might have to enable this option if you're using a HiDPI display. In most cases setting the font DPI in `winecfg`'s graphics tab to 192 will cause plugins to scale correctly at 200% size. Defaults to `false`. _This option is only available on the master branch._ |
|
||||||
|
|
||||||
These options are workarounds for issues mentioned in the [known
|
These options are workarounds for issues mentioned in the [known
|
||||||
issues](#runtime-dependencies-and-known-issues) section. Depending on the hosts
|
issues](#runtime-dependencies-and-known-issues) section. Depending on the hosts
|
||||||
@@ -327,6 +328,7 @@ editor_xembed = true
|
|||||||
cache_time_info = true
|
cache_time_info = true
|
||||||
|
|
||||||
["sforzando VST_x64.so"]
|
["sforzando VST_x64.so"]
|
||||||
|
editor_force_dnd = true
|
||||||
frame_rate = 24
|
frame_rate = 24
|
||||||
|
|
||||||
# Simple glob patterns can be used to avoid unneeded repetition
|
# Simple glob patterns can be used to avoid unneeded repetition
|
||||||
@@ -366,6 +368,7 @@ vst3_no_scaling = true
|
|||||||
# These options would be applied to all plugins that do not already have their
|
# These options would be applied to all plugins that do not already have their
|
||||||
# own configuration set
|
# own configuration set
|
||||||
["*"]
|
["*"]
|
||||||
|
editor_force_dnd = true
|
||||||
vst3_no_scaling = true
|
vst3_no_scaling = true
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -546,6 +549,12 @@ include:
|
|||||||
inotifywait -mre CLOSE_WRITE --format '%w%f' ~/.wine/drive_c
|
inotifywait -mre CLOSE_WRITE --format '%w%f' ~/.wine/drive_c
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Aside from the above mentioned Wine issue, _drag-and-drop_ to the plugin
|
||||||
|
window under **REAPER** doesn't work because of a long standing issue in
|
||||||
|
REAPER's FX window implementation. You can use a compatibility option to
|
||||||
|
[force drag-and-drop]([editor hosting mode](#compatibility-options)) to work
|
||||||
|
around this limitation.
|
||||||
|
|
||||||
Aside from that, these are some known caveats:
|
Aside from that, these are some known caveats:
|
||||||
|
|
||||||
- Most recent **iZotope** plugins don't have a functional GUI in a typical out
|
- Most recent **iZotope** plugins don't have a functional GUI in a typical out
|
||||||
|
|||||||
@@ -90,6 +90,12 @@ Configuration::Configuration(const fs::path& config_path,
|
|||||||
} else {
|
} else {
|
||||||
invalid_options.push_back(key);
|
invalid_options.push_back(key);
|
||||||
}
|
}
|
||||||
|
} else if (key == "editor_force_dnd") {
|
||||||
|
if (const auto parsed_value = value.as_boolean()) {
|
||||||
|
editor_force_dnd = parsed_value->get();
|
||||||
|
} else {
|
||||||
|
invalid_options.push_back(key);
|
||||||
|
}
|
||||||
} else if (key == "editor_xembed") {
|
} else if (key == "editor_xembed") {
|
||||||
if (const auto parsed_value = value.as_boolean()) {
|
if (const auto parsed_value = value.as_boolean()) {
|
||||||
editor_xembed = parsed_value->get();
|
editor_xembed = parsed_value->get();
|
||||||
|
|||||||
@@ -102,6 +102,15 @@ class Configuration {
|
|||||||
*/
|
*/
|
||||||
bool editor_double_embed = false;
|
bool editor_double_embed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to `true`, we'll remove the `XdndAware` property all ancestor
|
||||||
|
* windows in `editor.cpp`. This is needed for REAPER as REAPER implements
|
||||||
|
* (but doesn't use) drag-and-drop support on all of its windows. This
|
||||||
|
* causes the FX window to intercept the drop thus making it impossible to
|
||||||
|
* drag files onto plugin editors, native or otherwise.
|
||||||
|
*/
|
||||||
|
bool editor_force_dnd = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use XEmbed instead of yabridge's normal editor embedding method. Wine's
|
* Use XEmbed instead of yabridge's normal editor embedding method. Wine's
|
||||||
* XEmbed support is not very polished yet and tends to lead to rendering
|
* XEmbed support is not very polished yet and tends to lead to rendering
|
||||||
@@ -173,6 +182,7 @@ class Configuration {
|
|||||||
void serialize(S& s) {
|
void serialize(S& s) {
|
||||||
s.value1b(cache_time_info);
|
s.value1b(cache_time_info);
|
||||||
s.value1b(editor_double_embed);
|
s.value1b(editor_double_embed);
|
||||||
|
s.value1b(editor_force_dnd);
|
||||||
s.value1b(editor_xembed);
|
s.value1b(editor_xembed);
|
||||||
s.ext(frame_rate, bitsery::ext::StdOptional(),
|
s.ext(frame_rate, bitsery::ext::StdOptional(),
|
||||||
[](S& s, auto& v) { s.value4b(v); });
|
[](S& s, auto& v) { s.value4b(v); });
|
||||||
|
|||||||
@@ -162,6 +162,9 @@ class PluginBridge {
|
|||||||
if (config.editor_double_embed) {
|
if (config.editor_double_embed) {
|
||||||
other_options.push_back("editor: double embed");
|
other_options.push_back("editor: double embed");
|
||||||
}
|
}
|
||||||
|
if (config.editor_force_dnd) {
|
||||||
|
other_options.push_back("editor: force drag-and-drop");
|
||||||
|
}
|
||||||
if (config.editor_xembed) {
|
if (config.editor_xembed) {
|
||||||
other_options.push_back("editor: XEmbed");
|
other_options.push_back("editor: XEmbed");
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-11
@@ -39,6 +39,13 @@ constexpr uint16_t event_type_mask = 0b0111'1111;
|
|||||||
*/
|
*/
|
||||||
constexpr char active_window_property_name[] = "_NET_ACTIVE_WINDOW";
|
constexpr char active_window_property_name[] = "_NET_ACTIVE_WINDOW";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the X11 property that indicates whether a window supports
|
||||||
|
* drag-and-drop. If the `editor_force_dnd` option is enabled we'll remove this
|
||||||
|
* property from `topmost_window` to work around a bug in REAPER.
|
||||||
|
*/
|
||||||
|
constexpr char x_dnd_aware_property_name[] = "XdndAware";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client message name for XEmbed messages. See
|
* Client message name for XEmbed messages. See
|
||||||
* https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html.
|
* https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html.
|
||||||
@@ -61,17 +68,21 @@ constexpr uint32_t xembed_focus_first = 1;
|
|||||||
std::atomic_size_t window_class_id{};
|
std::atomic_size_t window_class_id{};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the topmost window (i.e. the window before the root window in the window
|
* Find the the ancestors for the given window. This returns a list of window
|
||||||
* tree) starting from a certain window.
|
* IDs that starts wit h`starting_at`, and then iteratively contains the parent
|
||||||
|
* of the previous window in the list until we reach the root window. The
|
||||||
|
* topmost window (i.e. the window that will show up in the user's window
|
||||||
|
* manager) will be the last window in this list.
|
||||||
*
|
*
|
||||||
* @param x11_connection The X11 connection to use.
|
* @param x11_connection The X11 connection to use.
|
||||||
* @param starting_at The window we want to know the topmost window of.
|
* @param starting_at The window we want to know the ancestor windows of.
|
||||||
*
|
*
|
||||||
* @return Either `starting_at`, if its parent is already the root window, or
|
* @return A non-empty list containing `starting_at` and all of its ancestor
|
||||||
* another another window that has `starting_at` as its descendent.
|
* windows `starting_at`.
|
||||||
*/
|
*/
|
||||||
xcb_window_t find_topmost_window(xcb_connection_t& x11_connection,
|
std::vector<xcb_window_t> find_ancestor_windows(
|
||||||
xcb_window_t starting_at);
|
xcb_connection_t& x11_connection,
|
||||||
|
xcb_window_t starting_at);
|
||||||
/**
|
/**
|
||||||
* Check whether `child` is a descendant of `parent` or the same window. Used
|
* Check whether `child` is a descendant of `parent` or the same window. Used
|
||||||
* during focus checks to only grab focus when needed.
|
* during focus checks to only grab focus when needed.
|
||||||
@@ -151,7 +162,8 @@ Editor::Editor(const Configuration& config,
|
|||||||
idle_timer_proc(std::move(timer_proc)),
|
idle_timer_proc(std::move(timer_proc)),
|
||||||
parent_window(parent_window_handle),
|
parent_window(parent_window_handle),
|
||||||
wine_window(get_x11_handle(win32_handle.get())),
|
wine_window(get_x11_handle(win32_handle.get())),
|
||||||
topmost_window(find_topmost_window(*x11_connection, parent_window)) {
|
topmost_window(
|
||||||
|
find_ancestor_windows(*x11_connection, parent_window).back()) {
|
||||||
xcb_generic_error_t* error;
|
xcb_generic_error_t* error;
|
||||||
|
|
||||||
// Used for input focus grabbing to only grab focus when the window is
|
// Used for input focus grabbing to only grab focus when the window is
|
||||||
@@ -177,6 +189,27 @@ Editor::Editor(const Configuration& config,
|
|||||||
<< std::endl;
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the `editor_force_dnd` option is set, we'll strip `XdndAware` from all
|
||||||
|
// of `wine_window`'s ancestors (including `parent_window`) to forcefully
|
||||||
|
// enable drag-and-drop support in REAPER. See the docstring on
|
||||||
|
// `Configuration::editor_force_dnd` and the option description in the
|
||||||
|
// readme for more information.
|
||||||
|
if (config.editor_force_dnd) {
|
||||||
|
atom_cookie = xcb_intern_atom(x11_connection.get(), true,
|
||||||
|
strlen(x_dnd_aware_property_name),
|
||||||
|
x_dnd_aware_property_name);
|
||||||
|
atom_reply =
|
||||||
|
xcb_intern_atom_reply(x11_connection.get(), atom_cookie, &error);
|
||||||
|
assert(!error);
|
||||||
|
|
||||||
|
for (const xcb_window_t& window :
|
||||||
|
find_ancestor_windows(*x11_connection, parent_window)) {
|
||||||
|
xcb_delete_property(x11_connection.get(), window, atom_reply->atom);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(atom_reply);
|
||||||
|
}
|
||||||
|
|
||||||
// When using XEmbed we'll need the atoms for the corresponding properties
|
// When using XEmbed we'll need the atoms for the corresponding properties
|
||||||
atom_cookie =
|
atom_cookie =
|
||||||
xcb_intern_atom(x11_connection.get(), true, strlen(xembed_message_name),
|
xcb_intern_atom(x11_connection.get(), true, strlen(xembed_message_name),
|
||||||
@@ -184,6 +217,7 @@ Editor::Editor(const Configuration& config,
|
|||||||
atom_reply =
|
atom_reply =
|
||||||
xcb_intern_atom_reply(x11_connection.get(), atom_cookie, &error);
|
xcb_intern_atom_reply(x11_connection.get(), atom_cookie, &error);
|
||||||
assert(!error);
|
assert(!error);
|
||||||
|
|
||||||
xcb_xembed_message = atom_reply->atom;
|
xcb_xembed_message = atom_reply->atom;
|
||||||
free(atom_reply);
|
free(atom_reply);
|
||||||
|
|
||||||
@@ -636,9 +670,11 @@ LRESULT CALLBACK window_proc(HWND handle,
|
|||||||
return DefWindowProc(handle, message, wParam, lParam);
|
return DefWindowProc(handle, message, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_window_t find_topmost_window(xcb_connection_t& x11_connection,
|
std::vector<xcb_window_t> find_ancestor_windows(
|
||||||
xcb_window_t starting_at) {
|
xcb_connection_t& x11_connection,
|
||||||
|
xcb_window_t starting_at) {
|
||||||
xcb_window_t current_window = starting_at;
|
xcb_window_t current_window = starting_at;
|
||||||
|
std::vector<xcb_window_t> ancestor_windows{current_window};
|
||||||
|
|
||||||
xcb_generic_error_t* error;
|
xcb_generic_error_t* error;
|
||||||
xcb_query_tree_cookie_t query_cookie =
|
xcb_query_tree_cookie_t query_cookie =
|
||||||
@@ -650,6 +686,7 @@ xcb_window_t find_topmost_window(xcb_connection_t& x11_connection,
|
|||||||
xcb_window_t root = query_reply->root;
|
xcb_window_t root = query_reply->root;
|
||||||
while (query_reply->parent != root) {
|
while (query_reply->parent != root) {
|
||||||
current_window = query_reply->parent;
|
current_window = query_reply->parent;
|
||||||
|
ancestor_windows.push_back(current_window);
|
||||||
|
|
||||||
free(query_reply);
|
free(query_reply);
|
||||||
query_cookie = xcb_query_tree(&x11_connection, current_window);
|
query_cookie = xcb_query_tree(&x11_connection, current_window);
|
||||||
@@ -659,7 +696,7 @@ xcb_window_t find_topmost_window(xcb_connection_t& x11_connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(query_reply);
|
free(query_reply);
|
||||||
return current_window;
|
return ancestor_windows;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_child_window_or_same(xcb_connection_t& x11_connection,
|
bool is_child_window_or_same(xcb_connection_t& x11_connection,
|
||||||
|
|||||||
Reference in New Issue
Block a user