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 +