From 0203c63b4c9cc36e74492b3f1d2d7b437582787e Mon Sep 17 00:00:00 2001 From: Shuanglei Tao Date: Sun, 17 Dec 2023 12:47:48 +0800 Subject: [PATCH] dynamic menu support --- README.md | 24 +++- src/menu.c | 339 ++++++++++++++++++++++++++++++++++++++---------- src/misc/bstr.c | 7 + src/misc/bstr.h | 1 + src/plugin.c | 188 ++++++++++++++++++++++++++- src/plugin.h | 58 ++++++++- 6 files changed, 540 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 529fb53..c97f2a2 100644 --- a/README.md +++ b/README.md @@ -23,21 +23,39 @@ Place `menu.dll` in your mpv `scripts` folder. ### input.conf -The menu syntax is similar to [mpv.net](https://github.com/mpvnet-player/mpv.net): +The menu syntax is similar to [mpv.net](https://github.com/mpvnet-player/mpv.net), with some enhancements: - define menu title after `#menu:` - define separator with `-` - split title with `>` to define submenus - - everything after a `#` is considered a comment + - use `#@keyword` to display selection menu for: + - `#@tracks/video`: video tracks + - `#@tracks/audio`: audio tracks + - `#@tracks/sub`: subtitles + - `#@tracks/sub-secondary`: secondary subtitle + - `#@chapters`: chapters + - `#@editions`: editions + - `#@audio-devices`: audio devices - use `_` if no keybinding - use `ignore` if no command ``` Ctrl+a show-text foobar #menu: Foo > Bar _ ignore #menu: - + +_ ignore #menu: Tracks > Video #@tracks/video +_ ignore #menu: Tracks > Audio #@tracks/audio +_ ignore #menu: - +_ ignore #menu: Subtitle #@tracks/sub +_ ignore #menu: Second Subtitle #@tracks/sub-secondary +_ ignore #menu: - +_ ignore #menu: Chapters #@chapters +_ ignore #menu: Editions #@editions +_ ignore #menu: - +_ ignore #menu: Audio Devices #@audio-devices ``` -Add a keybinding that trigger the menu: +Add a keybinding to trigger the menu (required): ``` MBTN_RIGHT script-message-to menu show diff --git a/src/menu.c b/src/menu.c index 947f7b9..e7a5098 100644 --- a/src/menu.c +++ b/src/menu.c @@ -6,13 +6,67 @@ #define MENU_PREFIX "#menu:" #define MENU_PREFIX_UOSC "#!" +#define MENU_PREFIX_DYN "#@" -struct item_data { - char *key; - char *cmd; +typedef struct dyn_menu { + HMENU hmenu; // submenu handle + UINT id; // menu command id + void *talloc_ctx; // talloc context + bool (*update)(struct plugin_ctx *ctx, struct dyn_menu *item); +} dyn_menu; + +typedef struct dyn_menu_list { + struct dyn_menu *entries; + int num_entries; +} dyn_menu_list; + +typedef struct dyn_provider { + char *keyword; // keyword in menu title + bool (*update)(struct plugin_ctx *ctx, struct dyn_menu *item); +} dyn_provider; + +// forward declarations for dynamic menu update functions +static bool update_video_track_menu(plugin_ctx *ctx, dyn_menu *item); +static bool update_audio_track_menu(plugin_ctx *ctx, dyn_menu *item); +static bool update_sub_track_menu(plugin_ctx *ctx, dyn_menu *item); +static bool update_second_sub_track_menu(plugin_ctx *ctx, dyn_menu *item); +static bool update_chapter_menu(plugin_ctx *ctx, dyn_menu *item); +static bool update_edition_menu(plugin_ctx *ctx, dyn_menu *item); +static bool update_audio_device_menu(plugin_ctx *ctx, dyn_menu *item); + +// dynamic menu providers +static const dyn_provider dyn_providers[] = { + {"tracks/video", update_video_track_menu}, + {"tracks/audio", update_audio_track_menu}, + {"tracks/sub", update_sub_track_menu}, + {"tracks/sub-secondary", update_second_sub_track_menu}, + {"chapters", update_chapter_menu}, + {"editions", update_edition_menu}, + {"audio-devices", update_audio_device_menu}, }; -static HMENU find_submenu(HMENU hmenu, wchar_t *name) { +// dynamic menu list +static dyn_menu_list *dyn_menus = NULL; + +static bool add_dyn_menu(void *talloc_ctx, HMENU hmenu, int id, bstr keyword) { + for (int i = 0; i < ARRAYSIZE(dyn_providers); i++) { + dyn_provider provider = dyn_providers[i]; + if (!bstr_equals0(keyword, provider.keyword)) continue; + + dyn_menu item = { + .hmenu = hmenu, + .id = id, + .talloc_ctx = talloc_new(talloc_ctx), + .update = provider.update, + }; + MP_TARRAY_APPEND(talloc_ctx, dyn_menus->entries, dyn_menus->num_entries, + item); + return true; + } + return false; +} + +static HMENU find_submenu(HMENU hmenu, wchar_t *name, UINT *id) { MENUITEMINFOW mii; int count = GetMenuItemCount(hmenu); @@ -25,45 +79,53 @@ static HMENU find_submenu(HMENU hmenu, wchar_t *name) { mii.cch++; wchar_t buf[mii.cch]; mii.dwTypeData = buf; - mii.fMask |= MIIM_SUBMENU; + mii.fMask |= MIIM_ID | MIIM_SUBMENU; if (!GetMenuItemInfoW(hmenu, i, TRUE, &mii) || !mii.hSubMenu) continue; - if (wcscmp(mii.dwTypeData, name) == 0) return mii.hSubMenu; + if (wcscmp(mii.dwTypeData, name) == 0) { + if (id) *id = mii.wID; + return mii.hSubMenu; + } } return NULL; } // escape & to && for menu title static wchar_t *escape_title(void *talloc_ctx, bstr title) { + void *tmp = talloc_new(NULL); bstr left, rest; bstr escaped = bstr0(NULL); + left = bstr_split(title, "&", &rest); while (rest.len > 0) { - bstr_xappend(NULL, &escaped, left); - bstr_xappend(NULL, &escaped, bstr0("&&")); + bstr_xappend(tmp, &escaped, left); + bstr_xappend(tmp, &escaped, bstr0("&&")); left = bstr_split(rest, "&", &rest); } - bstr_xappend(NULL, &escaped, left); - wchar_t *ret = mp_from_utf8(talloc_ctx, bstrdup0(escaped.start, escaped)); - talloc_free(escaped.start); + bstr_xappend(tmp, &escaped, left); + + wchar_t *ret = mp_from_utf8(talloc_ctx, bstrdup0(tmp, escaped)); + talloc_free(tmp); return ret; } // format title as name\tkey static wchar_t *format_title(void *talloc_ctx, bstr name, bstr key) { - bstr title = bstrdup(NULL, name); + void *tmp = talloc_new(NULL); + bstr title = bstrdup(tmp, name); + if (key.len > 0 && !bstr_equals0(key, "_")) { - bstr_xappend(NULL, &title, bstr0("\t")); - bstr_xappend(NULL, &title, key); + bstr_xappend(tmp, &title, bstr0("\t")); + bstr_xappend(tmp, &title, key); } + wchar_t *ret = escape_title(talloc_ctx, title); - talloc_free(title.start); + talloc_free(tmp); return ret; } -// return submenu if it is, otherwise NULL -static HMENU append_menu(HMENU hmenu, UINT fMask, UINT fType, wchar_t *title, - void *data) { - static UINT id = 0; +static int append_menu(HMENU hmenu, UINT fMask, UINT fType, UINT fState, + wchar_t *title, HMENU submenu, void *data) { + static UINT id = WM_USER + 100; MENUITEMINFOW mii; memset(&mii, 0, sizeof(mii)); @@ -71,32 +133,170 @@ static HMENU append_menu(HMENU hmenu, UINT fMask, UINT fType, wchar_t *title, mii.fMask = MIIM_ID | fMask; mii.wID = id++; + if (fMask & MIIM_FTYPE) mii.fType = fType; + if (fMask & MIIM_STATE) mii.fState = fState; + if (fMask & MIIM_STRING) { + mii.dwTypeData = title; + mii.cch = wcslen(title); + } + if (fMask & MIIM_SUBMENU) mii.hSubMenu = submenu; if (fMask & MIIM_DATA) mii.dwItemData = (ULONG_PTR)data; - if (fMask & MIIM_FTYPE) { - mii.fType = fType; - if (fType && MF_SEPARATOR) { - InsertMenuItemW(hmenu, -1, TRUE, &mii); - return NULL; - } + return InsertMenuItemW(hmenu, -1, TRUE, &mii) ? mii.wID : -1; +} + +static int append_seprator(HMENU hmenu) { + return append_menu(hmenu, MIIM_FTYPE, MFT_SEPARATOR, 0, NULL, NULL, NULL); +} + +static HMENU append_submenu(HMENU hmenu, wchar_t *title, int *id) { + int wid; + HMENU menu = find_submenu(hmenu, title, id); + if (menu != NULL) return menu; + + menu = CreatePopupMenu(); + wid = + append_menu(hmenu, MIIM_STRING | MIIM_SUBMENU, 0, 0, title, menu, NULL); + if (id) *id = wid; + return menu; +} + +static bool update_track_menu(plugin_ctx *ctx, dyn_menu *item, const char *type, + const char *prop) { + void *tmp = talloc_new(NULL); + mp_track_list *list = mp_get_track_list(tmp, type); + if (list == NULL) return false; + + bool is_sub = strcmp(type, "sub") == 0; + int64_t pos = mp_get_prop_int(prop); + + for (int i = 0; i < list->num_entries; i++) { + track_item *entry = &list->entries[i]; + UINT fState = entry->selected ? MFS_CHECKED : MFS_UNCHECKED; + if (is_sub && entry->selected && pos != entry->id) + fState |= MFS_DISABLED; + append_menu( + item->hmenu, MIIM_STRING | MIIM_DATA | MIIM_STATE, 0, fState, + format_title(item->talloc_ctx, bstr0(entry->title), + bstr0(entry->lang)), + NULL, + talloc_asprintf(item->talloc_ctx, "set %s %d", prop, entry->id)); } - if (fMask & MIIM_STRING) { - mii.dwTypeData = title; - mii.cch = wcslen(title); + if (is_sub) { + append_menu(item->hmenu, MIIM_STRING | MIIM_DATA | MIIM_STATE, 0, + pos < 0 ? MFS_CHECKED : MFS_UNCHECKED, + escape_title(item->talloc_ctx, bstr0("Off")), NULL, + talloc_asprintf(item->talloc_ctx, "set %s no", prop)); } - if (fMask & MIIM_SUBMENU) { - mii.hSubMenu = find_submenu(hmenu, mii.dwTypeData); - if (mii.hSubMenu == NULL) { - mii.hSubMenu = CreatePopupMenu(); - InsertMenuItemW(hmenu, -1, TRUE, &mii); - } - return mii.hSubMenu; + talloc_free(tmp); + return true; +} + +static bool update_video_track_menu(plugin_ctx *ctx, dyn_menu *item) { + return update_track_menu(ctx, item, "video", "vid"); +} + +static bool update_audio_track_menu(plugin_ctx *ctx, dyn_menu *item) { + return update_track_menu(ctx, item, "audio", "aid"); +} + +static bool update_sub_track_menu(plugin_ctx *ctx, dyn_menu *item) { + return update_track_menu(ctx, item, "sub", "sid"); +} + +static bool update_second_sub_track_menu(plugin_ctx *ctx, dyn_menu *item) { + return update_track_menu(ctx, item, "sub", "secondary-sid"); +} + +static bool update_chapter_menu(plugin_ctx *ctx, dyn_menu *item) { + void *tmp = talloc_new(NULL); + mp_chapter_list *list = mp_get_chapter_list(tmp); + if (list == NULL) return false; + + for (int i = 0; i < list->num_entries; i++) { + chapter_item *entry = &list->entries[i]; + append_menu( + item->hmenu, MIIM_STRING | MIIM_DATA, 0, 0, + escape_title(item->talloc_ctx, bstr0(entry->title)), NULL, + talloc_asprintf(item->talloc_ctx, "seek %f absolute", entry->time)); } + int64_t pos = mp_get_prop_int("chapter"); + if (pos >= 0) + CheckMenuRadioItem(item->hmenu, 0, list->num_entries, pos, + MF_BYPOSITION); - InsertMenuItemW(hmenu, -1, TRUE, &mii); - return NULL; + talloc_free(tmp); + return true; +} + +static bool update_edition_menu(plugin_ctx *ctx, dyn_menu *item) { + void *tmp = talloc_new(NULL); + mp_edition_list *list = mp_get_edition_list(tmp); + if (list == NULL) return false; + + for (int i = 0; i < list->num_entries; i++) { + edition_item *entry = &list->entries[i]; + append_menu( + item->hmenu, MIIM_STRING | MIIM_DATA, 0, 0, + escape_title(item->talloc_ctx, bstr0(entry->title)), NULL, + talloc_asprintf(item->talloc_ctx, "set edition %d", entry->id)); + } + int64_t pos = mp_get_prop_int("current-edition"); + if (pos >= 0) + CheckMenuRadioItem(item->hmenu, 0, list->num_entries, pos, + MF_BYPOSITION); + + talloc_free(tmp); + return true; +} + +static bool update_audio_device_menu(plugin_ctx *ctx, dyn_menu *item) { + void *tmp = talloc_new(NULL); + mp_audio_device_list *list = mp_get_audio_device_list(tmp); + if (list == NULL) return false; + + char *name = mp_get_prop_string(tmp, "audio-device"); + int pos = -1; + for (int i = 0; i < list->num_entries; i++) { + audio_device *entry = &list->entries[i]; + if (strcmp(entry->name, name) == 0) pos = i; + char *title = entry->desc; + if (title == NULL || strlen(title) == 0) + title = talloc_strdup(tmp, entry->name); + append_menu(item->hmenu, MIIM_STRING | MIIM_DATA, 0, 0, + escape_title(item->talloc_ctx, bstr0(title)), NULL, + talloc_asprintf(item->talloc_ctx, "set audio-device %s", + entry->name)); + } + if (pos >= 0) + CheckMenuRadioItem(item->hmenu, 0, list->num_entries, pos, + MF_BYPOSITION); + + talloc_free(tmp); + return true; +} + +static void dyn_menu_init(void *talloc_ctx) { + dyn_menus = talloc_ptrtype(talloc_ctx, dyn_menus); + memset(dyn_menus, 0, sizeof(*dyn_menus)); +} + +static void dyn_menu_update(plugin_ctx *ctx) { + if (dyn_menus == NULL) return; + + for (int i = 0; i < dyn_menus->num_entries; i++) { + dyn_menu *item = &dyn_menus->entries[i]; + + // clear menu + while (GetMenuItemCount(item->hmenu) > 0) + RemoveMenu(item->hmenu, 0, MF_BYPOSITION); + talloc_free_children(item->talloc_ctx); + + UINT enable = item->update(ctx, item) ? MF_ENABLED : MF_DISABLED; + EnableMenuItem(ctx->hmenu, item->id, MF_BYCOMMAND | enable); + } } static bool is_seprarator(bstr text, bool uosc) { @@ -105,47 +305,55 @@ static bool is_seprarator(bstr text, bool uosc) { static void parse_menu(void *talloc_ctx, HMENU hmenu, bstr key, bstr cmd, bstr text, bool uosc) { - bstr name, rest; + bstr name, rest, comment; + name = bstr_split(text, ">", &rest); + name = bstr_split(name, "#", &comment); name = bstr_strip(name); - if (rest.len == 0) { + if (!rest.len) { if (is_seprarator(name, uosc)) { - append_menu(hmenu, MIIM_FTYPE, MFT_SEPARATOR, NULL, NULL); + append_seprator(hmenu); } else { - if (cmd.len > 0 && !bstr_startswith0(cmd, "#")) { - struct item_data *data = talloc_ptrtype(talloc_ctx, data); - data->key = bstrdup0(data, key); - data->cmd = bstrdup0(data, cmd); - append_menu(hmenu, MIIM_STRING | MIIM_DATA, 0, - format_title(talloc_ctx, name, key), data); + bool dynamic = bstr_eatstart0(&comment, MENU_PREFIX_DYN); + if (!dynamic && cmd.len > 0 && !bstr_startswith0(cmd, "#")) { + append_menu(hmenu, MIIM_STRING | MIIM_DATA, 0, 0, + format_title(talloc_ctx, name, key), NULL, + bstrdup0(talloc_ctx, cmd)); } else { - append_menu(hmenu, MIIM_STRING | MIIM_SUBMENU, 0, - escape_title(talloc_ctx, name), NULL); + int id; + HMENU submenu = + append_submenu(hmenu, escape_title(talloc_ctx, name), &id); + if (dynamic && comment.len > 0) { + bstr keyword = bstr_split(comment, "#", NULL); + keyword = bstr_rstrip(keyword); + add_dyn_menu(talloc_ctx, submenu, id, keyword); + } } } } else { - HMENU submenu = append_menu(hmenu, MIIM_STRING | MIIM_SUBMENU, 0, - escape_title(talloc_ctx, name), NULL); - parse_menu(talloc_ctx, submenu, key, cmd, rest, uosc); + HMENU submenu = + append_submenu(hmenu, escape_title(talloc_ctx, name), NULL); + if (!comment.len) parse_menu(talloc_ctx, submenu, key, cmd, rest, uosc); } } static bool split_menu(bstr line, bstr *left, bstr *right, bool uosc) { - if (line.len == 0) return false; + 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; } - *right = bstr_split(*right, "#", NULL); *left = bstr_strip(*left); *right = bstr_strip(*right); return right->len > 0; } -HMENU load_menu(struct plugin_ctx *ctx) { +HMENU load_menu(plugin_ctx *ctx) { + dyn_menu_init(ctx); + void *tmp = talloc_new(NULL); - char *path = mp_get_prop(tmp, "input-conf"); + char *path = mp_get_prop_string(tmp, "input-conf"); if (path == NULL || strlen(path) == 0) path = "~~/input.conf"; HMENU hmenu = CreatePopupMenu(); @@ -154,7 +362,7 @@ HMENU load_menu(struct plugin_ctx *ctx) { while (data.len) { bstr line = bstr_strip_linebreaks(bstr_getline(data, &data)); line = bstr_lstrip(line); - if (line.len == 0) continue; + if (!line.len) continue; bstr key, cmd, left, right; if (bstr_eatstart0(&line, "#")) { @@ -174,26 +382,25 @@ HMENU load_menu(struct plugin_ctx *ctx) { return hmenu; } -void show_menu(struct plugin_ctx *ctx, POINT *pt) { +void show_menu(plugin_ctx *ctx, POINT *pt) { RECT rc; - GetClientRect(ctx->hwnd, &rc); ScreenToClient(ctx->hwnd, pt); + if (!PtInRect(&rc, *pt)) return; - if (PtInRect(&rc, *pt)) { - ClientToScreen(ctx->hwnd, pt); - TrackPopupMenuEx(ctx->hmenu, TPM_LEFTALIGN | TPM_LEFTBUTTON, pt->x, - pt->y, ctx->hwnd, NULL); - } + dyn_menu_update(ctx); + + ClientToScreen(ctx->hwnd, pt); + TrackPopupMenuEx(ctx->hmenu, TPM_LEFTALIGN | TPM_LEFTBUTTON, pt->x, pt->y, + ctx->hwnd, NULL); } -void handle_menu(struct plugin_ctx *ctx, int id) { +void handle_menu(plugin_ctx *ctx, int id) { MENUITEMINFOW mii; memset(&mii, 0, sizeof(mii)); mii.cbSize = sizeof(mii); mii.fMask = MIIM_DATA; if (!GetMenuItemInfoW(ctx->hmenu, id, FALSE, &mii)) return; - struct item_data *data = (struct item_data *)mii.dwItemData; - if (data != NULL) mp_command_async(data->cmd); + if (mii.dwItemData) mp_command_async((const char *)mii.dwItemData); } \ No newline at end of file diff --git a/src/misc/bstr.c b/src/misc/bstr.c index 7b8b422..d825b61 100644 --- a/src/misc/bstr.c +++ b/src/misc/bstr.c @@ -115,6 +115,13 @@ struct bstr bstr_lstrip(struct bstr str) return str; } +struct bstr bstr_rstrip(struct bstr str) +{ + while (str.len && mp_isspace(str.start[str.len - 1])) + str.len--; + return str; +} + struct bstr bstr_strip(struct bstr str) { str = bstr_lstrip(str); diff --git a/src/misc/bstr.h b/src/misc/bstr.h index 9d78aad..5be35ea 100644 --- a/src/misc/bstr.h +++ b/src/misc/bstr.h @@ -70,6 +70,7 @@ int bstrcspn(struct bstr str, const char *reject); int bstr_find(struct bstr haystack, struct bstr needle); struct bstr bstr_lstrip(struct bstr str); +struct bstr bstr_rstrip(struct bstr str); struct bstr bstr_strip(struct bstr str); struct bstr bstr_split(struct bstr str, const char *sep, struct bstr *rest); bool bstr_split_tok(bstr str, const char *tok, bstr *out_left, bstr *out_right); diff --git a/src/plugin.c b/src/plugin.c index 7c34736..cdb8048 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -8,6 +8,7 @@ #include #include "misc/bstr.h" +#include "misc/ctype.h" #include "menu.h" #include "plugin.h" @@ -78,8 +79,7 @@ static void handle_client_message(mpv_event *event) { if (msg->num_args < 1) return; const char *cmd = msg->args[0]; - if (strcmp(cmd, "show") == 0) - PostMessageW(ctx->hwnd, WM_SHOWMENU, 0, 0); + if (strcmp(cmd, "show") == 0) PostMessageW(ctx->hwnd, WM_SHOWMENU, 0, 0); } static MP_THREAD_VOID dispatch_thread(void *ptr) { @@ -130,13 +130,195 @@ wchar_t *mp_from_utf8(void *talloc_ctx, const char *s) { return ret; } -char *mp_get_prop(void *talloc_ctx, const char *prop) { +char *mp_get_prop_string(void *talloc_ctx, const char *prop) { char *val = mpv_get_property_string(ctx->mpv, prop); char *ret = talloc_strdup(talloc_ctx, val); if (val != NULL) mpv_free(val); return ret; } +int64_t mp_get_prop_int(const char *prop) { + int64_t ret = -1; + mpv_get_property(ctx->mpv, prop, MPV_FORMAT_INT64, &ret); + return ret; +} + +mp_track_list *mp_get_track_list(void *talloc_ctx, const char *type) { + mpv_node node; + + if (mpv_get_property(ctx->mpv, "track-list", MPV_FORMAT_NODE, &node) < 0) + return NULL; + if (node.format != MPV_FORMAT_NODE_ARRAY || node.u.list->num == 0) { + mpv_free_node_contents(&node); + return NULL; + } + + mp_track_list *list = talloc_ptrtype(talloc_ctx, list); + memset(list, 0, sizeof(*list)); + + for (int i = 0; i < node.u.list->num; i++) { + mpv_node track = node.u.list->values[i]; + + struct track_item item; + memset(&item, 0, sizeof(item)); + + for (int j = 0; j < track.u.list->num; j++) { + char *key = track.u.list->keys[j]; + mpv_node value = track.u.list->values[j]; + if (strcmp(key, "id") == 0) { + item.id = value.u.int64; + } else if (strcmp(key, "title") == 0) { + item.title = talloc_strdup(talloc_ctx, value.u.string); + } else if (strcmp(key, "lang") == 0) { + item.lang = talloc_strdup(talloc_ctx, value.u.string); + } else if (strcmp(key, "type") == 0) { + item.type = talloc_strdup(talloc_ctx, value.u.string); + } else if (strcmp(key, "selected") == 0) { + item.selected = value.u.flag; + } + } + + // set default title if not set + if (item.title == NULL) + item.title = + talloc_asprintf(talloc_ctx, "%s %d", item.type, item.id); + + // convert lang to uppercase + if (item.lang != NULL) { + for (int x = 0; item.lang[x]; x++) + item.lang[x] = mp_toupper(item.lang[x]); + } + + if (type != NULL && strcmp(item.type, type) != 0) continue; + + MP_TARRAY_APPEND(talloc_ctx, list->entries, list->num_entries, item); + } + + mpv_free_node_contents(&node); + + return list; +} + +mp_chapter_list *mp_get_chapter_list(void *talloc_ctx) { + mpv_node node; + + if (mpv_get_property(ctx->mpv, "chapter-list", MPV_FORMAT_NODE, &node) < 0) + return NULL; + if (node.format != MPV_FORMAT_NODE_ARRAY || node.u.list->num == 0) { + mpv_free_node_contents(&node); + return NULL; + } + + mp_chapter_list *list = talloc_ptrtype(talloc_ctx, list); + memset(list, 0, sizeof(*list)); + + for (int i = 0; i < node.u.list->num; i++) { + mpv_node chapter = node.u.list->values[i]; + + struct chapter_item item; + memset(&item, 0, sizeof(item)); + + for (int j = 0; j < chapter.u.list->num; j++) { + char *key = chapter.u.list->keys[j]; + mpv_node value = chapter.u.list->values[j]; + if (strcmp(key, "title") == 0) { + item.title = talloc_strdup(talloc_ctx, value.u.string); + } else if (strcmp(key, "time") == 0) { + item.time = value.u.double_; + } + } + + // set default title if not set + if (item.title == NULL) + item.title = talloc_asprintf(talloc_ctx, "chapter %d", i + 1); + + MP_TARRAY_APPEND(talloc_ctx, list->entries, list->num_entries, item); + } + + mpv_free_node_contents(&node); + + return list; +} + +mp_edition_list *mp_get_edition_list(void *talloc_ctx) { + mpv_node node; + + if (mpv_get_property(ctx->mpv, "edition-list", MPV_FORMAT_NODE, &node) < 0) + return NULL; + if (node.format != MPV_FORMAT_NODE_ARRAY || node.u.list->num == 0) { + mpv_free_node_contents(&node); + return NULL; + } + + mp_edition_list *list = talloc_ptrtype(talloc_ctx, list); + memset(list, 0, sizeof(*list)); + + for (int i = 0; i < node.u.list->num; i++) { + mpv_node edition = node.u.list->values[i]; + + struct edition_item item; + memset(&item, 0, sizeof(item)); + + for (int j = 0; j < edition.u.list->num; j++) { + char *key = edition.u.list->keys[j]; + mpv_node value = edition.u.list->values[j]; + if (strcmp(key, "title") == 0) { + item.title = talloc_strdup(talloc_ctx, value.u.string); + } else if (strcmp(key, "id") == 0) { + item.id = value.u.int64; + } + } + + // set default title if not set + if (item.title == NULL) + item.title = talloc_asprintf(talloc_ctx, "edition %d", item.id); + + MP_TARRAY_APPEND(talloc_ctx, list->entries, list->num_entries, item); + } + + mpv_free_node_contents(&node); + + return list; +} + +mp_audio_device_list *mp_get_audio_device_list(void *talloc_ctx) { + mpv_node node; + + if (mpv_get_property(ctx->mpv, "audio-device-list", MPV_FORMAT_NODE, + &node) < 0) + return NULL; + if (node.format != MPV_FORMAT_NODE_ARRAY || node.u.list->num == 0) { + mpv_free_node_contents(&node); + return NULL; + } + + mp_audio_device_list *list = talloc_ptrtype(talloc_ctx, list); + memset(list, 0, sizeof(*list)); + + for (int i = 0; i < node.u.list->num; i++) { + mpv_node device = node.u.list->values[i]; + + struct audio_device item; + memset(&item, 0, sizeof(item)); + + for (int j = 0; j < device.u.list->num; j++) { + char *key = device.u.list->keys[j]; + mpv_node value = device.u.list->values[j]; + if (strcmp(key, "name") == 0) { + item.name = talloc_strdup(talloc_ctx, value.u.string); + } else if (strcmp(key, "description") == 0) { + item.desc = talloc_strdup(talloc_ctx, value.u.string); + } + } + + MP_TARRAY_APPEND(talloc_ctx, list->entries, list->num_entries, item); + } + + mpv_free_node_contents(&node); + + return list; +} + char *mp_expand_path(void *talloc_ctx, char *path) { mpv_node node; const char *args[] = {"expand-path", path, NULL}; diff --git a/src/plugin.h b/src/plugin.h index ae75a10..3a27023 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -10,11 +10,11 @@ #include "osdep/threads.h" #include "misc/dispatch.h" -struct plugin_config { +typedef struct plugin_config { bool uosc; // use uosc menu syntax -}; +} plugin_config; -struct plugin_ctx { +typedef struct plugin_ctx { struct plugin_config *conf; // plugin config struct mp_dispatch_queue *dispatch; // dispatch queue @@ -25,10 +25,58 @@ struct plugin_ctx { HWND hwnd; // window handle HMENU hmenu; // menu handle WNDPROC wnd_proc; // previous window procedure -}; +} plugin_ctx; + +typedef struct track_item { + int64_t id; // the ID as it's used for -sid/--aid/--vid + char *type; // string describing the media type + char *title; // track title as it is stored in the file + char *lang; // track language as identified by the file + bool selected; // if the track is currently decoded +} track_item; + +typedef struct mp_track_list { + struct track_item *entries; + int num_entries; +} mp_track_list; + +typedef struct chapter_item { + char *title; // chapter title as stored in the file + double time; // chapter start time in seconds as float +} chapter_item; + +typedef struct mp_chapter_list { + struct chapter_item *entries; + int num_entries; +} mp_chapter_list; + +typedef struct edition_item { + char *title; // edition title as stored in the file + int64_t id; // edition ID as integer +} edition_item; + +typedef struct mp_edition_list { + struct edition_item *entries; + int num_entries; +} mp_edition_list; + +typedef struct audio_device { + char *name; // device name + char *desc; // device description +} audio_device; + +typedef struct mp_audio_device_list { + struct audio_device *entries; + int num_entries; +} mp_audio_device_list; wchar_t *mp_from_utf8(void *talloc_ctx, const char *s); -char *mp_get_prop(void *talloc_ctx, const char *prop); +char *mp_get_prop_string(void *talloc_ctx, const char *prop); +int64_t mp_get_prop_int(const char *prop); +mp_track_list *mp_get_track_list(void *talloc_ctx, const char *type); +mp_chapter_list *mp_get_chapter_list(void *talloc_ctx); +mp_edition_list *mp_get_edition_list(void *talloc_ctx); +mp_audio_device_list *mp_get_audio_device_list(void *talloc_ctx); 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);