diff --git a/DOCS/interface-changes/mpv-register.txt b/DOCS/interface-changes/mpv-register.txt new file mode 100644 index 0000000000..9da0fdb5cc --- /dev/null +++ b/DOCS/interface-changes/mpv-register.txt @@ -0,0 +1 @@ +Add `--register` and `--unregister` diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 3db88416c1..0a2f18097b 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -7969,6 +7969,11 @@ Miscellaneous ``--media-controls=`` (Windows only) Enable integration of media control interface SystemMediaTransportControls. + + Windows may display "Unknown app" or show a missing mpv icon in the media + control panel. To fully support it, you need to register mpv using the + ``--register`` command. + Default: yes (except for libmpv) ``--force-media-title=`` @@ -8213,3 +8218,72 @@ Miscellaneous On Wayland, this option only has effect on the ``wayland`` backend, and not for the ``vo`` backend. See ``current-clipboard-backend`` property for more details. + +``--register`` + (Windows only) + + Registers mpv as a media player on Windows. This includes adding registry + entries to associate mpv with media files and protocols, as well as enabling + autoplay handlers for Blu-ray, DVD, and CD-Audio. + + Note that the registration is done in-place, so the current mpv.exe path will + be used. If you move mpv after registering it, you can re-run this command to + update the registry entries. You can also ``--unregister`` at any time and + using any mpv binary that supports this command, it doesn't have to be + specifically the one that was used to register it. + + When using this option, mpv will exit after completing the process. + To see a detailed list of operations, run mpv with the ``-v`` option. + + The list of the file extensions to register, can be controlled with the + ``--video-exts``, ``--audio-exts``, ``--image-exts``, ``--playlist-exts`` + and ``--archive-exts`` options. + + By default, mpv will be registered for the current user. To register it for + all users, run mpv as an administrator with this option. However, this is + not recommended, as registering it per user is generally preferable. + + You can unregister mpv from the Windows Settings or by running mpv with the + ``--unregister`` option. + +``--register-rpath=`` + (Windows only) + + When registering with ``--register``, this option allows you to specify the + path(s) to prepend so that mpv can find the necessary DLLs. The specified + string will be prepended to the runtime PATH whenever mpv is executed. + + This is useful for setting up paths to external libraries required by mpv + without adding them to the global PATH environment variable. + + The format of the string follows the same structure as the PATH environment + variable, a semicolon-separated list of paths. + + .. note:: + + This sets the ``App Paths`` for mpv in the Windows registry, which + Windows Shell uses to locate the executable and its dependencies. As a + result, mpv can be launched seamlessly in most cases, but not in every + scenario. Notably, running mpv from the command line does not use + `ShellExecute` under the hood, it uses `CreateProcess`, which does not + handle the ``App Paths`` registry key. + + To work around this, you can create a small wrapper PowerShell script that + runs ``Start-Process `` and all will work as expected. + +``--unregister`` + (Windows only) + + Unregisters mpv as a media player on Windows, undoing all changes made by + the ``--register`` option. This will not remove mpv binary itself. + + You can use any mpv binary that supports this command, to unregister, doesn't + have to be specifically the one that was used to register it. + + Windows Settings Application entry is tied to the mpv.exe path. If you + remove the binary, it will not work. However, you can still unregister it + using this command, register it in a new location, or restore mpv to its + original location. + + If mpv was previously registered for all users, run this command as an + administrator to remove it for all users. diff --git a/meson.build b/meson.build index 94ee12feee..39a2e5e94c 100644 --- a/meson.build +++ b/meson.build @@ -518,6 +518,7 @@ if features['win32-desktop'] 'player/clipboard/clipboard-win.c', 'osdep/language-win.c', 'osdep/terminal-win.c', + 'osdep/w32_register.c', 'video/out/w32_common.c', 'video/out/win32/displayconfig.c', 'video/out/win32/droptarget.c', diff --git a/options/options.c b/options/options.c index 767283c790..a1ca7d19cc 100644 --- a/options/options.c +++ b/options/options.c @@ -107,6 +107,8 @@ extern const struct m_sub_options egl_conf; extern const struct m_sub_options mp_sub_filter_opts; +extern const struct m_sub_options w32_register_conf; + static const struct m_sub_options screenshot_conf = { .opts = image_writer_opts, .size = sizeof(struct image_writer_opts), @@ -522,6 +524,7 @@ static const m_option_t mp_opts[] = { {"msg-module", OPT_BOOL(msg_module), .flags = UPDATE_TERM}, {"msg-time", OPT_BOOL(msg_time), .flags = UPDATE_TERM}, #if HAVE_WIN32_DESKTOP + {"", OPT_SUBSTRUCT(w32_register_opts, w32_register_conf)}, {"priority", OPT_CHOICE(w32_priority, {"no", 0}, {"realtime", REALTIME_PRIORITY_CLASS}, diff --git a/options/options.h b/options/options.h index 6f16f50c25..7f2151eb01 100644 --- a/options/options.h +++ b/options/options.h @@ -350,6 +350,7 @@ typedef struct MPOpts { char **playlist_exts; bool osd_bar_visible; + struct w32_register_opts *w32_register_opts; int w32_priority; bool media_controls; diff --git a/osdep/w32_register.c b/osdep/w32_register.c new file mode 100644 index 0000000000..6086db7a4d --- /dev/null +++ b/osdep/w32_register.c @@ -0,0 +1,478 @@ +/* + * 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 . + */ + +#include "w32_register.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "windows_utils.h" + +#define MPV_NAME L"mpv" +#define MPV_FRIENDLY_NAME L"mpv media player" + +#define KEY_AUTOPLAY L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\AutoplayHandlers" +#define KEY_MPV_APP L"Software\\Classes\\Applications\\" MPV_NAME L".exe" +#define KEY_MPV_APP_PATHS L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +#define KEY_MPV_APP_PATH(suffix) KEY_MPV_APP_PATHS MPV_NAME suffix +#define KEY_MPV_CAPABILITIES_APP L"Software\\Clients\\Media\\" MPV_NAME +#define KEY_MPV_CAPABILITIES KEY_MPV_CAPABILITIES_APP L"\\Capabilities" +#define KEY_MPV_UNINSTALL L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" MPV_NAME + +#define MPV_PROG_ID_PREFIX L"io.mpv." +#define MPV_PROG_ID(h) MPV_PROG_ID_PREFIX h +#define KEY_MPV_PROG_ID(h) L"Software\\Classes\\" MPV_PROG_ID(h) + +struct w32_register_opts { + bool register_opt; + char *rpath; + bool unregister; +}; + +#define OPT_BASE_STRUCT struct w32_register_opts +const struct m_sub_options w32_register_conf = { + .opts = (const struct m_option[]) { + {"register", OPT_BOOL(register_opt)}, + {"register-rpath", OPT_STRING(rpath)}, + {"unregister", OPT_BOOL(unregister)}, + {0} + }, + .size = sizeof(struct w32_register_opts) +}; + +static bool is_admin(void) +{ + PSID sid; + SID_IDENTIFIER_AUTHORITY identifier_authority = SECURITY_NT_AUTHORITY; + if (!AllocateAndInitializeSid(&identifier_authority, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sid)) + { + return false; + } + + BOOL member = false; + CheckTokenMembership(NULL, sid, &member); + if (sid) + FreeSid(sid); + + return member; +} + +static void create_shortcut(struct mp_log *log, const wchar_t *target, + const wchar_t *shortcut) +{ + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + mp_msg(log, MSGL_ERR, "Failed to initialize COM: %s\n", + mp_HRESULT_to_str(hr)); + return; + } + + IShellLinkW *shell_link = NULL; + IPersistFile *persist_file = NULL; + hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + &IID_IShellLinkW, (void **)&shell_link); + if (FAILED(hr)) { + mp_msg(log, MSGL_ERR, "Failed to create IShellLinkW instance: %s\n", + mp_HRESULT_to_str(hr)); + goto done; + } + + hr = IShellLinkW_SetPath(shell_link, target); + if (FAILED(hr)) { + mp_msg(log, MSGL_ERR, "Failed to set target path: %s\n", + mp_HRESULT_to_str(hr)); + goto done; + } + + hr = IShellLinkW_QueryInterface(shell_link, &IID_IPersistFile, + (LPVOID *)&persist_file); + if (FAILED(hr)) { + mp_msg(log, MSGL_ERR, "Failed to get IPersistFile interface: %s\n", + mp_HRESULT_to_str(hr)); + goto done; + } + + hr = IPersistFile_Save(persist_file, shortcut, TRUE); + if (FAILED(hr)) { + mp_msg(log, MSGL_ERR, "Failed to save shortcut: %s\n", + mp_HRESULT_to_str(hr)); + goto done; + } + + mp_msg(log, MSGL_V, "Shortcut created: '%ls' -> '%ls'\n", shortcut, target); + +done: + SAFE_RELEASE(persist_file); + SAFE_RELEASE(shell_link); + CoUninitialize(); +} + +static void reg_add(struct mp_log *log, HKEY key, LPCWSTR sub_key, LPCWSTR name, + DWORD type, const BYTE *data, DWORD size) +{ + HKEY sub_key_handle; + if (RegCreateKeyExW(key, sub_key, 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_WRITE, NULL, &sub_key_handle, + NULL) != ERROR_SUCCESS) + { + mp_msg(log, MSGL_ERR, "Failed to create registry key\n"); + return; + } + + if (RegSetValueExW(sub_key_handle, name, 0, type, data, size) != ERROR_SUCCESS) + mp_msg(log, MSGL_ERR, "Failed to set registry value\n"); + + RegCloseKey(sub_key_handle); +} + +static void reg_add_str(struct mp_log *log, HKEY key, LPCWSTR sub_key, + LPCWSTR name, LPCWSTR value) +{ + const wchar_t *root = key == HKEY_LOCAL_MACHINE ? L"HKEY_LOCAL_MACHINE" + : L"HKEY_CURRENT_USER"; + mp_msg(log, MSGL_V, "Adding registry key: %ls\\%ls\\%ls -> '%ls'\n", root, + sub_key, name ? name : L"(Default)", value); + reg_add(log, key, sub_key, name, REG_SZ, (const BYTE *)value, + (wcslen(value) + 1) * sizeof(WCHAR)); +} + +static void reg_add_dwr(struct mp_log *log, HKEY key, LPCWSTR sub_key, + LPCWSTR name, DWORD value) +{ + const wchar_t *root = key == HKEY_LOCAL_MACHINE ? L"HKEY_LOCAL_MACHINE" + : L"HKEY_CURRENT_USER"; + mp_msg(log, MSGL_V, "Adding registry key: %ls\\%ls\\%ls -> %lu\n", root, + sub_key, name ? name : L"(Default)", value); + reg_add(log, key, sub_key, name, REG_DWORD, (const BYTE *)&value, + sizeof(DWORD)); +} + +static void reg_del(struct mp_log *log, HKEY key, LPCWSTR sub_key, + LPCWSTR value) +{ + const wchar_t *root = key == HKEY_LOCAL_MACHINE ? L"HKEY_LOCAL_MACHINE" + : L"HKEY_CURRENT_USER"; + const char *name = value ? "value" : "key"; + mp_msg(log, MSGL_V, "Removing registry %s: %ls\\%ls%ls%ls\n", name, root, + sub_key, value ? L"\\" : L"", value ? value : L""); + + LSTATUS status = value ? RegDeleteKeyValueW(key, sub_key, value) + : RegDeleteTreeW(key, sub_key); + if (status != ERROR_SUCCESS) { + mp_msg(log, status == ERROR_FILE_NOT_FOUND ? MSGL_DEBUG : MSGL_ERR, + "Failed to delete registry %s: %ls\\%ls%ls%ls -> %s\n", name, + root, sub_key, value ? L"\\" : L"", value ? value : L"", + mp_HRESULT_to_str(HRESULT_FROM_WIN32(status))); + } +} + +static uint64_t get_file_size(const wchar_t *path) +{ + WIN32_FILE_ATTRIBUTE_DATA fad; + if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fad)) + return 0; + + return ((uint64_t)fad.nFileSizeHigh << 32) | fad.nFileSizeLow; +} + +static wchar_t *w32_get_shortcut_path(struct mp_log *log) +{ + LPWSTR shortcut_path = NULL; + LPWSTR programs_path = NULL; + if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_Programs, 0, NULL, &programs_path))) { + PathAllocCombine(programs_path, MPV_NAME L".lnk", + PATHCCH_ALLOW_LONG_PATHS, &shortcut_path); + } else { + mp_msg(log, MSGL_ERR, "Failed to get Programs folder path.\n"); + } + CoTaskMemFree(programs_path); + return shortcut_path; +} + +#define REGISTER_AUTOPLAY_HANDLER(media, event, action) \ + reg_add_str(log, root, KEY_MPV_PROG_ID(media) L"\\shell", NULL, L"open"); \ + reg_add_str(log, root, KEY_MPV_PROG_ID(media) L"\\shell\\open\\command", \ + NULL, L"mpv.exe " media L":// --" media L"-device=%L"); \ + reg_add_str(log, root, \ + KEY_AUTOPLAY L"\\Handlers\\MpvPlay" event "OnArrival", \ + L"Action", L"" action); \ + reg_add_str(log, root, \ + KEY_AUTOPLAY L"\\Handlers\\MpvPlay" event "OnArrival", \ + L"DefaultIcon", icon_path); \ + reg_add_str(log, root, \ + KEY_AUTOPLAY L"\\Handlers\\MpvPlay" event "OnArrival", \ + L"InvokeProgID", MPV_PROG_ID(media)); \ + reg_add_str(log, root, \ + KEY_AUTOPLAY L"\\Handlers\\MpvPlay" event "OnArrival", \ + L"InvokeVerb", L"open"); \ + reg_add_str(log, root, \ + KEY_AUTOPLAY L"\\Handlers\\MpvPlay" event "OnArrival", \ + L"Provider", MPV_FRIENDLY_NAME); \ + reg_add_str(log, root, \ + KEY_AUTOPLAY L"\\EventHandlers\\Play" event "OnArrival", \ + L"MpvPlay" event "OnArrival", L""); + +static void w32_register(struct MPContext *mpctx) +{ + void *tmp = talloc_new(NULL); + struct mp_log *log = mp_log_new(tmp, mpctx->log, "win32"); + + LPWSTR mpv_path = talloc_array(tmp, wchar_t, MP_PATH_MAX); + DWORD mpv_path_len = GetModuleFileNameW(NULL, mpv_path, MP_PATH_MAX); + if (!mpv_path_len || mpv_path_len == MP_PATH_MAX) { + mp_msg(log, MSGL_ERR, "Failed to get mpv path.\n"); + return; + } + LPWSTR icon_path = talloc_array(tmp, wchar_t, mpv_path_len + 3); + memcpy(icon_path, mpv_path, mpv_path_len * sizeof(wchar_t)); + icon_path[mpv_path_len + 0] = L','; + icon_path[mpv_path_len + 1] = L'0'; + icon_path[mpv_path_len + 2] = L'\0'; + + HKEY root = is_admin() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + mp_msg(log, MSGL_INFO, "Registering mpv for %s...\n", + root == HKEY_LOCAL_MACHINE ? "all users" : "the current user"); + + // Register mpv as an application + // + reg_add_str(log, root, KEY_MPV_APP_PATH(".exe"), NULL, mpv_path); + reg_add_dwr(log, root, KEY_MPV_APP_PATH(".exe"), L"UseUrl", 1); + + char *rpath = mpctx->opts->w32_register_opts->rpath; + if (rpath) { + wchar_t *rpath_w = mp_from_utf8(tmp, rpath); + reg_add_str(log, root, KEY_MPV_APP_PATH(".exe"), L"Path", rpath_w); + reg_add_str(log, root, KEY_MPV_APP_PATH(".com"), L"Path", rpath_w); + } + + // Name of the "Open With" entry in the context menu + // I prefer a simple "mpv" here, so let's skip this. + // reg_add_str(log, root, KEY_MPV_APP, L"FriendlyAppName", MPV_FRIENDLY_NAME); + + // Add shell open command + reg_add_str(log, root, KEY_MPV_APP L"\\shell", NULL, L"open"); + reg_add_str(log, root, KEY_MPV_APP L"\\shell\\open\\command", NULL, + L"mpv.exe -- \"%L\""); + + // Register mpv capabilities and handlers + + // Note that we do not register any extensions or protocols directly, + // as mpv does not own any. It is simply a media player that can open them. + // Windows will prompt the user to choose the default application for each + // file type encountered. + + // Register only a single URL and file handler; there is no need to + // duplicate this for each supported protocol or file type. + + // Register URL handler + reg_add_str(log, root, KEY_MPV_PROG_ID("url"), NULL, L"URL:mpv"); + reg_add_str(log, root, KEY_MPV_PROG_ID("url"), L"URL Protocol", L""); + reg_add_dwr(log, root, KEY_MPV_PROG_ID("url"), L"EditFlags", FTA_Show); + reg_add_str(log, root, KEY_MPV_PROG_ID("url"), L"FriendlyTypeName", + L"mpv URL handler"); + reg_add_str(log, root, KEY_MPV_PROG_ID("url") L"\\shell", NULL, L"open"); + reg_add_str(log, root, KEY_MPV_PROG_ID("url") L"\\shell\\open\\command", + NULL, L"mpv.exe -- \"%L\""); + reg_add_str(log, root, KEY_MPV_PROG_ID("url") L"\\shell\\open\\ddeexec", + NULL, L""); + + // Register URL protocols + char **safe_protocols = talloc_steal(tmp, stream_get_proto_list(true)); + mp_assert(safe_protocols); + char *protocols_str = NULL; + for (int i = 0; safe_protocols[i]; ++i) { + reg_add_str(log, root, KEY_MPV_CAPABILITIES L"\\URLAssociations", + mp_from_utf8(tmp, safe_protocols[i]), + MPV_PROG_ID("url")); + if (!protocols_str) { + protocols_str = talloc_strdup(tmp, safe_protocols[i]); + } else { + protocols_str = talloc_asprintf_append(protocols_str, ":%s", + safe_protocols[i]); + } + } + if (protocols_str) { + reg_add_str(log, root, KEY_MPV_APP_PATH(".exe"), L"SupportedProtocols", + mp_from_utf8(tmp, protocols_str)); + } + + // Register file handler + reg_add_str(log, root, KEY_MPV_PROG_ID("file"), NULL, L"mpv"); + reg_add_str(log, root, KEY_MPV_PROG_ID("file"), L"FriendlyTypeName", L"mpv Media File"); + reg_add_dwr(log, root, KEY_MPV_PROG_ID("file"), L"EditFlags", FTA_OpenIsSafe); + reg_add_str(log, root, KEY_MPV_PROG_ID("file") L"\\shell", NULL, L"open"); + reg_add_str(log, root, KEY_MPV_PROG_ID("file") L"\\shell\\open\\command", + NULL, L"mpv.exe -- \"%L\""); + + // Register file associations + char **exts_groups[] = { +#if HAVE_LIBARCHIVE + mpctx->opts->archive_exts, +#endif + mpctx->opts->audio_exts, + mpctx->opts->image_exts, + mpctx->opts->playlist_exts, + mpctx->opts->video_exts, + }; + for (int i = 0; i < MP_ARRAY_SIZE(exts_groups); ++i) { + char **exts = exts_groups[i]; + for (int j = 0; exts && exts[j]; ++j) { + wchar_t *ext = mp_from_utf8(tmp, mp_tprintf(10, ".%s", exts[j])); + reg_add_str(log, root, KEY_MPV_APP L"\\SupportedTypes", ext, L""); + reg_add_str(log, root, KEY_MPV_CAPABILITIES L"\\FileAssociations", + ext, MPV_PROG_ID("file")); + } + } + + // Connect above handlers to the application + reg_add_str(log, root, KEY_MPV_CAPABILITIES, L"ApplicationName", MPV_NAME); + reg_add_str(log, root, KEY_MPV_CAPABILITIES, L"ApplicationDescription", MPV_FRIENDLY_NAME); + + // Register the application + reg_add_str(log, root, L"Software\\RegisteredApplications", MPV_NAME, KEY_MPV_CAPABILITIES); + + // + +#if HAVE_CDDA + // Register CDDA handler + REGISTER_AUTOPLAY_HANDLER("cdda", "CDAudio", "Play CD-Audio"); +#endif + +#if HAVE_DVDNAV + // Register DVD handler + REGISTER_AUTOPLAY_HANDLER("dvd", "DVDMovie", "Play DVD movie"); +#endif + +#if HAVE_LIBBLURAY + // Register Blu-ray handler + REGISTER_AUTOPLAY_HANDLER("bluray", "BluRay", "Play Blu-ray movie"); +#endif + + // Create a shortcut in the Start Menu + // Note that this is required for SystemMediaTransportControls to detect the + // app correctly. Which is quite stupid, but it's the only way to make it work. + wchar_t *shortcut_path = w32_get_shortcut_path(log); + if (shortcut_path) { + create_shortcut(log, mpv_path, shortcut_path); + LocalFree(shortcut_path); + } + + // Register uninstaller + // + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"DisplayName", L"mpv media player"); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"DisplayIcon", icon_path); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"DisplayVersion", L"" VERSION); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"Version", L"" VERSION); + reg_add_dwr(log, root, KEY_MPV_UNINSTALL, L"Language", 1033); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"UninstallString", L"mpv.exe --no-config --unregister"); + reg_add_dwr(log, root, KEY_MPV_UNINSTALL, L"NoModify", 1); + reg_add_dwr(log, root, KEY_MPV_UNINSTALL, L"NoRepair", 1); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"Publisher", L"mpv"); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"HelpLink", L"https://mpv.io/manual"); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"Readme", L"https://mpv.io/community"); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"URLInfoAbout", L"https://mpv.io"); + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"URLUpdateInfo", L"https://mpv.io/installation"); + + if (SUCCEEDED(PathCchRemoveFileSpec(mpv_path, MP_PATH_MAX))) + reg_add_str(log, root, KEY_MPV_UNINSTALL, L"InstallLocation", mpv_path); + + uint64_t size = get_file_size(mpv_path); + if (size) + reg_add_dwr(log, root, KEY_MPV_UNINSTALL, L"EstimatedSize", size); + + mp_msg(log, MSGL_INFO, "mpv has been successfully registered.\n"); + mp_msg(log, MSGL_INFO, "Note: The mpv binary has not been copied or moved. Please keep it in the current directory.\n"); + mp_msg(log, MSGL_INFO, "If you move it, simply run this process again to re-register.\n"); + + talloc_free(tmp); +} + +static void w32_unregister(struct MPContext *mpctx) +{ + struct mp_log *log = mp_log_new(NULL, mpctx->log, "win32"); + + HKEY root = is_admin() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + mp_msg(log, MSGL_INFO, "Unregistering mpv for %s...\n", + root == HKEY_LOCAL_MACHINE ? "all users" : "the current user"); + + reg_del(log, root, KEY_MPV_APP_PATH(".exe"), NULL); + reg_del(log, root, KEY_MPV_APP_PATH(".com"), NULL); + reg_del(log, root, KEY_MPV_APP, NULL); + reg_del(log, root, KEY_MPV_CAPABILITIES_APP, NULL); + reg_del(log, root, L"Software\\RegisteredApplications", MPV_NAME); + + reg_del(log, root, KEY_MPV_PROG_ID("bluray"), NULL); + reg_del(log, root, KEY_MPV_PROG_ID("cdda"), NULL); + reg_del(log, root, KEY_MPV_PROG_ID("dvd"), NULL); + reg_del(log, root, KEY_MPV_PROG_ID("file"), NULL); + reg_del(log, root, KEY_MPV_PROG_ID("url"), NULL); + + reg_del(log, root, KEY_AUTOPLAY L"\\Handlers\\MpvPlayCDAudioOnArrival", NULL); + reg_del(log, root, KEY_AUTOPLAY L"\\EventHandlers\\PlayCDAudioOnArrival", + L"MpvPlayCDAudioOnArrival"); + reg_del(log, root, KEY_AUTOPLAY L"\\Handlers\\MpvPlayDVDMovieOnArrival", NULL); + reg_del(log, root, KEY_AUTOPLAY L"\\EventHandlers\\PlayDVDMovieOnArrival", + L"MpvPlayDVDMovieOnArrival"); + reg_del(log, root, KEY_AUTOPLAY L"\\Handlers\\MpvPlayBluRayOnArrival", NULL); + reg_del(log, root, KEY_AUTOPLAY L"\\EventHandlers\\PlayBluRayOnArrival", + L"MpvPlayBluRayOnArrival"); + + wchar_t *shortcut_path = w32_get_shortcut_path(log); + if (shortcut_path) { + mp_msg(log, MSGL_V, "Removing shortcut: '%ls'\n", shortcut_path); + if (!DeleteFileW(shortcut_path)) { + DWORD err = GetLastError(); + mp_msg(log, err == ERROR_FILE_NOT_FOUND ? MSGL_DEBUG : MSGL_ERR, + "Failed to delete shortcut: '%ls' -> %s\n", shortcut_path, + mp_LastError_to_str()); + } + LocalFree(shortcut_path); + } + + reg_del(log, root, KEY_MPV_UNINSTALL, NULL); + + mp_msg(log, MSGL_INFO, "mpv has been successfully unregistered.\n"); + mp_msg(log, MSGL_INFO, "You may now safely delete the mpv binary if desired.\n"); + + talloc_free(log); +} + +bool mp_w32_handle_register(struct MPContext *mpctx) +{ + struct w32_register_opts *opts = mpctx->opts->w32_register_opts; + if (!opts->register_opt && !opts->unregister) + return false; + + if (opts->register_opt) { + w32_register(mpctx); + } else if (opts->unregister) { + w32_unregister(mpctx); + } + + return true; +} diff --git a/osdep/w32_register.h b/osdep/w32_register.h new file mode 100644 index 0000000000..88df3325d6 --- /dev/null +++ b/osdep/w32_register.h @@ -0,0 +1,26 @@ +/* + * 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 . + */ + +#pragma once + +#include + +struct MPContext; + +// Handles the installation of mpv on Windows. +// Return true if mpv should exit. +bool mp_w32_handle_register(struct MPContext *mpctx); diff --git a/player/main.c b/player/main.c index 84a8c2046e..37eb6b18f5 100644 --- a/player/main.c +++ b/player/main.c @@ -77,6 +77,10 @@ static const char def_config[] = #include "osdep/win32/smtc.h" #endif +#if HAVE_WIN32_DESKTOP +#include "osdep/w32_register.h" +#endif + #if HAVE_COCOA #include "osdep/mac/app_bridge.h" #endif @@ -387,6 +391,11 @@ int mp_initialize(struct MPContext *mpctx, char **options) if (handle_help_options(mpctx)) return 1; // help +#if HAVE_WIN32_DESKTOP + if (mp_w32_handle_register(mpctx)) + return 1; // register/unregister +#endif + check_library_versions(mp_null_log, 0); if (!mpctx->playlist->num_entries && !opts->player_idle_mode &&