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

add dialog interface #38

Merged
merged 1 commit into from
Jan 20, 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
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