diff --git a/README.md b/README.md
index af51080..950ff69 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,7 @@ Features
- Customizable context menu defined in the same file as the key bindings
- Config dialog
- Shorcut key editor
+- Global hotkeys
- Many features like the config editor and shortcut key editor are fully searchable
- Configuration files that are easy to read and edit
- Command palette to quickly find commands and keys
diff --git a/docs/Changelog.md b/docs/Changelog.md
index b94fe45..0cd81a0 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -1,15 +1,18 @@
-5.4.8.8 Beta (not yet released)
+5.4.8.8 Beta (2021-05-09)
=========================
- Improved window scaling.
- Title property implementation.
- Command palette shows commands without assigned menu item.
-- The code from the included JavaScript file was ported into the core player
- because JavaScript is currently broken in the builds of shinshiro.
+- The code from the included JavaScript file was ported into the core
+ player because JavaScript is currently broken in the builds of shinshiro.
- New option `--command=`, can be used in combination
with `process-instance=single` to control mpv.net via command line,
for instance to create global hotkeys with AutoHotkey.
+- New global hotkey feature added using the file global-input.conf.
+- The global-media-keys option was removed because global-input.conf
+ can be used instead.
- MediaInfo 21.3
- libmpv shinchiro 2021-04-04
diff --git a/docs/Manual.md b/docs/Manual.md
index 432a583..1b9acc6 100644
--- a/docs/Manual.md
+++ b/docs/Manual.md
@@ -10,6 +10,7 @@ Table of contents
* [Installation](#installation)
* [Support](#support)
* [Settings](#settings)
+* [Input and context menu](#input-and-context-menu)
* [Command Line Interface](#command-line-interface)
* [Terminal](#terminal)
* [mpv.net specific options](#mpvnet-specific-options)
@@ -66,6 +67,13 @@ There is a setup exe and a portable zip file download.
For internet streaming youtube-dl must be downloaded and installed manually,
meaning it must be located in the PATH environment variable or in the startup directory.
+mpvnet.exe is platform agnostic, users that need x86 have to replace 4 native tools:
+
+- Everything.dll
+- mpv-1.dll
+- MediaInfo.dll
+- mpvnet.com
+
#### File Associations
@@ -116,12 +124,16 @@ mpv.net generates it with the following defaults:
mpv.net specific settings are stored in the file mpvnet.conf.
+
+Input and context menu
+----------------------
+
The input (key/mouse) bindings and the context menu definitions are stored in the
input.conf file, if it's missing mpv.net generates it with the following defaults:
[input.conf defaults](../../../tree/master/src/Resources/input.conf.txt)
-mpv.net supports almost all mpv settings and features.
+Global hotkeys are supported via global-input.conf file.
The config folder can be opened from the context menu: `Settings > Open Config Folder`
@@ -185,7 +197,7 @@ Adds files to the playlist, requires [--process-instance=single](#--process-inst
#### --command=\
-Sends a input commands. Useful to control mpv.net from the command line, for instance
+Sends a input command. Useful to control mpv.net from the command line, for instance
to create global hotkeys with AutoHotkey, for that [process-instance=single](#--process-instancevalue)
must be used. Spaces have to be escaped with quotes and quotes have to be escaped with double quotes.
@@ -239,13 +251,6 @@ For single files automatically load the entire directory into the playlist.
Can be suppressed via shift key. Default: yes
-### Input
-
-#### --global-media-keys=\
-
-Enable global media keys next track, previous track, play/pause, stop. Default: no
-
-
### General
#### --update-check=\
diff --git a/src/Misc/App.cs b/src/Misc/App.cs
index 083170b..583d455 100644
--- a/src/Misc/App.cs
+++ b/src/Misc/App.cs
@@ -170,7 +170,6 @@ namespace mpvnet
{
switch (name)
{
- case "global-media-keys": GlobalMediaKeys = value == "yes"; return true;
case "remember-position": RememberPosition = value == "yes"; return true;
case "debug-mode": DebugMode = value == "yes"; return true;
case "remember-volume": RememberVolume = value == "yes"; return true;
diff --git a/src/Misc/GlobalHotkey.cs b/src/Misc/GlobalHotkey.cs
new file mode 100644
index 0000000..43ddeb9
--- /dev/null
+++ b/src/Misc/GlobalHotkey.cs
@@ -0,0 +1,191 @@
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Runtime.InteropServices;
+
+using static mpvnet.Core;
+
+namespace mpvnet
+{
+ class GlobalHotkey
+ {
+ public static Dictionary Commands { get; set; }
+ static int ID;
+ static IntPtr HWND;
+
+ public static void RegisterGlobalHotkeys(IntPtr hwnd)
+ {
+ HWND = hwnd;
+ string path = core.ConfigFolder + "global-input.conf";
+
+ if (!File.Exists(path))
+ return;
+
+ foreach (string i in File.ReadAllLines(path))
+ {
+ string line = i.Trim();
+
+ if (line.StartsWith("#") || !line.Contains(" "))
+ continue;
+
+ ProcessGlobalHotkeyLine(line);
+ }
+ }
+
+ static void ProcessGlobalHotkeyLine(string line)
+ {
+ string key = line.Substring(0, line.IndexOf(" "));
+ string command = line.Substring(line.IndexOf(" ") + 1);
+ string[] parts = key.Split('+');
+ KeyModifiers mod = KeyModifiers.None;
+ int vk;
+
+ for (int i = 0; i < parts.Length - 1; i++)
+ {
+ string umod = parts[i].ToUpper();
+
+ if (umod == "ALT") mod |= KeyModifiers.Alt;
+ if (umod == "CTRL") mod |= KeyModifiers.Ctrl;
+ if (umod == "SHIFT") mod |= KeyModifiers.Shift;
+ if (umod == "WIN") mod |= KeyModifiers.Win;
+ }
+
+ key = parts[parts.Length - 1];
+
+ if (key.Length == 1)
+ {
+ short result = VkKeyScanEx(key[0], GetKeyboardLayout(0));
+
+ int hi = result >> 8;
+ int lo = result & 0xFF;
+
+ if (lo == -1)
+ return;
+
+ vk = lo;
+
+ if ((hi & 1) == 1) mod |= KeyModifiers.Shift;
+ if ((hi & 2) == 2) mod |= KeyModifiers.Ctrl;
+ if ((hi & 4) == 4) mod |= KeyModifiers.Alt;
+ }
+ else
+ vk = mpv_to_VK(key);
+
+ if (Commands == null)
+ Commands = new Dictionary();
+
+ if (vk > 0)
+ {
+ Commands[ID] = command.Trim();
+ bool success = RegisterHotKey(HWND, ID++, mod, vk);
+
+ if (!success)
+ ConsoleHelp.WriteError(line + ": " + new Win32Exception().Message + "\n", "global-input.conf");
+ }
+ }
+
+ public static void Execute(int id)
+ {
+ if (Commands.ContainsKey(id))
+ core.command(Commands[id]);
+ }
+
+ static int mpv_to_VK(string value)
+ {
+ switch (value.ToUpperEx())
+ {
+ case "NEXT" : return 0xB0; // VK_MEDIA_NEXT_TRACK
+ case "PREV" : return 0xB1; // VK_MEDIA_PREV_TRACK
+ case "STOP" : return 0xB2; // VK_MEDIA_STOP
+ case "PLAYPAUSE" : return 0xB3; // VK_MEDIA_PLAY_PAUSE
+ case "SLEEP" : return 0x5F; // VK_SLEEP
+ case "RIGHT" : return 0x27; // VK_RIGHT
+ case "UP" : return 0x26; // VK_UP
+ case "LEFT" : return 0x25; // VK_LEFT
+ case "DOWN" : return 0x28; // VK_DOWN
+ case "PGUP" : return 0x21; // VK_PRIOR
+ case "PGDWN" : return 0x22; // VK_NEXT
+ case "PAUSE" : return 0x13; // VK_PAUSE
+ case "PRINT" : return 0x2A; // VK_PRINT
+ case "HOME" : return 0x24; // VK_HOME
+ case "INS" : return 0x2D; // VK_INSERT
+ case "KP_INS" : return 0x2D; // VK_INSERT
+ case "DEL" : return 0x2E; // VK_DELETE
+ case "KP_DEL" : return 0x2E; // VK_DELETE
+ case "END" : return 0x23; // VK_END
+ case "F1" : return 0x70; // VK_F1
+ case "F2" : return 0x71; // VK_F2
+ case "F3" : return 0x72; // VK_F3
+ case "F4" : return 0x73; // VK_F4
+ case "F5" : return 0x74; // VK_F5
+ case "F6" : return 0x75; // VK_F6
+ case "F7" : return 0x76; // VK_F7
+ case "F8" : return 0x77; // VK_F8
+ case "F9" : return 0x78; // VK_F9
+ case "F10" : return 0x79; // VK_F10
+ case "F11" : return 0x7A; // VK_F11
+ case "F12" : return 0x7B; // VK_F12
+ case "F13" : return 0x7C; // VK_F13
+ case "F14" : return 0x7D; // VK_F14
+ case "F15" : return 0x7E; // VK_F15
+ case "F16" : return 0x7F; // VK_F16
+ case "F17" : return 0x80; // VK_F17
+ case "F18" : return 0x81; // VK_F18
+ case "F19" : return 0x82; // VK_F19
+ case "F20" : return 0x83; // VK_F20
+ case "F21" : return 0x84; // VK_F21
+ case "F22" : return 0x85; // VK_F22
+ case "F23" : return 0x86; // VK_F23
+ case "F24" : return 0x87; // VK_F24
+ case "ENTER" : return 0x0D; // VK_RETURN
+ case "KP_ENTER" : return 0x0D; // VK_RETURN
+ case "TAB" : return 0x09; // VK_TAB
+ case "MENU" : return 0x5D; // VK_APPS
+ case "CANCEL" : return 0x03; // VK_CANCEL
+ case "BS" : return 0x08; // VK_BACK
+ case "KP_DEC" : return 0x6E; // VK_DECIMAL
+ case "ESC" : return 0x1B; // VK_ESCAPE
+ case "KP0" : return 0x60; // VK_NUMPAD0
+ case "KP1" : return 0x61; // VK_NUMPAD1
+ case "KP2" : return 0x62; // VK_NUMPAD2
+ case "KP3" : return 0x63; // VK_NUMPAD3
+ case "KP4" : return 0x64; // VK_NUMPAD4
+ case "KP5" : return 0x65; // VK_NUMPAD5
+ case "KP6" : return 0x66; // VK_NUMPAD6
+ case "KP7" : return 0x67; // VK_NUMPAD7
+ case "KP8" : return 0x68; // VK_NUMPAD8
+ case "KP9" : return 0x69; // VK_NUMPAD9
+ case "FAVORITES" : return 0xAB; // VK_BROWSER_FAVORITES
+ case "SEARCH" : return 0xAA; // VK_BROWSER_SEARCH
+ case "MAIL" : return 0xB4; // VK_LAUNCH_MAIL
+ case "VOLUME_UP" : return 0xAF; // VK_VOLUME_UP
+ case "VOLUME_DOWN": return 0xAE; // VK_VOLUME_DOWN
+ case "MUTE" : return 0xAD; // VK_VOLUME_MUTE
+ case "SPACE" : return 0x20; // VK_SPACE
+ case "IDEOGRAPHIC_SPACE": return 0x20; // VK_SPACE
+ default: return 0;
+ }
+ }
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)]
+ static extern short VkKeyScanEx(char ch, IntPtr dwhkl);
+
+ [DllImport("user32.dll")]
+ static extern IntPtr GetKeyboardLayout(uint idThread);
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, int vk);
+
+ [Flags]
+ enum KeyModifiers
+ {
+ None = 0,
+ Alt = 1,
+ Ctrl = 2,
+ Shift = 4,
+ Win = 8
+ }
+ }
+}
diff --git a/src/Misc/Help.cs b/src/Misc/Help.cs
index 2de046b..409a411 100644
--- a/src/Misc/Help.cs
+++ b/src/Misc/Help.cs
@@ -118,31 +118,6 @@ namespace mpvnet
public class mpvHelp
{
- public static string WM_APPCOMMAND_to_mpv_key(int value)
- {
- switch (value)
- {
- case 5: return "SEARCH"; // BROWSER_SEARCH
- case 6: return "FAVORITES"; // BROWSER_FAVORITES
- case 7: return "HOMEPAGE"; // BROWSER_HOME
- case 15: return "MAIL"; // LAUNCH_MAIL
- case 33: return "PRINT"; // PRINT
- case 11: return "NEXT"; // MEDIA_NEXTTRACK
- case 12: return "PREV"; // MEDIA_PREVIOUSTRACK
- case 13: return "STOP"; // MEDIA_STOP
- case 14: return "PLAYPAUSE"; // MEDIA_PLAY_PAUSE
- case 46: return "PLAY"; // MEDIA_PLAY
- case 47: return "PAUSE"; // MEDIA_PAUSE
- case 48: return "RECORD"; // MEDIA_RECORD
- case 49: return "FORWARD"; // MEDIA_FAST_FORWARD
- case 50: return "REWIND"; // MEDIA_REWIND
- case 51: return "CHANNEL_UP"; // MEDIA_CHANNEL_UP
- case 52: return "CHANNEL_DOWN"; // MEDIA_CHANNEL_DOWN
- }
-
- return null;
- }
-
public static string GetProfiles()
{
string code = @"
diff --git a/src/Misc/Misc.cs b/src/Misc/Misc.cs
index 610d915..85f318f 100644
--- a/src/Misc/Misc.cs
+++ b/src/Misc/Misc.cs
@@ -48,6 +48,34 @@ namespace mpvnet
int IComparer.Compare(string x, string y) => IComparerOfString_Compare(x, y);
}
+ public class Input
+ {
+ public static string WM_APPCOMMAND_to_mpv_key(int value)
+ {
+ switch (value)
+ {
+ case 5: return "SEARCH"; // BROWSER_SEARCH
+ case 6: return "FAVORITES"; // BROWSER_FAVORITES
+ case 7: return "HOMEPAGE"; // BROWSER_HOME
+ case 15: return "MAIL"; // LAUNCH_MAIL
+ case 33: return "PRINT"; // PRINT
+ case 11: return "NEXT"; // MEDIA_NEXTTRACK
+ case 12: return "PREV"; // MEDIA_PREVIOUSTRACK
+ case 13: return "STOP"; // MEDIA_STOP
+ case 14: return "PLAYPAUSE"; // MEDIA_PLAY_PAUSE
+ case 46: return "PLAY"; // MEDIA_PLAY
+ case 47: return "PAUSE"; // MEDIA_PAUSE
+ case 48: return "RECORD"; // MEDIA_RECORD
+ case 49: return "FORWARD"; // MEDIA_FAST_FORWARD
+ case 50: return "REWIND"; // MEDIA_REWIND
+ case 51: return "CHANNEL_UP"; // MEDIA_CHANNEL_UP
+ case 52: return "CHANNEL_DOWN"; // MEDIA_CHANNEL_DOWN
+ }
+
+ return null;
+ }
+ }
+
public class FileAssociation
{
static string ExePath = Application.ExecutablePath;
diff --git a/src/Native/Native.cs b/src/Native/Native.cs
index e3d39de..25a1e27 100644
--- a/src/Native/Native.cs
+++ b/src/Native/Native.cs
@@ -5,11 +5,6 @@ using System.Runtime.InteropServices;
public class WinAPI
{
- public const int VK_MEDIA_NEXT_TRACK = 0xB0;
- public const int VK_MEDIA_PREV_TRACK = 0xB1;
- public const int VK_MEDIA_STOP = 0xB2;
- public const int VK_MEDIA_PLAY_PAUSE = 0xB3;
-
[DllImport("kernel32.dll")]
public static extern bool AttachConsole(int dwProcessId);
@@ -19,9 +14,6 @@ public class WinAPI
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string path);
- [DllImport("user32")]
- public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
-
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindowEx(
IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);
diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs
index f2ff31e..2ad11d3 100644
--- a/src/Properties/AssemblyInfo.cs
+++ b/src/Properties/AssemblyInfo.cs
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("5.4.8.7")]
-[assembly: AssemblyFileVersion("5.4.8.7")]
+[assembly: AssemblyVersion("5.4.8.8")]
+[assembly: AssemblyFileVersion("5.4.8.8")]
diff --git a/src/Release.ps1 b/src/Release.ps1
index 5c35085..acd96bb 100644
--- a/src/Release.ps1
+++ b/src/Release.ps1
@@ -8,8 +8,6 @@ $7z = 'C:\Program Files\7-Zip\7z.exe'
$cloudDirectories = 'C:\Users\frank\OneDrive\Public\mpv.net\',
'C:\Users\frank\Dropbox\Public\mpv.net\'
-# cd $PSScriptRoot
-
function UploadBeta($sourceFile)
{
foreach ($cloudDirectory in $cloudDirectories)
diff --git a/src/Resources/editor.toml.txt b/src/Resources/editor.toml.txt
index 49e2230..8d717fc 100644
--- a/src/Resources/editor.toml.txt
+++ b/src/Resources/editor.toml.txt
@@ -524,15 +524,6 @@ help = "For single files automatically load the entire directory into the playli
options = [{ name = "yes" },
{ name = "no" }]
-[[settings]]
-name = "global-media-keys"
-file = "mpvnet"
-default = "no"
-filter = "Input"
-help = "Enable global media keys next track, previous track, play/pause, stop. (mpv.net specific setting)"
-options = [{ name = "yes" },
- { name = "no" }]
-
[[settings]]
name = "input-ar-delay"
file = "mpv"
diff --git a/src/WPF/LearnWindow.xaml.cs b/src/WPF/LearnWindow.xaml.cs
index ca27cba..eecd82a 100644
--- a/src/WPF/LearnWindow.xaml.cs
+++ b/src/WPF/LearnWindow.xaml.cs
@@ -1,6 +1,5 @@
using System;
-using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
@@ -54,7 +53,7 @@ namespace mpvnet
string ret = ToUnicode(vk, scanCode, keys);
- if (ret.Length == 1 && (int)ret[0] < 32)
+ if (ret.Length == 1 && ret[0] < 32)
return "";
if (ret == "" && (keys[VK_MENU] & 0x80) != 0)
@@ -187,7 +186,7 @@ namespace mpvnet
OnKeyDown((uint)m.WParam.ToInt64());
else if (m.Msg == WM_APPCOMMAND)
{
- string value = mpvHelp.WM_APPCOMMAND_to_mpv_key((int)(m.LParam.ToInt64() >> 16 & ~0xf000));
+ string value = Input.WM_APPCOMMAND_to_mpv_key((int)(m.LParam.ToInt64() >> 16 & ~0xf000));
if (value != null)
SetKey(value);
diff --git a/src/WinForms/MainForm.cs b/src/WinForms/MainForm.cs
index c40f9b8..397991c 100644
--- a/src/WinForms/MainForm.cs
+++ b/src/WinForms/MainForm.cs
@@ -48,14 +48,6 @@ namespace mpvnet
ConsoleHelp.Padding = 60;
core.Init();
- if (App.GlobalMediaKeys)
- {
- RegisterGlobalKey(VK_MEDIA_NEXT_TRACK);
- RegisterGlobalKey(VK_MEDIA_PREV_TRACK);
- RegisterGlobalKey(VK_MEDIA_PLAY_PAUSE);
- RegisterGlobalKey(VK_MEDIA_STOP);
- }
-
core.Shutdown += Shutdown;
core.VideoSizeChanged += VideoSizeChanged;
core.ScaleWindow += ScaleWindow;
@@ -601,7 +593,7 @@ namespace mpvnet
break;
case 0x319: // WM_APPCOMMAND
{
- string value = mpvHelp.WM_APPCOMMAND_to_mpv_key((int)(m.LParam.ToInt64() >> 16 & ~0xf000));
+ string value = Input.WM_APPCOMMAND_to_mpv_key((int)(m.LParam.ToInt64() >> 16 & ~0xf000));
if (value != null)
{
@@ -613,21 +605,7 @@ namespace mpvnet
}
break;
case 0x0312: // WM_HOTKEY
- switch (m.WParam.ToInt64())
- {
- case VK_MEDIA_NEXT_TRACK:
- core.command("keypress NEXT");
- break;
- case VK_MEDIA_PREV_TRACK:
- core.command("keypress PREV");
- break;
- case VK_MEDIA_PLAY_PAUSE:
- core.command("keypress PLAYPAUSE");
- break;
- case VK_MEDIA_STOP:
- core.command("keypress STOP");
- break;
- }
+ GlobalHotkey.Execute(m.WParam.ToInt32());
break;
case 0x0200: // WM_MOUSEMOVE
if (Environment.TickCount - LastCycleFullscreen > 500)
@@ -743,8 +721,6 @@ namespace mpvnet
Taskbar.SetValue(core.get_property_number("time-pos"), core.Duration.TotalSeconds);
}
- void RegisterGlobalKey(int key) => RegisterHotKey(Handle, key, 0, (uint)key);
-
void PropChangeOnTop(bool value) => BeginInvoke(new Action(() => TopMost = value));
void PropChangeAid(string value) => core.Aid = value;
@@ -852,6 +828,7 @@ namespace mpvnet
MinimumSize = new Size(FontHeight * 9, FontHeight * 9);
UpdateCheck.DailyCheck();
core.LoadScripts();
+ GlobalHotkey.RegisterGlobalHotkeys(Handle);
App.RunTask(() => App.Extension = new Extension());
CSharpScriptHost.ExecuteScriptsInFolder(core.ConfigFolder + "scripts-cs");
ShownTickCount = Environment.TickCount;
diff --git a/src/mpv.net.csproj b/src/mpv.net.csproj
index 4258003..924465d 100644
--- a/src/mpv.net.csproj
+++ b/src/mpv.net.csproj
@@ -111,6 +111,7 @@
MSBuild:Compile
+