diff --git a/CMakeLists.txt b/CMakeLists.txt index cc25a53..e66f0a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/src/dialog.c b/src/dialog.c new file mode 100644 index 0000000..2ec0a69 --- /dev/null +++ b/src/dialog.c @@ -0,0 +1,228 @@ +#include +#include +#include +#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_multiple(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; +} \ No newline at end of file diff --git a/src/dialog.h b/src/dialog.h new file mode 100644 index 0000000..2ee0208 --- /dev/null +++ b/src/dialog.h @@ -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_multiple(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 \ No newline at end of file diff --git a/src/plugin.c b/src/plugin.c index 0dbf3d7..c5f21d8 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -3,10 +3,12 @@ #include #include +#include #include #include #include "misc/bstr.h" #include "misc/ctype.h" +#include "dialog.h" #include "menu.h" #include "plugin.h" @@ -93,7 +95,49 @@ 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); + } else if (strcmp(cmd, "dialog/open") == 0 && msg->num_args > 1) { + char *path = open_dialog(NULL, ctx); + if (path != NULL) { + mpv_command(ctx->mpv, + (const char *[]){"script-message-to", msg->args[1], + "dialog-open-reply", path, NULL}); + talloc_free(path); + } + } else if (strcmp(cmd, "dialog/open-multi") == 0 && msg->num_args > 1) { + void *tmp = talloc_new(NULL); + char **paths = open_dialog_multiple(tmp, ctx); + if (paths != NULL) { + int count = 0; + while (paths[count] != NULL) count++; + + char **args = talloc_array(tmp, char *, count + 4); + args[0] = talloc_strdup(tmp, "script-message-to"); + args[1] = talloc_strdup(tmp, msg->args[1]); + args[2] = talloc_strdup(tmp, "dialog-open-multi-reply"); + for (int i = 0; i < count; i++) args[i + 3] = paths[i]; + args[count + 3] = NULL; + mpv_command(ctx->mpv, (const char **)args); + } + talloc_free(tmp); + } else if (strcmp(cmd, "dialog/open-folder") == 0 && msg->num_args > 1) { + char *path = open_folder(NULL, ctx); + if (path != NULL) { + mpv_command(ctx->mpv, (const char *[]){ + "script-message-to", msg->args[1], + "dialog-open-folder-reply", path, NULL}); + talloc_free(path); + } + } else if (strcmp(cmd, "dialog/save") == 0 && msg->num_args > 1) { + char *path = save_dialog(NULL, ctx); + if (path != NULL) { + mpv_command(ctx->mpv, + (const char *[]){"script-message-to", msg->args[1], + "dialog-save-reply", path, NULL}); + talloc_free(path); + } + } } // create and init plugin context @@ -119,6 +163,8 @@ static void destroy_plugin_ctx() { // entry point of plugin MPV_EXPORT int mpv_open_cplugin(mpv_handle *handle) { + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + create_plugin_ctx(handle); if (ctx->conf->load) { @@ -150,6 +196,8 @@ MPV_EXPORT int mpv_open_cplugin(mpv_handle *handle) { mpv_unobserve_property(handle, 0); destroy_plugin_ctx(); + CoUninitialize(); + return 0; } @@ -162,6 +210,15 @@ wchar_t *mp_from_utf8(void *talloc_ctx, const char *s) { return ret; } +// convert wchar_t string to utf8 +char *mp_to_utf8(void *talloc_ctx, const wchar_t *s) { + int count = WideCharToMultiByte(CP_UTF8, 0, s, -1, NULL, 0, NULL, NULL); + if (count <= 0) abort(); + char *ret = talloc_array(talloc_ctx, char, count); + WideCharToMultiByte(CP_UTF8, 0, s, -1, ret, count, NULL, NULL); + return ret; +} + // get property value as string char *mp_get_prop_string(void *talloc_ctx, const char *prop) { char *val = mpv_get_property_string(ctx->mpv, prop); diff --git a/src/plugin.h b/src/plugin.h index 4d591a3..746e490 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -4,6 +4,7 @@ #ifndef MPV_PLUGIN_H #define MPV_PLUGIN_H +#include #include #include #include "misc/dispatch.h" @@ -27,6 +28,7 @@ typedef struct { } plugin_ctx; 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);