diff --git a/CMakeLists.txt b/CMakeLists.txt index e37611a..6a2d9e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,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 .) diff --git a/src/lua/dyn_menu.lua b/src/lua/dyn_menu.lua index 6ceb403..0bfda4d 100644 --- a/src/lua/dyn_menu.lua +++ b/src/lua/dyn_menu.lua @@ -12,12 +12,12 @@ local o = { } 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) ------------------------------------------------------------------------ @@ -501,15 +501,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 mp.register_script_message('get', function(keyword, src) if not src or src == '' then @@ -570,13 +561,100 @@ mp.register_idle(function() end end) --- parse menu data when menu items ready --- --- 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 +-- 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 then title = cmd:match('#!%s*(.*)%s*') end + return title or '' + end + + local function split_title(title, ch) + local list = {} + if not title or title == '' then return list end + title = title:match('(.-)#%s*.-%s*$') or title -- strip trailing comment + + 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 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 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 + +-- NOTE: this script is 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 diff --git a/src/menu.c b/src/menu.c index ba810e0..b4344d6 100644 --- a/src/menu.c +++ b/src/menu.c @@ -5,9 +5,6 @@ #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); @@ -145,108 +142,6 @@ 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; diff --git a/src/menu.h b/src/menu.h index 80182d5..2fc7802 100644 --- a/src/menu.h +++ b/src/menu.h @@ -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); diff --git a/src/plugin.c b/src/plugin.c index 81d0c02..18c202f 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -99,30 +99,6 @@ static void set_clipboard(const char *text) { CloseClipboard(); } -// read and parse config file -static void read_conf(plugin_config *conf, const char *name) { - void *tmp = talloc_new(NULL); - char *path = talloc_asprintf(tmp, "~~/script-opts/%s.conf", name); - bstr data = bstr0(mp_read_file(tmp, path)); - - while (data.len) { - bstr line = bstr_strip_linebreaks(bstr_getline(data, &data)); - line = bstr_lstrip(line); - if (line.len == 0 || bstr_startswith0(line, "#")) continue; - - bstr name, value; - if (!bstr_split_tok(line, "=", &name, &value)) continue; - name = bstr_strip(name); - value = bstr_strip(value); - if (name.len == 0 || value.len == 0) continue; - - if (bstr_equals0(name, "load")) conf->load = bstr_equals0(value, "yes"); - if (bstr_equals0(name, "uosc")) conf->uosc = bstr_equals0(value, "yes"); - } - - talloc_free(tmp); -} - // create HMENU and register window procedure static void plugin_register(int64_t wid) { ctx->hmenu = CreatePopupMenu(); @@ -226,10 +202,6 @@ static void create_plugin_ctx(mpv_handle *mpv) { ctx = talloc_zero(NULL, plugin_ctx); ctx->mpv = mpv; - ctx->conf = talloc_zero(ctx, plugin_config); - ctx->conf->load = true; - read_conf(ctx->conf, mpv_client_name(mpv)); - ctx->dispatch = mp_dispatch_create(ctx); ctx->node = talloc_zero(ctx, mpv_node); } @@ -248,11 +220,6 @@ MPV_EXPORT int mpv_open_cplugin(mpv_handle *handle) { create_plugin_ctx(handle); - if (ctx->conf->load) { - load_menu(ctx->node, ctx->conf); - mpv_set_property(handle, MENU_DATA_PROP, MPV_FORMAT_NODE, ctx->node); - } - mpv_observe_property(handle, 0, "window-id", MPV_FORMAT_INT64); mpv_observe_property(handle, 0, MENU_DATA_PROP, MPV_FORMAT_NODE); @@ -308,17 +275,6 @@ char *mp_get_prop_string(void *talloc_ctx, const char *prop) { return ret; } -// wrapper of expand-path command -char *mp_expand_path(void *talloc_ctx, char *path) { - mpv_node node; - const char *args[] = {"expand-path", path, NULL}; - if (mpv_command_ret(ctx->mpv, args, &node) >= 0) { - path = talloc_strdup(talloc_ctx, node.u.string); - mpv_free_node_contents(&node); - } - return path; -} - static void async_cmd_fn(void *data) { mpv_command_string(ctx->mpv, (const char *)data); } @@ -328,27 +284,3 @@ void mp_command_async(const char *args) { mp_dispatch_enqueue(ctx->dispatch, async_cmd_fn, (void *)args); mpv_wakeup(ctx->mpv); } - -// read file from disk or memory:// -char *mp_read_file(void *talloc_ctx, char *path) { - if (bstr_startswith0(bstr0(path), "memory://")) - return talloc_strdup(talloc_ctx, path + strlen("memory://")); - - void *tmp = talloc_new(NULL); - char *path_m = mp_expand_path(tmp, path); - wchar_t *path_w = mp_from_utf8(tmp, path_m); - HANDLE hFile = CreateFileW(path_w, GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - talloc_free(tmp); - if (hFile == INVALID_HANDLE_VALUE) return NULL; - - DWORD dwFileSize = GetFileSize(hFile, NULL); - char *ret = talloc_array(talloc_ctx, char, dwFileSize + 1); - DWORD dwRead; - - ReadFile(hFile, ret, dwFileSize, &dwRead, NULL); - ret[dwFileSize] = '\0'; - CloseHandle(hFile); - - return ret; -} diff --git a/src/plugin.h b/src/plugin.h index ee398a8..7bad5a9 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -10,12 +10,6 @@ #include "misc/dispatch.h" typedef struct { - bool load; // load menu on startup, default: yes - bool uosc; // use uosc menu syntax, default: no -} plugin_config; - -typedef struct { - plugin_config *conf; // plugin config struct mp_dispatch_queue *dispatch; // dispatch queue mpv_handle *mpv; // mpv client handle @@ -30,8 +24,6 @@ typedef struct { wchar_t *mp_from_utf8(void *talloc_ctx, const char *s); char *mp_to_utf8(void *talloc_ctx, const wchar_t *s); char *mp_get_prop_string(void *talloc_ctx, const char *prop); -char *mp_expand_path(void *talloc_ctx, char *path); -char *mp_read_file(void *talloc_ctx, char *path); void mp_command_async(const char *args); #endif \ No newline at end of file