Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optional keyboard passthrough #259

Open
Goli4thus opened this issue Jun 24, 2023 · 4 comments · May be fixed by #263
Open

optional keyboard passthrough #259

Goli4thus opened this issue Jun 24, 2023 · 4 comments · May be fixed by #263

Comments

@Goli4thus
Copy link
Contributor

Feature description

Hi robbert-vdh!
First off, thanks to you and the community for creating yabridge!

I recently started trying it out to see how it performs.

Status quo

One thing that stuck out bit negatively to me is the keyboard input handling, specifically on reaper.
Right now it's like this:

  • when mouse is over the plugin window (oftentimes seems to be called "editor window"), all keyboard input goes towards plugin window
  • as mouse leaves plugin window, focus is given back to reaper

When I looked through existing issues, I found this one: #38
I certainly can see that it was quite a journey to get where it is now!
But even though it's an older topic, I hope suggestions are still welcome.

EDIT2: I ended up completely rewriting how keyboard input is handled to be simpler, more reliable, and to be able to give input focus back to the host when needed (can be useful in REAPER).

I'm not sure I fully understand the last part. Focus only seems to return to reaper when placing mouse outside of plugin window.
Furthermore, the README mentions in section Input focus grabbing, that there's this mechanism regarding "holding shift key" to direct all
keyboard input (e.g. spacebar) to plugin window. I tried it out with bitwig just to see how that behaves. It's certainly better than in reaper.

Suggestion

To get to the main point of me writing all this, here is a suggestion on how yabridge could handle keyboard input even better from an UX perspective:

I assume there's a way to forward keyboard input to host even if mouse is over plugin window (i.e. technically possible).
If so, let's say we add something like this keyboard_passthrough option to yabridge.toml:

["FabFilter*.so"]
group = "fabfilter"
keyboard_passthrough = [ 'spacebar', 'F1', 'F2', 'F3', 'q', 'w' ]

Or even something like this:

["FabFilter*.so"]
group = "fabfilter"
keyboard_passthrough = true

Then the behavior could be like this:

  • by default, (the first case) yabridge will forward all keyboard input regarding respective plugin group to plugin window, except for those events that are listed in this keyboard_passthrough list
  • by default, (the second case) yabridge will forward all keyboard input regarding respective plugin group to host (except modifier keys like alt, shift, etc. when used in conjunction with mouse events)
  • using the "hold shift" trick, any such forwarding would be temporarily disabled (i.e. all input would be directed to plugin window; basically what already exists)

Why would this be helpful?
From a workflow perspective, I certainly need the ability to have at least certain keys reach the host (reaper) by default at all times. I simply use this way more often than I type anything into a plugin window's input box.

But I'm aware that different users have different needs and the way things are now is perfectly fine for them.
This suggestion would allow for letting users decide the behavior themselves.

The above config ideas are based on editing plugin groups.
But it might make sense to have a "global" keyboard_passthrough list as well (e.g. spacebar might be a key that always should be forwarded to host, regardless of plugin group).
Of course, this will make it bit more complex, cause then the "global" and "plugin group" lists need to be merged on the fly.
Furthermore some edge cases like have keyboard_passthrough: true set globally, but then also having a list of keys in a plugin group would need some precedence re evaluation (e.g. "global" wins in this case).

But IMO it's important to have both the global and the plugin group specific configuration, as this gives the greatest amount of flexibility to the end user, helps avoiding having to repeat a common setting for all plugin groups and likely will cover most workflow scenarios that could come up.

Possible technical challenges here might be:

  • checking the actual keyboard input against the content of keyboard_passthrough list in case of individual keys:
    • I'm not sure if having such a "plain" listing is possible, or if some special "syntax" is needed (or even keycodes, etc., which would make configuration bit harder)
  • the keyboard input forwarding to host while plugin window has focus

Do you think this is technically doable?
Maybe I'm missing something for why this is a bad idea. So I'm open to critique or suggestions!

Anything else?

Some technical notes:

  • manjaro
  • wine 8.6
  • yabridge 5.0.4 (via pacman)
  • reaper (latest; linux native)
@Goli4thus
Copy link
Contributor Author

On second thought, if the suggestion of having a list of individual keys seems to complicated for what it's worth, just having the variant with keyboard_passthrough = true (maybe even placed "globally" in config.toml) likely would be the 80/20 (or rather 80/50) solution to this.

Reason being that once one starts to forward lots of individual keys (esp. numbers and letters), it likely ends up with oneself forming the habit of always "hovering with shift key held" whenever one wants to input some text into text fields, cause that's less of a mental burden as keeping track of which keys are being forwarded and which aren't and when shift might be needed or not.

@robbert-vdh
Copy link
Owner

The main problem with keyboard handling (and the reason why yabridge's current approach is about as good as it gets) is that there is (to my knowledge, I've spent hundreds of hours tweaking editor behavior) no real way to detect that the main Windows child window made a SetFocus() call. The host could grab keyboard events it cares about and pass the rest along to the child window (Bitwig does this), but passing unused keyboard events back to the host is very difficult. Especially when you consider that keyboard events are not just scan codes of keys being pressed and released. This also includes things like unicode text entry, which is another whole can of worms.

If REAPER won't implement similar behavior to Bitwig and moving your mouse off to the side a bit to press space is too much work (remember, you don't need to click, just move your mouse outside of the plugin's client area) then you could try using a MIDI controller for play/pause, or use some sort of WM scripting tool to send a space press directly to REAPER's main window.

@Goli4thus
Copy link
Contributor Author

The main problem with keyboard handling (and the reason why yabridge's current approach is about as good as it gets) is that there is (to my knowledge, I've spent hundreds of hours tweaking editor behavior) no real way to detect that the main Windows child window made a SetFocus() call.

Wasn't aware of that, so for now I'm simply trying to keep that mind.

the rabbit hole

The host could grab keyboard events it cares about and pass the rest along to the child window (Bitwig does this), but passing unused keyboard events back to the host is very difficult.

This sent me down somewhat of a rabbit hole.
I've done an actual comparison re different OS and DAWs (reaper and bitwig) just to be sure I know how it in general behaves
from a user perspective. Here are my notes on that: https://gist.github.com/Goli4thus/937433d93d881535f768944ac1e2f606

I had a couple takeaways from that:

  • windows and macOS behave identical
  • same do those DAWs in their native linux version in conjunction with native linux plugins
  • when a plugin (editor) window has focus (regardless of mouse hover location):
    • bitwig:
      • only reacts to spacebar
      • everything else (letters, numbers, special characters, function keys) is ignored by the host
      • an editor window can use it or leave it
    • reaper:
      • only reacts to spacebar and function keys
      • everything else (letters, numbers, special characters) is ignored by the host
      • an editor window can use it or leave it
    • but in general:
      • if a plugin (editor) window "consumes" a hotkey, the host no longer reacts to it
        • at least that's what it seems like with something like spacebar:
          • if an input box on an editor window has focus, spacebar is "consumed" there
          • in all other cases, the host reacts to it
          • I don't know how DAWs usually detect that for native plugins, but somehow there's a way
      • this whole concept of "editor window consumes a key event" became very apprent with the example of u-he Hive on
        linux:
        • for whatever reason, just on linux, it "consumes" function keys in reaper, which it doesn't on windows and macOS
    • and for current yabridge behavior:
      • for bitwig:
        • yabridge is basically exhibiting the same behavior as would loading resp. plugins on windows and macOS
        • only difference is that scrolling on an unfocused plugin window will focus the plugin window as a byproduct:
          • but considering this also happens for linux native VSTs, it very well could be a linux xserver thing
          • and IMO it's not really clear if the alternative is such an advantage
      • for reaper:
        • in a sense, when plugin window has focus, it behaves as on windows and macOS, but only if mouse is hovered outside of plugin window, not if mouse is over plugin window
        • apart from that it was surprising that function keys are handled differently:
          • bitwig: handles them like letters, numbers, special characters
          • reaper: handles them like spacebar:
            • i.e. unless a given native linux plugin (e.g. u-he Hive) has other ideas and captures function keys itself, which (again) is a rather strange behavior of a plugin, especially considering it only happens on linux, not windows or macOS and it doesn't even actively use it for anything

The host could grab keyboard events it cares about and pass the rest along to the child window (Bitwig does this), ...

So coming back to this comment, at least to me it seems like that isn't entirely the case:

  • bitwig very likely also allows plugins to "consume" spacebar when needed and won't toggle play/stop in that case
  • overall it seems rather well defined what both bitwig and reaper will care about when a plugin (editor) window has focus
    • after all, literally all keys could be mapped to DAW actions
    • by that logic, potentially nothing would reach the editor window ever

new suggestion

All this research actually makes me pretty much abandon my initial feature request and instead think if the following isn't possible for yabridge in reaper to make it behave very close to "native".
As noted above, seemingly yabridge already makes reaper and loaded plugins behave like on windows, macOS and like for native linux plugins, IF mouse is hovered outside of plugin window. Like literally, it let's reaper react to spacebar and function keys (just like it's normal for reaper), but won't make reaper react to letters, numbers or special characters.

So the new proposal is this:
Why not make the mouse hovers outside of editor window the default on reaper and only if hover into editor window with shift key held is happening, it exhibits the current default behavior on reaper, which directs all input to the editor window?

If REAPER won't implement similar behavior to Bitwig

I know you've wrote this I sense some kind of frustration in that short phrase, but would the above proposal maybe be doable? Or is there a rather clear reason for why that simply can't work? Would it maybe break dragndrop (maybe it would just work after hover with shift key held) or something else?
At least to me, at this point, it seems like there's not necessarily a need for reaper to change it's behavior that's more in line with bitwig, but rather yabridge could maybe accommodate for it? (just grepping the codebase for "reaper" makes it rather clear that yabridge is already doing lots to make things work on reaper).

further comments

Especially when you consider that keyboard events are not just scan codes of keys being pressed and released.
This also includes things like unicode text entry, which is another whole can of worms.

I honestly didn't think of that at first. But yeah, as noted, I've given up on having individual keys being forwarded as I initially
suggested in this feature request.

and moving your mouse off to the side a bit to press space is too much work

IMO the goal with these kind of workflow matters is not "works with a bit of effort by the user", but "as frictionless as possible (i.e. least amount of mouse and keyboard interaction needed)". Or in this specific case, "same as for other OS and native linux plugins".

(remember, you don't need to click, just move your mouse outside of the plugin's client area)

Yes, I've been aware of that. Certainly better than having to click in addition.

then you could try using a MIDI controller for play/pause,

Without even trying that out, the thought of having to reach one of my hands away from keyboard or mouse for something like hitting play/stop already feels annoying. Basically more workflow friction, not less.

or use some sort of WM scripting tool to send a space press directly to REAPER's main window.

This made me remember that I've actually done things like that already in the past (e.g. using autokey and xdotool or plenty of autohotkey back on windows). This is what I ended up with this time around:

import subprocess
import re
import time

# Remember original mouse position
output = subprocess.check_output(['xdotool', 'getmouselocation'])
windowID_plugin = subprocess.check_output(['xdotool', 'getactivewindow'])

m = re.search('x:([0-9]+) y:([0-9]+).+?window:([0-9]+)\\n', output.decode('utf-8'))
if m:
    xOrig = m.group(1)
    yOrig = m.group(2)
    windowID_host = m.group(3)

# focus Reaper and hit play
# use 'xdotool getmouselocation' to get exact mouse coordinates for here (some empty, non-button area re reaper)
subprocess.run(['xdotool', 'mousemove', '30', '102'])
# If deliberately focusing reaper is desired, the following click takes are of it.
#mouse.click_relative_self(0, 0, 1)
time.sleep(0.05)
keyboard.send_key(' ')
time.sleep(0.05)
subprocess.run(['xdotool', 'mousemove', xOrig, yOrig])
# In case window lost focus, this would refocus again.
#subprocess.run(['xdotool', 'windowactivate', windowID_plugin])

Using this python script in e.g. autokey-qt, assigning spacebar as hotkey and then thinking of proper window filter regex (e.g. ^(FX|VST):, even though this then matches any plugin window in reaper, not just yabridge ones) works indeed pretty well for a workaround at first glance.
But an approach like this has shortcomings:

  • really gotta make sure the target location for the mouse move re host is never obstructed by another plugin that's also bridged via yabridge
  • as already mentioned, finding a proper regex for matching the window can be tricky if the goal is to just have it affect yabridge bridged plugins (and not any other random window titles while autokey is running in background)

IMO, scripting user input like that should be a last resort. Always more robust all around for the end user if an application provides such functionality natively.

@ArtikusHG
Copy link

ArtikusHG commented Jul 10, 2023

+1 on this. Just installed Melodyne using YaBridge into Reaper. Was very happy to see it loaded up with no issues, but quickly realized keyboard shortcuts do not work at all, which slows me down by a lot. (Using GNOME on Wayland btw). Hope this issue can get resolved soon!

EDIT: upon further testing, I found out that shortcuts in Melodyne do work... but only when 1) "Send all keyboard input to plugin" is checked and 2) when the mouse is in the area that is outlined on the screenshot:

image

This small area is the only area drawn by Reaper - the rest is Melodyne (+ the GNOME window title bar). So I guess that's why it's capturing the events?

Not sure whether this is realted, but it would definitely be great to get this fixed.

EDIT 2: the same setup with the same install of Melodyne works fine in Ardour. So it seems like this is truly a Reaper-only issue.

EDIT 3 (final edit for today): using Carla-Rack inside of Reaper and loading Melodyne through it works as expected. This is a crappy setup and it comes with its own problems, but for now it works. Still hoping to get it resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants