├── README.md └── menu.lua /README.md: -------------------------------------------------------------------------------- 1 | # mpv-menu 2 | Simple mpv menu to launch commands from. 3 | 4 | This script allows you to make your own list of commands that you would like to have quick access to without 5 | the need to remember the specific keybind. 6 | 7 | ### Setup 8 | 9 | Put the `menu.lua` file to your `mpv/scripts/` directory. To enable the script you need a `menu.json` file in 10 | your `mpv/script-opts/` directory. 11 | 12 | Example of the structure of the json 13 | ```json 14 | [ 15 | { 16 | "label": "Simply quit mpv", 17 | "command": ["quit"] 18 | }, 19 | { 20 | "label": "Set music profile by running two commands", 21 | "command": [["apply-profile", "music-disable"], ["show-text", "Music!"]], 22 | }, 23 | { 24 | "label": "Increase brightness without closing menu", 25 | "command": ["add", "brightness", "1"], 26 | "keep_open": true 27 | } 28 | ] 29 | ``` 30 | 31 | To run arbitrary lua code you need to create a custom script that listens to script-messages. 32 | 33 | **mpv/scripts/my_custom_command.lua** 34 | ```lua 35 | function my_custom_command(some_data, some_flag) 36 | -- do something cool 37 | end 38 | 39 | mp.register_script_message("my_custom_command", my_custom_command) 40 | ``` 41 | 42 | You can then add it to the menu with the following 43 | 44 | ```json 45 | [ 46 | { 47 | "title": "My custom command", 48 | "command": ["script-message", "my_custom_command", "some_data", true] 49 | } 50 | ] 51 | ``` 52 | -------------------------------------------------------------------------------- /menu.lua: -------------------------------------------------------------------------------- 1 | local settings = { 2 | display_timeout = 5, 3 | 4 | loop_cursor = true, 5 | 6 | key_moveup = "UP WHEEL_UP", 7 | key_movedown = "DOWN WHEEL_DOWN", 8 | key_execute = "ENTER MBTN_MID", 9 | key_closemenu = "ESC MBTN_RIGHT", 10 | } 11 | 12 | local utils = require("mp.utils") 13 | local msg = require("mp.msg") 14 | local assdraw = require("mp.assdraw") 15 | local opts = require("mp.options") 16 | opts.read_options(settings, "simplemenu") 17 | 18 | local file = assert(io.open(mp.command_native({"expand-path", "~~/script-opts"}) .. "/menu.json")) 19 | local json = file:read("*all") 20 | file:close() 21 | local menu_items = utils.parse_json(json) 22 | 23 | if menu_items == nil then 24 | error("Invalid JSON format in menu.json. Please run it through a linter. The script is disabled.") 25 | end 26 | 27 | for _, item in pairs(menu_items) do 28 | local command_type = type(item.command) 29 | assert( 30 | command_type == "table", 31 | "Unexpected command type for \""..item.label.."\". Expected table, received "..command_type 32 | ) 33 | -- TODO: assert nested commands 34 | end 35 | 36 | if #menu_items == 0 then 37 | msg.warn("Menu list is empty. The script is disabled.") 38 | end 39 | 40 | local menu_size = #menu_items 41 | local menu_visible = false 42 | local cursor = 1 43 | 44 | function execute() 45 | local command = menu_items[cursor].command 46 | local is_nested_command = type(command[1]) == "table" 47 | 48 | if is_nested_command then 49 | for _, cmd in ipairs(command) do 50 | mp.command_native(cmd) 51 | end 52 | else 53 | mp.command_native(command) 54 | end 55 | 56 | if menu_items[cursor].keep_open then 57 | render() 58 | else 59 | remove_keybinds() 60 | end 61 | end 62 | 63 | function toggle_menu() 64 | if menu_visible then 65 | remove_keybinds() 66 | return 67 | end 68 | render() 69 | end 70 | 71 | function render() 72 | local font_size = mp.get_property("osd-font-size") 73 | 74 | local ass = assdraw.ass_new() 75 | ass:new_event() 76 | ass:pos(30, 15) 77 | 78 | for index, item in ipairs(menu_items) do 79 | local selected = index == cursor 80 | local prefix = selected and "● " or "○ " 81 | ass:append(prefix .. item.label .. "\\N") 82 | end 83 | 84 | local w, h = mp.get_osd_size() 85 | mp.set_osd_ass(w, h, ass.text) 86 | 87 | menu_visible = true 88 | add_keybinds() 89 | keybindstimer:kill() 90 | keybindstimer:resume() 91 | end 92 | 93 | function moveup() 94 | if cursor ~= 1 then 95 | cursor = cursor - 1 96 | elseif settings.loop_cursor then 97 | cursor = menu_size 98 | end 99 | render() 100 | end 101 | 102 | function movedown() 103 | if cursor ~= menu_size then 104 | cursor = cursor + 1 105 | elseif settings.loop_cursor then 106 | cursor = 1 107 | end 108 | render() 109 | end 110 | 111 | function bind_keys(keys, name, func, opts) 112 | if not keys then 113 | mp.add_forced_key_binding(keys, name, func, opts) 114 | return 115 | end 116 | local i = 1 117 | for key in keys:gmatch("[^%s]+") do 118 | local prefix = i == 1 and '' or i 119 | mp.add_forced_key_binding(key, name..prefix, func, opts) 120 | i = i + 1 121 | end 122 | end 123 | 124 | function unbind_keys(keys, name) 125 | if not keys then 126 | mp.remove_key_binding(name) 127 | return 128 | end 129 | local i = 1 130 | for key in keys:gmatch("[^%s]+") do 131 | local prefix = i == 1 and '' or i 132 | mp.remove_key_binding(name..prefix) 133 | i = i + 1 134 | end 135 | end 136 | 137 | function add_keybinds() 138 | bind_keys(settings.key_moveup, 'simplemenu-moveup', moveup, "repeatable") 139 | bind_keys(settings.key_movedown, 'simplemenu-movedown', movedown, "repeatable") 140 | bind_keys(settings.key_execute, 'simplemenu-execute', execute) 141 | bind_keys(settings.key_closemenu, 'simplemenu-closemenu', remove_keybinds) 142 | end 143 | 144 | function remove_keybinds() 145 | keybindstimer:kill() 146 | menu_visible = false 147 | mp.set_osd_ass(0, 0, "") 148 | unbind_keys(settings.key_moveup, 'simplemenu-moveup') 149 | unbind_keys(settings.key_movedown, 'simplemenu-movedown') 150 | unbind_keys(settings.key_execute, 'simplemenu-execute') 151 | unbind_keys(settings.key_closemenu, 'simplemenu-closemenu') 152 | end 153 | 154 | keybindstimer = mp.add_periodic_timer(settings.display_timeout, remove_keybinds) 155 | keybindstimer:kill() 156 | 157 | if menu_items and menu_size > 0 then 158 | mp.register_script_message("simplemenu-toggle", toggle_menu) 159 | mp.add_key_binding("MBTN_MID", "simplemenu-toggle", toggle_menu) 160 | end --------------------------------------------------------------------------------