Skip to content

Commit

Permalink
dynamic menu support
Browse files Browse the repository at this point in the history
  • Loading branch information
tsl0922 committed Dec 17, 2023
1 parent 46a0928 commit c314096
Show file tree
Hide file tree
Showing 6 changed files with 383 additions and 35 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Ctrl+a show-text foobar #menu: Foo > Bar
_ ignore #menu: -
```

Add a keybinding that trigger the menu:
Add a keybinding that trigger the menu (required):

```
MBTN_RIGHT script-message-to menu show
Expand Down
239 changes: 209 additions & 30 deletions src/menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,61 @@

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

struct item_data {
char *key;
char *cmd;
struct dyn_provider {
char *keyword; // keyword in menu title
void (*update)(void *talloc_ctx, HMENU hmenu);
};

struct dyn_menu {
HMENU hmenu; // submenu handle
void *talloc_ctx; // talloc context
void (*update)(void *talloc_ctx, HMENU hmenu);
};

struct dyn_menu_list {
struct dyn_menu *entries;
int num_entries;
};

// forward declarations for dynamic menu update functions
static void update_video_track_menu(void *talloc_ctx, HMENU hmenu);
static void update_audio_track_menu(void *talloc_ctx, HMENU hmenu);
static void update_sub_track_menu(void *talloc_ctx, HMENU hmenu);
static void update_secondary_sub_track_menu(void *talloc_ctx, HMENU hmenu);
static void update_chapter_menu(void *talloc_ctx, HMENU hmenu);
static void update_audio_device_menu(void *talloc_ctx, HMENU hmenu);

// dynamic menu providers
static const struct 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_secondary_sub_track_menu},
{"chapters", update_chapter_menu},
{"audio-devices", update_audio_device_menu},
};

// dynamic menu list
static struct dyn_menu_list *dyn_menus = NULL;

static bool add_dyn_menu(void *talloc_ctx, HMENU hmenu, bstr keyword) {
for (int i = 0; i < ARRAYSIZE(dyn_providers); i++) {
struct dyn_provider provider = dyn_providers[i];
if (!bstr_equals0(keyword, provider.keyword)) continue;

struct dyn_menu item;
item.hmenu = hmenu;
item.talloc_ctx = talloc_new(talloc_ctx);
item.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) {
MENUITEMINFOW mii;
int count = GetMenuItemCount(hmenu);
Expand Down Expand Up @@ -61,8 +110,8 @@ static wchar_t *format_title(void *talloc_ctx, bstr name, bstr key) {
}

// return submenu if it is, otherwise NULL
static HMENU append_menu(HMENU hmenu, UINT fMask, UINT fType, wchar_t *title,
void *data) {
static HMENU append_menu(HMENU hmenu, UINT fMask, UINT fType, UINT fState,
wchar_t *title, void *data) {
static UINT id = 0;
MENUITEMINFOW mii;
memset(&mii, 0, sizeof(mii));
Expand All @@ -81,6 +130,8 @@ static HMENU append_menu(HMENU hmenu, UINT fMask, UINT fType, wchar_t *title,
}
}

if (fMask & MIIM_STATE) mii.fState = fState;

if (fMask & MIIM_STRING) {
mii.dwTypeData = title;
mii.cch = wcslen(title);
Expand All @@ -99,53 +150,174 @@ static HMENU append_menu(HMENU hmenu, UINT fMask, UINT fType, wchar_t *title,
return NULL;
}

static void update_track_menu(void *talloc_ctx, HMENU hmenu, const char *type,
const char *prop) {
while (GetMenuItemCount(hmenu) > 0) RemoveMenu(hmenu, 0, MF_BYPOSITION);
talloc_free_children(talloc_ctx);

void *tmp = talloc_new(NULL);
struct mp_track_list *list = mp_get_track_list(tmp, type);
if (list == NULL) return;

bool is_sub = strcmp(type, "sub") == 0;
int64_t pos = mp_get_prop_int(prop);

for (int i = 0; i < list->num_entries; i++) {
struct track_item *entry = &list->entries[i];

char *title = entry->title;
if (title == NULL || strlen(title) == 0)
title = talloc_asprintf(tmp, "%s %d", type, entry->id);
if (entry->lang != NULL && strlen(entry->lang) > 0)
title = talloc_asprintf(tmp, "%s [%s]", title, entry->lang);
UINT fState = entry->selected ? MFS_CHECKED : MFS_UNCHECKED;
if (is_sub && entry->selected && pos != entry->id)
fState |= MFS_DISABLED;
append_menu(hmenu, MIIM_STRING | MIIM_DATA | MIIM_STATE, 0, fState,
escape_title(talloc_ctx, bstr0(title)),
talloc_asprintf(talloc_ctx, "set %s %d", prop, entry->id));
}

if (is_sub) {
append_menu(hmenu, MIIM_STRING | MIIM_DATA | MIIM_STATE, 0,
pos < 0 ? MFS_CHECKED : MFS_UNCHECKED,
escape_title(talloc_ctx, bstr0("Off")),
talloc_asprintf(talloc_ctx, "set %s no", prop));
}

talloc_free(tmp);
}

static void update_video_track_menu(void *talloc_ctx, HMENU hmenu) {
update_track_menu(talloc_ctx, hmenu, "video", "vid");
}

static void update_audio_track_menu(void *talloc_ctx, HMENU hmenu) {
update_track_menu(talloc_ctx, hmenu, "audio", "aid");
}

static void update_sub_track_menu(void *talloc_ctx, HMENU hmenu) {
update_track_menu(talloc_ctx, hmenu, "sub", "sid");
}

static void update_secondary_sub_track_menu(void *talloc_ctx, HMENU hmenu) {
update_track_menu(talloc_ctx, hmenu, "sub", "secondary-sid");
}

static void update_chapter_menu(void *talloc_ctx, HMENU hmenu) {
while (GetMenuItemCount(hmenu) > 0) RemoveMenu(hmenu, 0, MF_BYPOSITION);
talloc_free_children(talloc_ctx);

void *tmp = talloc_new(NULL);
struct mp_chapter_list *list = mp_get_chapter_list(tmp);
if (list == NULL) return;

for (int i = 0; i < list->num_entries; i++) {
struct chapter_item *entry = &list->entries[i];

char *title = entry->title;
if (title == NULL || strlen(title) == 0)
title = talloc_asprintf(tmp, "chapter %d", i + 1);
append_menu(
hmenu, MIIM_STRING | MIIM_DATA, 0, 0,
escape_title(talloc_ctx, bstr0(title)),
talloc_asprintf(talloc_ctx, "seek %f absolute", entry->time));
}
int64_t pos = mp_get_prop_int("chapter");
if (pos >= 0)
CheckMenuRadioItem(hmenu, 0, list->num_entries, pos, MF_BYPOSITION);

talloc_free(tmp);
}

static void update_audio_device_menu(void *talloc_ctx, HMENU hmenu) {
while (GetMenuItemCount(hmenu) > 0) RemoveMenu(hmenu, 0, MF_BYPOSITION);
talloc_free_children(talloc_ctx);

void *tmp = talloc_new(NULL);
struct mp_audio_device_list *list = mp_get_audio_device_list(tmp);
if (list == NULL) return;

char *name = mp_get_prop_string(tmp, "audio-device");
int pos = -1;
for (int i = 0; i < list->num_entries; i++) {
struct 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(
hmenu, MIIM_STRING | MIIM_DATA, 0, 0,
escape_title(talloc_ctx, bstr0(title)),
talloc_asprintf(talloc_ctx, "set audio-device %s", entry->name));
}
if (pos >= 0)
CheckMenuRadioItem(hmenu, 0, list->num_entries, pos, MF_BYPOSITION);

talloc_free(tmp);
}

static void dyn_menu_init(void *talloc_ctx) {
dyn_menus = talloc_ptrtype(talloc_ctx, dyn_menus);
memset(dyn_menus, 0, sizeof(*dyn_menus));
}

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

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_menu(hmenu, MIIM_FTYPE, MFT_SEPARATOR, 0, NULL, NULL);
} 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),
bstrdup0(talloc_ctx, cmd));
} else {
append_menu(hmenu, MIIM_STRING | MIIM_SUBMENU, 0,
escape_title(talloc_ctx, name), NULL);
HMENU submenu =
append_menu(hmenu, MIIM_STRING | MIIM_SUBMENU, 0, 0,
escape_title(talloc_ctx, name), NULL);
if (dynamic && comment.len > 0) {
bstr keyword = bstr_split(comment, "#", NULL);
keyword = bstr_rstrip(keyword);
add_dyn_menu(talloc_ctx, submenu, keyword);
}
}
}
} else {
HMENU submenu = append_menu(hmenu, MIIM_STRING | MIIM_SUBMENU, 0,
HMENU submenu = append_menu(hmenu, MIIM_STRING | MIIM_SUBMENU, 0, 0,
escape_title(talloc_ctx, name), NULL);
parse_menu(talloc_ctx, submenu, key, cmd, rest, uosc);
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) {
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();
Expand All @@ -154,7 +326,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, "#")) {
Expand All @@ -174,17 +346,25 @@ HMENU load_menu(struct plugin_ctx *ctx) {
return hmenu;
}

void update_menu(struct plugin_ctx *ctx) {
if (dyn_menus == NULL) return;
for (int i = 0; i < dyn_menus->num_entries; i++) {
struct dyn_menu *item = &dyn_menus->entries[i];
item->update(item->talloc_ctx, item->hmenu);
}
}

void show_menu(struct 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);
}
update_menu(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) {
Expand All @@ -194,6 +374,5 @@ void handle_menu(struct plugin_ctx *ctx, int id) {
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);
}
7 changes: 7 additions & 0 deletions src/misc/bstr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/misc/bstr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit c314096

Please sign in to comment.