diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /player/lua/defaults.lua | |
parent | Initial commit. (diff) | |
download | mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'player/lua/defaults.lua')
-rw-r--r-- | player/lua/defaults.lua | 836 |
1 files changed, 836 insertions, 0 deletions
diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua new file mode 100644 index 0000000..233d1d6 --- /dev/null +++ b/player/lua/defaults.lua @@ -0,0 +1,836 @@ +-- Compatibility shim for lua 5.2/5.3 +unpack = unpack or table.unpack + +-- these are used internally by lua.c +mp.UNKNOWN_TYPE.info = "this value is inserted if the C type is not supported" +mp.UNKNOWN_TYPE.type = "UNKNOWN_TYPE" + +mp.ARRAY.info = "native array" +mp.ARRAY.type = "ARRAY" + +mp.MAP.info = "native map" +mp.MAP.type = "MAP" + +function mp.get_script_name() + return mp.script_name +end + +function mp.get_opt(key, def) + local opts = mp.get_property_native("options/script-opts") + local val = opts[key] + if val == nil then + val = def + end + return val +end + +function mp.input_define_section(section, contents, flags) + if flags == nil or flags == "" then + flags = "default" + end + mp.commandv("define-section", section, contents, flags) +end + +function mp.input_enable_section(section, flags) + if flags == nil then + flags = "" + end + mp.commandv("enable-section", section, flags) +end + +function mp.input_disable_section(section) + mp.commandv("disable-section", section) +end + +function mp.get_mouse_pos() + local m = mp.get_property_native("mouse-pos") + return m.x, m.y +end + +-- For dispatching script-binding. This is sent as: +-- script-message-to $script_name $binding_name $keystate +-- The array is indexed by $binding_name, and has functions like this as value: +-- fn($binding_name, $keystate) +local dispatch_key_bindings = {} + +local message_id = 0 +local function reserve_binding() + message_id = message_id + 1 + return "__keybinding" .. tostring(message_id) +end + +local function dispatch_key_binding(name, state, key_name, key_text) + local fn = dispatch_key_bindings[name] + if fn then + fn(name, state, key_name, key_text) + end +end + +-- "Old", deprecated API + +-- each script has its own section, so that they don't conflict +local default_section = "input_dispatch_" .. mp.script_name + +-- Set the list of key bindings. These will override the user's bindings, so +-- you should use this sparingly. +-- A call to this function will remove all bindings previously set with this +-- function. For example, set_key_bindings({}) would remove all script defined +-- key bindings. +-- Note: the bindings are not active by default. Use enable_key_bindings(). +-- +-- list is an array of key bindings, where each entry is an array as follow: +-- {key, callback_press, callback_down, callback_up} +-- key is the key string as used in input.conf, like "ctrl+a" +-- +-- callback can be a string too, in which case the following will be added like +-- an input.conf line: key .. " " .. callback +-- (And callback_down is ignored.) +function mp.set_key_bindings(list, section, flags) + local cfg = "" + for i = 1, #list do + local entry = list[i] + local key = entry[1] + local cb = entry[2] + local cb_down = entry[3] + local cb_up = entry[4] + if type(cb) ~= "string" then + local mangle = reserve_binding() + dispatch_key_bindings[mangle] = function(name, state) + local event = state:sub(1, 1) + local is_mouse = state:sub(2, 2) == "m" + local def = (is_mouse and "u") or "d" + if event == "r" then + return + end + if event == "p" and cb then + cb() + elseif event == "d" and cb_down then + cb_down() + elseif event == "u" and cb_up then + cb_up() + elseif event == def and cb then + cb() + end + end + cfg = cfg .. key .. " script-binding " .. + mp.script_name .. "/" .. mangle .. "\n" + else + cfg = cfg .. key .. " " .. cb .. "\n" + end + end + mp.input_define_section(section or default_section, cfg, flags) +end + +function mp.enable_key_bindings(section, flags) + mp.input_enable_section(section or default_section, flags) +end + +function mp.disable_key_bindings(section) + mp.input_disable_section(section or default_section) +end + +function mp.set_mouse_area(x0, y0, x1, y1, section) + mp.input_set_section_mouse_area(section or default_section, x0, y0, x1, y1) +end + +-- "Newer" and more convenient API + +local key_bindings = {} +local key_binding_counter = 0 +local key_bindings_dirty = false + +function mp.flush_keybindings() + if not key_bindings_dirty then + return + end + key_bindings_dirty = false + + for i = 1, 2 do + local section, flags + local def = i == 1 + if def then + section = "input_" .. mp.script_name + flags = "default" + else + section = "input_forced_" .. mp.script_name + flags = "force" + end + local bindings = {} + for k, v in pairs(key_bindings) do + if v.bind and v.forced ~= def then + bindings[#bindings + 1] = v + end + end + table.sort(bindings, function(a, b) + return a.priority < b.priority + end) + local cfg = "" + for _, v in ipairs(bindings) do + cfg = cfg .. v.bind .. "\n" + end + mp.input_define_section(section, cfg, flags) + -- TODO: remove the section if the script is stopped + mp.input_enable_section(section, "allow-hide-cursor+allow-vo-dragging") + end +end + +local function add_binding(attrs, key, name, fn, rp) + if type(name) ~= "string" and name ~= nil then + rp = fn + fn = name + name = nil + end + rp = rp or "" + if name == nil then + name = reserve_binding() + end + local repeatable = rp == "repeatable" or rp["repeatable"] + if rp["forced"] then + attrs.forced = true + end + local key_cb, msg_cb + if not fn then + fn = function() end + end + if rp["complex"] then + local key_states = { + ["u"] = "up", + ["d"] = "down", + ["r"] = "repeat", + ["p"] = "press", + } + key_cb = function(name, state, key_name, key_text) + if key_text == "" then + key_text = nil + end + fn({ + event = key_states[state:sub(1, 1)] or "unknown", + is_mouse = state:sub(2, 2) == "m", + key_name = key_name, + key_text = key_text, + }) + end + msg_cb = function() + fn({event = "press", is_mouse = false}) + end + else + key_cb = function(name, state) + -- Emulate the same semantics as input.c uses for most bindings: + -- For keyboard, "down" runs the command, "up" does nothing; + -- for mouse, "down" does nothing, "up" runs the command. + -- Also, key repeat triggers the binding again. + local event = state:sub(1, 1) + local is_mouse = state:sub(2, 2) == "m" + if event == "r" and not repeatable then + return + end + if is_mouse and (event == "u" or event == "p") then + fn() + elseif not is_mouse and (event == "d" or event == "r" or event == "p") then + fn() + end + end + msg_cb = fn + end + if key and #key > 0 then + attrs.bind = key .. " script-binding " .. mp.script_name .. "/" .. name + end + attrs.name = name + -- new bindings override old ones (but do not overwrite them) + key_binding_counter = key_binding_counter + 1 + attrs.priority = key_binding_counter + key_bindings[name] = attrs + key_bindings_dirty = true + dispatch_key_bindings[name] = key_cb + mp.register_script_message(name, msg_cb) +end + +function mp.add_key_binding(...) + add_binding({forced=false}, ...) +end + +function mp.add_forced_key_binding(...) + add_binding({forced=true}, ...) +end + +function mp.remove_key_binding(name) + key_bindings[name] = nil + dispatch_key_bindings[name] = nil + key_bindings_dirty = true + mp.unregister_script_message(name) +end + +local timers = {} + +local timer_mt = {} +timer_mt.__index = timer_mt + +function mp.add_timeout(seconds, cb, disabled) + local t = mp.add_periodic_timer(seconds, cb, disabled) + t.oneshot = true + return t +end + +function mp.add_periodic_timer(seconds, cb, disabled) + local t = { + timeout = seconds, + cb = cb, + oneshot = false, + } + setmetatable(t, timer_mt) + if not disabled then + t:resume() + end + return t +end + +function timer_mt.stop(t) + if timers[t] then + timers[t] = nil + t.next_deadline = t.next_deadline - mp.get_time() + end +end + +function timer_mt.kill(t) + timers[t] = nil + t.next_deadline = nil +end +mp.cancel_timer = timer_mt.kill + +function timer_mt.resume(t) + if not timers[t] then + local timeout = t.next_deadline + if timeout == nil then + timeout = t.timeout + end + t.next_deadline = mp.get_time() + timeout + timers[t] = t + end +end + +function timer_mt.is_enabled(t) + return timers[t] ~= nil +end + +-- Return the timer that expires next. +local function get_next_timer() + local best = nil + for t, _ in pairs(timers) do + if best == nil or t.next_deadline < best.next_deadline then + best = t + end + end + return best +end + +function mp.get_next_timeout() + local timer = get_next_timer() + if not timer then + return + end + local now = mp.get_time() + return timer.next_deadline - now +end + +-- Run timers that have met their deadline at the time of invocation. +-- Return: time>0 in seconds till the next due timer, 0 if there are due timers +-- (aborted to avoid infinite loop), or nil if no timers +local function process_timers() + local t0 = nil + while true do + local timer = get_next_timer() + if not timer then + return + end + local now = mp.get_time() + local wait = timer.next_deadline - now + if wait > 0 then + return wait + else + if not t0 then + t0 = now -- first due callback: always executes, remember t0 + elseif timer.next_deadline > t0 then + -- don't block forever with slow callbacks and endless timers. + -- we'll continue right after checking mpv events. + return 0 + end + + if timer.oneshot then + timer:kill() + else + timer.next_deadline = now + timer.timeout + end + timer.cb() + end + end +end + +local messages = {} + +function mp.register_script_message(name, fn) + messages[name] = fn +end + +function mp.unregister_script_message(name) + messages[name] = nil +end + +local function message_dispatch(ev) + if #ev.args > 0 then + local handler = messages[ev.args[1]] + if handler then + handler(unpack(ev.args, 2)) + end + end +end + +local property_id = 0 +local properties = {} + +function mp.observe_property(name, t, cb) + local id = property_id + 1 + property_id = id + properties[id] = cb + mp.raw_observe_property(id, name, t) +end + +function mp.unobserve_property(cb) + for prop_id, prop_cb in pairs(properties) do + if cb == prop_cb then + properties[prop_id] = nil + mp.raw_unobserve_property(prop_id) + end + end +end + +local function property_change(ev) + local prop = properties[ev.id] + if prop then + prop(ev.name, ev.data) + end +end + +-- used by default event loop (mp_event_loop()) to decide when to quit +mp.keep_running = true + +local event_handlers = {} + +function mp.register_event(name, cb) + local list = event_handlers[name] + if not list then + list = {} + event_handlers[name] = list + end + list[#list + 1] = cb + return mp.request_event(name, true) +end + +function mp.unregister_event(cb) + for name, sub in pairs(event_handlers) do + local found = false + for i, e in ipairs(sub) do + if e == cb then + found = true + break + end + end + if found then + -- create a new array, just in case this function was called + -- from an event handler + local new = {} + for i = 1, #sub do + if sub[i] ~= cb then + new[#new + 1] = sub[i] + end + end + event_handlers[name] = new + if #new == 0 then + mp.request_event(name, false) + end + end + end +end + +-- default handlers +mp.register_event("shutdown", function() mp.keep_running = false end) +mp.register_event("client-message", message_dispatch) +mp.register_event("property-change", property_change) + +-- called before the event loop goes back to sleep +local idle_handlers = {} + +function mp.register_idle(cb) + idle_handlers[#idle_handlers + 1] = cb +end + +function mp.unregister_idle(cb) + local new = {} + for _, handler in ipairs(idle_handlers) do + if handler ~= cb then + new[#new + 1] = handler + end + end + idle_handlers = new +end + +-- sent by "script-binding" +mp.register_script_message("key-binding", dispatch_key_binding) + +mp.msg = { + log = mp.log, + fatal = function(...) return mp.log("fatal", ...) end, + error = function(...) return mp.log("error", ...) end, + warn = function(...) return mp.log("warn", ...) end, + info = function(...) return mp.log("info", ...) end, + verbose = function(...) return mp.log("v", ...) end, + debug = function(...) return mp.log("debug", ...) end, + trace = function(...) return mp.log("trace", ...) end, +} + +_G.print = mp.msg.info + +package.loaded["mp"] = mp +package.loaded["mp.msg"] = mp.msg + +function mp.wait_event(t) + local r = mp.raw_wait_event(t) + if r and r.file_error and not r.error then + -- compat; deprecated + r.error = r.file_error + end + return r +end + +_G.mp_event_loop = function() + mp.dispatch_events(true) +end + +local function call_event_handlers(e) + local handlers = event_handlers[e.event] + if handlers then + for _, handler in ipairs(handlers) do + handler(e) + end + end +end + +mp.use_suspend = false + +local suspend_warned = false + +function mp.dispatch_events(allow_wait) + local more_events = true + if mp.use_suspend then + if not suspend_warned then + mp.msg.error("mp.use_suspend is now ignored.") + suspend_warned = true + end + end + while mp.keep_running do + local wait = 0 + if not more_events then + wait = process_timers() or 1e20 -- infinity for all practical purposes + if wait ~= 0 then + local idle_called = nil + for _, handler in ipairs(idle_handlers) do + idle_called = true + handler() + end + if idle_called then + -- handlers don't complete in 0 time, and may modify timers + wait = mp.get_next_timeout() or 1e20 + if wait < 0 then + wait = 0 + end + end + end + if allow_wait ~= true then + return + end + end + local e = mp.wait_event(wait) + more_events = false + if e.event ~= "none" then + call_event_handlers(e) + more_events = true + end + end +end + +mp.register_idle(mp.flush_keybindings) + +-- additional helpers + +function mp.osd_message(text, duration) + if not duration then + duration = "-1" + else + duration = tostring(math.floor(duration * 1000)) + end + mp.commandv("show-text", text, duration) +end + +local hook_table = {} + +local hook_mt = {} +hook_mt.__index = hook_mt + +function hook_mt.cont(t) + if t._id == nil then + mp.msg.error("hook already continued") + else + mp.raw_hook_continue(t._id) + t._id = nil + end +end + +function hook_mt.defer(t) + t._defer = true +end + +mp.register_event("hook", function(ev) + local fn = hook_table[tonumber(ev.id)] + local hookobj = { + _id = ev.hook_id, + _defer = false, + } + setmetatable(hookobj, hook_mt) + if fn then + fn(hookobj) + end + if not hookobj._defer and hookobj._id ~= nil then + hookobj:cont() + end +end) + +function mp.add_hook(name, pri, cb) + local id = #hook_table + 1 + hook_table[id] = cb + -- The C API suggests using 0 for a neutral priority, but lua.rst suggests + -- 50 (?), so whatever. + mp.raw_hook_add(id, name, pri - 50) +end + +local async_call_table = {} +local async_next_id = 1 + +function mp.command_native_async(node, cb) + local id = async_next_id + async_next_id = async_next_id + 1 + cb = cb or function() end + local res, err = mp.raw_command_native_async(id, node) + if not res then + mp.add_timeout(0, function() cb(false, nil, err) end) + return res, err + end + local t = {cb = cb, id = id} + async_call_table[id] = t + return t +end + +mp.register_event("command-reply", function(ev) + local id = tonumber(ev.id) + local t = async_call_table[id] + local cb = t.cb + t.id = nil + async_call_table[id] = nil + if ev.error then + cb(false, nil, ev.error) + else + cb(true, ev.result, nil) + end +end) + +function mp.abort_async_command(t) + if t.id ~= nil then + mp.raw_abort_async_command(t.id) + end +end + +local overlay_mt = {} +overlay_mt.__index = overlay_mt +local overlay_new_id = 0 + +function mp.create_osd_overlay(format) + overlay_new_id = overlay_new_id + 1 + local overlay = { + format = format, + id = overlay_new_id, + data = "", + res_x = 0, + res_y = 720, + } + setmetatable(overlay, overlay_mt) + return overlay +end + +function overlay_mt.update(ov) + local cmd = {} + for k, v in pairs(ov) do + cmd[k] = v + end + cmd.name = "osd-overlay" + cmd.res_x = math.floor(cmd.res_x) + cmd.res_y = math.floor(cmd.res_y) + return mp.command_native(cmd) +end + +function overlay_mt.remove(ov) + mp.command_native { + name = "osd-overlay", + id = ov.id, + format = "none", + data = "", + } +end + +-- legacy API +function mp.set_osd_ass(res_x, res_y, data) + if not mp._legacy_overlay then + mp._legacy_overlay = mp.create_osd_overlay("ass-events") + end + if mp._legacy_overlay.res_x ~= res_x or + mp._legacy_overlay.res_y ~= res_y or + mp._legacy_overlay.data ~= data + then + mp._legacy_overlay.res_x = res_x + mp._legacy_overlay.res_y = res_y + mp._legacy_overlay.data = data + mp._legacy_overlay:update() + end +end + +function mp.get_osd_size() + local prop = mp.get_property_native("osd-dimensions") + return prop.w, prop.h, prop.aspect +end + +function mp.get_osd_margins() + local prop = mp.get_property_native("osd-dimensions") + return prop.ml, prop.mt, prop.mr, prop.mb +end + +local mp_utils = package.loaded["mp.utils"] + +function mp_utils.format_table(t, set) + if not set then + set = { [t] = true } + end + local res = "{" + -- pretty expensive but simple way to distinguish array and map parts of t + local keys = {} + local vals = {} + local arr = 0 + for i = 1, #t do + if t[i] == nil then + break + end + keys[i] = i + vals[i] = t[i] + arr = i + end + for k, v in pairs(t) do + if not (type(k) == "number" and k >= 1 and k <= arr and keys[k]) then + keys[#keys + 1] = k + vals[#keys] = v + end + end + for i = 1, #keys do + if #res > 1 then + res = res .. ", " + end + if i > arr then + res = res .. mp_utils.to_string(keys[i], set) .. " = " + end + res = res .. mp_utils.to_string(vals[i], set) + end + res = res .. "}" + return res +end + +function mp_utils.to_string(v, set) + if type(v) == "string" then + return "\"" .. v .. "\"" + elseif type(v) == "table" then + if set then + if set[v] then + return "[cycle]" + end + set[v] = true + end + return mp_utils.format_table(v, set) + else + return tostring(v) + end +end + +function mp_utils.getcwd() + return mp.get_property("working-directory") +end + +function mp_utils.getpid() + return mp.get_property_number("pid") +end + +function mp_utils.format_bytes_humanized(b) + local d = {"Bytes", "KiB", "MiB", "GiB", "TiB", "PiB"} + local i = 1 + while b >= 1024 do + b = b / 1024 + i = i + 1 + end + return string.format("%0.2f %s", b, d[i] and d[i] or "*1024^" .. (i-1)) +end + +function mp_utils.subprocess(t) + local cmd = {} + cmd.name = "subprocess" + cmd.capture_stdout = true + for k, v in pairs(t) do + if k == "cancellable" then + k = "playback_only" + elseif k == "max_size" then + k = "capture_size" + end + cmd[k] = v + end + local res, err = mp.command_native(cmd) + if res == nil then + -- an error usually happens only if parsing failed (or no args passed) + res = {error_string = err, status = -1} + end + if res.error_string ~= "" then + res.error = res.error_string + end + return res +end + +function mp_utils.subprocess_detached(t) + mp.commandv("run", unpack(t.args)) +end + +function mp_utils.shared_script_property_set(name, value) + if value ~= nil then + -- no such thing as change-list with mpv_node, so build a string value + mp.commandv("change-list", "shared-script-properties", "append", + name .. "=" .. value) + else + mp.commandv("change-list", "shared-script-properties", "remove", name) + end +end + +function mp_utils.shared_script_property_get(name) + local map = mp.get_property_native("shared-script-properties") + return map and map[name] +end + +-- cb(name, value) on change and on init +function mp_utils.shared_script_property_observe(name, cb) + -- it's _very_ wasteful to observe the mpv core "super" property for every + -- shared sub-property, but then again you shouldn't use this + mp.observe_property("shared-script-properties", "native", function(_, val) + cb(name, val and val[name]) + end) +end + +return {} |