diff options
Diffstat (limited to 'mac/.config/mpv/script-modules/extended-menu.lua')
| -rw-r--r-- | mac/.config/mpv/script-modules/extended-menu.lua | 1214 |
1 files changed, 1214 insertions, 0 deletions
diff --git a/mac/.config/mpv/script-modules/extended-menu.lua b/mac/.config/mpv/script-modules/extended-menu.lua new file mode 100644 index 0000000..b663e93 --- /dev/null +++ b/mac/.config/mpv/script-modules/extended-menu.lua @@ -0,0 +1,1214 @@ +local mp = require("mp") +local utils = require("mp.utils") +local assdraw = require("mp.assdraw") + +-- create namespace with default values +local em = { + + -- customisable values ------------------------------------------------------ + + loop_when_navigating = false, -- Loop when navigating through list + lines_to_show = 17, -- NOT including search line + pause_on_open = true, + resume_on_exit = "only-if-was-paused", -- another possible value is true + + -- styles (earlyer it was a table, but required many more steps to pass def-s + -- here from .conf file) + font_size = 21, + --font size scales by window + scale_by_window = false, + -- cursor 'width', useful to change if you have hidpi monitor + cursor_x_border = 0.3, + line_bottom_margin = 1, -- basically space between lines + text_color = { + default = "ffffff", + accent = "d8a07b", + current = "aaaaaa", + comment = "636363", + }, + menu_x_padding = 5, -- this padding for now applies only to 'left', not x + menu_y_padding = 2, -- but this one applies to both - top & bottom + + -- values that should be passed from main script ---------------------------- + + search_heading = "Default search heading", + -- 'full' is required from main script, 'current_i' is optional + -- others are 'private' + list = { + full = {}, + filtered = {}, + current_i = nil, + pointer_i = 1, + show_from_to = {}, + }, + -- field to compare with when searching for 'current value' by 'current_i' + index_field = "index", + -- fields to use when searching for string match / any other custom searching + -- if value has 0 length, then search list item itself + filter_by_fields = {}, + + -- 'private' values that are not supposed to be changed from the outside ---- + + is_active = false, + -- https://mpv.io/manual/master/#lua-scripting-mp-create-osd-overlay(format) + ass = mp.create_osd_overlay("ass-events"), + was_paused = false, -- flag that indicates that vid was paused by this script + + line = "", + -- if there was no cursor it wouldn't have been needed, but for now we need + -- variable below only to compare it with 'line' and see if we need to filter + prev_line = "", + cursor = 1, + history = {}, + history_pos = 1, + key_bindings = {}, + insert_mode = false, + + -- used only in 'update' func to get error text msgs + error_codes = { + no_match = "Match required", + no_submit_provided = "No submit function provided", + }, +} + +-- PRIVATE METHODS ------------------------------------------------------------ + +-- declare constructor function +function em:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + + -- some options might be customised by user in .conf file and read as strings + -- in that case parse those + if type(o.filter_by_fields) == "string" then + o.filter_by_fields = utils.parse_json(o.filter_by_fields) + end + + if type(o.text_color) == "string" then + o.text_color = utils.parse_json(o.text_color) + end + + return o +end + +-- this func is just a getter of a current list depending on search line +function em:current() + return self.line == "" and self.list.full or self.list.filtered +end + +-- REVIEW: how to get rid of this wrapper and handle filter func sideeffects +-- in a more elegant way? +function em:filter_wrapper() + -- handles sideeffect that are needed to be run on filtering list + -- cuz the filter func may be redefined in main script and therefore needs + -- to be straight forward - only doing filtering and returning the table + + -- passing current query just in case, so ppl can use it in their custom funcs + self.list.filtered = self:filter(self.line) + + self.prev_line = self.line + self.list.pointer_i = 1 + self:set_from_to(true) +end + +function em:set_from_to(reset_flag) + -- additional variables just for shorter var name + local i = self.list.pointer_i + local to_show = self.lines_to_show + local total = #self:current() + + if reset_flag or to_show >= total then + self.list.show_from_to = { 1, math.min(to_show, total) } + return + end + + -- If menu is opened with something already selected we want this 'selected' + -- to be displayed close to the middle of the menu. That's why 'show_from_to' + -- is not initially set, so we can know - if show_from_to length is 0 - it is + -- first call of this func in cur. init + if #self.list.show_from_to == 0 then + -- set show_from_to so chosen item will be displayed close to middle + local half_list = math.ceil(to_show / 2) + if i < half_list then + self.list.show_from_to = { 1, to_show } + elseif total - i < half_list then + self.list.show_from_to = { total - to_show + 1, total } + else + self.list.show_from_to = { i - half_list + 1, i - half_list + to_show } + end + else + table.unpack = table.unpack or unpack -- 5.1 compatibility + local first, last = table.unpack(self.list.show_from_to) + + -- handle cursor moving towards start / end bondary + if first ~= 1 and i - first < 2 then + self.list.show_from_to = { first - 1, last - 1 } + end + if last ~= total and last - i < 2 then + self.list.show_from_to = { first + 1, last + 1 } + end + + -- handle index jumps from beginning to end and backwards + if i > last then + self.list.show_from_to = { i - to_show + 1, i } + end + if i < first then + self.list.show_from_to = { 1, to_show } + end + end +end + +function em:change_selected_index(num) + self.list.pointer_i = self.list.pointer_i + num + if self.loop_when_navigating then + if self.list.pointer_i < 1 then + self.list.pointer_i = #self:current() + elseif self.list.pointer_i > #self:current() then + self.list.pointer_i = 1 + end + else + if self.list.pointer_i < 1 then + self.list.pointer_i = 1 + elseif self.list.pointer_i > #self:current() then + self.list.pointer_i = #self:current() + end + end + self:set_from_to() + self:update() +end + +-- Render the REPL and console as an ASS OSD +function em:update(err_code) + -- ASS tags documentation here - https://aegi.vmoe.info/docs/3.0/ASS_Tags/ + + -- do not bother if function was called to close the menu.. + if not self.is_active then + em.ass:remove() + return + end + + local line_height = self.font_size + self.line_bottom_margin + local _, h, aspect = mp.get_osd_size() + local wh = self.scale_by_window and 720 or h + local ww = wh * aspect + + -- '+ 1' below is a search string + local menu_y_pos = wh - (line_height * (self.lines_to_show + 1) + self.menu_y_padding * 2) + + -- didn't find better place to handle filtered list update + if self.line ~= self.prev_line then + self:filter_wrapper() + end + + local function get_background() + local a = self:ass_new_wrapper() + a:append("{\\1c&H1c1c1c\\1a&H19}") -- background color & opacity + a:pos(0, 0) + a:draw_start() + a:rect_cw(0, menu_y_pos, ww, wh) + a:draw_stop() + return a.text + end + + local function get_search_header() + local a = self:ass_new_wrapper() + + a:pos(self.menu_x_padding, menu_y_pos + self.menu_y_padding) + + local search_prefix = table.concat({ + self:get_font_color("accent"), + (#self:current() ~= 0 and self.list.pointer_i or "!"), + "/", + #self:current(), + "\\h\\h", + self.search_heading, + ":\\h", + }) + + a:append(search_prefix) + -- reset font color after search prefix + a:append(self:get_font_color("default")) + + -- Create the cursor glyph as an ASS drawing. ASS will draw the cursor + -- inline with the surrounding text, but it sets the advance to the width + -- of the drawing. So the cursor doesn't affect layout too much, make it as + -- thin as possible and make it appear to be 1px wide by giving it 0.5px + -- horizontal borders. + local cheight = self.font_size * 8 + -- TODO: maybe do it using draw_rect from ass? + local cglyph = "{\\r" -- styles reset + .. "\\1c&Hffffff&\\3c&Hffffff" -- font color and border color + .. "\\xbord" + .. self.cursor_x_border + .. "\\p4\\pbo24}" -- xborder, scale x8 and baseline offset + .. "m 0 0 l 0 " + .. cheight -- drawing just a line + .. "{\\p0\\r}" -- finish drawing and reset styles + local before_cur = self:ass_escape(self.line:sub(1, self.cursor - 1)) + local after_cur = self:ass_escape(self.line:sub(self.cursor)) + + a:append(table.concat({ + before_cur, + cglyph, + self:reset_styles(), + self:get_font_color("default"), + after_cur, + (err_code and "\\h" .. self.error_codes[err_code] or ""), + })) + + return a.text + + -- NOTE: perhaps this commented code will some day help me in coding cursor + -- like in M-x emacs menu: + -- Redraw the cursor with the REPL text invisible. This will make the + -- cursor appear in front of the text. + -- ass:new_event() + -- ass:an(1) + -- ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur) + -- ass:append(cglyph) + -- ass:append(style .. '{\\alpha&HFF&}' .. after_cur) + end + + local function get_list() + local a = assdraw.ass_new() + + local function apply_highlighting(y) + a:new_event() + a:append(self:reset_styles()) + a:append("{\\1c&Hffffff\\1a&HE6}") -- background color & opacity + a:pos(0, 0) + a:draw_start() + a:rect_cw(0, y, ww, y + self.font_size) + a:draw_stop() + end + + -- REVIEW: maybe make another function 'get_line_str' and move there + -- everything from this for loop? + -- REVIEW: how to use something like table.unpack below? + for i = self.list.show_from_to[1], self.list.show_from_to[2] do + local value = assert(self:current()[i], "no value with index " .. i) + local y_offset = menu_y_pos + self.menu_y_padding + (line_height * (i - self.list.show_from_to[1] + 1)) + + if i == self.list.pointer_i then + apply_highlighting(y_offset) + end + + a:new_event() + a:append(self:reset_styles()) + a:pos(self.menu_x_padding, y_offset) + a:append(self:get_line(i, value)) + end + + return a.text + end + + em.ass.res_x = ww + em.ass.res_y = wh + em.ass.data = table.concat({ + get_background(), + get_search_header(), + get_list(), + }, "\n") + + em.ass:update() +end + +-- params: +-- - data : {list: {}, [current_i] : num} +function em:init(data) + self.list.full = data.list or {} + self.list.current_i = data.current_i or nil + self.list.pointer_i = data.current_i or 1 + self:set_active(true) +end + +function em:exit() + self:undefine_key_bindings() + collectgarbage() +end + +-- TODO: write some idle func like this +-- function idle() +-- if pending_selection then +-- gallery:set_selection(pending_selection) +-- pending_selection = nil +-- end +-- if ass_changed or geometry_changed then +-- local ww, wh = mp.get_osd_size() +-- if geometry_changed then +-- geometry_changed = false +-- compute_geometry(ww, wh) +-- end +-- if ass_changed then +-- ass_changed = false +-- mp.set_osd_ass(ww, wh, ass) +-- end +-- end +-- end +-- ... +-- and handle it as follows +-- init(): +-- mp.register_idle(idle) +-- idle() +-- exit(): +-- mp.unregister_idle(idle) +-- idle() +-- And in these observers he is setting a flag, that's being checked in func above +-- mp.observe_property("osd-width", "native", mark_geometry_stale) +-- mp.observe_property("osd-height", "native", mark_geometry_stale) + +-- PRIVATE METHODS END -------------------------------------------------------- + +-- PUBLIC METHODS ------------------------------------------------------------- + +function em:filter() + -- default filter func, might be redefined in main script + local result = {} + + local function get_full_search_str(v) + local str = "" + for _, key in ipairs(self.filter_by_fields) do + str = str .. (v[key] or "") + end + return str + end + + for _, v in ipairs(self.list.full) do + -- if filter_by_fields has 0 length, then search list item itself + if #self.filter_by_fields == 0 then + if self:search_method(v) then + table.insert(result, v) + end + else + -- NOTE: we might use search_method on fiels separately like this: + -- for _,key in ipairs(self.filter_by_fields) do + -- if self:search_method(v[key]) then table.insert(result, v) end + -- end + -- But since im planning to implement fuzzy search in future i need full + -- search string here + if self:search_method(get_full_search_str(v)) then + table.insert(result, v) + end + end + end + return result +end + +-- TODO: implement fuzzy search and maybe match highlights +function em:search_method(str) + -- also might be redefined by main script + + -- convert to string just to make sure.. + return tostring(str):lower():find(self.line:lower(), 1, true) +end + +-- this module requires submit function to be defined in main script +function em:submit() + self:update("no_submit_provided") +end + +function em:update_list(list) + -- for now this func doesn't handle cases when we have 'current_i' to update + -- it + self.list.full = list + if self.line ~= self.prev_line then + self:filter_wrapper() + end +end + +-- PUBLIC METHODS END --------------------------------------------------------- + +-- HELPER METHODS ------------------------------------------------------------- + +function em:get_line(_, v) -- [i]ndex, [v]alue + -- this func might be redefined in main script to get a custom-formatted line + -- default implementation of this func supposes that value.content field is a + -- String + local a = assdraw.ass_new() + local style = (self.list.current_i == v[self.index_field]) and "current" or "default" + + a:append(self:reset_styles()) + a:append(self:get_font_color(style)) + -- content as default field, which is holding string + -- no point in moving it to main object since content itself is being + -- composed in THIS function, that might (and most likely, should) be + -- redefined in main script + a:append(v.content or "Something is off in `get_line` func") + return a.text +end + +-- REVIEW: for now i don't see normal way of mergin this func with below one +-- but it's being used only once +function em:reset_styles() + local a = assdraw.ass_new() + -- alignment top left, no word wrapping, border 0, shadow 0 + a:append("{\\an7\\q2\\bord0\\shad0}") + a:append("{\\fs" .. self.font_size .. "}") + return a.text +end + +-- function to get rid of some copypaste +function em:ass_new_wrapper() + local a = assdraw.ass_new() + a:new_event() + a:append(self:reset_styles()) + return a +end + +function em:get_font_color(style) + return "{\\1c&H" .. self.text_color[style] .. "}" +end + +-- HELPER METHODS END --------------------------------------------------------- + +--[[ + The below code is a modified implementation of text input from mpv's console.lua: + https://github.com/mpv-player/mpv/blob/87c9eefb2928252497f6141e847b74ad1158bc61/player/lua/console.lua + + I was too lazy to list all modifications i've done to the script, but if u + rly need to see those - do diff with the original code +]] +-- + +------------------------------------------------------------------------------- +-- START ORIGINAL MPV CODE -- +------------------------------------------------------------------------------- + +-- Copyright (C) 2019 the mpv developers +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +function em:detect_platform() + local o = {} + -- Kind of a dumb way of detecting the platform but whatever + if mp.get_property_native("options/vo-mmcss-profile", o) ~= o then + return "windows" + elseif mp.get_property_native("options/macos-force-dedicated-gpu", o) ~= o then + return "macos" + elseif os.getenv("WAYLAND_DISPLAY") then + return "wayland" + end + return "x11" +end + +-- Escape a string for verbatim display on the OSD +function em:ass_escape(str) + -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if + -- it isn't followed by a recognised character, so add a zero-width + -- non-breaking space + str = str:gsub("\\", "\\\239\187\191") + str = str:gsub("{", "\\{") + str = str:gsub("}", "\\}") + -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of + -- consecutive newlines + str = str:gsub("\n", "\239\187\191\\N") + -- Turn leading spaces into hard spaces to prevent ASS from stripping them + str = str:gsub("\\N ", "\\N\\h") + str = str:gsub("^ ", "\\h") + return str +end + +-- Set the REPL visibility ("enable", Esc) +function em:set_active(active) + if active == self.is_active then + return + end + if active then + self.is_active = true + self.insert_mode = false + mp.enable_messages("terminal-default") + self:define_key_bindings() + + -- set flag 'was_paused' only if vid wasn't paused before EM init + if self.pause_on_open and not mp.get_property_bool("pause", false) then + mp.set_property_bool("pause", true) + self.was_paused = true + end + + self:set_from_to() + self:update() + else + -- no need to call 'update' in this block cuz 'clear' method is calling it + self.is_active = false + self:undefine_key_bindings() + + if self.resume_on_exit == true or (self.resume_on_exit == "only-if-was-paused" and self.was_paused) then + mp.set_property_bool("pause", false) + end + + self:clear() + collectgarbage() + end +end + +-- Naive helper function to find the next UTF-8 character in 'str' after 'pos' +-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8. +function em:next_utf8(str, pos) + if pos > str:len() then + return pos + end + repeat + pos = pos + 1 + until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf + return pos +end + +-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos' +function em:prev_utf8(str, pos) + if pos <= 1 then + return pos + end + repeat + pos = pos - 1 + until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf + return pos +end + +-- Insert a character at the current cursor position (any_unicode) +function em:handle_char_input(c) + if self.insert_mode then + self.line = self.line:sub(1, self.cursor - 1) .. c .. self.line:sub(self:next_utf8(self.line, self.cursor)) + else + self.line = self.line:sub(1, self.cursor - 1) .. c .. self.line:sub(self.cursor) + end + self.cursor = self.cursor + #c + self:update() +end + +-- Remove the character behind the cursor (Backspace) +function em:handle_backspace() + if self.cursor <= 1 then + return + end + local prev = self:prev_utf8(self.line, self.cursor) + self.line = self.line:sub(1, prev - 1) .. self.line:sub(self.cursor) + self.cursor = prev + self:update() +end + +-- Remove the character in front of the cursor (Del) +function em:handle_del() + if self.cursor > self.line:len() then + return + end + self.line = self.line:sub(1, self.cursor - 1) .. self.line:sub(self:next_utf8(self.line, self.cursor)) + self:update() +end + +-- Toggle insert mode (Ins) +function em:handle_ins() + self.insert_mode = not self.insert_mode +end + +-- Move the cursor to the next character (Right) +function em:next_char() + self.cursor = self:next_utf8(self.line, self.cursor) + self:update() +end + +-- Move the cursor to the previous character (Left) +function em:prev_char() + self.cursor = self:prev_utf8(self.line, self.cursor) + self:update() +end + +-- Clear the current line (Ctrl+C) +function em:clear() + self.line = "" + self.prev_line = "" + + self.list.current_i = nil + self.list.pointer_i = 1 + self.list.filtered = {} + self.list.show_from_to = {} + + self.was_paused = false + + self.cursor = 1 + self.insert_mode = false + self.history_pos = #self.history + 1 + + self:update() +end + +-- Run the current command and clear the line (Enter) +function em:handle_enter() + if #self:current() == 0 then + self:update("no_match") + return + end + + if self.history[#self.history] ~= self.line then + self.history[#self.history + 1] = self.line + end + + self:submit(self:current()[self.list.pointer_i]) + self:set_active(false) +end + +-- Go to the specified position in the command history +function em:go_history(new_pos) + local old_pos = self.history_pos + self.history_pos = new_pos + + -- Restrict the position to a legal value + if self.history_pos > #self.history + 1 then + self.history_pos = #self.history + 1 + elseif self.history_pos < 1 then + self.history_pos = 1 + end + + -- Do nothing if the history position didn't actually change + if self.history_pos == old_pos then + return + end + + -- If the user was editing a non-history line, save it as the last history + -- entry. This makes it much less frustrating to accidentally hit Up/Down + -- while editing a line. + if old_pos == #self.history + 1 and self.line ~= "" and self.history[#self.history] ~= self.line then + self.history[#self.history + 1] = self.line + end + + -- Now show the history line (or a blank line for #history + 1) + if self.history_pos <= #self.history then + self.line = self.history[self.history_pos] + else + self.line = "" + end + self.cursor = self.line:len() + 1 + self.insert_mode = false + self:update() +end + +-- Go to the specified relative position in the command history (Up, Down) +function em:move_history(amount) + self:go_history(self.history_pos + amount) +end + +-- Go to the first command in the command history (PgUp) +function em:handle_pgup() + -- Determine the number of items to move up (half a page) + local half_page = math.ceil(self.lines_to_show / 2) + + -- Move the history position up by half a page + self:change_selected_index(-half_page) +end + +-- Stop browsing history and start editing a blank line (PgDown) +function em:handle_pgdown() + -- Determine the number of items to move down (half a page) + local half_page = math.ceil(self.lines_to_show / 2) + + -- Move the history position down by half a page + self:change_selected_index(half_page) +end + +-- Move to the start of the current word, or if already at the start, the start +-- of the previous word. (Ctrl+Left) +function em:prev_word() + -- This is basically the same as next_word() but backwards, so reverse the + -- string in order to do a "backwards" find. This wouldn't be as annoying + -- to do if Lua didn't insist on 1-based indexing. + self.cursor = self.line:len() + - select(2, self.line:reverse():find("%s*[^%s]*", self.line:len() - self.cursor + 2)) + + 1 + self:update() +end + +-- Move to the end of the current word, or if already at the end, the end of +-- the next word. (Ctrl+Right) +function em:next_word() + self.cursor = select(2, self.line:find("%s*[^%s]*", self.cursor)) + 1 + self:update() +end + +-- Move the cursor to the beginning of the line (HOME) +function em:go_home() + self.cursor = 1 + self:update() +end + +-- Move the cursor to the end of the line (END) +function em:go_end() + self.cursor = self.line:len() + 1 + self:update() +end + +-- Delete from the cursor to the beginning of the word (Ctrl+Backspace) +function em:del_word() + local before_cur = self.line:sub(1, self.cursor - 1) + local after_cur = self.line:sub(self.cursor) + + before_cur = before_cur:gsub("[^%s]+%s*$", "", 1) + self.line = before_cur .. after_cur + self.cursor = before_cur:len() + 1 + self:update() +end + +-- Delete from the cursor to the end of the word (Ctrl+Del) +function em:del_next_word() + if self.cursor > self.line:len() then + return + end + + local before_cur = self.line:sub(1, self.cursor - 1) + local after_cur = self.line:sub(self.cursor) + + after_cur = after_cur:gsub("^%s*[^%s]+", "", 1) + self.line = before_cur .. after_cur + self:update() +end + +-- Delete from the cursor to the end of the line (Ctrl+K) +function em:del_to_eol() + self.line = self.line:sub(1, self.cursor - 1) + self:update() +end + +-- Delete from the cursor back to the start of the line (Ctrl+U) +function em:del_to_start() + self.line = self.line:sub(self.cursor) + self.cursor = 1 + self:update() +end + +-- Returns a string of UTF-8 text from the clipboard (or the primary selection) +function em:get_clipboard(clip) + -- Pick a better default font for Windows and macOS + local platform = self:detect_platform() + + if platform == "x11" then + local res = utils.subprocess({ + args = { "xclip", "-selection", clip and "clipboard" or "primary", "-out" }, + playback_only = false, + }) + if not res.error then + return res.stdout + end + elseif platform == "wayland" then + local res = utils.subprocess({ + args = { "wl-paste", clip and "-n" or "-np" }, + playback_only = false, + }) + if not res.error then + return res.stdout + end + elseif platform == "windows" then + local res = utils.subprocess({ + args = { + "powershell", + "-NoProfile", + "-Command", + [[& { + Trap { + Write-Error -ErrorRecord $_ + Exit 1 + } + + $clip = "" + if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) { + $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText + } else { + Add-Type -AssemblyName PresentationCore + $clip = [Windows.Clipboard]::GetText() + } + + $clip = $clip -Replace "`r","" + $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip) + [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length) + }]], + }, + playback_only = false, + }) + if not res.error then + return res.stdout + end + elseif platform == "macos" then + local res = utils.subprocess({ + args = { "pbpaste" }, + playback_only = false, + }) + if not res.error then + return res.stdout + end + end + return "" +end + +-- Paste text from the window-system's clipboard. 'clip' determines whether the +-- clipboard or the primary selection buffer is used (on X11 and Wayland only.) +function em:paste(clip) + local text = self:get_clipboard(clip) + local before_cur = self.line:sub(1, self.cursor - 1) + local after_cur = self.line:sub(self.cursor) + self.line = before_cur .. text .. after_cur + self.cursor = self.cursor + text:len() + self:update() +end + +-- List of input bindings. This is a weird mashup between common GUI text-input +-- bindings and readline bindings. +function em:get_bindings() + local bindings = { + { + "ctrl+[", + function() + self:set_active(false) + end, + }, + { + "ctrl+g", + function() + self:set_active(false) + end, + }, + { + "esc", + function() + self:set_active(false) + end, + }, + { + "enter", + function() + self:handle_enter() + end, + }, + { + "kp_enter", + function() + self:handle_enter() + end, + }, + { + "ctrl+m", + function() + self:handle_enter() + end, + }, + { + "bs", + function() + self:handle_backspace() + end, + }, + { + "shift+bs", + function() + self:handle_backspace() + end, + }, + { + "ctrl+h", + function() + self:handle_backspace() + end, + }, + { + "del", + function() + self:handle_del() + end, + }, + { + "shift+del", + function() + self:handle_del() + end, + }, + { + "ins", + function() + self:handle_ins() + end, + }, + { + "shift+ins", + function() + self:paste(false) + end, + }, + { + "mbtn_mid", + function() + self:paste(false) + end, + }, + { + "left", + function() + self:prev_char() + end, + }, + { + "ctrl+b", + function() + self:prev_char() + end, + }, + { + "right", + function() + self:next_char() + end, + }, + { + "ctrl+f", + function() + self:next_char() + end, + }, + { + "ctrl+k", + function() + self:change_selected_index(-1) + end, + }, + { + "ctrl+p", + function() + self:change_selected_index(-1) + end, + }, + { + "ctrl+j", + function() + self:change_selected_index(1) + end, + }, + { + "ctrl+n", + function() + self:change_selected_index(1) + end, + }, + { + "up", + function() + self:move_history(-1) + end, + }, + { + "alt+p", + function() + self:move_history(-1) + end, + }, + { + "wheel_up", + function() + self:move_history(-1) + end, + }, + { + "down", + function() + self:move_history(1) + end, + }, + { + "alt+n", + function() + self:move_history(1) + end, + }, + { + "wheel_down", + function() + self:move_history(1) + end, + }, + { "wheel_left", function() end }, + { "wheel_right", function() end }, + { + "ctrl+left", + function() + self:prev_word() + end, + }, + { + "alt+b", + function() + self:prev_word() + end, + }, + { + "ctrl+right", + function() + self:next_word() + end, + }, + { + "alt+f", + function() + self:next_word() + end, + }, + { + "ctrl+a", + function() + self:go_home() + end, + }, + { + "home", + function() + self:go_home() + end, + }, + { + "ctrl+e", + function() + self:go_end() + end, + }, + { + "end", + function() + self:go_end() + end, + }, + { + "ctrl+shift+f", + function() + self:handle_pgdown() + end, + }, + { + "ctrl+shift+b", + function() + self:handle_pgup() + end, + }, + { + "pgdwn", + function() + self:handle_pgdown() + end, + }, + { + "pgup", + function() + self:handle_pgup() + end, + }, + { + "ctrl+c", + function() + self:clear() + end, + }, + { + "ctrl+d", + function() + self:handle_del() + end, + }, + { + "ctrl+u", + function() + self:del_to_start() + end, + }, + { + "ctrl+v", + function() + self:paste(true) + end, + }, + { + "meta+v", + function() + self:paste(true) + end, + }, + { + "ctrl+bs", + function() + self:del_word() + end, + }, + { + "ctrl+w", + function() + self:del_word() + end, + }, + { + "ctrl+del", + function() + self:del_next_word() + end, + }, + { + "alt+d", + function() + self:del_next_word() + end, + }, + { + "kp_dec", + function() + self:handle_char_input(".") + end, + }, + } + + for i = 0, 9 do + bindings[#bindings + 1] = { + "kp" .. i, + function() + self:handle_char_input("" .. i) + end, + } + end + + return bindings +end + +function em:text_input(info) + if info.key_text and (info.event == "press" or info.event == "down" or info.event == "repeat") then + self:handle_char_input(info.key_text) + end +end + +function em:define_key_bindings() + if #self.key_bindings > 0 then + return + end + for _, bind in ipairs(self:get_bindings()) do + -- Generate arbitrary name for removing the bindings later. + local name = "search_" .. (#self.key_bindings + 1) + self.key_bindings[#self.key_bindings + 1] = name + mp.add_forced_key_binding(bind[1], name, bind[2], { repeatable = true }) + end + mp.add_forced_key_binding("any_unicode", "search_input", function(...) + self:text_input(...) + end, { repeatable = true, complex = true }) + self.key_bindings[#self.key_bindings + 1] = "search_input" +end + +function em:undefine_key_bindings() + for _, name in ipairs(self.key_bindings) do + mp.remove_key_binding(name) + end + self.key_bindings = {} +end + +------------------------------------------------------------------------------- +-- END ORIGINAL MPV CODE -- +------------------------------------------------------------------------------- + +return em |
