reaimgui icon indicating copy to clipboard operation
reaimgui copied to clipboard

Race condition on initial keyboard capture allows keypresses to pass through to main window

Open fliprubin opened this issue 2 months ago • 2 comments

If you have a script where you need to capture the user's keyboard input, if the user types too fast after loading the script, the keypress will go to Reaper before ImGui can capture it, potentially triggering another unintended shortcut.

The window of time where this happens is fairly short, it's a small fraction of a second, but it's long enough for me to run into the issue multiple times in a session when I'm moving fast.

An example of an excellent script that suffers from this problem is nvk_SEARCH (even in it's 'persistent' mode meant to make it load faster).

This doesn't seem to happen with the old gfx API. Seems no matter how fast I type the second key, it always gets captured.

Steps to reproduce:

Assign the following script to a single key shortcut, for example 'A'

package.path = reaper.ImGui_GetBuiltinPath() .. '/?.lua;' .. package.path
local ImGui = require('imgui')('0.10')
local ctx = ImGui.CreateContext('Keyboard Capture Test')
local text = ''

local function loop()
  ImGui.SetNextFrameWantCaptureKeyboard(ctx, true)
  local i = 0
  while true do
    local ok, char = ImGui.GetInputQueueCharacter(ctx, i); if not ok then break end
    text = text .. string.char(char); i = i + 1
  end

  ImGui.SetNextWindowSize(ctx, 400, 100, ImGui.Cond_FirstUseEver)
  local visible, open = ImGui.Begin(ctx, 'Keyboard Capture Test', true)

  if visible then
    ImGui.TextWrapped(ctx, 'Captured: ' .. text)
  end
  ImGui.End(ctx)

  if open and not ImGui.IsKeyPressed(ctx, ImGui.Key_Escape) then
    reaper.defer(loop)
  end
end
reaper.defer(loop)

Type 'A' and then another single key shortcut in very rapid succession (helps to have the keys close to each other)

Current Result: The UI will not show the second key as captured, and reaper will launch that shortcut

Expected result: The UI should show the key as captured and it should not be passed through to reaper

Note: I'm using SetNextFrameWantCaptureKeyboard here but the issue is the same if you use a text input box.

And for reference, here's the gfx API version of this script:

local text = ''
gfx.init('Keyboard Capture Test', 400, 100)

local function loop()
  local char = gfx.getchar()
  if char == -1 then return end

  while char ~= 0 do
    if char == 27 then -- Escape key
      gfx.quit()
      return
    end
    text = text .. string.char(char)
    char = gfx.getchar()
    if char == -1 then return end
  end

  gfx.clear = 0
  gfx.x, gfx.y = 5, 5
  gfx.drawstr('Captured: ' .. text)
  gfx.update()

  reaper.defer(loop)
end
reaper.defer(loop)

It catches the next key every time without fail.

Tested on Reaper 7.48, Mac OS

fliprubin avatar Oct 18 '25 16:10 fliprubin

There is a 2 frames delay before a window requested via Begin actually opens. So between 60 and 90 ms (depending when the script is launched within a defer timer interval).

And SetNextFrameWantCaptureKeyboard only takes effect on the next frame as the name suggests (also requires a window to be open and in focus).

I don't think this can be avoided. (Would be possible to eagerly stop the keys from activating shortcuts when there are one or more pending windows, however those key presses would be lost as it's unknown which script they should be sent to as there may be more than one. There would still be 0-30ms remaining...)

cfillion avatar Oct 18 '25 17:10 cfillion

Ah ok thanks! That's a bummer. I tried using gfx to capture key presses until ImGui is ready, but it looks like you can't run them simultaneously (I must admit I'm bit out of my depths here):

...keyboard-capture-combined.lua:15: ImGui_SetNextFrameWantCaptureKeyboard: expected a valid ImGui_Context*, got 0x6000072c0180

fliprubin avatar Oct 18 '25 21:10 fliprubin