1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-11 17:37:23 +00:00
mpv/video/out/win32/droptarget.c
nanahi e0b517985b win32: resolve dropped shell links (Windows shortcuts)
When a shell link is dropped onto the mpv window, the file name will be
replaced by the file name of its target so that the linked file is played.
2024-03-19 11:34:00 +01:00

231 lines
6.8 KiB
C

/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdatomic.h>
#include <windows.h>
#include <ole2.h>
#include <shellapi.h>
#include <shobjidl.h>
#include "common/msg.h"
#include "common/common.h"
#include "input/input.h"
#include "input/event.h"
#include "osdep/io.h"
#include "osdep/windows_utils.h"
#include "mpv_talloc.h"
#include "droptarget.h"
struct droptarget {
IDropTarget iface;
atomic_int ref_cnt;
struct mp_log *log;
struct input_ctx *input_ctx;
struct mp_vo_opts *opts;
DWORD last_effect;
IDataObject *data_obj;
};
static FORMATETC fmtetc_file = {
.cfFormat = CF_HDROP,
.dwAspect = DVASPECT_CONTENT,
.lindex = -1,
.tymed = TYMED_HGLOBAL,
};
static FORMATETC fmtetc_url = {
.dwAspect = DVASPECT_CONTENT,
.lindex = -1,
.tymed = TYMED_HGLOBAL,
};
static void DropTarget_Destroy(struct droptarget *t)
{
SAFE_RELEASE(t->data_obj);
talloc_free(t);
}
static STDMETHODIMP DropTarget_QueryInterface(IDropTarget *self, REFIID riid,
void **ppvObject)
{
if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDropTarget)) {
*ppvObject = self;
IDropTarget_AddRef(self);
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
static STDMETHODIMP_(ULONG) DropTarget_AddRef(IDropTarget *self)
{
struct droptarget *t = (struct droptarget *)self;
return atomic_fetch_add(&t->ref_cnt, 1) + 1;
}
static STDMETHODIMP_(ULONG) DropTarget_Release(IDropTarget *self)
{
struct droptarget *t = (struct droptarget *)self;
ULONG ref_cnt = atomic_fetch_add(&t->ref_cnt, -1) - 1;
if (ref_cnt == 0)
DropTarget_Destroy(t);
return ref_cnt;
}
static STDMETHODIMP DropTarget_DragEnter(IDropTarget *self,
IDataObject *pDataObj,
DWORD grfKeyState, POINTL pt,
DWORD *pdwEffect)
{
struct droptarget *t = (struct droptarget *)self;
IDataObject_AddRef(pDataObj);
if (FAILED(IDataObject_QueryGetData(pDataObj, &fmtetc_file)) &&
FAILED(IDataObject_QueryGetData(pDataObj, &fmtetc_url)))
{
*pdwEffect = DROPEFFECT_NONE;
}
SAFE_RELEASE(t->data_obj);
t->data_obj = pDataObj;
t->last_effect = *pdwEffect;
return S_OK;
}
static STDMETHODIMP DropTarget_DragOver(IDropTarget *self, DWORD grfKeyState,
POINTL pt, DWORD *pdwEffect)
{
struct droptarget *t = (struct droptarget *)self;
*pdwEffect = t->last_effect;
return S_OK;
}
static STDMETHODIMP DropTarget_DragLeave(IDropTarget *self)
{
struct droptarget *t = (struct droptarget *)self;
SAFE_RELEASE(t->data_obj);
return S_OK;
}
static STDMETHODIMP DropTarget_Drop(IDropTarget *self, IDataObject *pDataObj,
DWORD grfKeyState, POINTL pt,
DWORD *pdwEffect)
{
struct droptarget *t = (struct droptarget *)self;
enum mp_dnd_action action;
if (t->opts->drag_and_drop >= 0) {
action = t->opts->drag_and_drop;
} else {
action = (grfKeyState & MK_SHIFT) ? DND_APPEND : DND_REPLACE;
}
SAFE_RELEASE(t->data_obj);
STGMEDIUM medium;
if (t->opts->drag_and_drop == -2) {
t->last_effect = DROPEFFECT_NONE;
} else if (SUCCEEDED(IDataObject_GetData(pDataObj, &fmtetc_file, &medium))) {
if (GlobalLock(medium.hGlobal)) {
HDROP drop = medium.hGlobal;
UINT files_num = DragQueryFileW(drop, 0xFFFFFFFF, NULL, 0);
char **files = talloc_zero_array(NULL, char*, files_num);
UINT recvd_files = 0;
for (UINT i = 0; i < files_num; i++) {
UINT len = DragQueryFileW(drop, i, NULL, 0);
wchar_t *buf = talloc_array(NULL, wchar_t, len + 1);
if (DragQueryFileW(drop, i, buf, len + 1) == len) {
wchar_t *target = mp_w32_get_shell_link_target(buf);
char *fname = mp_to_utf8(files, target ? target : buf);
files[recvd_files++] = fname;
talloc_free(target);
MP_VERBOSE(t, "received dropped file: %s\n", fname);
} else {
MP_ERR(t, "error getting dropped file name\n");
}
talloc_free(buf);
}
GlobalUnlock(medium.hGlobal);
mp_event_drop_files(t->input_ctx, recvd_files, files, action);
talloc_free(files);
}
ReleaseStgMedium(&medium);
} else if (SUCCEEDED(IDataObject_GetData(pDataObj, &fmtetc_url, &medium))) {
wchar_t *wurl = GlobalLock(medium.hGlobal);
if (wurl) {
char *url = mp_to_utf8(NULL, wurl);
if (mp_event_drop_mime_data(t->input_ctx, "text/uri-list",
bstr0(url), action) > 0)
{
MP_VERBOSE(t, "received dropped URL: %s\n", url);
} else {
MP_ERR(t, "error getting dropped URL\n");
}
talloc_free(url);
GlobalUnlock(medium.hGlobal);
}
ReleaseStgMedium(&medium);
} else {
t->last_effect = DROPEFFECT_NONE;
}
*pdwEffect = t->last_effect;
return S_OK;
}
static IDropTargetVtbl idroptarget_vtbl = {
.QueryInterface = DropTarget_QueryInterface,
.AddRef = DropTarget_AddRef,
.Release = DropTarget_Release,
.DragEnter = DropTarget_DragEnter,
.DragOver = DropTarget_DragOver,
.DragLeave = DropTarget_DragLeave,
.Drop = DropTarget_Drop,
};
IDropTarget *mp_w32_droptarget_create(struct mp_log *log,
struct mp_vo_opts *opts,
struct input_ctx *input_ctx)
{
fmtetc_url.cfFormat = RegisterClipboardFormatW(L"UniformResourceLocatorW");
struct droptarget *dt = talloc(NULL, struct droptarget);
dt->iface.lpVtbl = &idroptarget_vtbl;
atomic_store(&dt->ref_cnt, 0);
dt->last_effect = 0;
dt->data_obj = NULL;
dt->log = mp_log_new(dt, log, "droptarget");
dt->opts = opts;
dt->input_ctx = input_ctx;
return &dt->iface;
}