Skip to content

mp.input: allow clients to reliably override keybinds#17418

Merged
kasper93 merged 4 commits intompv-player:masterfrom
CogentRedTester:mp.input/flushed-keybinds
Feb 24, 2026
Merged

mp.input: allow clients to reliably override keybinds#17418
kasper93 merged 4 commits intompv-player:masterfrom
CogentRedTester:mp.input/flushed-keybinds

Conversation

@CogentRedTester
Copy link
Contributor

@CogentRedTester CogentRedTester commented Feb 18, 2026

One of the interesting things that can be done with mp.input.select() is to override the default keybinds. This allows scripts to extend what can be done when a select window is open. For example, I have a youtube-search script which uses input.select() to display search results that the user can open. I want to provide an additional Shift+Enter keybind to append an entry to the playlist instead of replacing the whole playlist, but this conflicts with the newline keyboard input used by console.lua.

To have this work, I need to register my Shift+Enter keybind after console.lua. The intuitive way to do this is to add the keybinds during the opened callback, as this is only run after console.lua has opened and activated its keybinds. However, for optimisation reasons, defaults.lua actually buffers new keybind registrations and flushes them only once the script goes idle. As the opened event is sent before this happens, clients processing the event may end up flushing their own keybinds before console.lua does. This makes it unreliable to use the opened event to override keybinds set by console.lua

This PR uses the undocumented mp.flush_keybindings() method to ensure that all console.lua keybinds are set before sending the opened event to mp.input clients, which allows the opened callback to be used to safely override console.lua keybinds.

The PR also updates stats.lua to use this new method to override the UP/DOWN keys and removes the previous hack that allowed this to work.

@CogentRedTester
Copy link
Contributor Author

Using undocumented internal functions may be questionable. But I wonder if this problem may be an indication that this particular function should be documented after all.

Copy link
Contributor

@guidocella guidocella left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah so that is what was happening. In the original incarnation of select.lua I just defined keybindings with a timeout (https://github.com/guidocella/mpv-console-select/blob/7712c7cba1bf0b7dfa8ddd567ac56bf18c3f11fb/console-select.lua#L114).

Makes sense though this delays opened by 0.077 seconds half the time in my tests.

@guidocella
Copy link
Contributor

Also with this we can probably replace the dont_bind_up_down hack for stats.lua by redefining the scroll bindings in opened.

@CogentRedTester
Copy link
Contributor Author

CogentRedTester commented Feb 18, 2026

Also with this we can probably replace the dont_bind_up_down hack for stats.lua by redefining the scroll bindings in opened.

Yes, but it's not quite as easy as it seems. When defaults.lua batches the keybinds, it combines them all into an input section, one for each script (technically two, the forced and unforced keybinds are separated). When new keybinds are added, the entire section is re-enabled. This means that, if we re-add the UP/DOWN keybinds, then all of the stats.lua keybinds will be re-added, overriding any keybinds set by console.lua. For example, the ESC keybind will quit stats.lua instead of closing the input request. The only way to avoid this is by setting the UP/DOWN overrides in a separate input section and enabling/disabling that instead, which can only be done manually:

local function define_scroll_override_section()
    local section = "mp_input_override_" .. mp.get_script_name()

    -- I just derived this formatting from defaults.lua, not certain this is ideal (but does work in my testing).
    local cfg = o.key_scroll_up .. " nonscalable script-binding " .. mp.get_script_name() ..
                                "/__forced_" .. o.key_scroll_up .. "\n" ..
                o.key_scroll_down .. " nonscalable script-binding " .. mp.get_script_name() ..
                                "/__forced_" .. o.key_scroll_down

    mp.input_define_section(section, cfg, "force")
    return section
end

local function filter_bindings()
    local section = define_scroll_override_section()
    input.get({
        prompt = "Filter bindings:",
        opened = function ()
            -- This is necessary to close the console if the oneshot
            -- display_timer expires without typing anything.
            searched_text = ""

            -- Must be re-bound to override the console.lua bindings.
            mp.input_enable_section(section, "allow-hide-cursor+allow-vo-dragging")
        end,
        edited = function (text)
            reset_scroll_offsets()
            searched_text = text:lower()
            print_page(curr_page)
            if display_timer.oneshot then
                display_timer:kill()
                display_timer:resume()
            end
        end,
        closed = function ()
            mp.input_disable_section(section)
            searched_text = nil
            if display_timer:is_enabled() then
                print_page(curr_page)
                if display_timer.oneshot then
                    display_timer:kill()
                    display_timer:resume()
                end
            end
        end,
    })
end

Ideally there would be a proper API provided to implement this, something like mp.register/unregister/enable/disable_key_binding_group(), which abstracts away the difficult things like concatenating the config string and needing to specify allow-hide-cursor+allow-vo-dragging, but input sections have been marked as deprecated in the manual for a long time, for reasons I am unclear on.

@guidocella
Copy link
Contributor

Heh I guess dont_bind_up_down is simpler.

input sections have been marked as deprecated in the manual for a long time, for reasons I am unclear on.

See #14050 and #15344

@CogentRedTester
Copy link
Contributor Author

CogentRedTester commented Feb 18, 2026

Heh I guess dont_bind_up_down is simpler.

Upon further reflection, we can do this relatively simply by unbinding all of the other key bindings:

local function filter_bindings()
    input.get({
        prompt = "Filter bindings:",
        opened = function ()
            -- This is necessary to close the console if the oneshot
            -- display_timer expires without typing anything.
            searched_text = ""

            -- Must be re-bound to override the console.lua bindings.
            remove_page_bindings()
            bind_scroll()
        end,
        edited = function (text)
            reset_scroll_offsets()
            searched_text = text:lower()
            print_page(curr_page)
            if display_timer.oneshot then
                display_timer:kill()
                display_timer:resume()
            end
        end,
        closed = function ()
            searched_text = nil
            if display_timer:is_enabled() then
                add_page_bindings()
                print_page(curr_page)
                if display_timer.oneshot then
                    display_timer:kill()
                    display_timer:resume()
                end
            end
        end,
    })
end

@guidocella
Copy link
Contributor

Yeah that works.

@CogentRedTester CogentRedTester changed the title console.lua: flush keybinds before sending mp.input opened events mp.input: allow clients to reliably override keybinds Feb 18, 2026
@CogentRedTester
Copy link
Contributor Author

Ah so that is what was happening. In the original incarnation of select.lua I just defined keybindings with a timeout (https://github.com/guidocella/mpv-console-select/blob/7712c7cba1bf0b7dfa8ddd567ac56bf18c3f11fb/console-select.lua#L114).

Makes sense though this delays opened by 0.077 seconds half the time in my tests.

Yea, I used a timeout as well in the script I linked in the description, but I think it makes more sense to delay the opened event slightly in order to prevent the need to do this. I see overriding keybinds as one of the most valuable uses for the opened event as doing so is actually reliant on the timing of console.lua, so I think making doing so reliable is well worth it.

@guidocella
Copy link
Contributor

The delay seems very random and variable anyway. Now I'm getting 20ms or < 1 ms.

How about:

  • Documenting that opened is only useful to override keybindings, short of the edge cases of --load-console=no or console having crashed
  • Document that if you just want to call input.set_log(), it's faster to to do it after calling input.get
  • Move commands.lua's opened callback below input.get to make it faster

@guidocella
Copy link
Contributor

Um nevermind calling input.set_log() after input.get() won't be faster since console is single threaded and still won't process it until keybindings are flushed. Still we can document that it's useful to override keybindings at least. Also it still suggests to present a list of options through the log which is obsoleted by input.select().

This commit uses the undocumented `mp.flush_keybindings()` method
to ensure that all `console.lua` keybinds are set before
sending the `opened` event to `mp.input` clients.

Previously, the keybinds would only be flushed when `console.lua` went
idle. As the `opened` event is sent before this happens, clients
processing the event may end up flushing their own keybinds before this
happens. This makes it unreliable to use the `opened` event to override
keybinds set by `console.lua`; for example, adding different
behaviour to `input.select` when using `Shift+Enter` instead of `Enter`.

This commit allows the `opened` callback to be used to safely override
`console.lua` keybinds.

.luacheckrc: move flush_keybindings to globals
This commit uses the opened event to ensure that the page scroll keys
(`UP`/`DOWN` by default) override the mp.input bindings,
without overriding any other mp.input keys.

This is much safer than the previous method, which required an
undocumented option to be passed to `mp.input`, and was hardcoded to the
default scroll keys.
This undocumented option was used by `stats.lua` to prevent the page
scroll keys from being overridden by `console.lua`. The previous commits
have removed the need for this hack, so it is now safe to remove.
@CogentRedTester CogentRedTester force-pushed the mp.input/flushed-keybinds branch from 315eb06 to 37f0261 Compare February 20, 2026 03:37
Now cites overriding keybinds as the most notable use of the `opened`
event.
@CogentRedTester CogentRedTester force-pushed the mp.input/flushed-keybinds branch from 37f0261 to 6069977 Compare February 20, 2026 03:40
@CogentRedTester
Copy link
Contributor Author

I have added this use-case to the documentation, but I haven't mentioned the input section issue, I figure that might derail the discussion somewhat.

Copy link
Member

@kasper93 kasper93 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@kasper93 kasper93 merged commit cf481fa into mpv-player:master Feb 24, 2026
50 of 52 checks passed
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 this pull request may close these issues.

3 participants