Skip to content

Commit

Permalink
add dialog interface
Browse files Browse the repository at this point in the history
  • Loading branch information
tsl0922 committed Jan 20, 2024
1 parent 637b795 commit f62cd20
Show file tree
Hide file tree
Showing 6 changed files with 431 additions and 10 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ add_library(menu SHARED
mpv/ta/ta_talloc.c
mpv/ta/ta_utils.c

src/dialog.c
src/menu.c
src/plugin.c
)
Expand Down
89 changes: 80 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ MBTN_RIGHT script-message-to menu show

#### `user-data/menu/items`

> [!TIP]
> To reduce update frequency, it's recommended to update this property in [mp.register_idle(fn)](https://mpv.io/manual/master/#lua-scripting-mp-register-idle(fn)).
```
MPV_FORMAT_NODE_ARRAY
MPV_FORMAT_NODE_MAP (menu item)
Expand All @@ -98,23 +101,93 @@ MPV_FORMAT_NODE_ARRAY

The menu data of the C plugin is stored in this property, updating it will trigger an update of the menu UI.

To reduce update frequency, it's recommended to update this property in [mp.register_idle(fn)](https://mpv.io/manual/master/#lua-scripting-mp-register-idle(fn)).
> [!NOTE]
> Be aware that `dyn_menu.lua` is conflict with other scripts that also update the `user-data/menu/items` property,
> you may use the messages below if you only want to update part of the menu.
#### `user-data/menu/dialog/filters`

```
MPV_FORMAT_NODE_ARRAY
MPV_FORMAT_NODE_MAP
"name" MPV_FORMAT_STRING
"spec" MPV_FORMAT_STRING
```

Be aware that `dyn_menu.lua` is conflict with other scripts that also update the `user-data/menu/items` property,
you may use the messages below if you only want to update part of the menu.
Custom file type filters used for open dialog, the first one will be selected by default.

Example:

```lua
local file_types = {
{ name = 'All Files (*.*)', spec = '*.*' },
{ name = 'Video Files', spec = '*mp4;*.mkv' },
{ name = 'Audio Files', spec = '*.mp3;*.m4a' },
{ name = 'Subtitle Files', spec = '*.srt;*.ass' },
{ name = 'Playlist Files', spec = '*.m3u;*.m3u8' },
}
```

#### `user-data/menu/dialog/default-path`

Default path for open and save dialog.

#### `user-data/menu/dialog/default-name`

Default file name for save dialog.

### Messages

> [!TIP]
> Want a usage example? Check [Scripting example](https://github.com/tsl0922/mpv-menu-plugin/wiki/Scripting-example) in the wiki.
#### `menu-ready`
#### Script Messages supported by `menu.dll`:

##### `clipboard/get <src>`

Retrieves data from the clipboard (text only).

The result is replied via: `srcript-message-to <src> clipboard-get-reply <text>`.

##### `clipboard/set <text>`

Places data on the clipboard (text only).

##### `dialog/open <src>`

Show an open dialog.

The result is replied via: `srcript-message-to <src> dialog-open-reply <path>`.

##### `dialog/open-multi <src>`

Show an open dialog that can select multiple files.

The result is replied via: `srcript-message-to <src> dialog-open-multi-reply <path1> <path2> ...`.

##### `dialog/open-folder <src>`

Show an open dialog that can select folder only.

The result is replied via: `srcript-message-to <src> dialog-open-folder-reply <path>`.

##### `dialog/save <src>`

Show a save dialog.

The result is replied via: `srcript-message-to <src> dialog-save-reply <path>`.

#### Script Messages supported by `dyn_menu.lua`:

##### `menu-ready`

Broadcasted when `dyn_menu.lua` has initialized itself.

#### `get <keyword> <src>`
##### `get <keyword> <src>`

Get the menu item structure of `keyword`, and send a json reply to `src`.
Get the menu item structure of `keyword`.

The result is replied via: `srcript-message-to <src> menu-get-reply <json>`.

```json
{
Expand All @@ -127,11 +200,9 @@ Get the menu item structure of `keyword`, and send a json reply to `src`.
}
```

The reply is sent via `srcript-message-to <src> menu-get-reply <json>`.

If `keyword` is not found, the result json will contain an additional `error` field, and no `item` field.

#### `update <keyword> <json>`
##### `update <keyword> <json>`

Update the menu item structure of `keyword` with `json`.

Expand Down
228 changes: 228 additions & 0 deletions src/dialog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#include <windows.h>
#include <shobjidl.h>
#include <mpv/client.h>
#include "mpv_talloc.h"
#include "dialog.h"

#define DIALOG_FILTER_PROP "user-data/menu/dialog/filters"
#define DIALOG_DEF_PATH_PROP "user-data/menu/dialog/default-path"
#define DIALOG_DEF_NAME_PROP "user-data/menu/dialog/default-name"

// add filters to dialog from user property, default to first one
static void add_filters(mpv_handle *mpv, IFileDialog *pfd) {
mpv_node node = {0};
if (mpv_get_property(mpv, DIALOG_FILTER_PROP, MPV_FORMAT_NODE, &node) < 0)
return;
if (node.format != MPV_FORMAT_NODE_ARRAY) goto done;

void *tmp = talloc_new(NULL);
mpv_node_list *list = node.u.list;
COMDLG_FILTERSPEC *specs = talloc_array(tmp, COMDLG_FILTERSPEC, list->num);
UINT count = 0;

for (int i = 0; i < list->num; i++) {
mpv_node *item = &list->values[i];
if (item->format != MPV_FORMAT_NODE_MAP) continue;

char *name = NULL, *spec = NULL;
mpv_node_list *values = item->u.list;

for (int j = 0; j < values->num; j++) {
char *key = values->keys[j];
mpv_node *value = &values->values[j];
if (value->format != MPV_FORMAT_STRING) continue;

if (strcmp(key, "name") == 0) name = value->u.string;
if (strcmp(key, "spec") == 0) spec = value->u.string;
}

if (name != NULL && spec != NULL) {
specs[count].pszName = mp_from_utf8(tmp, name);
specs[count].pszSpec = mp_from_utf8(tmp, spec);
count++;
}
}

if (count > 0) {
pfd->lpVtbl->SetFileTypes(pfd, count, specs);
pfd->lpVtbl->SetFileTypeIndex(pfd, 1);
pfd->lpVtbl->SetDefaultExtension(pfd, specs[0].pszSpec);
}

talloc_free(tmp);

done:
mpv_free_node_contents(&node);
}

// set default path from user property
static void set_default_path(mpv_handle *mpv, IFileDialog *pfd) {
char *path = mpv_get_property_string(mpv, DIALOG_DEF_PATH_PROP);
if (path == NULL) return;

IShellItem *folder;
wchar_t *w_path = mp_from_utf8(NULL, path);

if (SUCCEEDED(SHCreateItemFromParsingName(w_path, NULL, &IID_IShellItem,
(void **)&folder)))
pfd->lpVtbl->SetDefaultFolder(pfd, folder);

talloc_free(w_path);
mpv_free(path);
}

// set default name used for save dialog
static void set_default_name(mpv_handle *mpv, IFileDialog *pfd) {
char *name = mpv_get_property_string(mpv, DIALOG_DEF_NAME_PROP);
if (name == NULL) return;

wchar_t *w_name = mp_from_utf8(NULL, name);
pfd->lpVtbl->SetFileName(pfd, w_name);
talloc_free(w_name);
}

static void add_options(IFileDialog *pfd, DWORD options) {
DWORD dwOptions;
if (pfd->lpVtbl->GetOptions(pfd, &dwOptions) == S_OK) {
pfd->lpVtbl->SetOptions(pfd, dwOptions | options);
}
}

// single file open dialog
char *open_dialog(void *talloc_ctx, plugin_ctx *ctx) {
IFileOpenDialog *pfd = NULL;
if (FAILED(CoCreateInstance(&CLSID_FileOpenDialog, NULL,
CLSCTX_INPROC_SERVER, &IID_IFileDialog,
(void **)&pfd)))
return NULL;

add_filters(ctx->mpv, (IFileDialog *)pfd);
set_default_path(ctx->mpv, (IFileDialog *)pfd);
add_options((IFileDialog *)pfd, FOS_FORCEFILESYSTEM);

char *path = NULL;

if (SUCCEEDED(pfd->lpVtbl->Show(pfd, ctx->hwnd))) {
IShellItem *psi;
if (SUCCEEDED(pfd->lpVtbl->GetResult(pfd, &psi))) {
wchar_t *w_path;
if (SUCCEEDED(psi->lpVtbl->GetDisplayName(psi, SIGDN_FILESYSPATH,
&w_path))) {
path = mp_to_utf8(talloc_ctx, w_path);
CoTaskMemFree(w_path);
}
psi->lpVtbl->Release(psi);
}
}

pfd->lpVtbl->Release(pfd);

return path;
}

// multiple file open dialog
char **open_dialog_multi(void *talloc_ctx, plugin_ctx *ctx) {
IFileOpenDialog *pfd = NULL;
if (FAILED(CoCreateInstance(&CLSID_FileOpenDialog, NULL,
CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog,
(void **)&pfd)))
return NULL;

add_filters(ctx->mpv, (IFileDialog *)pfd);
set_default_path(ctx->mpv, (IFileDialog *)pfd);
add_options((IFileDialog *)pfd, FOS_FORCEFILESYSTEM | FOS_ALLOWMULTISELECT);

char **paths = NULL;

if (SUCCEEDED(pfd->lpVtbl->Show(pfd, ctx->hwnd))) {
IShellItemArray *psia;
if (SUCCEEDED(pfd->lpVtbl->GetResults(pfd, &psia))) {
DWORD count;
if (SUCCEEDED(psia->lpVtbl->GetCount(psia, &count))) {
paths =
talloc_zero_size(talloc_ctx, sizeof(char *) * (count + 1));
for (DWORD i = 0; i < count; i++) {
IShellItem *psi;
if (SUCCEEDED(psia->lpVtbl->GetItemAt(psia, i, &psi))) {
wchar_t *w_path;
if (SUCCEEDED(psi->lpVtbl->GetDisplayName(
psi, SIGDN_FILESYSPATH, &w_path))) {
paths[i] = mp_to_utf8(talloc_ctx, w_path);
CoTaskMemFree(w_path);
}
psi->lpVtbl->Release(psi);
}
}
}
psia->lpVtbl->Release(psia);
}
}

pfd->lpVtbl->Release(pfd);

return paths;
}

// folder open dialog
char *open_folder(void *talloc_ctx, plugin_ctx *ctx) {
IFileOpenDialog *pfd = NULL;
if (FAILED(CoCreateInstance(&CLSID_FileOpenDialog, NULL,
CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog,
(void **)&pfd)))
return NULL;

set_default_path(ctx->mpv, (IFileDialog *)pfd);
add_options((IFileDialog *)pfd, FOS_FORCEFILESYSTEM | FOS_PICKFOLDERS);

char *path = NULL;

if (SUCCEEDED(pfd->lpVtbl->Show(pfd, ctx->hwnd))) {
IShellItem *psi;
if (SUCCEEDED(pfd->lpVtbl->GetResult(pfd, &psi))) {
wchar_t *w_path;
if (SUCCEEDED(psi->lpVtbl->GetDisplayName(psi, SIGDN_FILESYSPATH,
&w_path))) {
path = mp_to_utf8(talloc_ctx, w_path);
CoTaskMemFree(w_path);
}
psi->lpVtbl->Release(psi);
}
}

pfd->lpVtbl->Release(pfd);

return path;
}

// save dialog
char *save_dialog(void *talloc_ctx, plugin_ctx *ctx) {
IFileSaveDialog *pfd = NULL;
if (FAILED(CoCreateInstance(&CLSID_FileSaveDialog, NULL,
CLSCTX_INPROC_SERVER, &IID_IFileSaveDialog,
(void **)&pfd)))
return NULL;

add_filters(ctx->mpv, (IFileDialog *)pfd);
set_default_path(ctx->mpv, (IFileDialog *)pfd);
set_default_name(ctx->mpv, (IFileDialog *)pfd);
add_options((IFileDialog *)pfd, FOS_FORCEFILESYSTEM);

char *path = NULL;

if (SUCCEEDED(pfd->lpVtbl->Show(pfd, ctx->hwnd))) {
IShellItem *psi;
if (SUCCEEDED(pfd->lpVtbl->GetResult(pfd, &psi))) {
wchar_t *w_path;
if (SUCCEEDED(psi->lpVtbl->GetDisplayName(psi, SIGDN_FILESYSPATH,
&w_path))) {
path = mp_to_utf8(talloc_ctx, w_path);
CoTaskMemFree(w_path);
}
psi->lpVtbl->Release(psi);
}
}

pfd->lpVtbl->Release(pfd);

return path;
}
13 changes: 13 additions & 0 deletions src/dialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) 2023 tsl0922. All rights reserved.
// SPDX-License-Identifier: GPL-2.0-only

#ifndef MPV_PLUGIN_DIALOG_H
#define MPV_PLUGIN_DIALOG_H

#include "plugin.h"

char *open_dialog(void *talloc_ctx, plugin_ctx *ctx);
char **open_dialog_multi(void *talloc_ctx, plugin_ctx *ctx);
char *open_folder(void *talloc_ctx, plugin_ctx *ctx);
char *save_dialog(void *talloc_ctx, plugin_ctx *ctx);
#endif
Loading

0 comments on commit f62cd20

Please sign in to comment.