Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

split menu load logic to lua #50

Merged
merged 1 commit into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ set(CMAKE_SHARED_LIBRARY_PREFIX "")
add_library(menu SHARED
src/mpv/misc/bstr.c
src/mpv/misc/dispatch.c
src/mpv/misc/node.c
src/mpv/ta/ta.c
src/mpv/ta/ta_talloc.c
src/mpv/ta/ta_utils.c
Expand All @@ -31,7 +30,6 @@ add_library(menu SHARED
set_property(TARGET menu PROPERTY POSITION_INDEPENDENT_CODE ON)

target_include_directories(menu PRIVATE src/mpv ${MPV_INCLUDE_DIRS})
target_link_libraries(menu PRIVATE shlwapi)
target_compile_definitions(menu PRIVATE MPV_CPLUGIN_DYNAMIC_SYM)

install(TARGETS menu RUNTIME DESTINATION .)
Expand Down
126 changes: 104 additions & 22 deletions src/lua/dyn_menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ local msg = require('mp.msg')

-- user options
local o = {
uosc_syntax = false, -- toggle uosc menu syntax support
max_title_length = 80, -- limit the title length, set to 0 to disable.
max_playlist_items = 20, -- limit the playlist items in submenu, set to 0 to disable.
}
opts.read_options(o)

local menu_prop = 'user-data/menu/items' -- menu data property
local menu_items = mp.get_property_native(menu_prop, {}) -- raw menu data
local menu_items_dirty = false -- menu data dirty flag
local dyn_menus = {} -- dynamic menu list
local keyword_to_menu = {} -- keyword -> menu
local has_uosc = false -- uosc installed flag
local menu_prop = 'user-data/menu/items' -- menu data property
local menu_items = {} -- raw menu data
local menu_items_dirty = false -- menu data dirty flag
local dyn_menus = {} -- dynamic menu list
local keyword_to_menu = {} -- keyword -> menu
local has_uosc = false -- uosc installed flag

-- lua expression compiler (copied from mpv auto_profiles.lua)
------------------------------------------------------------------------
Expand Down Expand Up @@ -501,15 +502,6 @@ local function load_dyn_menus()
mp.commandv('script-message', 'menu-ready')
end

-- menu data update callback
local function menu_data_cb(name, items)
if not items or #items == 0 then return end
mp.unobserve_property(menu_data_cb)

menu_items = items
load_dyn_menus()
end

-- script message: get <keyword> <src>
mp.register_script_message('get', function(keyword, src)
if not src or src == '' then
Expand Down Expand Up @@ -570,13 +562,103 @@ mp.register_idle(function()
end
end)

-- parse menu data when menu items ready
-- read input.conf content
local function get_input_conf()
local prop = mp.get_property_native('input-conf')
if prop:sub(1, 9) == 'memory://' then return prop:sub(10) end

prop = prop == '' and '~~/input.conf' or prop
local conf_path = mp.command_native({ 'expand-path', prop })

local f, err = io.open(conf_path, 'rb')
if not f then
msg.error('failed to open file: ' .. conf_path)
return nil
end

local conf = f:read('*all')
f:close()
return conf
end

-- parse input.conf, return menu items
local function parse_input_conf(conf)
local items = {}
local by_id = {}

local function extract_title(cmd)
if not cmd or cmd == '' then return '' end
local title = cmd:match('#menu:%s*(.*)%s*')
if not title and o.uosc_syntax then title = cmd:match('#!%s*(.*)%s*') end
if title then title = title:match('(.-)%s+#.*$') or title end
return title or ''
end

local function split_title(title)
local list = {}
if not title or title == '' then return list end

local pattern = '(.-)%s*>%s*'
local last_ends = 1
local starts, ends, match = title:find(pattern)
while starts do
list[#list + 1] = match
last_ends = ends + 1
starts, ends, match = title:find(pattern, last_ends)
end
if last_ends < (#title + 1) then list[#list + 1] = title:sub(last_ends) end

return list
end

for line in conf:gmatch('[^\r\n]+') do
if line:sub(1, 1) ~= '#' or o.uosc_syntax then
local key, cmd = line:match('%s*([%S]+)%s+(.-)%s*$')
local title = extract_title(cmd)
local list = split_title(title)

local submenu_id = ''
local target_menu = items

for id, name in ipairs(list) do
if id < #list then
submenu_id = submenu_id .. name
if not by_id[submenu_id] then
local submenu = {}
by_id[submenu_id] = submenu
target_menu[#target_menu + 1] = {
title = name,
type = 'submenu',
submenu = submenu,
}
end
target_menu = by_id[submenu_id]
else
if name == '-' or (o.uosc_syntax and name:sub(1, 3) == '---') then
target_menu[#target_menu + 1] = {
type = 'separator',
}
else
target_menu[#target_menu + 1] = {
title = (key ~= '' and key ~= '_' and key ~= '#') and (name .. "\t" .. key) or name,
cmd = cmd,
}
end
end
end
end
end

return items
end

-- load menu data from input.conf
--
-- NOTE: to simplify the code, we only procss the first valid update
-- event and ignore the rest, this make it conflict with other
-- scripts that also update the menu data property.
if #menu_items > 0 then
-- NOTE: to simplify the code, we don't watch for the menu data change event, this
-- make it conflict with other scripts that also update the menu data property.
local conf = get_input_conf()
if conf then
menu_items = parse_input_conf(conf)
menu_items_dirty = true
load_dyn_menus()
else
mp.observe_property(menu_prop, 'native', menu_data_cb)
end
120 changes: 5 additions & 115 deletions src/menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-only

#include "misc/bstr.h"
#include "misc/node.h"
#include "menu.h"

#define MENU_PREFIX "#menu:"
#define MENU_PREFIX_UOSC "#!"

// escape & to && for menu title
static wchar_t *escape_title(void *talloc_ctx, char *title) {
void *tmp = talloc_new(NULL);
Expand Down Expand Up @@ -145,120 +141,14 @@ void build_menu(void *talloc_ctx, HMENU hmenu, mpv_node *node) {
}
}

static bool is_separator(bstr text, bool uosc) {
return bstr_equals0(text, "-") || (uosc && bstr_startswith0(text, "---"));
}

// return submenu node if exists, otherwise create a new one
static mpv_node *add_submenu(mpv_node *list, char *title) {
for (int i = 0; i < list->u.list->num; i++) {
mpv_node *item = &list->u.list->values[i];

mpv_node *type_node = node_map_get(item, "type");
if (!type_node || strcmp(type_node->u.string, "submenu") != 0) continue;

mpv_node *title_node = node_map_get(item, "title");
if (title_node && strcmp(title_node->u.string, title) == 0) {
mpv_node *submenu = node_map_get(item, "submenu");
if (!submenu)
submenu = node_map_add(item, "submenu", MPV_FORMAT_NODE_ARRAY);
return submenu;
}
}

mpv_node *node = node_array_add(list, MPV_FORMAT_NODE_MAP);
node_map_add_string(node, "title", title);
node_map_add_string(node, "type", "submenu");
return node_map_add(node, "submenu", MPV_FORMAT_NODE_ARRAY);
}

// parse menu line and add to menu node
static void parse_menu(mpv_node *list, bstr key, bstr cmd, bstr text,
bool uosc) {
bstr name, rest, comment;

name = bstr_split(text, ">", &rest);
name = bstr_split(name, "#", &comment);
name = bstr_strip(name);
if (!name.len) return;

void *tmp = talloc_new(NULL);

if (!rest.len) {
if (is_separator(name, uosc)) {
mpv_node *node = node_array_add(list, MPV_FORMAT_NODE_MAP);
node_map_add_string(node, "type", "separator");
} else {
bstr title = bstrdup(tmp, name);
if (key.len > 0 && !bstr_equals0(key, "_")) {
bstr_xappend(tmp, &title, bstr0("\t"));
bstr_xappend(tmp, &title, key);
}
mpv_node *node = node_array_add(list, MPV_FORMAT_NODE_MAP);
node_map_add_string(node, "title", bstrdup0(tmp, title));
node_map_add_string(node, "cmd", bstrdup0(tmp, cmd));
}
} else {
mpv_node *sub = add_submenu(list, bstrdup0(tmp, name));
if (!comment.len) parse_menu(sub, key, cmd, rest, uosc);
}

talloc_free(tmp);
}

static bool split_menu(bstr line, bstr *left, bstr *right, bool uosc) {
if (!line.len) return false;
if (!bstr_split_tok(line, MENU_PREFIX, left, right)) {
if (!uosc || !bstr_split_tok(line, MENU_PREFIX_UOSC, left, right))
return false;
}
*left = bstr_strip(*left);
*right = bstr_strip(*right);
return right->len > 0;
}

// build menu node from input.conf
void load_menu(mpv_node *node, plugin_config *conf) {
void *tmp = talloc_new(NULL);
char *path = mp_get_prop_string(tmp, "input-conf");
if (path == NULL || strlen(path) == 0) path = "~~/input.conf";

bstr data = bstr0(mp_read_file(tmp, path));
node_init(node, MPV_FORMAT_NODE_ARRAY, NULL);

while (data.len > 0) {
bstr line = bstr_strip_linebreaks(bstr_getline(data, &data));
line = bstr_lstrip(line);
if (!line.len) continue;

bstr key, cmd, left, right;
if (bstr_eatstart0(&line, "#")) {
if (!conf->uosc) continue;
key = bstr0(NULL);
cmd = bstr_strip(line);
} else {
key = bstr_split(line, WHITESPACE, &cmd);
cmd = bstr_strip(cmd);
}
if (split_menu(cmd, &left, &right, conf->uosc))
parse_menu(node, key, cmd, right, conf->uosc);
}

talloc_free(tmp);
}

// update HMENU if menu node changed
void update_menu(plugin_ctx *ctx, mpv_node *node) {
if (equal_mpv_node(ctx->node, node)) return;

copy_mpv_node(ctx->node, node);
if (!ctx->hmenu) return;

if (ctx->hmenu) {
while (GetMenuItemCount(ctx->hmenu) > 0)
RemoveMenu(ctx->hmenu, 0, MF_BYPOSITION);
talloc_free_children(ctx->hmenu_ctx);
build_menu(ctx->hmenu_ctx, ctx->hmenu, ctx->node);
}
while (GetMenuItemCount(ctx->hmenu) > 0)
RemoveMenu(ctx->hmenu, 0, MF_BYPOSITION);
talloc_free_children(ctx->hmenu_ctx);
build_menu(ctx->hmenu_ctx, ctx->hmenu, node);
}

// show menu at position if it is in window
Expand Down
1 change: 0 additions & 1 deletion src/menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

#define MENU_DATA_PROP "user-data/menu/items"

void load_menu(mpv_node *node, plugin_config *conf);
void build_menu(void *talloc_ctx, HMENU hmenu, mpv_node *node);
void update_menu(plugin_ctx *ctx, mpv_node *node);
void show_menu(plugin_ctx *ctx, POINT *pt);
Expand Down
1 change: 1 addition & 0 deletions src/mpv/misc/dispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

typedef void (*mp_dispatch_fn)(void *data);
struct mp_dispatch_queue;
typedef struct mp_dispatch_queue mp_dispatch_queue;

struct mp_dispatch_queue *mp_dispatch_create(void *talloc_parent);
void mp_dispatch_set_wakeup_fn(struct mp_dispatch_queue *queue,
Expand Down
Loading
Loading