Compare commits

..

17 Commits
4.5 ... 4.7

Author SHA1 Message Date
Frank Skare
5c053bb5eb fix borderless window not minimizable from taskbar 2019-07-13 03:24:15 +02:00
Frank Skare
83658aa476 - 2019-07-12 23:53:43 +02:00
Frank Skare
e0111b6f12 - 2019-07-12 06:23:53 +02:00
Frank Skare
e308bbf928 - 2019-07-11 23:55:07 +02:00
Frank Skare
1817fbc0fd - 2019-07-09 09:17:35 +02:00
Frank Skare
3b149aea54 - 2019-07-08 18:18:55 +02:00
Frank Skare
7575a2d736 - 2019-07-06 23:51:28 +02:00
Frank Skare
17c198e711 - 2019-07-06 23:46:14 +02:00
Frank Skare
1f8635e9c4 - 2019-07-06 09:35:14 +02:00
Frank Skare
37e6c198a1 - 2019-07-06 03:46:36 +02:00
Frank Skare
751c8779f4 - 2019-07-06 02:39:25 +02:00
Frank Skare
9e2bf46636 - 2019-07-05 07:17:58 +02:00
Frank Skare
3529323d93 - 2019-07-05 06:36:15 +02:00
Frank Skare
ba52c1f8b5 - 2019-07-05 05:40:14 +02:00
Frank Skare
bf2070d758 - 2019-07-05 05:38:52 +02:00
Frank Skare
7a1de4cf0f - 2019-07-05 05:05:37 +02:00
Frank Skare
caf0bee161 - 2019-07-01 04:17:16 +02:00
21 changed files with 395 additions and 337 deletions

View File

@@ -1,3 +1,37 @@
### 4.7
- remember-height was replaced with start-size, when start-size is set
to video the main video starts directly with the native video size,
before it was starting with the autofit size first and was only
afterwards resized to the native video size
- on exit the window location can be saved with remember-position
- in the learn window of the input editor underscores were stripped
because they have a special meaning in WPF labels
- keys/input not working for MBTN_LEFT_DBL, MBTN_BACK, MBTN_FORWARD
- in the learn window of the input editor support was added for
mouse left, mouse left double, mouse mid, mouse forward, mouse back
- libmpv updated to shinchiro 2019-07-07
- when border is none it wasn't possible to minimize the window from
the task bar because this is the WinForms default behavier. This
was fixed by calling Spy++ to the rescue and adding WS_MINIMIZEBOX
in CreateParams
### 4.6
- fix for middle mouse button not working
- fix of logo overlay using a huge amount of memory (thx for the [ghacks article](https://www.ghacks.net/2019/07/05/a-look-at-mpv-net-a-mpv-frontend-with-everything-integration/))
- fix config dialog showing a message about app restart without reason
- when multiple files are selected in Windows File Explorer and enter is
pressed, the files are opened as selected, the order is random though
because Explorer starts multiple mpv.net processes concurrently
- libmpv was updated to shinchiro 2019-06-30
- the [mpv.conf defaults](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/mpvConf.txt) were changed to show a larger OSC
- in case a file is opened that has a aspect ratio smaller then 1.2 then
the window size will use a aspect ratio of 1.8
- new JavaScript script osc-visibility.js included in the distribution
under startup\scripts. It sets the OSC to be always on for audio files
and auto for non audio files
### 4.5
- opening a URL manually no longer uses a input box but uses the clipboard directly

View File

@@ -102,9 +102,9 @@ mpv.net is meant to be a small single person project, it's designed to be mpv co
### Target Audience
The target audience of mpv.net are Windows programmers and users that need something more advanced than common media players.
The target audience of mpv.net are programmers, nerds and software enthusiasts that need something more advanced than typical media players.
Furthermore mpv.net is well suited for Windows users who are interested to learn about the Linux operating system and portable apps, even though mpv.net self is not portable.
Furthermore mpv.net is well suited for users who are interested to learn mpv, Linux, portable apps and the command line.
## Requirements

View File

@@ -52,12 +52,13 @@ Table of contents
- Searchable command palette to quickly launch commands and look for keys ([Screenshot](#command-palette-screenshot))
- Modern UI with dark mode ([Screenshot](#config-editor-screenshot))
- Addon/extension API for .NET languages
- Scripting API for Python, C#, Lua, JavaScript and PowerShell ([wiki](https://github.com/stax76/mpv.net/wiki/Scripting))
- Scripting API for Python, C#, Lua, JavaScript and PowerShell ([Wiki](https://github.com/stax76/mpv.net/wiki/Scripting))
- mpv's OSC, IPC and conf files
- [Command Line Interface](https://mpv.io/manual/master/#options)
- DXVA2 video decoding acceleration
- OpenGL based video output capable of features loved by videophiles, such as video scaling with popular high quality algorithms, color management, frame timing, interpolation, HDR, and more
- Search feature powered by [Everything](https://www.voidtools.com) to find and play media ([Screenshot](#media-search-screenshot))
- Extension to start mpv.net from Google Chrome ([Manual](Manual.md#chrome-extension))
### Screenshots
@@ -86,7 +87,7 @@ A searchable key and mouse binding editor.
#### Command Palette Screenshot
Forgot where a command in the menu is located or what shortcut key it has?
Just press Ctrl+Shift+P and find it easily in the searchable command palette.
Just press F1 and find it easily in the searchable command palette.
![Command Palette](https://raw.githubusercontent.com/stax76/mpv.net/master/img/CommandPalette.png)
@@ -194,6 +195,10 @@ Third party components:
Please click on the star at the top of the page and like mpv.net at [alternativeto.net](https://alternativeto.net/software/mpv-net/).
If you like you can express your appreciation for my player by sending little beer money with paypal.
<https://www.paypal.me/stax76>
### Links
mpv manual: <https://mpv.io/manual/master/>

View File

@@ -1752,7 +1752,6 @@ namespace Tommy
}
else
stringBuilder.Append(c);
break;
}
}

View File

@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
@@ -13,42 +12,43 @@ namespace mpvnet
{
public class Command
{
public string Name { get; set; }
public Action<string[]> Action { get; set; }
static List<Command> commands;
public static List<Command> Commands
public static void Execute(string id, string[] args)
{
get
switch (id)
{
if (commands == null)
{
commands = new List<Command>();
Type type = typeof(Command);
MethodInfo[] methods = type.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
foreach (MethodInfo i in methods)
{
ParameterInfo[] parameters = i.GetParameters();
if (parameters == null ||
parameters.Length != 1 ||
parameters[0].ParameterType != typeof(string[]))
continue;
Command cmd = new Command() {
Name = i.Name.Replace("_", "-"),
Action = (Action<string[]>)i.CreateDelegate(typeof(Action<string[]>)) };
commands.Add(cmd);
}
}
return commands;
case "manage-file-associations": ManageFileAssociations(); break;
case "cycle-audio": CycleAudio(); break;
case "load-audio": LoadAudio(); break;
case "load-sub": LoadSubtitle(); break;
case "open-url": OpenURL(); break;
case "execute-mpv-command": ExecuteMpvCommand(); break;
case "show-history": ShowHistory(); break;
case "show-media-search": ShowDialog(typeof(EverythingWindow)); break;
case "show-command-palette": ShowDialog(typeof(CommandPaletteWindow)); break;
case "show-about": ShowDialog(typeof(AboutWindow)); break;
case "show-conf-editor": ShowDialog(typeof(ConfWindow)); break;
case "show-input-editor": ShowDialog(typeof(InputWindow)); break;
case "open-conf-folder": Process.Start(mp.ConfFolder); break;
case "open-files": OpenFiles(args); break;
case "shell-execute": Process.Start(args[0]); break;
case "show-info": ShowInfo(); break;
case "add-files-to-playlist": OpenFiles("append"); break; // deprecated 2019
default: Msg.ShowError($"No command '{id}' found."); break;
}
}
public static void open_files(string[] args)
public static void InvokeOnMainThread(Action action) => MainForm.Instance.Invoke(action);
public static void ShowDialog(Type winType)
{
InvokeOnMainThread(new Action(() => {
Window win = Activator.CreateInstance(winType) as Window;
new WindowInteropHelper(win).Owner = MainForm.Instance.Handle;
win.ShowDialog();
}));
}
public static void OpenFiles(params string[] args)
{
bool append = Control.ModifierKeys.HasFlag(Keys.Control);
bool loadFolder = true;
@@ -59,75 +59,14 @@ namespace mpvnet
if (arg == "no-folder") loadFolder = false;
}
MainForm.Instance.Invoke(new Action(() => {
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog() { Multiselect = true })
if (d.ShowDialog() == DialogResult.OK)
mp.Load(d.FileNames, loadFolder, append);
}));
}
// deprecated in 2019
public static void add_files_to_playlist(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
using (var d = new OpenFileDialog() { Multiselect = true })
if (d.ShowDialog() == DialogResult.OK)
foreach (string file in d.FileNames)
mp.commandv("loadfile", file, "append");
}));
}
public static void open_conf_folder(string[] args)
{
Process.Start(mp.ConfFolder);
}
public static void show_input_editor(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
InputWindow w = new InputWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_conf_editor(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
ConfWindow w = new ConfWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_about(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
AboutWindow w = new AboutWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_command_palette(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
var w = new CommandPaletteWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_media_search(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
var w = new EverythingWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_history(string[] args)
public static void ShowHistory()
{
var fp = mp.ConfFolder + "history.txt";
@@ -139,9 +78,7 @@ namespace mpvnet
File.WriteAllText(fp, "");
}
public static void shell_execute(string[] args) => Process.Start(args[0]);
public static void show_info(string[] args)
public static void ShowInfo()
{
try
{
@@ -205,9 +142,9 @@ namespace mpvnet
}
}
public static void execute_mpv_command(string[] args)
public static void ExecuteMpvCommand()
{
MainForm.Instance.Invoke(new Action(() => {
InvokeOnMainThread(new Action(() => {
string command = VB.Interaction.InputBox("Enter a mpv command to be executed.", "Execute Command", RegHelp.GetString(App.RegPath, "RecentExecutedCommand"));
if (string.IsNullOrEmpty(command)) return;
RegHelp.SetObject(App.RegPath, "RecentExecutedCommand", command);
@@ -215,22 +152,22 @@ namespace mpvnet
}));
}
public static void open_url(string[] args)
public static void OpenURL()
{
MainForm.Instance.Invoke(new Action(() => {
string clipboard = Clipboard.GetText();
InvokeOnMainThread(new Action(() => {
string clipboard = System.Windows.Forms.Clipboard.GetText();
if (string.IsNullOrEmpty(clipboard) || (!clipboard.Contains("://") && !File.Exists(clipboard)) || clipboard.Contains("\n"))
{
Msg.ShowError("The clipboard does not contain a valid URL or file, URLs have to contain :// and is not allowed to contain a newline character.");
Msg.ShowError("The clipboard does not contain a valid URL or file, URLs have to contain :// and are not allowed to contain a newline character.");
return;
}
mp.Load(new [] { clipboard }, false, Control.ModifierKeys.HasFlag(Keys.Control));
}));
}
public static void load_sub(string[] args)
public static void LoadSubtitle()
{
MainForm.Instance.Invoke(new Action(() => {
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog())
{
d.InitialDirectory = Path.GetDirectoryName(mp.get_property_string("path", false));
@@ -242,9 +179,9 @@ namespace mpvnet
}));
}
public static void load_audio(string[] args)
public static void LoadAudio()
{
MainForm.Instance.Invoke(new Action(() => {
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog())
{
d.InitialDirectory = Path.GetDirectoryName(mp.get_property_string("path", false));
@@ -257,7 +194,7 @@ namespace mpvnet
}));
}
public static void cycle_audio(string[] args)
public static void CycleAudio()
{
string filePath = mp.get_property_string("path", false);
if (!File.Exists(filePath)) return;
@@ -274,7 +211,7 @@ namespace mpvnet
}
}
public static void manage_file_associations(string[] args)
public static void ManageFileAssociations()
{
using (var td = new TaskDialog<string>())
{
@@ -283,7 +220,7 @@ namespace mpvnet
td.AddCommandLink("Register video file extensions", "video");
td.AddCommandLink("Register audio file extensions", "audio");
td.AddCommandLink("Unregister file extensions", "unreg");
td.AddCommandLink("Unregister file extensions", "unreg");
string result = td.Show();
@@ -291,14 +228,10 @@ namespace mpvnet
{
using (var proc = new Process())
{
proc.StartInfo.FileName = Application.ExecutablePath;
proc.StartInfo.FileName = System.Windows.Forms.Application.ExecutablePath;
proc.StartInfo.Arguments = "--reg-file-assoc " + result;
proc.StartInfo.Verb = "runas";
try {
proc.Start();
}
catch (Exception)
{ }
try { proc.Start(); } catch { }
}
}
}

View File

@@ -28,6 +28,8 @@ namespace mpvnet
public static string[] SubtitleTypes { get; } = "srt ass idx sup ttxt ssa smi".Split(' ');
public static string[] UrlWhitelist { get; set; } = { "tube", "vimeo", "ard", "zdf" };
public static bool RememberHeight { get; set; } = true;
public static bool RememberPosition { get; set; }
public static bool DebugMode { get; set; } = false;
public static bool IsDarkMode {
@@ -36,6 +38,9 @@ namespace mpvnet
public static void Init()
{
string dummy = mp.ConfFolder;
var dummy2 = mp.Conf;
foreach (var i in Conf)
ProcessProperty(i.Key, i.Value);
@@ -83,6 +88,8 @@ namespace mpvnet
{
switch (name)
{
case "remember-position": RememberPosition = value == "yes"; break;
case "start-size": RememberHeight = value == "previous"; break;
case "process-instance": ProcessInstance = value; break;
case "dark-mode": DarkMode = value; break;
case "debug-mode": DebugMode = value == "yes"; break;
@@ -109,15 +116,9 @@ namespace mpvnet
}
else
{
string switchName = i.Substring(2);
switch (switchName)
{
case "fs":
case "fullscreen":
mp.Fullscreen = true;
break;
}
string name = i.Substring(2);
mp.ProcessProperty(name, "yes");
ProcessProperty(name, "yes");
}
}
}
@@ -228,6 +229,13 @@ namespace mpvnet
return val.ToString();
}
public static int GetInt(string path, string name)
{
object val = GetObject(path, name);
if (val == null || !(val is int)) return 0;
return (int)val;
}
public static object GetObject(string path, string name)
{
using (RegistryKey rk = GetRootKey(path).OpenSubKey(path.Substring(5)))
@@ -393,15 +401,4 @@ namespace mpvnet
Math.Abs(screenPos.Y - Control.MousePosition.Y) > 10;
}
}
public class SingleProcess
{
public static int Message { get; } = RegisterWindowMessage("mpvnet_IPC");
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int RegisterWindowMessage(string id);
[DllImport("user32.dll")]
public static extern bool AllowSetForegroundWindow(int dwProcessId);
}
}

View File

@@ -31,24 +31,31 @@ namespace mpvnet
if ((App.ProcessInstance == "single" || App.ProcessInstance == "queue") && !isFirst)
{
List<string> files = new List<string>();
files.Add(App.ProcessInstance);
foreach (string arg in args)
if (!arg.StartsWith("--") && (arg == "-" || arg.Contains("://") || File.Exists(arg)))
files.Add(arg);
if (files.Count > 0)
RegHelp.SetObject(App.RegPath, "ShellFiles", files.ToArray());
Process[] procs = Process.GetProcessesByName("mpvnet");
RegHelp.SetObject(App.RegPath, "ProcessInstanceMode", App.ProcessInstance);
foreach(Process process in Process.GetProcessesByName("mpvnet"))
for (int i = 0; i < 20; i++)
{
try {
SingleProcess.AllowSetForegroundWindow(process.Id);
Native.SendMessage(process.MainWindowHandle, SingleProcess.Message, IntPtr.Zero, IntPtr.Zero);
} catch {}
foreach (Process proc in procs)
{
if (proc.MainWindowHandle != IntPtr.Zero)
{
Native.AllowSetForegroundWindow(proc.Id);
var data = new Native.COPYDATASTRUCT();
data.lpData = string.Join("\n", files.ToArray());
data.cbData = data.lpData.Length * 2 + 1;
Native.SendMessage(proc.MainWindowHandle, 0x004A /*WM_COPYDATA*/, IntPtr.Zero, ref data);
mutex.Dispose();
return;
}
}
Thread.Sleep(50);
}
mutex.Dispose();
return;
}

View File

@@ -15,9 +15,15 @@ namespace mpvnet
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref COPYDATASTRUCT lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool AllowSetForegroundWindow(int dwProcessId);
[DllImport("user32.dll")]
public static extern void ReleaseCapture();
@@ -33,7 +39,7 @@ namespace mpvnet
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
private static extern IntPtr GetWindowLong64(IntPtr hWnd, int nIndex);
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
public static IntPtr GetWindowLong(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLong64(hWnd, nIndex);
@@ -70,5 +76,14 @@ namespace mpvnet
public int Width => Right - Left;
public int Height => Bottom - Top;
}
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
}
}

View File

@@ -32,7 +32,7 @@ namespace mpvnet
public static void AddWindowBorders(IntPtr hwnd, ref Native.RECT rc)
{
Native.AdjustWindowRect(ref rc, (uint)Native.GetWindowLongPtr(hwnd, -16 /* GWL_STYLE */), false);
Native.AdjustWindowRect(ref rc, (uint)Native.GetWindowLong(hwnd, -16 /* GWL_STYLE */), false);
}
}
}

View File

@@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("mpv.net")]
[assembly: AssemblyDescription("A modern media player")]
[assembly: AssemblyDescription("media player")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Frank Skare (stax76)")]
[assembly: AssemblyProduct("mpv.net")]
@@ -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("4.5.0.0")]
[assembly: AssemblyFileVersion("4.5.0.0")]
[assembly: AssemblyVersion("4.7.0.0")]
[assembly: AssemblyFileVersion("4.7.0.0")]

View File

@@ -24,6 +24,8 @@
# mpv input keys: https://github.com/stax76/mpv.net/wiki/mpv-input-keys
# run mpv.net in input test mode with: mpvnet --input-test
o script-message mpv.net open-files #menu: Open > Open Files...
u script-message mpv.net open-url #menu: Open > Open URL or file path from clipboard
_ ignore #menu: Open > -
@@ -183,3 +185,4 @@
Ctrl+Wheel_Up no-osd seek 7
Ctrl+Wheel_Down no-osd seek -7
MBTN_LEFT_DBL cycle fullscreen
KP_ENTER cycle fullscreen

View File

@@ -12,3 +12,4 @@ keep-open-pause = no
osd-playing-msg = '${filename}'
screenshot-directory = '~~desktop/'
input-default-bindings = no
script-opts=osc-scalewindowed=1.5

View File

@@ -254,14 +254,6 @@ default = "50%"
filter = "Screen"
help = "Set the initial window size in percent. Please note that this setting is only partly implemented in mpv.net, accepted are only integer values with percent sign added. Default: 50%"
[[settings]]
name = "remember-height"
default = "yes"
filter = "Screen"
help = "mpv.net specific setting to remember the window height, otherwise the video's native resolution is used."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "keep-open-pause"
default = "yes"

View File

@@ -29,3 +29,19 @@ filter = "General"
help = "mpv.net specific setting that writes debug info to a file located on the desktop."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "start-size"
default = "previous"
filter = "Screen"
help = "mpv.net specific setting to remember the window height in the current session, otherwise the video's native resolution is used."
options = [{ name = "video", help = "Window size is set to native video resolution" },
{ name = "previous", help = "Window size is remembered but only from the current session" }]
[[settings]]
name = "remember-position"
default = "no"
filter = "Screen"
help = "mpv.net specific setting to save the window position on exit."
options = [{ name = "yes" },
{ name = "no" }]

View File

@@ -59,7 +59,7 @@ namespace mpvnet
if (setting.Name == pair.Key)
{
setting.Value = pair.Value.Trim('\'', '"');
setting.StartValue = pair.Value;
setting.StartValue = pair.Value.Trim('\'', '"');
continue;
}
}

View File

@@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Learn Input" Height="200" Width="400" WindowStartupLocation="CenterOwner"
ResizeMode="NoResize" Loaded="Window_Loaded" Background="Black" MouseWheel="Window_MouseWheel">
ResizeMode="NoResize" Loaded="Window_Loaded" Background="Black" MouseWheel="Window_MouseWheel" MouseUp="Window_MouseUp" MouseDoubleClick="Window_MouseDoubleClick">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
@@ -16,8 +16,8 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label x:Name="MenuLabel" Grid.ColumnSpan="2" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="16"></Label>
<Label x:Name="KeyLabel" Grid.Row="1" Grid.ColumnSpan="2" Foreground="White" VerticalAlignment="Top" HorizontalAlignment="Center" FontSize="16"></Label>
<TextBlock x:Name="MenuTextBlock" Grid.ColumnSpan="2" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="16"></TextBlock>
<TextBlock x:Name="KeyTextBlock" Grid.Row="1" Grid.ColumnSpan="2" Foreground="White" VerticalAlignment="Top" HorizontalAlignment="Center" FontSize="16"></TextBlock>
<Button x:Name="ConfirmButton" Grid.Row="2" Click="ConfirmButton_Click">Confirm</Button>
<Button x:Name="ClearButton" Grid.Row="2" Click="ClearButton_Click" Grid.Column="1">Clear</Button>
</Grid>

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
@@ -12,10 +13,7 @@ namespace mpvnet
public CommandItem InputItem { get; set; }
public string NewKey { get; set; } = "";
public LearnWindow()
{
InitializeComponent();
}
public LearnWindow() => InitializeComponent();
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
@@ -43,8 +41,7 @@ namespace mpvnet
else
try {
text = Convert.ToChar(charValue).ToString().ToLower().Trim();
}
catch {}
} catch {}
for (int i = 0; i < 13; i++)
if ("D" + i.ToString() == text)
@@ -144,8 +141,8 @@ namespace mpvnet
void SetKey(string key)
{
NewKey = key;
MenuLabel.Content = InputItem.Path;
KeyLabel.Content = key;
MenuTextBlock.Text = InputItem.Path;
KeyTextBlock.Text = key;
}
[DllImport("user32.dll")]
@@ -295,5 +292,38 @@ namespace mpvnet
else
SetKey("WHEEL_DOWN");
}
private void Window_MouseUp(object sender, MouseButtonEventArgs e)
{
switch (e.ChangedButton)
{
case MouseButton.Left:
if (BlockMBTN_LEFT)
BlockMBTN_LEFT = false;
else
SetKey("MBTN_LEFT");
break;
case MouseButton.Middle:
SetKey("MBTN_MID");
break;
case MouseButton.XButton1:
SetKey("MBTN_BACK");
break;
case MouseButton.XButton2:
SetKey("MBTN_FORWARD");
break;
}
}
bool BlockMBTN_LEFT;
private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
SetKey("MBTN_LEFT_DBL");
BlockMBTN_LEFT = true;
}
}
}
}

View File

@@ -45,7 +45,7 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(288F, 288F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.BackColor = System.Drawing.Color.Black;
this.ClientSize = new System.Drawing.Size(606, 368);
this.ClientSize = new System.Drawing.Size(1777, 1109);
this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);

View File

@@ -8,6 +8,7 @@ using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Threading.Tasks;
namespace mpvnet
{
@@ -36,7 +37,6 @@ namespace mpvnet
WPF.WPF.Init();
System.Windows.Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;
Hwnd = Handle;
MinimumSize = new Size(FontHeight * 16, FontHeight * 9);
Text += " " + Application.ProductVersion;
object recent = RegHelp.GetObject(App.RegPath, "Recent");
@@ -46,11 +46,40 @@ namespace mpvnet
else
RecentFiles = new List<string>();
var dummy = mp.Conf;
App.ProcessCommandLineEarly();
if (mp.Screen == -1) mp.Screen = Array.IndexOf(Screen.AllScreens, Screen.PrimaryScreen);
SetScreen(mp.Screen);
CycleFullscreen(mp.Fullscreen);
int targetIndex = mp.Screen;
Screen[] screens = Screen.AllScreens;
if (targetIndex < 0) targetIndex = 0;
if (targetIndex > screens.Length - 1) targetIndex = screens.Length - 1;
Screen screen = screens[Array.IndexOf(screens, screens[targetIndex])];
Rectangle target = screen.Bounds;
Left = target.X + (target.Width - Width) / 2;
Top = target.Y + (target.Height - Height) / 2;
int posX = RegHelp.GetInt(App.RegPath, "PosX");
int posY = RegHelp.GetInt(App.RegPath, "PosY");
if (posX != 0 && posY != 0 && App.RememberPosition)
{
Left = posX - Width / 2;
Top = posY - Height / 2;
}
mp.Shutdown += Shutdown;
mp.VideoSizeChanged += VideoSizeChanged;
mp.FileLoaded += FileLoaded;
mp.Idle += Idle;
Task.Run(() => mp.Init());
mp.VideoSizeAutoResetEvent.WaitOne(1000);
mp.observe_property_bool("fullscreen", PropChangeFullscreen);
mp.observe_property_bool("ontop", PropChangeOnTop);
mp.observe_property_bool("border", PropChangeBorder);
mp.observe_property_string("sid", PropChangeSid);
mp.observe_property_string("aid", PropChangeAid);
mp.observe_property_string("vid", PropChangeVid);
mp.observe_property_int("edition", PropChangeEdition);
}
catch (Exception ex)
{
@@ -60,7 +89,7 @@ namespace mpvnet
public MenuItem FindMenuItem(string text) => FindMenuItem(text, ContextMenu.Items);
void Idle() => BeginInvoke(new Action(() => { Text = "mpv.net " + Application.ProductVersion; }));
void Idle() => BeginInvoke(new Action(() => Text = "mpv.net " + Application.ProductVersion));
void CM_Popup(object sender, EventArgs e) => CursorHelp.Show();
@@ -192,53 +221,81 @@ namespace mpvnet
return null;
}
protected void SetScreen(int targetIndex)
{
Screen[] screens = Screen.AllScreens;
if (targetIndex < 0) targetIndex = 0;
if (targetIndex > screens.Length - 1) targetIndex = screens.Length - 1;
SetScreen(screens[Array.IndexOf(screens, screens[targetIndex])]);
}
protected void SetScreen(Screen screen)
{
Rectangle target = screen.Bounds;
Left = target.X + Convert.ToInt32((target.Width - Width) / 2.0);
Top = target.Y + Convert.ToInt32((target.Height - Height) / 2.0);
SetStartFormPositionAndSize();
}
void SetStartFormPositionAndSize()
{
if (IsFullscreen || mp.VideoSize.Width == 0) return;
Screen screen = Screen.FromControl(this);
int height = Convert.ToInt32(screen.Bounds.Height * mp.Autofit);
int width = Convert.ToInt32(height * mp.VideoSize.Width / (double)mp.VideoSize.Height);
Point middlePos = new Point(Left + Width / 2, Top + Height / 2);
var rect = new Native.RECT(new Rectangle(screen.Bounds.X, screen.Bounds.Y, width, height));
NativeHelp.AddWindowBorders(Handle, ref rect);
int left = middlePos.X - rect.Width / 2;
int top = middlePos.Y - rect.Height / 2;
Native.SetWindowPos(Handle, IntPtr.Zero /* HWND_TOP */, left, top, rect.Width, rect.Height, 4 /* SWP_NOZORDER */);
}
void SetFormPosAndSize()
{
if (IsFullscreen || mp.VideoSize.Width == 0) return;
if (mp.Fullscreen)
{
CycleFullscreen(true);
return;
}
Size size = mp.VideoSize;
Screen screen = Screen.FromControl(this);
int height = mp.VideoSize.Height;
if (mp.RememberHeight) height = ClientSize.Height;
if (height > screen.Bounds.Height * 0.9) height = Convert.ToInt32(screen.Bounds.Height * mp.Autofit);
int width = Convert.ToInt32(height * mp.VideoSize.Width / (double)mp.VideoSize.Height);
int fixedHeight = Convert.ToInt32(screen.Bounds.Height * mp.Autofit);
if (size.Height == 0 || size.Width == 0 || size.Width / (float)size.Height < 1.3)
{
size.Height = fixedHeight;
size.Width = (int)(fixedHeight * 1.7);
}
int height = size.Height;
if (App.RememberHeight)
height = ClientSize.Height;
if (height > screen.Bounds.Height * 0.9)
height = fixedHeight;
int width = Convert.ToInt32(height * size.Width / (double)size.Height);
Point middlePos = new Point(Left + Width / 2, Top + Height / 2);
var rect = new Native.RECT(new Rectangle(screen.Bounds.X, screen.Bounds.Y, width, height));
NativeHelp.AddWindowBorders(Handle, ref rect);
int left = middlePos.X - rect.Width / 2;
int top = middlePos.Y - rect.Height / 2;
Screen[] screens = Screen.AllScreens;
int minLeft = screens.Select(val => val.WorkingArea.X).Min();
int maxRight = screens.Select(val => val.WorkingArea.Right).Max();
int minTop = screens.Select(val => val.WorkingArea.Y).Min();
int maxBottom = screens.Select(val => val.WorkingArea.Bottom).Max();
if (left < minLeft) left = minLeft;
if (left + rect.Width > maxRight) left = maxRight - rect.Width;
if (top < minTop) top = minTop;
if (top + rect.Height > maxBottom) top = maxBottom - rect.Height;
Native.SetWindowPos(Handle, IntPtr.Zero /* HWND_TOP */, left, top, rect.Width, rect.Height, 4 /* SWP_NOZORDER */);
}
public void CycleFullscreen(bool enabled)
{
mp.Fullscreen = enabled;
if (enabled)
{
if (WindowState != FormWindowState.Maximized)
{
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
}
}
else
{
if (WindowState == FormWindowState.Maximized)
{
WindowState = FormWindowState.Normal;
if (mp.Border)
FormBorderStyle = FormBorderStyle.Sizable;
else
FormBorderStyle = FormBorderStyle.None;
SetFormPosAndSize();
}
}
}
public void BuildMenu()
{
string content = File.ReadAllText(mp.InputConfPath);
@@ -293,26 +350,11 @@ namespace mpvnet
Msg.ShowError(e.ExceptionObject.ToString());
}
public void CycleFullscreen(bool enabled)
{
if (enabled)
{
if (WindowState != FormWindowState.Maximized)
{
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
}
}
else
{
WindowState = FormWindowState.Normal;
if (mp.Border)
FormBorderStyle = FormBorderStyle.Sizable;
else
FormBorderStyle = FormBorderStyle.None;
SetFormPosAndSize();
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.Style |= 0x00020000 /* WS_MINIMIZEBOX */;
return cp;
}
}
@@ -324,18 +366,24 @@ namespace mpvnet
{
case 0x0201: // WM_LBUTTONDOWN
case 0x0202: // WM_LBUTTONUP
case 0x0207: // WM_MBUTTONDOWN
case 0x0208: // WM_MBUTTONUP
case 0x020A: // WM_MOUSEWHEEL
case 0x020C: // WM_XBUTTONUP
case 0x020B: // WM_XBUTTONDOWN
case 0x0100: // WM_KEYDOWN
case 0x0101: // WM_KEYUP
case 0x0104: // WM_SYSKEYDOWN
case 0x0105: // WM_SYSKEYUP
case 0x020A: // WM_MOUSEWHEEL
if (mp.WindowHandle != IntPtr.Zero)
Native.SendMessage(mp.WindowHandle, m.Msg, m.WParam, m.LParam);
break;
case 0x0200: // WM_MOUSEMOVE
Point pos = PointToClient(Cursor.Position);
mp.command_string($"mouse {pos.X} {pos.Y}");
if (CursorHelp.IsPosDifferent(LastCursorPosChanged)) CursorHelp.Show();
{
Point pos = PointToClient(Cursor.Position);
mp.command_string($"mouse {pos.X} {pos.Y}");
if (CursorHelp.IsPosDifferent(LastCursorPosChanged)) CursorHelp.Show();
}
break;
case 0x2a3: // WM_MOUSELEAVE
mp.command_string("mouse 1 1"); // osc won't always auto hide
@@ -345,7 +393,10 @@ namespace mpvnet
Native.PostMessage(mp.WindowHandle, m.Msg, m.WParam, m.LParam);
break;
case 0x203: // Native.WM.LBUTTONDBLCLK
if (!IsMouseInOSC()) mp.command_string("cycle fullscreen");
{
Point pos = PointToClient(Cursor.Position);
mp.command_string($"mouse {pos.X} {pos.Y} 0 double");
}
break;
case 0x02E0: // WM_DPICHANGED
if (IgnoreDpiChanged) break;
@@ -363,22 +414,17 @@ namespace mpvnet
int[] d_corners = { d_w, d_h, -d_w, -d_h };
int[] corners = { rc.Left, rc.Top, rc.Right, rc.Bottom };
int corner = NativeHelp.GetResizeBorder(m.WParam.ToInt32());
if (corner >= 0)
corners[corner] -= d_corners[corner];
if (corner >= 0) corners[corner] -= d_corners[corner];
Marshal.StructureToPtr<Native.RECT>(new Native.RECT(corners[0], corners[1], corners[2], corners[3]), m.LParam, false);
m.Result = new IntPtr(1);
return;
}
case 0x004A: // WM_COPYDATA
var copyData = (Native.COPYDATASTRUCT)m.GetLParam(typeof(Native.COPYDATASTRUCT));
string[] files = copyData.lpData.Split('\n');
string mode = files[0];
files = files.Skip(1).ToArray();
if (m.Msg == SingleProcess.Message)
{
object filesObject = RegHelp.GetObject(App.RegPath, "ShellFiles");
if (filesObject is string[] files)
{
switch (RegHelp.GetString(App.RegPath, "ProcessInstanceMode"))
switch (mode)
{
case "single":
mp.Load(files, true, Control.ModifierKeys.HasFlag(Keys.Control));
@@ -388,11 +434,9 @@ namespace mpvnet
mp.commandv("loadfile", file, "append");
break;
}
}
RegHelp.RemoveValue(App.RegPath, "ShellFiles");
Activate();
return;
Activate();
return;
}
base.WndProc(ref m);
@@ -452,23 +496,6 @@ namespace mpvnet
}));
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
mp.Init();
mp.observe_property_bool("fullscreen", PropChangeFullscreen);
mp.observe_property_bool("ontop", PropChangeOnTop);
mp.observe_property_bool("border", PropChangeBorder);
mp.observe_property_string("sid", PropChangeSid);
mp.observe_property_string("aid", PropChangeAid);
mp.observe_property_string("vid", PropChangeVid);
mp.observe_property_int("edition", PropChangeEdition);
mp.Shutdown += Shutdown;
mp.VideoSizeChanged += VideoSizeChanged;
mp.FileLoaded += FileLoaded;
mp.Idle += Idle;
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
@@ -479,13 +506,13 @@ namespace mpvnet
BuildMenu();
ContextMenuStrip = ContextMenu;
IgnoreDpiChanged = false;
CheckUrlInClipboard();
CheckClipboardForURL();
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
CheckUrlInClipboard();
CheckClipboardForURL();
Message m = new Message() { Msg = 0x0202 }; // WM_LBUTTONUP
Native.SendMessage(Handle, m.Msg, m.WParam, m.LParam);
}
@@ -496,13 +523,20 @@ namespace mpvnet
if (mp.IsLogoVisible) mp.ShowLogo();
}
protected override void OnFormClosed(FormClosedEventArgs e)
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosed(e);
base.OnFormClosing(e);
if (WindowState == FormWindowState.Normal)
{
RegHelp.SetObject(App.RegPath, "PosX", Left + Width / 2);
RegHelp.SetObject(App.RegPath, "PosY", Top + Height / 2);
}
RegHelp.SetObject(App.RegPath, "Recent", RecentFiles.ToArray());
App.Exit();
mp.commandv("quit");
mp.AutoResetEvent.WaitOne(3000);
mp.ShutdownAutoResetEvent.WaitOne(3000);
}
protected override void OnMouseDown(MouseEventArgs e)
@@ -528,7 +562,7 @@ namespace mpvnet
CursorHelp.Show();
}
void CheckUrlInClipboard()
void CheckClipboardForURL()
{
string clipboard = Clipboard.GetText();

View File

@@ -144,7 +144,7 @@ namespace mpvnet
MPV_END_FILE_REASON_REDIRECT = 5
}
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Sequential)]
public struct mpv_event_log_message
{
public string prefix;

View File

@@ -58,9 +58,10 @@ namespace mpvnet
public static List<KeyValuePair<string, Action<bool>>> BoolPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<bool>>>();
public static List<KeyValuePair<string, Action<int>>> IntPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<int>>>();
public static List<KeyValuePair<string, Action<string>>> StringPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<string>>>();
public static Size VideoSize { get; set; } = new Size(1920, 1080);
public static Size VideoSize { get; set; }
public static List<PythonScript> PythonScripts { get; set; } = new List<PythonScript>();
public static AutoResetEvent AutoResetEvent { get; set; } = new AutoResetEvent(false);
public static AutoResetEvent ShutdownAutoResetEvent { get; set; } = new AutoResetEvent(false);
public static AutoResetEvent VideoSizeAutoResetEvent { get; set; } = new AutoResetEvent(false);
public static List<MediaTrack> MediaTracks { get; set; } = new List<MediaTrack>();
public static List<KeyValuePair<string, double>> Chapters { get; set; } = new List<KeyValuePair<string, double>>();
@@ -72,13 +73,29 @@ namespace mpvnet
public static bool Fullscreen { get; set; }
public static bool Border { get; set; } = true;
public static bool RememberHeight { get; set; } = true;
public static int Screen { get; set; } = -1;
public static int Edition { get; set; }
public static float Autofit { get; set; } = 0.5f;
public static void Init()
{
LoadLibrary("mpv-1.dll");
Handle = mpv_create();
set_property_string("osc", "yes");
set_property_string("config", "yes");
set_property_string("wid", MainForm.Hwnd.ToString());
set_property_string("force-window", "yes");
set_property_string("input-media-keys", "yes");
mpv_initialize(Handle);
ShowLogo();
ProcessCommandLine();
Task.Run(() => { LoadScripts(); });
Task.Run(() => { Addon = new Addon(); });
Task.Run(() => { EventLoop(); });
}
public static void ProcessProperty(string name, string value)
{
switch (name)
@@ -92,7 +109,6 @@ namespace mpvnet
case "fullscreen": Fullscreen = value == "yes"; break;
case "border": Border = value == "yes"; break;
case "screen": Screen = Convert.ToInt32(value); break;
case "remember-height": RememberHeight = value == "yes"; break;
}
}
@@ -161,24 +177,6 @@ namespace mpvnet
}
}
public static void Init()
{
string dummy = ConfFolder;
LoadLibrary("mpv-1.dll");
Handle = mpv_create();
set_property_string("osc", "yes");
set_property_string("config", "yes");
set_property_string("wid", MainForm.Hwnd.ToString());
set_property_string("force-window", "yes");
set_property_string("input-media-keys", "yes");
mpv_initialize(Handle);
ShowLogo();
ProcessCommandLine();
Task.Run(() => { LoadScripts(); });
Task.Run(() => { Addon = new Addon(); });
Task.Run(() => { EventLoop(); });
}
public static void LoadScripts()
{
string[] startupScripts = Directory.GetFiles(Application.StartupPath + "\\Scripts");
@@ -222,7 +220,7 @@ namespace mpvnet
case mpv_event_id.MPV_EVENT_SHUTDOWN:
Shutdown?.Invoke();
WriteHistory(null);
AutoResetEvent.Set();
ShutdownAutoResetEvent.Set();
return;
case mpv_event_id.MPV_EVENT_LOG_MESSAGE:
LogMessage?.Invoke();
@@ -274,27 +272,8 @@ namespace mpvnet
case mpv_event_id.MPV_EVENT_CLIENT_MESSAGE:
var client_messageData = (mpv_event_client_message)Marshal.PtrToStructure(evt.data, typeof(mpv_event_client_message));
string[] args = NativeUtf8StrArray2ManagedStrArray(client_messageData.args, client_messageData.num_args);
if (args.Length > 1 && args[0] == "mpv.net")
{
bool found = false;
foreach (Command i in Command.Commands)
{
if (args[1] == i.Name)
{
found = true;
i.Action.Invoke(args.Skip(2).ToArray());
}
}
if (!found)
{
List<string> names = mpvnet.Command.Commands.Select((item) => item.Name).ToList();
names.Sort();
Msg.ShowError($"No command '{args[1]}' found.", $"Available commands are:\n\n{string.Join("\n", names)}\n\nHow to bind these commands can be seen in the [default input bindings and menu definition](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt).");
}
}
Command.Execute(args[1], args.Skip(2).ToArray());
else if (args.Length > 0)
ClientMessage?.Invoke(args);
break;
@@ -338,6 +317,8 @@ namespace mpvnet
VideoSizeChanged?.Invoke();
}
VideoSizeAutoResetEvent.Set();
Task.Run(new Action(() => ReadMetaData()));
break;
case mpv_event_id.MPV_EVENT_CHAPTER_CHANGE:
@@ -535,6 +516,8 @@ namespace mpvnet
Load(files.ToArray(), App.ProcessInstance != "queue", Control.ModifierKeys.HasFlag(Keys.Control));
if (files.Count == 0) VideoSizeAutoResetEvent.Set();
foreach (string i in args)
{
if (i.StartsWith("--"))
@@ -558,11 +541,18 @@ namespace mpvnet
}
}
public static DateTime LastLoad;
public static void Load(string[] files, bool loadFolder, bool append)
{
if (files is null || files.Length == 0) return;
HideLogo();
if ((DateTime.Now - LastLoad).TotalMilliseconds < 500)
append = true;
LastLoad = DateTime.Now;
for (int i = 0; i < files.Length; i++)
if (App.SubtitleTypes.Contains(Path.GetExtension(files[i]).TrimStart('.').ToLower()))
commandv("sub-add", files[i]);
@@ -579,7 +569,7 @@ namespace mpvnet
public static void LoadFolder()
{
Thread.Sleep(50); // user reported race condition
Thread.Sleep(200); // user reported race condition
string path = get_property_string("path");
if (!File.Exists(path) || get_property_int("playlist-count") != 1) return;
List<string> files = Directory.GetFiles(Path.GetDirectoryName(path)).ToList();
@@ -660,18 +650,20 @@ namespace mpvnet
if (MainForm.Instance is null) return;
Rectangle cr = MainForm.Instance.ClientRectangle;
if (cr.Width == 0 || cr.Height == 0) return;
int len = cr.Height / 5;
using (Bitmap b = new Bitmap(cr.Width, cr.Height))
using (Bitmap b = new Bitmap(len, len))
{
using (Graphics g = Graphics.FromImage(b))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.Clear(Color.Black);
int iconWidth = cr.Height / 7;
Rectangle r = new Rectangle(cr.Width / 2 - iconWidth / 2, cr.Height / 2 - iconWidth / 2, iconWidth, iconWidth);
g.DrawImage(Properties.Resources.mpvnet, r);
BitmapData bd = b.LockBits(cr, ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
commandv("overlay-add", "0", "0", "0", "&" + bd.Scan0.ToInt64().ToString(), "0", "bgra", bd.Width.ToString(), bd.Height.ToString(), bd.Stride.ToString());
Rectangle rect = new Rectangle(0, 0, len, len);
g.DrawImage(Properties.Resources.mpvnet, rect);
BitmapData bd = b.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
int x = Convert.ToInt32((cr.Width - len) / 2.0);
int y = Convert.ToInt32(((cr.Height - len) / 2.0) * 0.9);
commandv("overlay-add", "0", $"{x}", $"{y}", "&" + bd.Scan0.ToInt64().ToString(), "0", "bgra", bd.Width.ToString(), bd.Height.ToString(), bd.Stride.ToString());
b.UnlockBits(bd);
IsLogoVisible = true;
}