player/command: add discnav command and bind default keys

User-facing entry point for in-disc menu navigation. The command
forwards an action (up/down/left/right/select/menu/title-menu/popup/
prev/mouse-move/mouse-click) to the current disc stream handler.

The new discnav input section allows binding controls that are only
active while a disc menu is on screen.

Setting the edition property on a disc jumps to the title through the
stream directly, so selecting the currently-playing edition can still
return to it from a menu.
This commit is contained in:
Kacper Michajłow committed 2026-08-26 01:47:41 +02:00
1 parent a5682eb2ad
commit 216e26c871
5 files changed
+155 -4

No files matched your search

+1
View File
@@ -1,2 +1,3 @@
add `--disc-menu` option add `--disc-menu` option
add `disc-menu-active` property add `disc-menu-active` property
add `discnav` command
+40
View File
@@ -1771,6 +1771,46 @@ Miscellaneous Commands
``context-menu`` ``context-menu``
Show context menu on the video window. See `Context Menu`_ section for details. Show context menu on the video window. See `Context Menu`_ section for details.
``discnav <action> [<x> <y>]``
Send a navigation command to the optical-disc stream that is currently
playing. The command does nothing when no disc is being played.
``<action>`` is one of:
up, down, left, right
Move the on-disc button selection.
select
Activate the currently highlighted button.
menu
Jump to the root menu of the current title set (DVD: VTSM root menu,
BD: HDMV top menu).
title-menu
Jump to the disc's title menu (DVD: VMGM title menu, treated like
``menu`` on BD).
popup
Show / dismiss the Blu-ray popup menu. For DVDs, falls back to the
chapter menu where available.
prev
Return from a menu to playback, or to the previous menu domain.
mouse-move
Forward a mouse position to the disc, so it can update the focused
button under the cursor. With no ``<x> <y>`` arguments, the position
is read from the ``mouse-pos`` property and mapped from window into
source-video coordinates automatically.
mouse-click
Like ``mouse-move`` but also activates the button under the cursor.
``<x>`` and ``<y>`` are optional and only meaningful for the ``mouse-*``
actions. They are normalized coordinates in the range 0..1 (top-left at
``0,0``), and are used instead of the live ``mouse-pos``.
mpv ships default bindings for this command in the ``{discnav}`` input
section (``UP``/``DOWN``/``LEFT``/``RIGHT`` for navigation, ``ENTER`` to
select, ``ESC`` / ``BS`` to leave, ``MBTN_LEFT`` / ``MOUSE_MOVE`` for
mouse). The player enables and disables that section automatically as
the menu appears and disappears, so the bindings only shadow the normal
ones while a menu is actually on screen.
``update-clipboard <type> [timeout]`` ``update-clipboard <type> [timeout]``
Update the clipboard content so that the ``clipboard`` property reflects Update the clipboard content so that the ``clipboard`` property reflects
up-to-date value. This command is required to update the ``clipboard`` up-to-date value. This command is required to update the ``clipboard``
+16 -4
View File
@@ -174,6 +174,15 @@ z and Z
l l
Set/clear A-B loop points. See ``ab-loop`` command for details. Set/clear A-B loop points. See ``ab-loop`` command for details.
Ctrl+m
Jump to the disc menu when playing a DVD or Blu-ray (see the ``discnav``
command). While a menu with a button highlight is on screen, the arrow
keys, ENTER, ESC/BACKSPACE and the mouse control the menu instead of
their usual functions.
Ctrl+M
Show or hide the Blu-ray popup menu.
L L
Toggle infinite looping. Toggle infinite looping.
@@ -1328,7 +1337,8 @@ PROTOCOLS
by passing them to ``--bluray-device``. by passing them to ``--bluray-device``.
``title`` can be: ``longest`` or ``first`` (selects the default ``title`` can be: ``longest`` or ``first`` (selects the default
playlist); ``mpls/<number>`` (selects <number>.mpls playlist); playlist); ``menu`` (starts in the disc menu, see ``--disc-menu`` and the
``discnav`` command); ``mpls/<number>`` (selects <number>.mpls playlist);
``<number>`` (select playlist with the same index). mpv will list ``<number>`` (select playlist with the same index). mpv will list
the available playlists on loading. the available playlists on loading.
@@ -1336,9 +1346,11 @@ PROTOCOLS
``dvd://[title][/device]`` ``--dvd-device=PATH`` ``dvd://[title][/device]`` ``--dvd-device=PATH``
Play a DVD. DVD menus are not supported. If no title is given, the longest Play a DVD. If no title is given, the longest title is auto-selected.
title is auto-selected. Without ``--dvd-device``, it will probably try ``title`` can also be ``menu`` to start in the disc menu (see
to open an actual optical drive, if available and implemented for the OS. ``--disc-menu`` and the ``discnav`` command). Without ``--dvd-device``,
it will probably try to open an actual optical drive, if available and
implemented for the OS.
``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same ``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same
thing. thing.
+16
View File
@@ -234,6 +234,22 @@
#Ctrl+KP_UP add video-align-y -0.01 # align video up #Ctrl+KP_UP add video-align-y -0.01 # align video up
#Ctrl+KP_PGUP add video-align-x 0.01; add video-align-y -0.01 # align video right and up #Ctrl+KP_PGUP add video-align-x 0.01; add video-align-y -0.01 # align video right and up
#
# The "{discnav}" section is enabled only while a disc menu with a button
# highlight is actually on screen.
#
#UP {discnav} discnav up
#DOWN {discnav} discnav down
#LEFT {discnav} discnav left
#RIGHT {discnav} discnav right
#ENTER {discnav} discnav select
#ESC {discnav} discnav prev
#BS {discnav} discnav prev
#MOUSE_MOVE {discnav} discnav mouse-move
#MBTN_LEFT {discnav} discnav mouse-click
#Ctrl+m discnav menu # jump to the disc's root menu
#Ctrl+M discnav popup # show/hide Blu-ray popup menu
# #
# Legacy bindings (may or may not be removed in the future) # Legacy bindings (may or may not be removed in the future)
# #
+82
View File
@@ -1182,6 +1182,26 @@ static int mp_property_edition(void *ctx, struct m_property *prop,
} }
return M_PROPERTY_OK; return M_PROPERTY_OK;
} }
case M_PROPERTY_SET: {
// For disc demuxers, jump the title directly, we sometimes need to
// react even if the actual edition "value" doesn't change.
if (disc_nav_get_stream(mpctx)) {
int new_ed = *(int *)arg;
if (new_ed < 0 || new_ed >= demuxer->num_editions)
return M_PROPERTY_ERROR;
unsigned new_title = new_ed;
if (stream_control(demuxer->stream, STREAM_CTRL_SET_CURRENT_TITLE,
&new_title) < 1)
return M_PROPERTY_ERROR;
mpctx->opts->edition_id = new_ed;
m_config_notify_change_opt_ptr(mpctx->mconfig,
&mpctx->opts->edition_id);
mp_notify_property(mpctx, "edition");
mp_wakeup_core(mpctx);
return M_PROPERTY_OK;
}
return mp_property_generic_option(mpctx, prop, action, arg);
}
default: default:
return mp_property_generic_option(mpctx, prop, action, arg); return mp_property_generic_option(mpctx, prop, action, arg);
} }
@@ -7449,6 +7469,47 @@ static void cmd_context_menu(void *p)
vo_control(vo, VOCTRL_SHOW_MENU, NULL); vo_control(vo, VOCTRL_SHOW_MENU, NULL);
} }
static void cmd_discnav(void *p)
{
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
int action = cmd->args[0].v.i;
struct stream *s = disc_nav_get_stream(mpctx);
if (!s) {
cmd->success = false;
return;
}
struct stream_nav_cmd nc = { .action = action };
struct stream_nav_state pre = {0};
stream_control(s, STREAM_CTRL_GET_NAV_STATE, &pre);
if (action == STREAM_NAV_MOUSE_MOVE || action == STREAM_NAV_MOUSE_CLICK) {
double nx = cmd->args[1].v.d;
double ny = cmd->args[2].v.d;
if (nx >= 0 && ny >= 0) {
if (pre.src_w <= 0 || pre.src_h <= 0 || nx > 1 || ny > 1) {
cmd->success = false;
return;
}
nc.x = (int)(nx * pre.src_w);
nc.y = (int)(ny * pre.src_h);
} else if (!disc_nav_mouse_pos_to_src(mpctx, pre.src_w, pre.src_h,
&nc.x, &nc.y))
{
cmd->success = false;
return;
}
}
if (stream_control(s, STREAM_CTRL_NAV_CMD, &nc) < 1)
cmd->success = false;
mp_wakeup_core(mpctx);
}
static void cmd_flush_status_line(void *p) static void cmd_flush_status_line(void *p)
{ {
struct mp_cmd_ctx *cmd = p; struct mp_cmd_ctx *cmd = p;
@@ -8013,6 +8074,23 @@ const struct mp_cmd_def mp_cmds[] = {
{ "context-menu", cmd_context_menu }, { "context-menu", cmd_context_menu },
{ "discnav", cmd_discnav,
{ {"action", OPT_CHOICE(v.i,
{"up", STREAM_NAV_UP},
{"down", STREAM_NAV_DOWN},
{"left", STREAM_NAV_LEFT},
{"right", STREAM_NAV_RIGHT},
{"select", STREAM_NAV_SELECT},
{"menu", STREAM_NAV_MENU_ROOT},
{"title-menu", STREAM_NAV_MENU_TITLE},
{"popup", STREAM_NAV_MENU_POPUP},
{"prev", STREAM_NAV_PREV_MENU},
{"mouse-move", STREAM_NAV_MOUSE_MOVE},
{"mouse-click", STREAM_NAV_MOUSE_CLICK})},
{"x", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(-1)},
{"y", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(-1)} },
},
{ "flush-status-line", cmd_flush_status_line, { {"clear", OPT_BOOL(v.b)} } }, { "flush-status-line", cmd_flush_status_line, { {"clear", OPT_BOOL(v.b)} } },
{ "notify-property", cmd_notify_property, { {"property", OPT_STRING(v.s)} } }, { "notify-property", cmd_notify_property, { {"property", OPT_STRING(v.s)} } },
@@ -8438,6 +8516,10 @@ void mp_option_run_callback(struct MPContext *mpctx, struct mp_option_callback *
mp_notify_property(mpctx, "current-edition"); mp_notify_property(mpctx, "current-edition");
print_track_list(mpctx, print_track_list(mpctx,
mp_tprintf(42, "Selected edition %d:", demuxer->edition)); mp_tprintf(42, "Selected edition %d:", demuxer->edition));
} else if (disc_nav_get_stream(mpctx)) {
unsigned new_title = opts->edition_id;
stream_control(demuxer->stream,
STREAM_CTRL_SET_CURRENT_TITLE, &new_title);
} else { } else {
if (!mpctx->stop_play) if (!mpctx->stop_play)
mpctx->stop_play = PT_CURRENT_ENTRY; mpctx->stop_play = PT_CURRENT_ENTRY;