new folder structure and new C# script host

This commit is contained in:
Frank Skare
2021-05-06 15:23:28 +02:00
parent 642c36bacf
commit 3583aa11ed
101 changed files with 96 additions and 244 deletions

8
src/App.config Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
</configuration>

View File

@@ -0,0 +1,163 @@

using mpvnet;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Navigation;
using Tommy;
namespace DynamicGUI
{
public class Settings
{
public static List<SettingBase> LoadSettings(string content)
{
TomlTable table;
using (StringReader reader = new StringReader(content))
table = TOML.Parse(reader);
List<SettingBase> settingsList = new List<SettingBase>();
foreach (TomlTable setting in table["settings"])
{
SettingBase baseSetting = null;
if (setting.HasKey("options"))
{
OptionSetting optionSetting = new OptionSetting();
baseSetting = optionSetting;
optionSetting.Default = setting["default"];
optionSetting.Value = optionSetting.Default;
foreach (TomlTable option in setting["options"])
{
var opt = new OptionSettingOption();
opt.Name = option["name"];
if (option.HasKey("help"))
opt.Help = option["help"];
if (option.HasKey("text"))
opt.Text = option["text"];
else if (opt.Name == optionSetting.Default)
opt.Text = opt.Name + " (Default)";
opt.OptionSetting = optionSetting;
optionSetting.Options.Add(opt);
}
}
else
{
StringSetting stringSetting = new StringSetting();
baseSetting = stringSetting;
stringSetting.Default = setting.HasKey("default") ? setting["default"].ToString() : "";
}
baseSetting.Name = setting["name"];
baseSetting.File = setting["file"];
baseSetting.Filter = setting["filter"];
if (setting.HasKey("help")) baseSetting.Help = setting["help"];
if (setting.HasKey("url")) baseSetting.URL = setting["url"];
if (setting.HasKey("width")) baseSetting.Width = setting["width"];
if (setting.HasKey("type")) baseSetting.Type = setting["type"];
settingsList.Add(baseSetting);
}
return settingsList;
}
}
public class ConfItem
{
public string Name { get; set; } = "";
public string Value { get; set; } = "";
public string Comment { get; set; } = "";
public string LineComment { get; set; } = "";
public string Section { get; set; } = "";
public string File { get; set; } = "";
public bool IsSectionItem { get; set; }
public SettingBase SettingBase { get; set; }
}
public abstract class SettingBase
{
public string Name { get; set; }
public string File { get; set; }
public string Value { get; set; }
public string Help { get; set; }
public string Default { get; set; }
public string URL { get; set; }
public string Filter { get; set; }
public string Type { get; set; }
public int Width { get; set; }
public ConfItem ConfItem { get; set; }
}
public class StringSetting : SettingBase
{
}
public class OptionSetting : SettingBase
{
public List<OptionSettingOption> Options = new List<OptionSettingOption>();
}
public class OptionSettingOption
{
public string Name { get; set; }
public string Help { get; set; }
public OptionSetting OptionSetting { get; set; }
string _Text;
public string Text
{
get => string.IsNullOrEmpty(_Text) ? Name : _Text;
set => _Text = value;
}
public bool Checked
{
get => OptionSetting.Value == Name ;
set {
if (value)
OptionSetting.Value = Name;
}
}
public Visibility Visibility
{
get => string.IsNullOrEmpty(Help) ? Visibility.Collapsed : Visibility.Visible;
}
}
interface ISettingControl
{
bool Contains(string searchString);
SettingBase SettingBase { get; }
}
public class HyperlinkEx : Hyperlink
{
void HyperLinkEx_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
ProcessHelp.ShellExecute(e.Uri.AbsoluteUri);
}
public void SetURL(string url)
{
if (string.IsNullOrEmpty(url)) return;
NavigateUri = new Uri(url);
RequestNavigate += HyperLinkEx_RequestNavigate;
Inlines.Clear();
Inlines.Add(url);
}
}
}

View File

@@ -0,0 +1,64 @@
<UserControl x:Name="OptionSettingControl1" x:Class="DynamicGUI.OptionSettingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DynamicGUI"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid Margin="20,0">
<StackPanel>
<TextBox x:Name="TitleTextBox"
FontSize="24"
Margin="0,10"
BorderThickness="0"
IsReadOnly="True"
Foreground="{x:Static mpvnet:Theme.Heading}"
Background="{x:Static mpvnet:Theme.Background}" />
<ItemsControl x:Name="ItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Vertical">
<RadioButton x:Name="RadioButton"
VerticalContentAlignment="Center"
IsChecked="{Binding Checked}"
GroupName="{Binding OptionSetting.Name}"
Content="{Binding Text}"
FontSize="16"
FontWeight="Normal"
VerticalAlignment="Top"
Foreground="{x:Static mpvnet:Theme.Foreground}"/>
<TextBox x:Name="ItemHelpTextBox"
TextWrapping="WrapWithOverflow"
Text="{Binding Help}"
Visibility="{Binding Visibility}"
Margin="10,0,0,0" BorderThickness="0"
IsReadOnly="True" Padding="7,0,0,0"
MinHeight="0"
Foreground="{x:Static mpvnet:Theme.Foreground2}"
Background="{x:Static mpvnet:Theme.Background}"/>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBox x:Name="HelpTextBox"
TextWrapping="WrapWithOverflow"
BorderThickness="0"
IsReadOnly="True"
Margin="0,10,0,0"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"/>
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
</TextBlock>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,49 @@

using System.Windows;
using System.Windows.Controls;
namespace DynamicGUI
{
public partial class OptionSettingControl : UserControl, ISettingControl
{
OptionSetting OptionSetting;
public OptionSettingControl(OptionSetting optionSetting)
{
OptionSetting = optionSetting;
InitializeComponent();
TitleTextBox.Text = optionSetting.Name;
if (string.IsNullOrEmpty(optionSetting.Help))
HelpTextBox.Visibility = Visibility.Collapsed;
HelpTextBox.Text = optionSetting.Help;
ItemsControl.ItemsSource = optionSetting.Options;
if (string.IsNullOrEmpty(optionSetting.URL))
LinkTextBlock.Visibility = Visibility.Collapsed;
Link.SetURL(optionSetting.URL);
}
string _SearchableText;
public string SearchableText {
get {
if (_SearchableText is null)
{
_SearchableText = TitleTextBox.Text + HelpTextBox.Text;
foreach (var i in OptionSetting.Options)
_SearchableText += i.Text + i.Help + i.Name;
_SearchableText = _SearchableText.ToLower();
}
return _SearchableText;
}
}
public SettingBase SettingBase => OptionSetting;
public bool Contains(string searchString) => SearchableText.Contains(searchString.ToLower());
}
}

View File

@@ -0,0 +1,58 @@
<UserControl x:Name="StringSettingControl1" x:Class="DynamicGUI.StringSettingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DynamicGUI"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid Margin="20,0">
<StackPanel>
<TextBox x:Name="TitleTextBox"
FontSize="24"
Margin="0,10"
BorderThickness="0"
IsReadOnly="True"
Foreground="{x:Static mpvnet:Theme.Heading}"
Background="{x:Static mpvnet:Theme.Background}"/>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="ValueTextBox"
Text="{Binding Path=Text, ElementName=StringSettingControl1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="150"
Height="20"
HorizontalAlignment="Left"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
CaretBrush="{x:Static mpvnet:Theme.Foreground}"
TextChanged="ValueTextBox_TextChanged"/>
<Button x:Name="Button"
Height="20"
Grid.Column="1"
Visibility="{Binding Path=Text, ElementName=StringSettingControl1}"
Margin="5,0,0,0"
Width="20"
Click="Button_Click">...</Button>
</Grid>
<TextBox x:Name="HelpTextBox"
TextWrapping="WrapWithOverflow"
BorderThickness="0"
IsReadOnly="True"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"/>
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
</TextBlock>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,122 @@

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using WinForms = System.Windows.Forms;
namespace DynamicGUI
{
public partial class StringSettingControl : UserControl, ISettingControl
{
StringSetting StringSetting;
public StringSettingControl(StringSetting stringSetting)
{
StringSetting = stringSetting;
InitializeComponent();
TitleTextBox.Text = stringSetting.Name;
HelpTextBox.Text = stringSetting.Help;
ValueTextBox.Text = StringSetting.Value;
if (StringSetting.Width > 0)
ValueTextBox.Width = StringSetting.Width;
if (StringSetting.Type != "folder" && StringSetting.Type != "color")
Button.Visibility = Visibility.Hidden;
Link.SetURL(StringSetting.URL);
if (string.IsNullOrEmpty(stringSetting.URL))
LinkTextBlock.Visibility = Visibility.Collapsed;
}
string _SearchableText;
public string SearchableText {
get {
if (_SearchableText is null)
_SearchableText = (TitleTextBox.Text + HelpTextBox.Text +ValueTextBox.Text).ToLower();
return _SearchableText;
}
}
public bool Contains(string searchString) => SearchableText.Contains(searchString.ToLower());
public SettingBase SettingBase => StringSetting;
public string Text
{
get => StringSetting.Value;
set => StringSetting.Value = value;
}
void Button_Click(object sender, RoutedEventArgs e)
{
switch (StringSetting.Type)
{
case "folder":
using (var d = new WinForms.FolderBrowserDialog())
{
d.Description = "Choose a folder.";
d.SelectedPath = ValueTextBox.Text;
if (d.ShowDialog() == WinForms.DialogResult.OK)
ValueTextBox.Text = d.SelectedPath;
}
break;
case "color":
using (var dialog = new WinForms.ColorDialog())
{
dialog.FullOpen = true;
try
{
if (!string.IsNullOrEmpty(ValueTextBox.Text))
{
Color col = GetColor(ValueTextBox.Text);
dialog.Color = System.Drawing.Color.FromArgb(col.A, col.R, col.G, col.B);
}
} catch {}
if (dialog.ShowDialog() == WinForms.DialogResult.OK)
ValueTextBox.Text = "#" + dialog.Color.ToArgb().ToString("X8");
}
break;
}
}
void ValueTextBox_TextChanged(object sender, TextChangedEventArgs e) => Update();
Color GetColor(string value)
{
if (value.Contains("/"))
{
string[] a = value.Split('/');
if (a.Length == 3)
return Color.FromRgb(ToByte(a[0]), ToByte(a[1]), ToByte(a[2]));
else if (a.Length == 4)
return Color.FromArgb(ToByte(a[3]), ToByte(a[0]), ToByte(a[1]), ToByte(a[2]));
}
return (Color)ColorConverter.ConvertFromString(value);
Byte ToByte(string val) => Convert.ToByte(Convert.ToSingle(val, CultureInfo.InvariantCulture) * 255);
}
public void Update()
{
if (StringSetting.Type == "color")
{
Color c = Colors.Transparent;
if (ValueTextBox.Text != "")
try { c = GetColor(ValueTextBox.Text); } catch {}
ValueTextBox.Background = new SolidColorBrush(c);
}
}
}
}

1824
src/DynamicGUI/Tommy.cs Normal file

File diff suppressed because it is too large Load Diff

216
src/Misc/App.cs Normal file
View File

@@ -0,0 +1,216 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using System.Threading.Tasks;
using static mpvnet.Core;
namespace mpvnet
{
public static class App
{
public static string RegPath { get; } = @"HKCU\Software\" + Application.ProductName;
public static string ConfPath { get => core.ConfigFolder + "mpvnet.conf"; }
public static string ProcessInstance { get; set; } = "single";
public static string DarkMode { get; set; } = "always";
public static string DarkTheme { get; set; } = "dark";
public static string LightTheme { get; set; } = "light";
public static string StartSize { get; set; } = "previous";
public static bool RememberPosition { get; set; }
public static bool DebugMode { get; set; }
public static bool IsStartedFromTerminal { get; } = Environment.GetEnvironmentVariable("_started_from_console") == "yes";
public static bool RememberVolume { get; set; } = true;
public static bool AutoLoadFolder { get; set; } = true;
public static bool Queue { get; set; }
public static bool UpdateCheck { get; set; }
public static bool GlobalMediaKeys { get; set; }
public static int StartThreshold { get; set; } = 1500;
public static int RecentCount { get; set; } = 15;
public static float MinimumAspectRatio { get; set; } = 1.2f;
public static Extension Extension { get; set; }
public static bool IsDarkMode {
get => (DarkMode == "system" && Sys.IsDarkTheme) || DarkMode == "always";
}
public static void Init()
{
string dummy = core.ConfigFolder;
var dummy2 = core.Conf;
foreach (var i in Conf)
ProcessProperty(i.Key, i.Value, true);
if (DebugMode)
{
try
{
string filePath = core.ConfigFolder + "mpvnet-debug.log";
if (File.Exists(filePath))
File.Delete(filePath);
Trace.Listeners.Add(new TextWriterTraceListener(filePath));
Trace.AutoFlush = true;
//if (App.DebugMode)
// Trace.WriteLine("");
}
catch (Exception e)
{
Msg.ShowException(e);
}
}
string themeContent = null;
if (File.Exists(core.ConfigFolder + "theme.conf"))
themeContent = File.ReadAllText(core.ConfigFolder + "theme.conf");
Theme.Init(
themeContent,
Properties.Resources.theme,
IsDarkMode ? DarkTheme : LightTheme);
core.Shutdown += Shutdown;
core.Initialized += Initialized;
}
public static void RunTask(Action action)
{
Task.Run(() => {
try {
action.Invoke();
} catch (Exception e) {
ShowException(e);
}
});
}
public static string Version {
get {
return "Copyright (C) 2017-2021 mpv.net/mpv/mplayer\n" +
$"mpv.net {Application.ProductVersion} ({File.GetLastWriteTime(Application.ExecutablePath).ToShortDateString()})\n" +
$"{core.get_property_string("mpv-version")} ({File.GetLastWriteTime(Folder.Startup + "mpv-1.dll").ToShortDateString()})\nffmpeg {core.get_property_string("ffmpeg-version")}\nMIT License";
}
}
public static void ShowException(object obj)
{
if (obj is Exception e)
{
if (IsStartedFromTerminal)
ConsoleHelp.WriteError(e.ToString());
else
Msg.ShowException(e);
}
else
{
if (IsStartedFromTerminal)
ConsoleHelp.WriteError(obj.ToString());
else
Msg.ShowError(obj.ToString());
}
}
public static void ShowError(string title, string msg)
{
if (IsStartedFromTerminal)
{
ConsoleHelp.WriteError(title);
ConsoleHelp.WriteError(msg);
}
else
Msg.ShowError(title, msg);
}
static void Initialized()
{
if (RememberVolume)
{
core.set_property_int("volume", RegistryHelp.GetInt(RegPath, "Volume", 70));
core.set_property_string("mute", RegistryHelp.GetString(RegPath, "Mute", "no"));
}
}
static void Shutdown()
{
if (RememberVolume)
{
RegistryHelp.SetValue(RegPath, "Volume", core.get_property_int("volume"));
RegistryHelp.SetValue(RegPath, "Mute", core.get_property_string("mute"));
}
}
static Dictionary<string, string> _Conf;
public static Dictionary<string, string> Conf {
get {
if (_Conf == null)
{
_Conf = new Dictionary<string, string>();
if (File.Exists(ConfPath))
foreach (string i in File.ReadAllLines(ConfPath))
if (i.Contains("=") && !i.StartsWith("#"))
_Conf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
}
return _Conf;
}
}
public static bool ProcessProperty(string name, string value, bool writeError = false)
{
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;
case "queue": Queue = value == "yes"; return true;
case "auto-load-folder": AutoLoadFolder = value == "yes"; return true;
case "update-check": UpdateCheck = value == "yes"; return true;
case "start-size": StartSize = value; return true;
case "process-instance": ProcessInstance = value; return true;
case "dark-mode": DarkMode = value; return true;
case "start-threshold": StartThreshold = value.ToInt(); return true;
case "recent-count": RecentCount = value.ToInt(); return true;
case "minimum-aspect-ratio": MinimumAspectRatio = value.ToFloat(); return true;
case "dark-theme": DarkTheme = value.Trim('\'', '"'); return true;
case "light-theme": LightTheme = value.Trim('\'', '"'); return true;
case "video-file-extensions": VideoTypes = value.Split(" ,;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); return true;
case "audio-file-extensions": AudioTypes = value.Split(" ,;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); return true;
case "image-file-extensions": ImageTypes = value.Split(" ,;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); return true;
default:
if (writeError)
ConsoleHelp.WriteError($"unknown mpvnet.conf property: {name}");
return false;
}
}
public static void ShowSetup()
{
int value = RegistryHelp.GetInt(RegistryHelp.ApplicationKey, Folder.Startup);
if (value != 1)
{
if (Msg.ShowQuestion("Would you like to setup mpv.net?",
"The setup allows to create a start menu shortcut, file associations and " +
"adding mpv.net to the Path environment variable.") == MsgResult.OK)
Commands.Execute("show-setup-dialog");
else
Msg.Show("The setup dialog can be found in the context menu at:\n\nTools > Setup");
RegistryHelp.SetValue(RegistryHelp.ApplicationKey, Folder.Startup, 1);
}
}
}
}

View File

@@ -0,0 +1,74 @@

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Microsoft.CSharp;
namespace mpvnet
{
class CSharpScriptHost
{
static List<object> References = new List<object>();
public static void ExecuteScriptsInFolder(string folder)
{
if (Directory.Exists(folder))
foreach (string file in Directory.GetFiles(folder, "*.cs"))
App.RunTask(() => Execute(file));
}
static void Execute(string file)
{
string code = File.ReadAllText(file);
string filename = Path.GetFileNameWithoutExtension(file) + " " + GetMD5(code) + ".dll";
string outputFile = Path.Combine(Path.GetTempPath(), filename);
if (!File.Exists(outputFile))
Compile(outputFile, file);
if (File.Exists(outputFile))
References.Add(Assembly.LoadFile(outputFile).CreateInstance("Script"));
}
public static void Compile(string outputFile, string file)
{
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
string[] dependencies = {
"Microsoft.VisualBasic.dll",
"System.Core.dll", "System.Data.dll", "System.dll", "System.Drawing.dll", "System.Web.dll",
"System.Windows.Forms.dll", "System.Xaml.dll", "System.Xml.dll", "System.Xml.Linq.dll",
"WPF\\PresentationCore.dll", "WPF\\PresentationFramework.dll", "WPF\\WindowsBase.dll"
};
foreach (string i in dependencies)
parameters.ReferencedAssemblies.Add(i);
parameters.OutputAssembly = outputFile;
CompilerResults results = provider.CompileAssemblyFromFile(parameters, file);
var errors = results.Errors.Cast<CompilerError>().Select((i) => "Line Number " +
i.Line + "\r\n" + "Error Number: " + i.ErrorNumber + "\r\n" + i.ErrorText);
if (errors.Count() > 0)
ConsoleHelp.WriteError(string.Join("\r\n\r\n", errors), Path.GetFileName(file));
}
static string GetMD5(string code)
{
using (MD5 md5 = MD5.Create())
{
byte[] inputBuffer = Encoding.UTF8.GetBytes(code);
byte[] hashBuffer = md5.ComputeHash(inputBuffer);
return BitConverter.ToString(md5.ComputeHash(inputBuffer)).Replace("-", "");
}
}
}
}

390
src/Misc/Commands.cs Normal file
View File

@@ -0,0 +1,390 @@

using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows;
using VB = Microsoft.VisualBasic;
using static mpvnet.NewLine;
using static mpvnet.Core;
using System.Threading.Tasks;
namespace mpvnet
{
public class Commands
{
public static void Execute(string id, string[] args = null)
{
switch (id)
{
case "add-files-to-playlist": OpenFiles("append"); break; // deprecated 2019
case "cycle-audio": CycleAudio(); break;
case "execute-mpv-command": ExecuteMpvCommand(); break;
case "load-audio": LoadAudio(); break;
case "load-sub": LoadSubtitle(); break;
case "manage-file-associations": // deprecated 2019
case "open-conf-folder": ProcessHelp.ShellExecute(core.ConfigFolder); break;
case "open-files": OpenFiles(args); break;
case "open-optical-media": Open_DVD_Or_BD_Folder(); break;
case "open-url": OpenURL(); break;
case "playlist-first": PlaylistFirst(); break;
case "playlist-last": PlaylistLast(); break;
case "scale-window": ScaleWindow(float.Parse(args[0], CultureInfo.InvariantCulture)); break;
case "shell-execute": ProcessHelp.ShellExecute(args[0]); break;
case "show-about": ShowDialog(typeof(AboutWindow)); break;
case "show-audio-devices": ShowTextWithEditor("audio-device-list", core.get_property_osd_string("audio-device-list")); break;
case "show-command-palette": ShowDialog(typeof(CommandPaletteWindow)); break;
case "show-commands": ShowCommands(); break;
case "show-conf-editor": ShowDialog(typeof(ConfWindow)); break;
case "show-decoders": ShowTextWithEditor("decoder-list", mpvHelp.GetDecoders()); break;
case "show-demuxers": ShowTextWithEditor("demuxer-lavf-list", mpvHelp.GetDemuxers()); break;
case "show-history": ShowHistory(); break;
case "show-info": ShowInfo(); break;
case "show-input-editor": ShowDialog(typeof(InputWindow)); break;
case "show-keys": ShowTextWithEditor("input-key-list", core.get_property_string("input-key-list").Replace(",", BR)); break;
case "show-media-search": ShowDialog(typeof(EverythingWindow)); break;
case "show-profiles": ShowTextWithEditor("profile-list", mpvHelp.GetProfiles()); break;
case "show-playlist": ShowPlaylist(); break;
case "show-properties": ShowProperties(); break;
case "show-protocols": ShowTextWithEditor("protocol-list", mpvHelp.GetProtocols()); break;
case "show-setup-dialog": ShowDialog(typeof(SetupWindow)); break;
case "show-text": ShowText(args[0], Convert.ToInt32(args[1]), Convert.ToInt32(args[2])); break;
case "update-check": UpdateCheck.CheckOnline(true); break;
default: Msg.ShowError($"No command '{id}' found."); break;
}
}
public static void InvokeOnMainThread(Action action) => MainForm.Instance.BeginInvoke(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;
foreach (string arg in args)
{
if (arg == "append") append = true;
if (arg == "no-folder") loadFolder = false;
}
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog() { Multiselect = true })
if (d.ShowDialog() == DialogResult.OK)
core.LoadFiles(d.FileNames, loadFolder, append);
}));
}
public static void Open_DVD_Or_BD_Folder()
{
InvokeOnMainThread(new Action(() => {
using (var dialog = new FolderBrowserDialog())
{
dialog.Description = "Select a DVD or Blu-ray folder.";
dialog.ShowNewFolderButton = false;
if (dialog.ShowDialog() == DialogResult.OK)
{
core.command("stop");
Thread.Sleep(500);
if (Directory.Exists(dialog.SelectedPath + "\\BDMV"))
{
core.set_property_string("bluray-device", dialog.SelectedPath);
core.LoadFiles(new[] { @"bd://" }, false, false);
}
else
{
core.set_property_string("dvd-device", dialog.SelectedPath);
core.LoadFiles(new[] { @"dvd://" }, false, false);
}
}
}
}));
}
public static void PlaylistFirst()
{
int pos = core.get_property_int("playlist-pos");
if (pos != 0)
core.set_property_int("playlist-pos", 0);
}
public static void PlaylistLast()
{
int pos = core.get_property_int("playlist-pos");
int count = core.get_property_int("playlist-count");
if (pos < count - 1)
core.set_property_int("playlist-pos", count - 1);
}
public static void ShowHistory()
{
if (File.Exists(core.ConfigFolder + "history.txt"))
ProcessHelp.ShellExecute(core.ConfigFolder + "history.txt");
else
{
if (Msg.ShowQuestion("Create history.txt file in config folder?",
"mpv.net will write the date, time and filename of opened files to it.") == MsgResult.OK)
File.WriteAllText(core.ConfigFolder + "history.txt", "");
}
}
public static void ShowInfo()
{
try
{
string performer, title, album, genre, date, duration, text = "";
long fileSize = 0;
string path = core.get_property_string("path");
if (path.Contains("://"))
path = core.get_property_string("media-title");
int width = core.get_property_int("video-params/w");
int height = core.get_property_int("video-params/h");
if (File.Exists(path))
{
fileSize = new FileInfo(path).Length;
if (AudioTypes.Contains(path.Ext()))
{
using (MediaInfo mediaInfo = new MediaInfo(path))
{
performer = mediaInfo.GetInfo(MediaInfoStreamKind.General, "Performer");
title = mediaInfo.GetInfo(MediaInfoStreamKind.General, "Title");
album = mediaInfo.GetInfo(MediaInfoStreamKind.General, "Album");
genre = mediaInfo.GetInfo(MediaInfoStreamKind.General, "Genre");
date = mediaInfo.GetInfo(MediaInfoStreamKind.General, "Recorded_Date");
duration = mediaInfo.GetInfo(MediaInfoStreamKind.Audio, "Duration/String");
if (performer != "") text += "Artist: " + performer + "\n";
if (title != "") text += "Title: " + title + "\n";
if (album != "") text += "Album: " + album + "\n";
if (genre != "") text += "Genre: " + genre + "\n";
if (date != "") text += "Year: " + date + "\n";
if (duration != "") text += "Length: " + duration + "\n";
text += "Size: " + mediaInfo.GetInfo(MediaInfoStreamKind.General, "FileSize/String") + "\n";
text += "Type: " + path.Ext().ToUpper();
core.commandv("show-text", text, "5000");
return;
}
}
else if (ImageTypes.Contains(path.Ext()))
{
using (MediaInfo mediaInfo = new MediaInfo(path))
{
text =
"Width: " + mediaInfo.GetInfo(MediaInfoStreamKind.Image, "Width") + "\n" +
"Height: " + mediaInfo.GetInfo(MediaInfoStreamKind.Image, "Height") + "\n" +
"Size: " + mediaInfo.GetInfo(MediaInfoStreamKind.General, "FileSize/String") + "\n" +
"Type: " + path.Ext().ToUpper();
core.commandv("show-text", text, "5000");
return;
}
}
}
TimeSpan position = TimeSpan.FromSeconds(core.get_property_number("time-pos"));
TimeSpan duration2 = TimeSpan.FromSeconds(core.get_property_number("duration"));
string videoFormat = core.get_property_string("video-format").ToUpper();
string audioCodec = core.get_property_string("audio-codec-name").ToUpper();
text = path.FileName() + "\n" +
FormatTime(position.TotalMinutes) + ":" +
FormatTime(position.Seconds) + " / " +
FormatTime(duration2.TotalMinutes) + ":" +
FormatTime(duration2.Seconds) + "\n" +
$"{width} x {height}\n";
if (fileSize > 0)
text += Convert.ToInt32(fileSize / 1024.0 / 1024.0) + " MB\n";
text += $"{videoFormat}\n{audioCodec}";
core.commandv("show-text", text, "5000");
string FormatTime(double value) => ((int)value).ToString("00");
}
catch (Exception e)
{
App.ShowException(e);
}
}
public static void ExecuteMpvCommand() // deprecated 2019
{
InvokeOnMainThread(new Action(() => {
string command = VB.Interaction.InputBox("Enter a mpv command to be executed.", "Execute Command", RegistryHelp.GetString(App.RegPath, "RecentExecutedCommand"));
if (string.IsNullOrEmpty(command))
return;
RegistryHelp.SetValue(App.RegPath, "RecentExecutedCommand", command);
core.command(command, false);
}));
}
public static void OpenURL()
{
InvokeOnMainThread(new Action(() => {
string clipboard = System.Windows.Forms.Clipboard.GetText();
if (string.IsNullOrEmpty(clipboard) || (!clipboard.Contains("://") && !File.Exists(clipboard)) ||
clipboard.Contains("\n"))
{
App.ShowError("No URL found", "The clipboard does not contain a valid URL or file.");
return;
}
core.LoadFiles(new [] { clipboard }, false, Control.ModifierKeys.HasFlag(Keys.Control));
}));
}
public static void LoadSubtitle()
{
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog())
{
string path = core.get_property_string("path");
if (File.Exists(path))
d.InitialDirectory = Path.GetDirectoryName(path);
d.Multiselect = true;
if (d.ShowDialog() == DialogResult.OK)
foreach (string filename in d.FileNames)
core.commandv("sub-add", filename);
}
}));
}
public static void LoadAudio()
{
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog())
{
string path = core.get_property_string("path");
if (File.Exists(path))
d.InitialDirectory = Path.GetDirectoryName(path);
d.Multiselect = true;
if (d.ShowDialog() == DialogResult.OK)
foreach (string i in d.FileNames)
core.commandv("audio-add", i);
}
}));
}
public static void CycleAudio()
{
MediaTrack[] tracks = core.MediaTracks.Where(track => track.Type == "a").ToArray();
if (tracks.Length < 2)
return;
int aid = core.get_property_int("aid");
if (++aid > tracks.Length)
aid = 1;
core.commandv("set", "aid", aid.ToString());
core.commandv("show-text", aid + ": " + tracks[aid - 1].Text.Substring(3), "5000");
}
public static void ShowCommands()
{
string code = @"
foreach ($item in ($json | ConvertFrom-Json | foreach { $_ } | sort name))
{
''
$item.name
foreach ($arg in $item.args)
{
$value = $arg.name + ' <' + $arg.type.ToLower() + '>'
if ($arg.optional -eq $true)
{
$value = '[' + $value + ']'
}
' ' + $value
}
}";
string json = core.get_property_string("command-list");
ShowTextWithEditor("command-list", PowerShell.InvokeAndReturnString(code, "json", json));
}
public static void ShowProperties()
{
var props = core.get_property_string("property-list").Split(',').OrderBy(prop => prop);
ShowTextWithEditor("property-list", string.Join(BR, props));
}
public static void ShowTextWithEditor(string name, string text)
{
string file = Path.GetTempPath() + $"\\{name}.txt";
File.WriteAllText(file, BR + text.Trim() + BR);
ProcessHelp.ShellExecute(file);
}
public static void ScaleWindow(float factor)
{
core.RaiseScaleWindow(factor);
}
public static void ShowText(string text, int duration = 0, int fontSize = 0)
{
if (string.IsNullOrEmpty(text))
return;
if (duration == 0)
duration = core.get_property_int("osd-duration");
if (fontSize == 0)
fontSize = core.get_property_int("osd-font-size");
core.command("show-text \"${osd-ass-cc/0}{\\\\fs" + fontSize +
"}${osd-ass-cc/1}" + text + "\" " + duration);
}
public static void ShowPlaylist(string[] args = null)
{
int duration = 5000;
if (args?.Length == 1)
duration = Convert.ToInt32(args[0]);
var size = core.get_property_number("osd-font-size");
core.set_property_number("osd-font-size", 40);
core.command("show-text ${playlist} " + duration);
App.RunTask(() => {
Thread.Sleep(6000);
core.set_property_number("osd-font-size", size);
});
}
}
}

65
src/Misc/Extension.cs Normal file
View File

@@ -0,0 +1,65 @@

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using static mpvnet.Core;
namespace mpvnet
{
public class Extension
{
[ImportMany]
public IEnumerable<IExtension> Extensions = null;
readonly CompositionContainer CompositionContainer;
public Extension()
{
try
{
AggregateCatalog catalog = new AggregateCatalog();
string dir = Folder.Startup + "Extensions";
if (Directory.Exists(dir))
{
string[] knownExtensions = { "RatingExtension", "ScriptingExtension" };
foreach (string extDir in Directory.GetDirectories(dir))
{
if (knownExtensions.Contains(Path.GetFileName(extDir)))
catalog.Catalogs.Add(new DirectoryCatalog(extDir, Path.GetFileName(extDir) + ".dll"));
else
ConsoleHelp.WriteError("Failed to load extension:\n\n" + extDir +
"\n\nOnly extensions that ship with mpv.net are allowed in <startup>\\extensions" +
"\n\nUser extensions have to use <config folder>\\extensions" +
"\n\nNever copy or install a new mpv.net version over a old mpv.net version.");
}
}
dir = core.ConfigFolder + "extensions";
if (Directory.Exists(dir))
foreach (string extDir in Directory.GetDirectories(dir))
catalog.Catalogs.Add(new DirectoryCatalog(extDir, Path.GetFileName(extDir) + ".dll"));
if (catalog.Catalogs.Count > 0)
{
CompositionContainer = new CompositionContainer(catalog);
CompositionContainer.ComposeParts(this);
}
}
catch (Exception ex)
{
App.ShowException(ex);
}
}
}
public interface IExtension
{
}
}

View File

@@ -0,0 +1,78 @@

using System.Globalization;
using System.IO;
public static class Extensions
{
public static bool ContainsEx(this string instance, string value)
{
if (instance != null && value != null)
return instance.Contains(value);
return false;
}
public static bool StartsWithEx(this string instance, string value)
{
if (instance != null && value != null)
return instance.StartsWith(value);
return false;
}
public static string ToUpperEx(this string instance)
{
if (instance != null)
return instance.ToUpper();
return "";
}
public static string ToLowerEx(this string instance)
{
if (instance != null)
return instance.ToLower();
return "";
}
public static string FileName(this string instance)
{
if (string.IsNullOrEmpty(instance))
return "";
int index = instance.LastIndexOf('\\');
if (index > -1)
return instance.Substring(index + 1);
index = instance.LastIndexOf('/');
if (index > -1)
return instance.Substring(index + 1);
return instance;
}
public static string Ext(this string instance)
{
if (instance == null)
return "";
return Path.GetExtension(instance).TrimStart('.').ToLower();
}
public static int ToInt(this string instance)
{
int.TryParse(instance, out int result);
return result;
}
public static float ToFloat(this string instance)
{
float.TryParse(instance.Replace(",", "."), NumberStyles.Float,
CultureInfo.InvariantCulture, out float result);
return result;
}
}

250
src/Misc/Help.cs Normal file
View File

@@ -0,0 +1,250 @@

using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Win32;
using static mpvnet.Core;
using static mpvnet.NewLine;
namespace mpvnet
{
public static class ProcessHelp
{
public static void Execute(string file, string arguments = null)
{
using (Process proc = new Process())
{
proc.StartInfo.FileName = file;
proc.StartInfo.Arguments = arguments;
proc.StartInfo.UseShellExecute = false;
proc.Start();
}
}
public static void ShellExecute(string file, string arguments = null)
{
using (Process proc = new Process())
{
proc.StartInfo.FileName = file;
proc.StartInfo.Arguments = arguments;
proc.StartInfo.UseShellExecute = true;
proc.Start();
}
}
}
public static class ConsoleHelp
{
public static int Padding { get; set; }
public static void WriteError(object obj, string module = "mpv.net")
{
Write(obj, module, ConsoleColor.DarkRed, false);
}
public static void Write(object obj, string module = "mpv.net")
{
Write(obj, module, ConsoleColor.Black, true);
}
public static void Write(object obj, string module, ConsoleColor color)
{
Write(obj, module, color, false);
}
public static void Write(object obj, string module, ConsoleColor color, bool useDefaultColor)
{
if (obj == null)
return;
string value = obj.ToString();
if (!string.IsNullOrEmpty(module))
module = "[" + module + "] ";
if (useDefaultColor)
Console.ResetColor();
else
Console.ForegroundColor = color;
value = module + value;
if (Padding > 0 && value.Length < Padding)
value = value.PadRight(Padding);
if (color == ConsoleColor.Red || color == ConsoleColor.DarkRed)
Console.Error.WriteLine(value);
else
Console.WriteLine(value);
Console.ResetColor();
Trace.WriteLine(obj);
}
}
public class CursorHelp
{
static bool IsVisible = true;
public static void Show()
{
if (!IsVisible)
{
Cursor.Show();
IsVisible = true;
}
}
public static void Hide()
{
if (IsVisible)
{
Cursor.Hide();
IsVisible = false;
}
}
public static bool IsPosDifferent(Point screenPos)
{
return
Math.Abs(screenPos.X - Control.MousePosition.X) > 10 ||
Math.Abs(screenPos.Y - Control.MousePosition.Y) > 10;
}
}
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 = @"
foreach ($item in ($json | ConvertFrom-Json | foreach { $_ } | sort name))
{
$item.name
''
foreach ($option in $item.options)
{
' ' + $option.key + ' = ' + $option.value
}
''
}";
string json = core.get_property_string("profile-list");
return PowerShell.InvokeAndReturnString(code, "json", json).Trim();
}
public static string GetDecoders()
{
string code = @"
foreach ($item in ($json | ConvertFrom-Json | foreach { $_ } | sort codec))
{
$item.codec + ' - ' + $item.description
}";
string json = core.get_property_string("decoder-list");
return PowerShell.InvokeAndReturnString(code, "json", json).Trim();
}
public static string GetProtocols()
{
string list = core.get_property_string("protocol-list");
return string.Join(BR, list.Split(',').OrderBy(a => a));
}
public static string GetDemuxers()
{
string list = core.get_property_string("demuxer-lavf-list");
return string.Join(BR, list.Split(',').OrderBy(a => a));
}
}
public class RegistryHelp
{
public static string ApplicationKey { get; } = @"HKCU\Software\" + Application.ProductName;
public static void SetValue(string path, string name, object value)
{
using (RegistryKey regKey = GetRootKey(path).CreateSubKey(path.Substring(5), RegistryKeyPermissionCheck.ReadWriteSubTree))
regKey.SetValue(name, value);
}
public static string GetString(string path, string name, string defaultValue = "")
{
object value = GetValue(path, name, defaultValue);
return !(value is string) ? defaultValue : value.ToString();
}
public static int GetInt(string path, string name, int defaultValue = 0)
{
object value = GetValue(path, name, defaultValue);
return !(value is int) ? defaultValue : (int)value;
}
public static object GetValue(string path, string name, object defaultValue = null)
{
using (RegistryKey regKey = GetRootKey(path).OpenSubKey(path.Substring(5)))
return regKey == null ? null : regKey.GetValue(name, defaultValue);
}
public static void RemoveKey(string path)
{
try
{
GetRootKey(path).DeleteSubKeyTree(path.Substring(5), false);
}
catch { }
}
public static void RemoveValue(string path, string name)
{
try
{
using (RegistryKey regKey = GetRootKey(path).OpenSubKey(path.Substring(5), true))
if (regKey != null)
regKey.DeleteValue(name, false);
}
catch { }
}
static RegistryKey GetRootKey(string path)
{
switch (path.Substring(0, 4))
{
case "HKLM": return Registry.LocalMachine;
case "HKCU": return Registry.CurrentUser;
case "HKCR": return Registry.ClassesRoot;
default: throw new Exception();
}
}
}
}

189
src/Misc/Misc.cs Normal file
View File

@@ -0,0 +1,189 @@

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Windows.Forms;
using Microsoft.Win32;
using static mpvnet.Core;
namespace mpvnet
{
public static class NewLine
{
public static string BR = Environment.NewLine;
public static string BR2 = Environment.NewLine + Environment.NewLine;
}
public class Sys
{
public static bool IsDarkTheme {
get {
object value = Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", 1);
if (value is null)
value = 1;
return (int)value == 0;
}
}
}
public class StringLogicalComparer : IComparer, IComparer<string>
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern int StrCmpLogical(string x, string y);
int IComparer_Compare(object x, object y) => StrCmpLogical(x.ToString(), y.ToString());
int IComparer.Compare(object x, object y) => IComparer_Compare(x, y);
int IComparerOfString_Compare(string x, string y) => StrCmpLogical(x, y);
int IComparer<string>.Compare(string x, string y) => IComparerOfString_Compare(x, y);
}
public class FileAssociation
{
static string ExePath = Application.ExecutablePath;
static string ExeFilename = Path.GetFileName(Application.ExecutablePath);
static string ExeFilenameNoExt = Path.GetFileNameWithoutExtension(Application.ExecutablePath);
static string[] Types;
public static void Register(string[] types)
{
Types = types;
RegistryHelp.SetValue(@"HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\" + ExeFilename, null, ExePath);
RegistryHelp.SetValue(@"HKCR\Applications\" + ExeFilename, "FriendlyAppName", "mpv.net media player");
RegistryHelp.SetValue($@"HKCR\Applications\{ExeFilename}\shell\open\command", null, $"\"{ExePath}\" \"%1\"");
RegistryHelp.SetValue(@"HKLM\SOFTWARE\Clients\Media\mpv.net\Capabilities", "ApplicationDescription", "mpv.net media player");
RegistryHelp.SetValue(@"HKLM\SOFTWARE\Clients\Media\mpv.net\Capabilities", "ApplicationName", "mpv.net");
RegistryHelp.SetValue(@"HKCR\SystemFileAssociations\video\OpenWithList\" + ExeFilename, null, "");
RegistryHelp.SetValue(@"HKCR\SystemFileAssociations\audio\OpenWithList\" + ExeFilename, null, "");
RegistryHelp.SetValue(@"HKLM\SOFTWARE\RegisteredApplications", "mpv.net", @"SOFTWARE\Clients\Media\mpv.net\Capabilities");
foreach (string ext in Types)
{
RegistryHelp.SetValue($@"HKCR\Applications\{ExeFilename}\SupportedTypes", "." + ext, "");
RegistryHelp.SetValue($@"HKCR\" + "." + ext, null, ExeFilenameNoExt + "." + ext);
RegistryHelp.SetValue($@"HKCR\" + "." + ext + @"\OpenWithProgIDs", ExeFilenameNoExt + "." + ext, "");
if (VideoTypes.Contains(ext))
RegistryHelp.SetValue(@"HKCR\" + "." + ext, "PerceivedType", "video");
if (AudioTypes.Contains(ext))
RegistryHelp.SetValue(@"HKCR\" + "." + ext, "PerceivedType", "audio");
if (ImageTypes.Contains(ext))
RegistryHelp.SetValue(@"HKCR\" + "." + ext, "PerceivedType", "image");
RegistryHelp.SetValue($@"HKCR\" + ExeFilenameNoExt + "." + ext + @"\shell\open\command", null, $"\"{ExePath}\" \"%1\"");
RegistryHelp.SetValue(@"HKLM\SOFTWARE\Clients\Media\mpv.net\Capabilities\FileAssociations", "." + ext, ExeFilenameNoExt + "." + ext);
}
}
}
public class MediaTrack
{
public string Text { get; set; }
public string Type { get; set; }
public int ID { get; set; }
}
public class CommandItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Path { get; set; } = "";
public string Command { get; set; } = "";
public string Display { get { return string.IsNullOrEmpty(Path) ? Command : Path; } }
public CommandItem() { }
public CommandItem(SerializationInfo info, StreamingContext context) { }
void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string _Input = "";
public string Input {
get => _Input;
set {
_Input = value;
NotifyPropertyChanged();
}
}
public static ObservableCollection<CommandItem> GetItems(string content)
{
var items = new ObservableCollection<CommandItem>();
if (!string.IsNullOrEmpty(content))
{
foreach (string line in content.Split('\r', '\n'))
{
string val = line.Trim();
if (val.StartsWith("#"))
continue;
if (!val.Contains(" "))
continue;
CommandItem item = new CommandItem();
item.Input = val.Substring(0, val.IndexOf(" "));
if (item.Input == "_")
item.Input = "";
val = val.Substring(val.IndexOf(" ") + 1);
if (val.Contains("#menu:"))
{
item.Path = val.Substring(val.IndexOf("#menu:") + 6).Trim();
val = val.Substring(0, val.IndexOf("#menu:"));
if (item.Path.Contains(";"))
item.Path = item.Path.Substring(item.Path.IndexOf(";") + 1).Trim();
}
item.Command = val.Trim();
if (item.Command == "")
continue;
if (item.Command.ToLower() == "ignore")
item.Command = "";
items.Add(item);
}
}
return items;
}
static ObservableCollection<CommandItem> _Items;
public static ObservableCollection<CommandItem> Items {
get {
if (_Items is null)
_Items = GetItems(File.ReadAllText(core.InputConfPath));
return _Items;
}
}
}
public class Folder
{
public static string Startup { get; } = Application.StartupPath + @"\";
}
}

243
src/Misc/PowerShell.cs Normal file
View File

@@ -0,0 +1,243 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading;
using static mpvnet.Core;
using static mpvnet.NewLine;
namespace mpvnet
{
public class PowerShell
{
public Runspace Runspace { get; set; }
public Pipeline Pipeline { get; set; }
public string Module { get; set; }
public bool Print { get; set; }
public List<string> Scripts { get; } = new List<string>();
public List<KeyValuePair<string, object>> Variables = new List<KeyValuePair<string, object>>();
public string[] Arguments { get; }
public event Action<string, object[]> Event;
public event Action<string, object> PropertyChanged;
public List<KeyValuePair<string, ScriptBlock>> EventHandlers = new List<KeyValuePair<string, ScriptBlock>>();
public List<KeyValuePair<string, ScriptBlock>> PropChangedHandlers = new List<KeyValuePair<string, ScriptBlock>>();
public static List<PowerShell> References { get; } = new List<PowerShell>();
public object Invoke() => Invoke(null, null);
public object Invoke(string variable, object obj)
{
try
{
Runspace = RunspaceFactory.CreateRunspace();
Runspace.ApartmentState = ApartmentState.STA;
Runspace.Open();
Pipeline = Runspace.CreatePipeline();
foreach (string script in Scripts)
Pipeline.Commands.AddScript(script);
if (Arguments != null)
foreach (string param in Arguments)
foreach (Command command in Pipeline.Commands)
command.Parameters.Add(null, param);
Runspace.SessionStateProxy.SetVariable("mp", this);
foreach (var i in Variables)
Runspace.SessionStateProxy.SetVariable(i.Key, i.Value);
if (!string.IsNullOrEmpty(variable))
Runspace.SessionStateProxy.SetVariable(variable, obj);
if (Print)
{
Pipeline.Output.DataReady += Output_DataReady;
Pipeline.Error.DataReady += Error_DataReady;
}
return Pipeline.Invoke();
}
catch (RuntimeException e)
{
string message = e.Message + BR + BR + e.ErrorRecord.ScriptStackTrace.Replace(
" <ScriptBlock>, <No file>", "") + BR + BR + Module + BR;
throw new PowerShellException(message);
}
catch (Exception e)
{
throw e;
}
}
public static string InvokeAndReturnString(string code, string varName, object varValue)
{
PowerShell ps = new PowerShell() { Print = false };
ps.Scripts.Add(code);
string ret = string.Join(Environment.NewLine, (ps.Invoke(varName, varValue)
as IEnumerable<object>).Select(item => item.ToString())).ToString();
ps.Runspace.Dispose();
return ret;
}
public void Output_DataReady(object sender, EventArgs e)
{
var output = sender as PipelineReader<PSObject>;
while (output.Count > 0)
ConsoleHelp.Write(output.Read(), Module);
}
public void Error_DataReady(object sender, EventArgs e)
{
var output = sender as PipelineReader<Object>;
while (output.Count > 0)
ConsoleHelp.WriteError(output.Read(), Module);
}
public void RedirectStreams(PSEventJob job)
{
if (Print)
{
job.Output.DataAdded += Output_DataAdded;
job.Error.DataAdded += Error_DataAdded;
}
}
public void commandv(params string[] args) => core.commandv(args);
public void command(string command) => core.command(command);
public bool get_property_bool(string name) => core.get_property_bool(name);
public void set_property_bool(string name, bool value) => core.set_property_bool(name, value);
public int get_property_int(string name) => core.get_property_int(name);
public void set_property_int(string name, int value) => core.set_property_int(name, value);
public double get_property_number(string name) => core.get_property_number(name);
public void set_property_number(string name, double value) => core.set_property_number(name, value);
public string get_property_string(string name) => core.get_property_string(name);
public void set_property_string(string name, string value) => core.set_property_string(name, value);
public void observe_property(string name, string type, ScriptBlock sb)
{
PropChangedHandlers.Add(new KeyValuePair<string, ScriptBlock>(name, sb));
switch (type)
{
case "bool": case "boolean":
core.observe_property_bool(name, (value) => App.RunTask(() => PropertyChanged.Invoke(name, value)));
break;
case "string":
core.observe_property_string(name, (value) => App.RunTask(() => PropertyChanged.Invoke(name, value)));
break;
case "int": case "integer":
core.observe_property_int(name, (value) => App.RunTask(() => PropertyChanged.Invoke(name, value)));
break;
case "float": case "double":
core.observe_property_double(name, (value) => App.RunTask(() => PropertyChanged.Invoke(name, value)));
break;
case "nil": case "none": case "native":
core.observe_property(name, () => App.RunTask(() => PropertyChanged.Invoke(name, null)));
break;
default:
App.ShowError("Invalid Type", "Valid types are: bool or boolean, string, int or integer, float or double, nil or none or native");
break;
}
}
public void register_event(string name, ScriptBlock sb)
{
EventHandlers.Add(new KeyValuePair<string, ScriptBlock>(name, sb));
switch (name)
{
case "log-message":
core.LogMessageAsync += (level, msg) => Event.Invoke("log-message", new object[] { level, msg });
break;
case "end-file":
core.EndFileAsync += (reason) => Event.Invoke("end-file", new object[] { reason });
break;
case "client-message":
core.ClientMessageAsync += (args) => Event.Invoke("client-message", args);
break;
case "shutdown":
core.Shutdown += () => Event.Invoke("shutdown", null);
break;
case "get-property-reply":
core.GetPropertyReplyAsync += () => Event.Invoke("get-property-reply", null);
break;
case "set-property-reply":
core.SetPropertyReplyAsync += () => Event.Invoke("set-property-reply", null);
break;
case "command-reply":
core.CommandReplyAsync += () => Event.Invoke("command-reply", null);
break;
case "start-file":
core.StartFileAsync += () => Event.Invoke("start-file", null);
break;
case "file-loaded":
core.FileLoadedAsync += () => Event.Invoke("file-loaded", null);
break;
case "idle":
core.IdleAsync += () => Event.Invoke("idle", null);
break;
case "video-reconfig":
core.VideoReconfigAsync += () => Event.Invoke("video-reconfig", null);
break;
case "audio-reconfig":
core.AudioReconfigAsync += () => Event.Invoke("audio-reconfig", null);
break;
case "seek":
core.SeekAsync += () => Event.Invoke("seek", null);
break;
case "playback-restart":
core.PlaybackRestartAsync += () => Event.Invoke("playback-restart", null);
break;
}
}
void Output_DataAdded(object sender, DataAddedEventArgs e)
{
var output = sender as PSDataCollection<PSObject>;
ConsoleHelp.Write(output[e.Index], Module);
}
void Error_DataAdded(object sender, DataAddedEventArgs e)
{
var error = sender as PSDataCollection<ErrorRecord>;
ConsoleHelp.WriteError(error[e.Index], Module);
}
}
public class PowerShellException : Exception
{
public PowerShellException(string message) : base(message)
{
}
}
}

105
src/Misc/Program.cs Normal file
View File

@@ -0,0 +1,105 @@

using System;
using System.Windows.Forms;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
using static mpvnet.Core;
namespace mpvnet
{
static class Program
{
[STAThread]
static void Main()
{
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (App.IsStartedFromTerminal)
WinAPI.AttachConsole(-1 /*ATTACH_PARENT_PROCESS*/);
if (core.ConfigFolder == "")
return;
string[] args = Environment.GetCommandLineArgs().Skip(1).ToArray();
if (args.Length >= 2 && args[0] == "--reg-file-assoc")
{
if (args[1] == "audio")
FileAssociation.Register(Core.AudioTypes);
else if (args[1] == "video")
FileAssociation.Register(Core.VideoTypes);
else if (args[1] == "image")
FileAssociation.Register(Core.ImageTypes);
else
FileAssociation.Register(args.Skip(1).ToArray());
return;
}
App.Init();
Mutex mutex = new Mutex(true, "mpvnetProcessInstance", out bool isFirst);
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("://") ||
arg.Contains(":\\") || arg.StartsWith("\\\\")))
files.Add(arg);
else if (arg == "--queue")
files[0] = "queue";
}
Process[] procs = Process.GetProcessesByName("mpvnet");
for (int i = 0; i < 20; i++)
{
foreach (Process proc in procs)
{
if (proc.MainWindowHandle != IntPtr.Zero)
{
WinAPI.AllowSetForegroundWindow(proc.Id);
var data = new WinAPI.COPYDATASTRUCT();
data.lpData = string.Join("\n", files.ToArray());
data.cbData = data.lpData.Length * 2 + 1;
WinAPI.SendMessage(proc.MainWindowHandle, 0x004A /*WM_COPYDATA*/, IntPtr.Zero, ref data);
mutex.Dispose();
if (App.IsStartedFromTerminal)
WinAPI.FreeConsole();
return;
}
}
Thread.Sleep(50);
}
mutex.Dispose();
return;
}
Application.Run(new MainForm());
if (App.IsStartedFromTerminal)
WinAPI.FreeConsole();
mutex.Dispose();
}
catch (Exception ex)
{
Msg.ShowException(ex);
}
}
}
}

96
src/Misc/Theme.cs Normal file
View File

@@ -0,0 +1,96 @@

using System.Collections.Generic;
using System.Windows.Media;
namespace mpvnet
{
public class Theme
{
public string Name { get; set; }
public Dictionary<string, string> Dictionary { get; } = new Dictionary<string, string>();
public static List<Theme> DefaultThemes { get; set; }
public static List<Theme> CustomThemes { get; set; }
public static Theme Current { get; set; }
public static Brush Foreground { get; set; }
public static Brush Foreground2 { get; set; }
public static Brush Background { get; set; }
public static Brush Heading { get; set; }
public System.Drawing.Color GetWinFormsColor(string key)
{
return System.Drawing.ColorTranslator.FromHtml(Dictionary[key]);
}
public Brush GetBrush(string key)
{
return new SolidColorBrush((Color)ColorConverter.ConvertFromString(Dictionary[key]));
}
public static void Init(string customContent, string defaultContent, string activeTheme)
{
DefaultThemes = Load(defaultContent);
CustomThemes = Load(customContent);
foreach (Theme theme in CustomThemes)
{
if (theme.Name == activeTheme)
{
bool isKeyMissing = false;
foreach (string key in DefaultThemes[0].Dictionary.Keys)
{
if (!theme.Dictionary.ContainsKey(key))
{
isKeyMissing = true;
ConsoleHelp.WriteError($"Theme '{activeTheme}' misses '{key}'");
break;
}
}
if (!isKeyMissing)
Current = theme;
break;
}
}
if (Current == null)
foreach (Theme theme in DefaultThemes)
if (theme.Name == activeTheme)
Current = theme;
if (Current == null)
Current = DefaultThemes[0];
Foreground = Current.GetBrush("foreground");
Foreground2 = Current.GetBrush("foreground2");
Background = Current.GetBrush("background");
Heading = Current.GetBrush("heading");
}
static List<Theme> Load(string content)
{
List<Theme> list = new List<Theme>();
Theme theme = null;
foreach (string currentLine in (content ?? "").Split(new [] { '\r', '\n' }))
{
string line = currentLine.Trim();
if (line.StartsWith("[") && line.EndsWith("]"))
list.Add(theme = new Theme() { Name = line.Substring(1, line.Length - 2).Trim() });
if (line.Contains("=") && theme != null)
{
string left = line.Substring(0, line.IndexOf("=")).Trim();
theme.Dictionary[left] = line.Substring(line.IndexOf("=") + 1).Trim();
}
}
return list;
}
}
}

79
src/Misc/UpdateCheck.cs Normal file
View File

@@ -0,0 +1,79 @@

using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using static mpvnet.Core;
namespace mpvnet
{
class UpdateCheck
{
public static void DailyCheck()
{
if (App.UpdateCheck && RegistryHelp.GetInt(RegistryHelp.ApplicationKey, "UpdateCheckLast")
!= DateTime.Now.DayOfYear)
CheckOnline();
}
public static async void CheckOnline(bool showUpToDateMessage = false)
{
try
{
using (HttpClient client = new HttpClient())
{
RegistryHelp.SetValue(RegistryHelp.ApplicationKey, "UpdateCheckLast", DateTime.Now.DayOfYear);
client.DefaultRequestHeaders.Add("User-Agent", "mpv.net");
var response = await client.GetAsync("https://api.github.com/repos/stax76/mpv.net/releases/latest");
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
Match match = Regex.Match(content, @"""mpv\.net-([\d\.]+)-portable\.zip""");
Version onlineVersion = Version.Parse(match.Groups[1].Value);
Version currentVersion = Assembly.GetEntryAssembly().GetName().Version;
if (onlineVersion <= currentVersion)
{
if (showUpToDateMessage)
Msg.Show($"{Application.ProductName} is up to date.");
return;
}
if ((RegistryHelp.GetString(RegistryHelp.ApplicationKey, "UpdateCheckVersion")
!= onlineVersion.ToString() || showUpToDateMessage) && Msg.ShowQuestion(
$"New version {onlineVersion} is available, update now?") == MsgResult.OK)
{
string url = $"https://github.com/stax76/mpv.net/releases/download/{onlineVersion}/mpv.net-{onlineVersion}-portable.zip";
using (Process proc = new Process())
{
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
proc.StartInfo.FileName = "powershell.exe";
proc.StartInfo.Arguments = $"-NoExit -ExecutionPolicy Bypass -File \"{Folder.Startup + "Setup\\update.ps1"}\" \"{url}\" \"{Folder.Startup.TrimEnd(Path.DirectorySeparatorChar)}\"";
if (Folder.Startup.Contains("Program Files"))
proc.StartInfo.Verb = "runas";
proc.Start();
}
core.command("quit");
}
RegistryHelp.SetValue(RegistryHelp.ApplicationKey, "UpdateCheckVersion", onlineVersion.ToString());
}
}
catch (Exception ex)
{
if (showUpToDateMessage)
Msg.ShowException(ex);
}
}
}
}

113
src/Native/MediaInfo.cs Normal file
View File

@@ -0,0 +1,113 @@

using System;
using System.Runtime.InteropServices;
public class MediaInfo : IDisposable
{
IntPtr Handle;
public MediaInfo(string file)
{
if ((Handle = MediaInfo_New()) == IntPtr.Zero)
throw new Exception("Failed to call MediaInfo_New");
if (MediaInfo_Open(Handle, file) == 0)
throw new Exception("Error MediaInfo_Open");
}
public string GetInfo(MediaInfoStreamKind kind, string parameter)
{
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, kind, 0,
parameter, MediaInfoKind.Text, MediaInfoKind.Name));
}
public int GetCount(MediaInfoStreamKind kind) => MediaInfo_Count_Get(Handle, kind, -1);
public string GetVideo(int stream, string parameter)
{
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, MediaInfoStreamKind.Video,
stream, parameter, MediaInfoKind.Text, MediaInfoKind.Name));
}
public string GetAudio(int stream, string parameter)
{
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, MediaInfoStreamKind.Audio,
stream, parameter, MediaInfoKind.Text, MediaInfoKind.Name));
}
public string GetText(int stream, string parameter)
{
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, MediaInfoStreamKind.Text,
stream, parameter, MediaInfoKind.Text, MediaInfoKind.Name));
}
bool Disposed;
public void Dispose()
{
if (!Disposed)
{
if (Handle != IntPtr.Zero)
{
MediaInfo_Close(Handle);
MediaInfo_Delete(Handle);
}
Disposed = true;
}
}
~MediaInfo()
{
Dispose();
}
[DllImport("MediaInfo.dll")]
static extern IntPtr MediaInfo_New();
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
static extern int MediaInfo_Open(IntPtr handle, string path);
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
static extern IntPtr MediaInfo_Option(IntPtr handle, string option, string value);
[DllImport("MediaInfo.dll")]
static extern IntPtr MediaInfo_Inform(IntPtr handle, int reserved);
[DllImport("MediaInfo.dll")]
static extern int MediaInfo_Close(IntPtr handle);
[DllImport("MediaInfo.dll")]
static extern void MediaInfo_Delete(IntPtr handle);
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
static extern IntPtr MediaInfo_Get(IntPtr handle, MediaInfoStreamKind kind,
int stream, string parameter, MediaInfoKind infoKind, MediaInfoKind searchKind);
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
static extern int MediaInfo_Count_Get(IntPtr handle, MediaInfoStreamKind streamKind, int stream);
}
public enum MediaInfoStreamKind
{
General,
Video,
Audio,
Text,
Other,
Image,
Menu,
Max,
}
public enum MediaInfoKind
{
Name,
Text,
Measure,
Options,
NameText,
MeasureText,
Info,
HowTo
}

108
src/Native/Native.cs Normal file
View File

@@ -0,0 +1,108 @@

using System;
using System.Drawing;
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);
[DllImport("kernel32.dll")]
public static extern bool FreeConsole();
[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);
[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", CharSet = CharSet.Unicode)]
public static extern IntPtr PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int RegisterWindowMessage(string id);
[DllImport("user32.dll")]
public static extern bool AllowSetForegroundWindow(int dwProcessId);
[DllImport("user32.dll")]
public static extern void ReleaseCapture();
[DllImport("user32.dll")]
public static extern bool AdjustWindowRect(ref RECT lpRect, uint dwStyle, bool bMenu);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
static extern IntPtr GetWindowLong64(IntPtr hWnd, int nIndex);
public static IntPtr GetWindowLong(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLong64(hWnd, nIndex);
else
return GetWindowLong32(hWnd, nIndex);
}
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(Rectangle r)
{
Left = r.Left;
Top = r.Top;
Right = r.Right;
Bottom = r.Bottom;
}
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
public Size Size => new Size(Right - Left, Bottom - Top);
public int Width => Right - Left;
public int Height => Bottom - Top;
}
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpData;
}
}

41
src/Native/NativeHelp.cs Normal file
View File

@@ -0,0 +1,41 @@

using System;
using static WinAPI;
namespace mpvnet
{
public static class NativeHelp
{
public static int GetResizeBorder(int v)
{
switch (v)
{
case 1 /* WMSZ_LEFT */ : return 3;
case 3 /* WMSZ_TOP */ : return 2;
case 2 /* WMSZ_RIGHT */ : return 3;
case 6 /* WMSZ_BOTTOM */ : return 2;
case 4 /* WMSZ_TOPLEFT */ : return 1;
case 5 /* WMSZ_TOPRIGHT */ : return 1;
case 7 /* WMSZ_BOTTOMLEFT */ : return 3;
case 8 /* WMSZ_BOTTOMRIGHT */ : return 3;
default: return -1;
}
}
public static void SubtractWindowBorders(IntPtr hwnd, ref RECT rc)
{
RECT r = new RECT(0, 0, 0, 0);
AddWindowBorders(hwnd, ref r);
rc.Left -= r.Left;
rc.Top -= r.Top;
rc.Right -= r.Right;
rc.Bottom -= r.Bottom;
}
public static void AddWindowBorders(IntPtr hwnd, ref RECT rc)
{
AdjustWindowRect(ref rc, (uint)GetWindowLong(hwnd, -16 /* GWL_STYLE */), false);
}
}
}

131
src/Native/StockIcon.cs Normal file
View File

@@ -0,0 +1,131 @@

using System;
using System.Runtime.InteropServices;
public class StockIcon
{
[DllImport("shell32.dll")]
public static extern int SHGetStockIconInfo(SHSTOCKICONID siid, SHSTOCKICONFLAGS uFlags, ref SHSTOCKICONINFO info);
[DllImport("user32.dll")]
public static extern bool DestroyIcon(IntPtr handle);
public static IntPtr GetIcon(SHSTOCKICONID identifier, SHSTOCKICONFLAGS flags)
{
SHSTOCKICONINFO info = new SHSTOCKICONINFO();
info.cbSize = Convert.ToUInt32(Marshal.SizeOf(typeof(SHSTOCKICONINFO)));
Marshal.ThrowExceptionForHR(SHGetStockIconInfo(identifier, flags, ref info));
return info.hIcon;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SHSTOCKICONINFO
{
public uint cbSize;
public IntPtr hIcon;
int iSysImageIndex;
int iIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
string szPath;
}
public enum SHSTOCKICONFLAGS : uint
{
SHGSI_ICONLOCATION = 0,
SHGSI_ICON = 0x000000100,
SHGSI_SYSICONINDEX = 0x000004000,
SHGSI_LINKOVERLAY = 0x000008000,
SHGSI_SELECTED = 0x000010000,
SHGSI_LARGEICON = 0x000000000,
SHGSI_SMALLICON = 0x000000001,
SHGSI_SHELLICONSIZE = 0x000000004
}
public enum SHSTOCKICONID : uint
{
DocumentNotAssociated = 0,
DocumentAssociated = 1,
Application = 2,
Folder = 3,
FolderOpen = 4,
Drive525 = 5,
Drive35 = 6,
DriveRemove = 7,
DriveFixed = 8,
DriveNetwork = 9,
DriveNetworkDisabled = 10,
DriveCD = 11,
DriveRAM = 12,
World = 13,
Server = 15,
Printer = 16,
MyNetwork = 17,
Find = 22,
Help = 23,
Share = 28,
Link = 29,
SlowFile = 30,
Recycler = 31,
RecyclerFull = 32,
MediaCDAudio = 40,
Lock = 47,
AutoList = 49,
PrinterNet = 50,
ServerShare = 51,
PrinterFax = 52,
PrinterFaxNet = 53,
PrinterFile = 54,
Stack = 55,
MediaSVCD = 56,
StuffedFolder = 57,
DriveUnknown = 58,
DriveDVD = 59,
MediaDVD = 60,
MediaDVDRAM = 61,
MediaDVDRW = 62,
MediaDVDR = 63,
MediaDVDROM = 64,
MediaCDAudioPlus = 65,
MediaCDRW = 66,
MediaCDR = 67,
MediaCDBurn = 68,
MediaBlankCD = 69,
MediaCDROM = 70,
AudioFiles = 71,
ImageFiles = 72,
VideoFiles = 73,
MixedFiles = 74,
FolderBack = 75,
FolderFront = 76,
Shield = 77,
Warning = 78,
Info = 79,
Error = 80,
Key = 81,
Software = 82,
Rename = 83,
Delete = 84,
MediaAudioDVD = 85,
MediaMovieDVD = 86,
MediaEnhancedCD = 87,
MediaEnhancedDVD = 88,
MediaHDDVD = 89,
MediaBluRay = 90,
MediaVCD = 91,
MediaDVDPlusR = 92,
MediaDVDPlusRW = 93,
DesktopPC = 94,
MobilePC = 95,
Users = 96,
MediaSmartMedia = 97,
MediaCompactFlash = 98,
DeviceCellPhone = 99,
DeviceCamera = 100,
DeviceVideoCamera = 101,
DeviceAudioPlayer = 102,
NetworkConnect = 103,
Internet = 104,
ZipFile = 105,
Settings = 106
}
}

670
src/Native/TaskDialog.cs Normal file
View File

@@ -0,0 +1,670 @@

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
public class Msg
{
static string ShownMessages;
public static string SupportURL { get; set; }
public static void Show(string mainInstruction, string content = null)
{
Msg.Show(mainInstruction, content, MsgIcon.Info, MsgButtons.Ok, MsgResult.None);
}
public static void ShowError(string mainInstruction, string content = null)
{
try
{
using (TaskDialog<string> td = new TaskDialog<string>())
{
td.AllowCancel = false;
if (string.IsNullOrEmpty(content))
{
if (mainInstruction.Length < 80)
td.MainInstruction = mainInstruction;
else
td.Content = mainInstruction;
}
else
{
td.MainInstruction = mainInstruction;
td.Content = content;
}
td.MainIcon = MsgIcon.Error;
td.Footer = "[Copy Message](copymsg)";
if (!string.IsNullOrEmpty(Msg.SupportURL))
td.Footer += $" [Contact Support]({SupportURL})";
td.Show();
}
}
catch (Exception e)
{
MessageBox.Show(e.GetType().Name + "\n\n" + e.Message + "\n\n" + e,
Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
public static void ShowException(Exception exception)
{
try
{
using (TaskDialog<string> td = new TaskDialog<string>())
{
td.MainInstruction = exception.GetType().Name;
td.Content = exception.Message;
td.MainIcon = MsgIcon.Error;
td.ExpandedInformation = exception.StackTrace;
td.Footer = "[Copy Message](copymsg)";
if (!string.IsNullOrEmpty(Msg.SupportURL))
td.Footer += $" [Contact Support]({SupportURL})";
td.Show();
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
public static void ShowWarning(string mainInstruction,
string content = null,
bool onlyOnce = false)
{
if (onlyOnce && Msg.ShownMessages != null &&
Msg.ShownMessages.Contains(mainInstruction + content))
return;
Msg.Show(mainInstruction, content, MsgIcon.Warning, MsgButtons.Ok, MsgResult.None);
if (!onlyOnce) return;
Msg.ShownMessages += mainInstruction + content;
}
public static MsgResult ShowQuestion(
string mainInstruction, MsgButtons buttons = MsgButtons.OkCancel)
{
return Msg.Show(mainInstruction, null, MsgIcon.None, buttons, MsgResult.None);
}
public static MsgResult ShowQuestion(
string mainInstruction, string content, MsgButtons buttons = MsgButtons.OkCancel)
{
return Msg.Show(mainInstruction, content, MsgIcon.None, buttons, MsgResult.None);
}
public static MsgResult Show(
string mainInstruction,
string content,
MsgIcon icon,
MsgButtons buttons,
MsgResult defaultButton = MsgResult.None)
{
try
{
using (TaskDialog<MsgResult> td = new TaskDialog<MsgResult>())
{
td.AllowCancel = false;
td.DefaultButton = defaultButton;
td.MainIcon = icon;
if (content == null)
{
if (mainInstruction.Length < 80)
td.MainInstruction = mainInstruction;
else
td.Content = mainInstruction;
}
else
{
td.MainInstruction = mainInstruction;
td.Content = content;
}
if (buttons == MsgButtons.OkCancel)
{
td.AddButton("OK", MsgResult.OK);
td.AddButton("Cancel", MsgResult.Cancel);
}
else
td.CommonButtons = buttons;
return td.Show();
}
}
catch (Exception e)
{
return (MsgResult)MessageBox.Show(e.GetType().Name + "\n\n" + e.Message + "\n\n" + e,
Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
public class TaskDialog<T> : TaskDialogNative, IDisposable
{
Dictionary<int, T> IdValueDic;
Dictionary<int, string> IdTextDic;
List<int> CommandLinkShieldList;
IntPtr ButtonArray;
IntPtr RadioButtonArray;
T SelectedValueValue;
string SelectedTextValue;
int TimeoutValue;
int ExitTickCount;
bool Disposed;
List<TaskDialogNative.TASKDIALOG_BUTTON> Buttons;
List<TaskDialogNative.TASKDIALOG_BUTTON> RadioButtons;
TaskDialogNative.TASKDIALOGCONFIG Config;
const int TDE_CONTENT = 0;
const int TDE_EXPANDED_INFORMATION = 1;
const int TDE_FOOTER = 2;
const int TDE_MAIN_INSTRUCTION = 3;
const int TDN_CREATED = 0;
const int TDN_NAVIGATED = 1;
const int TDN_BUTTON_CLICKED = 2;
const int TDN_HYPERLINK_CLICKED = 3;
const int TDN_TIMER = 4;
const int TDN_DESTROYED = 5;
const int TDN_RADIO_BUTTON_CLICKED = 6;
const int TDN_DIALOG_CONSTRUCTED = 7;
const int TDN_VERIFICATION_CLICKED = 8;
const int TDN_HELP = 9;
const int TDN_EXPANDO_BUTTON_CLICKED = 10;
const int TDM_NAVIGATE_PAGE = 1125;
const int TDM_CLICK_BUTTON = 1126;
const int TDM_SET_MARQUEE_PROGRESS_BAR = 1127;
const int TDM_SET_PROGRESS_BAR_STATE = 1128;
const int TDM_SET_PROGRESS_BAR_RANGE = 1129;
const int TDM_SET_PROGRESS_BAR_POS = 1130;
const int TDM_SET_PROGRESS_BAR_MARQUEE = 1131;
const int TDM_SET_ELEMENT_TEXT = 1132;
const int TDM_CLICK_RADIO_BUTTON = 1134;
const int TDM_ENABLE_BUTTON = 1135;
const int TDM_ENABLE_RADIO_BUTTON = 1136;
const int TDM_CLICK_VERIFICATION = 1137;
const int TDM_UPDATE_ELEMENT_TEXT = 1138;
const int TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = 1139;
const int TDM_UPDATE_ICON = 1140;
public TaskDialog()
{
IdValueDic = new Dictionary<int, T>();
IdTextDic = new Dictionary<int, string>();
CommandLinkShieldList = new List<int>();
Buttons = new List<TaskDialogNative.TASKDIALOG_BUTTON>();
RadioButtons = new List<TaskDialogNative.TASKDIALOG_BUTTON>();
_SelectedID = -1;
Config = new TaskDialogNative.TASKDIALOGCONFIG();
Config.cbSize = (uint)Marshal.SizeOf(Config);
Config.hwndParent = GetHandle();
Config.hInstance = IntPtr.Zero;
Config.dwFlags = TaskDialogNative.TASKDIALOG_FLAGS.TDF_ALLOW_DIALOG_CANCELLATION;
Config.dwCommonButtons = MsgButtons.None;
Config.MainIcon = new TaskDialogNative.TASKDIALOGCONFIG_ICON_UNION(0);
Config.FooterIcon = new TaskDialogNative.TASKDIALOGCONFIG_ICON_UNION(0);
Config.cxWidth = 0U;
Config.cButtons = 0U;
Config.cRadioButtons = 0U;
Config.pButtons = IntPtr.Zero;
Config.pRadioButtons = IntPtr.Zero;
Config.nDefaultButton = 0;
Config.nDefaultRadioButton = 0;
Config.pszWindowTitle = ((AssemblyProductAttribute)Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), true)[0]).Product;
Config.pszMainInstruction = "";
Config.pszContent = "";
Config.pfCallback = new PFTASKDIALOGCALLBACK(this.DialogProc);
}
public IntPtr GetHandle()
{
StringBuilder lpszFileName = new StringBuilder(260);
IntPtr foregroundWindow = TaskDialogNative.GetForegroundWindow();
TaskDialogNative.GetWindowModuleFileName(foregroundWindow, lpszFileName, 260U);
if (Path.GetFileName(lpszFileName.ToString().Replace(".vshost", "")) ==
Path.GetFileName(Assembly.GetEntryAssembly().Location))
return foregroundWindow;
return IntPtr.Zero;
}
public bool AllowCancel {
set {
if (value)
Config.dwFlags |= TaskDialogNative.TASKDIALOG_FLAGS.TDF_ALLOW_DIALOG_CANCELLATION;
else
Config.dwFlags ^= TaskDialogNative.TASKDIALOG_FLAGS.TDF_ALLOW_DIALOG_CANCELLATION;
}
}
public string MainInstruction {
get => Config.pszMainInstruction;
set => Config.pszMainInstruction = value;
}
public string Content {
get => Config.pszContent;
set => Config.pszContent = ExpandMarkdownMarkup(value);
}
public string ExpandedInformation {
get => Config.pszExpandedInformation;
set => Config.pszExpandedInformation = ExpandMarkdownMarkup(value);
}
public string VerificationText {
get => Config.pszVerificationText;
set => Config.pszVerificationText = value;
}
public MsgResult DefaultButton {
get => (MsgResult)Config.nDefaultButton;
set => Config.nDefaultButton = (int)value;
}
public string Footer {
get => Config.pszFooter;
set => Config.pszFooter = ExpandMarkdownMarkup(value);
}
public MsgIcon MainIcon {
set => Config.MainIcon = new TaskDialogNative.TASKDIALOGCONFIG_ICON_UNION((int)value);
}
int _SelectedID;
public int SelectedID {
get => _SelectedID;
set {
foreach (var i in IdValueDic)
if (i.Key == value)
_SelectedID = value;
}
}
public T SelectedValue {
get {
if (IdValueDic.ContainsKey(SelectedID))
return IdValueDic[SelectedID];
return SelectedValueValue;
}
set => SelectedValueValue = value;
}
public string SelectedText {
get {
if (IdTextDic.ContainsKey(SelectedID))
return IdTextDic[SelectedID];
return SelectedTextValue;
}
set => SelectedTextValue = value;
}
public bool CheckBoxChecked {
get => (Config.dwFlags & TaskDialogNative.TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED)
== TaskDialogNative.TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED;
set {
if (value)
Config.dwFlags |= TaskDialogNative.TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED;
else
Config.dwFlags ^= TaskDialogNative.TASKDIALOG_FLAGS.TDF_VERIFICATION_FLAG_CHECKED;
}
}
public MsgButtons CommonButtons {
get => Config.dwCommonButtons;
set => Config.dwCommonButtons = value;
}
public int Timeout {
get => Convert.ToInt32(TimeoutValue / 1000.0);
set {
TimeoutValue = value * 1000;
Config.dwFlags |= TaskDialogNative.TASKDIALOG_FLAGS.TDF_CALLBACK_TIMER;
}
}
public void AddButton(string text, T value)
{
int n = 1000 + IdValueDic.Count + 1;
IdValueDic[n] = value;
Buttons.Add(new TaskDialogNative.TASKDIALOG_BUTTON(n, text));
}
public string ExpandMarkdownMarkup(string value)
{
if (value.Contains("["))
{
Regex regex = new Regex(@"\[(.+)\]\((.+)\)");
if (regex.Match(value).Success)
{
Config.dwFlags |= TaskDialogNative.TASKDIALOG_FLAGS.TDF_ENABLE_HYPERLINKS;
value = regex.Replace(value, "<a href=\"$2\">$1</a>");
}
}
return value;
}
public void AddCommand(string text)
{
object obj = text;
AddCommand(text, (T)obj);
}
public void AddCommand(string text, T value)
{
int n = 1000 + IdValueDic.Count + 1;
IdValueDic[n] = value;
IdTextDic[n] = text;
Buttons.Add(new TaskDialogNative.TASKDIALOG_BUTTON(n, text));
Config.dwFlags |= TaskDialogNative.TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS;
}
public void AddCommand(string text, string description, T value, bool setShield = false)
{
int n = 1000 + IdValueDic.Count + 1;
IdValueDic[n] = value;
if (setShield)
CommandLinkShieldList.Add(n);
if (!string.IsNullOrEmpty(description))
text += "\n" + description;
Buttons.Add(new TaskDialogNative.TASKDIALOG_BUTTON(n, text));
Config.dwFlags |= TaskDialogNative.TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS;
}
public void AddRadioButton(string text, T value)
{
int n = 1000 + IdValueDic.Count + 1;
IdValueDic[n] = value;
RadioButtons.Add(new TaskDialogNative.TASKDIALOG_BUTTON(n, text));
}
public T Show()
{
MarshalDialogControlStructs();
TaskDialogNative.TASKDIALOGCONFIG config = Config;
int hr = TaskDialogNative.TaskDialogIndirect(
config, out int dummy1, out int dummy2, out bool isChecked);
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
CheckBoxChecked = isChecked;
if (SelectedValue is MsgResult)
SelectedValue = (T)(object)SelectedID;
return SelectedValue;
}
public int DialogProc(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam, IntPtr lpRefData)
{
switch (msg)
{
case 0: //TDN_CREATED
foreach (var i in CommandLinkShieldList)
SendMessage(hwnd, TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE, new IntPtr(i), new IntPtr(1));
break;
case 2: //TDN_BUTTON_CLICKED
case 6: //TDN_RADIO_BUTTON_CLICKED
if (SelectedValue is MsgResult)
_SelectedID = wParam.ToInt32();
else
SelectedID = wParam.ToInt32();
break;
case 3: //TDN_HYPERLINK_CLICKED
string stringUni = Marshal.PtrToStringUni(lParam);
if (stringUni.StartsWith("mailto") || stringUni.StartsWith("http"))
Process.Start(stringUni);
if (stringUni == "copymsg")
{
Thread thread = new Thread((ThreadStart)(() => {
Clipboard.SetText(MainInstruction + "\r\n\r\n" + Content + "\r\n\r\n" + ExpandedInformation);
MessageBox.Show("Message was copied to clipboard.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
}));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
break;
case 4: //TDN_TIMER
if (ExitTickCount == 0) ExitTickCount = Environment.TickCount + Timeout * 1000;
if (Environment.TickCount > ExitTickCount)
TaskDialogNative.SendMessage(hwnd, 1126, new IntPtr(1), IntPtr.Zero);
break;
}
return 0;
}
public void MarshalDialogControlStructs()
{
if (Buttons != null && Buttons.Count > 0)
{
ButtonArray = AllocateAndMarshalButtons(Buttons);
Config.pButtons = ButtonArray;
Config.cButtons = (uint)Buttons.Count;
}
if (RadioButtons == null || RadioButtons.Count <= 0)
return;
RadioButtonArray = AllocateAndMarshalButtons(RadioButtons);
Config.pRadioButtons = RadioButtonArray;
Config.cRadioButtons = (uint)RadioButtons.Count;
}
public static IntPtr AllocateAndMarshalButtons(List<TASKDIALOG_BUTTON> structs)
{
var initialPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(TASKDIALOG_BUTTON)) * structs.Count);
var currentPtr = initialPtr;
foreach (var button in structs)
{
Marshal.StructureToPtr(button, currentPtr, false);
currentPtr = (IntPtr)(currentPtr.ToInt64() + Marshal.SizeOf(button));
}
return initialPtr;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~TaskDialog()
{
Dispose(false);
}
protected void Dispose(bool disposing)
{
if (Disposed)
return;
Disposed = true;
if (ButtonArray != IntPtr.Zero)
{
Marshal.FreeHGlobal(ButtonArray);
ButtonArray = IntPtr.Zero;
}
if (RadioButtonArray != IntPtr.Zero)
{
Marshal.FreeHGlobal(RadioButtonArray);
RadioButtonArray = IntPtr.Zero;
}
}
}
public delegate int PFTASKDIALOGCALLBACK(
IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam, IntPtr lpRefData);
public class TaskDialogNative
{
[DllImport("comctl32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int TaskDialogIndirect(
TaskDialogNative.TASKDIALOGCONFIG pTaskConfig,
out int pnButton,
out int pnRadioButton,
out bool pVerificationFlagChecked);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern uint GetWindowModuleFileName(
IntPtr hwnd, StringBuilder lpszFileName, uint cchFileNameMax);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(
IntPtr handle, int message, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Unicode)]
public class TASKDIALOGCONFIG
{
public uint cbSize;
public IntPtr hwndParent;
public IntPtr hInstance;
public TaskDialogNative.TASKDIALOG_FLAGS dwFlags;
public MsgButtons dwCommonButtons;
public string pszWindowTitle;
public TaskDialogNative.TASKDIALOGCONFIG_ICON_UNION MainIcon;
public string pszMainInstruction;
public string pszContent;
public uint cButtons;
public IntPtr pButtons;
public int nDefaultButton;
public uint cRadioButtons;
public IntPtr pRadioButtons;
public int nDefaultRadioButton;
public string pszVerificationText;
public string pszExpandedInformation;
public string pszExpandedControlText;
public string pszCollapsedControlText;
public TaskDialogNative.TASKDIALOGCONFIG_ICON_UNION FooterIcon;
public string pszFooter;
public PFTASKDIALOGCALLBACK pfCallback;
public IntPtr lpCallbackData;
public uint cxWidth;
}
public enum TASKDIALOG_FLAGS
{
NONE = 0,
TDF_ENABLE_HYPERLINKS = 1,
TDF_USE_HICON_MAIN = 2,
TDF_USE_HICON_FOOTER = 4,
TDF_ALLOW_DIALOG_CANCELLATION = 8,
TDF_USE_COMMAND_LINKS = 16,
TDF_USE_COMMAND_LINKS_NO_ICON = 32,
TDF_EXPAND_FOOTER_AREA = 64,
TDF_EXPANDED_BY_DEFAULT = 128,
TDF_VERIFICATION_FLAG_CHECKED = 256,
TDF_SHOW_PROGRESS_BAR = 512,
TDF_SHOW_MARQUEE_PROGRESS_BAR = 1024,
TDF_CALLBACK_TIMER = 2048,
TDF_POSITION_RELATIVE_TO_WINDOW = 4096,
TDF_RTL_LAYOUT = 8192,
TDF_NO_DEFAULT_RADIO_BUTTON = 16384,
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct TASKDIALOGCONFIG_ICON_UNION
{
[FieldOffset(0)]
public int hMainIcon;
[FieldOffset(0)]
public int pszIcon;
[FieldOffset(0)]
public IntPtr spacer;
public TASKDIALOGCONFIG_ICON_UNION(int i)
{
this = new TaskDialogNative.TASKDIALOGCONFIG_ICON_UNION();
spacer = IntPtr.Zero;
pszIcon = 0;
hMainIcon = i;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Unicode)]
public struct TASKDIALOG_BUTTON
{
public int nButtonID;
[MarshalAs(UnmanagedType.LPWStr)]
public string pszButtonText;
public TASKDIALOG_BUTTON(int n, string txt)
{
this = new TaskDialogNative.TASKDIALOG_BUTTON();
nButtonID = n;
pszButtonText = txt;
}
}
}
public enum MsgButtons
{
None = 0,
Ok = 1,
Yes = 2,
No = 4,
YesNo = 6,
Cancel = 8,
OkCancel = 9,
YesNoCancel = 14,
Retry = 16,
RetryCancel = 24,
Close = 32,
}
public enum MsgResult
{
None,
OK,
Cancel,
Abort,
Retry,
Ignore,
Yes,
No,
}
public enum MsgIcon
{
None = 0,
SecurityShieldGray = 65527,
SecuritySuccess = 65528,
SecurityError = 65529,
SecurityWarning = 65530,
SecurityShieldBlue = 65531,
Shield = 65532,
Info = 65533,
Error = 65534,
Warning = 65535,
}

55
src/Native/Taskbar.cs Normal file
View File

@@ -0,0 +1,55 @@

using System;
using System.Runtime.InteropServices;
public class Taskbar
{
public IntPtr Handle { get; set; }
public Taskbar(IntPtr handle) => Handle = handle;
ITaskbarList3 Instance = (ITaskbarList3)new TaskBarCommunication();
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF")]
interface ITaskbarList3
{
// ITaskbarList
[PreserveSig] void HrInit();
[PreserveSig] void AddTab(IntPtr hwnd);
[PreserveSig] void DeleteTab(IntPtr hwnd);
[PreserveSig] void ActivateTab(IntPtr hwnd);
[PreserveSig] void SetActiveAlt(IntPtr hwnd);
// ITaskbarList2
[PreserveSig] void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);
// ITaskbarList3
[PreserveSig] void SetProgressValue(IntPtr hwnd, ulong ullCompleted, ulong ullTotal);
[PreserveSig] void SetProgressState(IntPtr hwnd, TaskbarStates state);
}
[ComImport]
[ClassInterface(ClassInterfaceType.None)]
[Guid("56FDF344-FD6D-11d0-958A-006097C9A090")]
class TaskBarCommunication
{
}
public void SetState(TaskbarStates taskbarState)
{
Instance.SetProgressState(Handle, taskbarState);
}
public void SetValue(double progressValue, double progressMax)
{
Instance.SetProgressValue(Handle, (ulong)progressValue, (ulong)progressMax);
}
}
public enum TaskbarStates
{
NoProgress = 0,
Indeterminate = 0x1,
Normal = 0x2,
Error = 0x4,
Paused = 0x8
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("mpv.net")]
[assembly: AssemblyDescription("mpv based media player")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("mpv.net/mpv/mplayer")]
[assembly: AssemblyProduct("mpv.net")]
[assembly: AssemblyCopyright("Copyright (C) 2000-2021 mpv.net/mpv/mplayer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1751f378-8edf-4b62-be6d-304c7c287089")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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")]

182
src/Properties/Resources.Designer.cs generated Normal file
View File

@@ -0,0 +1,182 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace mpvnet.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("mpvnet.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to
///[[settings]]
///name = &quot;hwdec&quot;
///file = &quot;mpv&quot;
///default = &quot;no&quot;
///filter = &quot;Video&quot;
///url = &quot;https://mpv.io/manual/master/#options-hwdec&quot;
///help = &quot;Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If hardware decoding is not possible, mpv will fall back on software decoding.\n\nFor more information visit:&quot;
///options = [{ name = &quot;no&quot;, help = &quot;always use software decoding&quot; },
/// { name = &quot;auto&quot;, h [rest of string was truncated]&quot;;.
/// </summary>
internal static string editor_toml {
get {
return ResourceManager.GetString("editor_toml", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to
/// # This file defines the key and mouse bindings and the context menu of mpv.net.
///
/// # A input and config editor can be found in the context menu under &apos;Settings&apos;.
///
/// # The mpv.conf defaults of mpv.net contain: &apos;input-default-bindings = no&apos;
/// # which disables the input defaults of mpv.
///
/// # Every line in this file begins with a space character to make search easier,
/// # if you want to know if &apos;o&apos; has already a binding you can search for &apos; o &apos;.
///
/// # input test mode:
/// # mpvnet --input-test
///
/// # The [rest of string was truncated]&quot;;.
/// </summary>
internal static string input_conf {
get {
return ResourceManager.GetString("input_conf", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to
///input-default-bindings = no
///input-ar-delay = 500
///input-ar-rate = 20
///keep-open = yes
///keep-open-pause = no
///osd-duration = 2000
///osd-playing-msg = &apos;${filename}&apos;
///script-opts = osc-scalewindowed=1.5,osc-hidetimeout=2000,osc-greenandgrumpy=yes,console-scale=1
///screenshot-directory = &apos;~~desktop/&apos;
///
///[protocol.https]
///osd-playing-msg = &apos;${media-title}&apos;
///.
/// </summary>
internal static string mpv_conf {
get {
return ResourceManager.GetString("mpv_conf", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap mpvnet {
get {
object obj = ResourceManager.GetObject("mpvnet", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap mpvnet_santa {
get {
object obj = ResourceManager.GetObject("mpvnet_santa", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized string similar to
///[dark]
///
///heading = #3C8CC8
///foreground = #DDDDDD
///foreground2 = #AAAAAA
///background = #323232
///
///menu-foreground = #DDDDDD
///menu-background = #323232
///menu-highlight = #505050
///menu-border = #FFFFFF
///menu-checked = #5A5A5A
///
///
///[light]
///
///heading = #0068B2
///foreground = #000000
///foreground2 = #4C4C4C
///background = #F7F7F7
///
///menu-foreground = #000000
///menu-background = #DFDFDF
///menu-highlight = #BFBFBF
///menu-border = #6A6A6A
///menu-checked = #AAAAAA
///.
/// </summary>
internal static string theme {
get {
return ResourceManager.GetString("theme", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="editor_toml" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\editor.toml.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
<data name="input_conf" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\input.conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>
<data name="mpvnet" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\mpvnet.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="mpvnet_santa" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\mpvnet-santa.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="mpv_conf" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\mpv.conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
<data name="theme" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\theme.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
</root>

26
src/Properties/Settings.Designer.cs generated Normal file
View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace mpvnet.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@@ -0,0 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles />
<Settings />
</SettingsFile>

65
src/Release.ps1 Normal file
View File

@@ -0,0 +1,65 @@
$tmpDir = 'D:\Work'
$exePath = $PSScriptRoot + '\mpv.net\bin\mpvnet.exe'
$versionInfo = [Diagnostics.FileVersionInfo]::GetVersionInfo($exePath)
$inno = 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe'
$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)
{
if (-not (Test-Path $cloudDirectory))
{
throw $cloudDirectory
}
$targetFile = $cloudDirectory + (Split-Path $sourceFile -Leaf)
if (Test-Path $targetFile)
{
throw $targetFile
}
Copy-Item $sourceFile $targetFile
}
}
if ($versionInfo.FilePrivatePart -eq 0)
{
& $inno setup.iss
if ($LastExitCode) { throw $LastExitCode }
$targetDir = $tmpDir + "\mpv.net-$($versionInfo.FileVersion)-portable"
Copy-Item .\mpv.net\bin $targetDir -Recurse -Exclude System.Management.Automation.xml
& $7z a -tzip -mx9 "$targetDir.zip" -r "$targetDir\*"
if ($LastExitCode) { throw $LastExitCode }
Set-Clipboard ($versionInfo.FileVersion + "`n`nChangelog:`n`n" +
'https://github.com/stax76/mpv.net/blob/master/Changelog.md' + "`n`nDownload:`n`n" +
'https://github.com/stax76/mpv.net/blob/master/Manual.md#stable')
}
else
{
$targetDir = "$tmpDir\mpv.net-$($versionInfo.FileVersion)-portable-beta"
Copy-Item .\mpv.net\bin $targetDir -Recurse -Exclude System.Management.Automation.xml
& $7z a -t7z -mx9 "$targetDir.7z" -r "$targetDir\*"
if ($LastExitCode) { throw $LastExitCode }
UploadBeta "$targetDir.7z"
foreach ($cloudDirectory in $cloudDirectories)
{
Invoke-Item $cloudDirectory
}
Set-Clipboard ($versionInfo.FileVersion + " Beta`n`nChangelog:`n`n" +
'https://github.com/stax76/mpv.net/blob/master/Changelog.md' + "`n`nDownload:`n`n" +
'https://github.com/stax76/mpv.net/blob/master/Manual.md#beta')
}
Write-Host 'successfully finished' -ForegroundColor Green

View File

@@ -0,0 +1,625 @@
[[settings]]
name = "hwdec"
file = "mpv"
default = "no"
filter = "Video"
url = "https://mpv.io/manual/master/#options-hwdec"
help = "Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If hardware decoding is not possible, mpv will fall back on software decoding.\n\nFor more information visit:"
options = [{ name = "no", help = "always use software decoding" },
{ name = "auto", help = "enable best hw decoder (see below)" },
{ name = "yes", help = "exactly the same as auto" },
{ name = "auto-copy", help = "enable best hw decoder with copy-back (see below)" },
{ name = "dxva2", help = "requires vo=gpu with gpu-context=d3d11, gpu-context=angle or gpu-context=dxinterop (Windows only)" },
{ name = "dxva2-copy", help = "copies video back to system RAM (Windows only)" },
{ name = "d3d11va", help = "requires vo=gpu with gpu-context=d3d11 or gpu-context=angle (Windows 8+ only)" },
{ name = "d3d11va-copy", help = "copies video back to system RAM (Windows 8+ only)" },
{ name = "cuda", help = "requires vo=gpu (Any platform CUDA is available)" },
{ name = "cuda-copy", help = "copies video back to system RAM (Any platform CUDA is available)" },
{ name = "nvdec", help = "requires vo=gpu (Any platform CUDA is available)" },
{ name = "nvdec-copy", help = "copies video back to system RAM (Any platform CUDA is available)" },
{ name = "crystalhd", help = "copies video back to system RAM (Any platform supported by hardware)" },
{ name = "rkmpp", help = "requires vo=gpu (some RockChip devices only)" }]
[[settings]]
name = "gpu-api"
file = "mpv"
default = "auto"
filter = "Video"
help = "Controls which type of graphics APIs will be accepted. Auto uses d3d11, it should only be changed in case of problems, Vulkan is not recommended."
options = [{ name = "auto", help = "Use any available API" },
{ name = "opengl", help = "Allow only OpenGL (requires OpenGL 2.1+ or GLES 2.0+)" },
{ name = "vulkan", help = "Allow only Vulkan (not recommended). " },
{ name = "d3d11", help = "Allow only gpu-context=d3d11" }]
[[settings]]
name = "gpu-context"
file = "mpv"
default = "auto"
filter = "Video"
options = [{ name = "auto", help = "auto-select" },
{ name = "win", help = "Win32/WGL" },
{ name = "winvk", help = "VK_KHR_win32_surface" },
{ name = "angle", help = "Direct3D11 through the OpenGL ES translation layer ANGLE. This supports almost everything the win backend does (if the ANGLE build is new enough)." },
{ name = "dxinterop", help = "(experimental) Win32, using WGL for rendering and Direct3D 9Ex for presentation. Works on Nvidia and AMD. Newer Intel chips with the latest drivers may also work." },
{ name = "d3d11", help = "Win32, with native Direct3D 11 rendering." }]
[[settings]]
name = "vo"
file = "mpv"
default = "gpu"
filter = "Video"
help = "Video output drivers to be used.\n\nFor more information visit:"
url = "https://mpv.io/manual/master/#video-output-drivers-vo"
options = [{ name = "gpu", help = "General purpose, customizable, GPU-accelerated video output driver. It supports extended scaling methods, dithering, color management, custom shaders, HDR, and more." },
{ name = "direct3d", help = "Video output driver that uses the Direct3D interface" }]
[[settings]]
name = "video-sync"
file = "mpv"
default = "audio"
filter = "Video"
help = "How the player synchronizes audio and video.\n\nFor more information visit:"
url = "https://mpv.io/manual/master/#options-video-sync"
options = [{ name = "audio" },
{ name = "display-resample" },
{ name = "display-resample-vdrop" },
{ name = "display-resample-desync" },
{ name = "display-vdrop" },
{ name = "display-adrop" },
{ name = "display-desync" },
{ name = "desync" }]
[[settings]]
name = "scale"
file = "mpv"
default = "bilinear"
filter = "Video"
help = "The GPU renderer filter function to use when upscaling video. There are some more filters, but most are not as useful. For a complete list, pass help as value, e.g.: mpv --scale=help"
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality)." },
{ name = "spline36", help = "Mid quality and speed. This is the default when using gpu-hq." },
{ name = "lanczos", help = "Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)" },
{ name = "ewa_lanczos", help = "Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)" },
{ name = "ewa_lanczossharp", help = "A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default." },
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see tscale)." },
{ name = "linear", help = "A tscale filter." }]
[[settings]]
name = "cscale"
file = "mpv"
default = "bilinear"
filter = "Video"
help = "As scale, but for interpolating chroma information. If the image is not subsampled, this option is ignored entirely."
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality)." },
{ name = "spline36", help = "Mid quality and speed. This is the default when using gpu-hq." },
{ name = "lanczos", help = "Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)" },
{ name = "ewa_lanczos", help = "Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)" },
{ name = "ewa_lanczossharp", help = "A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default." },
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see tscale)." },
{ name = "linear", help = "A tscale filter." }]
[[settings]]
name = "dscale"
file = "mpv"
default = "bilinear"
filter = "Video"
help = "Like scale, but apply these filters on downscaling instead. If this option is unset, the filter implied by scale will be applied."
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality)." },
{ name = "spline36", help = "Mid quality and speed. This is the default when using gpu-hq." },
{ name = "lanczos", help = "Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)" },
{ name = "ewa_lanczos", help = "Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)" },
{ name = "ewa_lanczossharp", help = "A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default." },
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see tscale)." },
{ name = "linear", help = "A tscale filter." }]
[[settings]]
name = "dither-depth"
file = "mpv"
default = "no"
filter = "Video"
help = "Set dither target depth to N. Note that the depth of the connected video display device cannot be detected. Often, LCD panels will do dithering on their own, which conflicts with this option and leads to ugly output."
options = [{ name = "no", help = "Disable any dithering done by mpv." },
{ name = "auto", help = "Automatic selection. If output bit depth cannot be detected, 8 bits per component are assumed." },
{ name = "8", help = "Dither to 8 bit output." }]
[[settings]]
name = "correct-downscaling"
file = "mpv"
default = "no"
filter = "Video"
help = "When using convolution based filters, extend the filter size when downscaling. Increases quality, but reduces performance while downscaling.\n\nThis will perform slightly sub-optimally for anamorphic video (but still better than without it) since it will extend the size to match only the milder of the scale factors between the axes."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "sigmoid-upscaling"
file = "mpv"
default = "no"
filter = "Video"
help = "When upscaling, use a sigmoidal color transform to avoid emphasizing ringing artifacts. This also implies linear-scaling."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "deband"
file = "mpv"
default = "no"
filter = "Video"
help = "Enable the debanding algorithm. This greatly reduces the amount of visible banding, blocking and other quantization artifacts, at the expense of very slightly blurring some of the finest details. In practice, it's virtually always an improvement - the only reason to disable it would be for performance."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "d3d11va-zero-copy"
file = "mpv"
default = "no"
filter = "Video"
help = "By default, when using hardware decoding with --gpu-api=d3d11, the video image will be copied (GPU-to-GPU) from the decoder surface to a shader resource. Set this option to avoid that copy by sampling directly from the decoder image. This may increase performance and reduce power usage, but can cause the image to be sampled incorrectly on the bottom and right edges due to padding, and may invoke driver bugs, since Direct3D 11 technically does not allow sampling from a decoder surface (though most drivers support it.)"
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "hdr-compute-peak"
file = "mpv"
default = "auto"
filter = "Video"
help = "Compute the HDR peak and frame average brightness per-frame instead of relying on tagged metadata. These values are averaged over local regions as well as over several frames to prevent the value from jittering around too much. This option basically gives you dynamic, per-scene tone mapping. Requires compute shaders, which is a fairly recent OpenGL feature, and will probably also perform horribly on some drivers, so enable at your own risk. The special value auto (default) will enable HDR peak computation automatically if compute shaders and SSBOs are supported."
options = [{ name = "auto" },
{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "volume"
file = "mpv"
filter = "Audio"
help = "Set the startup volume. 0 means silence, 100 means no volume reduction or amplification. Negative values can be passed for compatibility, but are treated as 0. Since mpv 0.18.1, this always controls the internal mixer (aka \"softvol\"). Default: 100"
[[settings]]
name = "remember-volume"
file = "mpvnet"
default = "yes"
filter = "Audio"
help = "Save volume and mute on exit and restore it on start. (mpv.net specific setting)"
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "alang"
file = "mpv"
filter = "Audio"
type = "string"
help = "Specify a priority list of audio languages to use. Different container formats employ different language codes. DVDs use ISO 639-1 two-letter language codes, Matroska, MPEG-TS and NUT use ISO 639-2 three-letter language codes, while OGM uses a free-form identifier. See also aid.\n\nExamples\n\nmpv dvd://1 alang=hu,en chooses the Hungarian language track on a DVD and falls back on English if Hungarian is not available.\n\nmpv alang=jpn example.mkv plays a Matroska file with Japanese audio."
[[settings]]
name = "audio-file-auto"
file = "mpv"
default = "no"
filter = "Audio"
help = "Load additional audio files matching the video filename. The parameter specifies how external audio files are matched."
options = [{ name = "no", help = "Don't automatically load external audio files." },
{ name = "exact", help = "Load the media filename with audio file extension." },
{ name = "fuzzy", help = "Load all audio files containing media filename." },
{ name = "all", help = "Load all audio files in the current and audio-file-paths directories." }]
[[settings]]
name = "audio-device"
file = "mpv"
filter = "Audio"
type = "string"
url = "https://mpv.io/manual/master/#options-audio-device"
help = "<name> Use the given audio device. This consists of the audio output name, e.g. alsa, followed by /, followed by the audio output specific device name. The default value for this option is auto, which tries every audio output in preference order with the default device.\nAvailable devices can be found in the mpv.net context menu under:\nView > Show Audio Devices"
[[settings]]
name = "slang"
file = "mpv"
filter = "Subtitle"
type = "string"
help = "Specify a priority list of subtitle languages to use. Different container formats employ different language codes. DVDs use ISO 639-1 two letter language codes, Matroska uses ISO 639-2 three letter language codes while OGM uses a free-form identifier. See also sid."
[[settings]]
name = "sub-auto"
file = "mpv"
default = "exact"
filter = "Subtitle"
help = "Load additional subtitle files matching the video filename. The parameter specifies how external subtitle files are matched. exact is enabled by default."
options = [{ name = "no", help = "Don't automatically load external subtitle files." },
{ name = "exact", help = "Load the media filename with subtitle file extension." },
{ name = "fuzzy", help = "Load all subs containing media filename." },
{ name = "all", help = "Load all subs in the current and sub-file-paths directories." }]
[[settings]]
name = "sub-font"
file = "mpv"
filter = "Subtitle"
type = "string"
help = "Specify font to use for subtitles that do not themselves specify a particular font. The default is sans-serif."
[[settings]]
name = "sub-font-size"
file = "mpv"
filter = "Subtitle"
help = "Specify the sub font size. The unit is the size in scaled pixels at a window height of 720. The actual pixel size is scaled with the window height: if the window height is larger or smaller than 720, the actual size of the text increases or decreases as well. Default: 55"
[[settings]]
name = "sub-color"
file = "mpv"
type = "color"
filter = "Subtitle"
help = "Specify the color used for unstyled text subtitles.\n\nThe color is specified in the form r/g/b, where each color component is specified as number in the range 0.0 to 1.0. It's also possible to specify the transparency by using r/g/b/a, where the alpha value 0 means fully transparent, and 1.0 means opaque. If the alpha component is not given, the color is 100% opaque.\n\nPassing a single number to the option sets the sub to gray, and the form gray/a lets you specify alpha additionally.\n\nExamples\n\n1.0/0.0/0.0 set sub to opaque red\n1.0/0.0/0.0/0.75 set sub to opaque red with 75% alpha\n0.5/0.75 set sub to 50% gray with 75% alpha\n\nAlternatively, the color can be specified as a RGB hex triplet in the form #RRGGBB, where each 2-digit group expresses a color value in the range 0 (00) to 255 (FF). For example, #FF0000 is red. This is similar to web colors. Alpha is given with #AARRGGBB.\n\nExamples\n\n#FF0000 set sub to opaque red\n#C0808080 set sub to 50% gray with 75% alpha"
[[settings]]
name = "sub-border-color"
file = "mpv"
type = "color"
filter = "Subtitle"
help = "See sub-color. Color used for the sub font border. Ignored when sub-back-color is specified (or more exactly: when that option is not set to completely transparent)."
[[settings]]
name = "sub-back-color"
file = "mpv"
type = "color"
filter = "Subtitle"
help = "See sub-color. Color used for sub text background. You can use sub-shadow-offset to change its size relative to the text."
[[settings]]
name = "fullscreen"
file = "mpv"
default = "no"
filter = "Screen"
help = "Start the player in fullscreen mode."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "border"
file = "mpv"
default = "yes"
filter = "Screen"
help = "Show window with decoration (titlebar, border)."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "screen"
file = "mpv"
filter = "Screen"
help = "<0-32> In multi-monitor configurations (i.e. a single desktop that spans across multiple displays), this option tells mpv which screen to display the video on."
[[settings]]
name = "osd-playing-msg"
file = "mpv"
width = 300
filter = "Screen"
type = "string"
help = "Show a message on OSD when playback starts. The string is expanded for properties, e.g. osd-playing-msg='file: ${filename}' will show the message file: followed by a space and the currently played filename. For more information visit:"
url = "https://mpv.io/manual/master/#property-expansion"
[[settings]]
name = "osd-font-size"
file = "mpv"
filter = "Screen"
help = "Specify the OSD font size. See sub-font-size for details. Default: 55"
[[settings]]
name = "osd-duration"
file = "mpv"
filter = "Screen"
help = "Set the duration of the OSD messages in ms. Default: 1000"
[[settings]]
name = "osd-scale-by-window"
file = "mpv"
default = "yes"
filter = "Screen"
help = "Whether to scale the OSD with the window size. If this is disabled, osd-font-size and other OSD options that use scaled pixels are always in actual pixels. The effect is that changing the window size won't change the OSD font size."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "autofit"
file = "mpv"
filter = "Screen"
help = "<int> Initial window height in percent. Default: 60"
[[settings]]
name = "autofit-smaller"
file = "mpv"
filter = "Screen"
help = "<int> Minimum window height in percent. Default: 30"
[[settings]]
name = "autofit-larger"
file = "mpv"
filter = "Screen"
help = "<int> Maximum window height in percent. Default: 80"
[[settings]]
name = "start-size"
file = "mpvnet"
default = "previous"
filter = "Screen"
help = "Setting to remember the window height. (mpv.net specific setting)"
options = [{ name = "video", help = "Window size is set to video resolution" },
{ name = "previous", help = "Window size is remembered but only in the current session" },
{ name = "always", help = "Window size is always remembered"}]
[[settings]]
name = "start-threshold"
file = "mpvnet"
filter = "Screen"
help = "Threshold in milliseconds to wait for libmpv returning the video resolution before the window is shown, otherwise default dimensions are used as defined by autofit and start-size. Default: 1500 (mpv.net specific setting)"
[[settings]]
name = "minimum-aspect-ratio"
file = "mpvnet"
filter = "Screen"
help = "<float> Minimum aspect ratio, if the AR is smaller than the defined value then the window AR is set to 16/9. This avoids a square window for Music with cover art. Default: 1.2 (mpv.net specific setting)"
[[settings]]
name = "remember-position"
file = "mpvnet"
default = "no"
filter = "Screen"
help = "Save the window position on exit. (mpv.net specific setting)"
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "window-maximized"
file = "mpv"
default = "no"
filter = "Screen"
help = "Start with a maximized window."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "screenshot-directory"
file = "mpv"
width = 500
type = "folder"
filter = "Screen"
help = "Store screenshots in this directory. This path is joined with the filename generated by screenshot-template. If the template filename is already absolute, the directory is ignored.\n\nIf the directory does not exist, it is created on the first screenshot. If it is not a directory, an error is generated when trying to write a screenshot."
[[settings]]
name = "screenshot-format"
file = "mpv"
default = "jpg"
filter = "Screen"
help = "Set the image file type used for saving screenshots."
options = [{ name = "jpg" },
{ name = "png" }]
[[settings]]
name = "screenshot-tag-colorspace"
file = "mpv"
default = "no"
filter = "Screen"
help = "Tag screenshots with the appropriate colorspace. Note that not all formats are supported."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "screenshot-high-bit-depth"
file = "mpv"
default = "yes"
filter = "Screen"
help = "If possible, write screenshots with a bit depth similar to the source video. This is interesting in particular for PNG, as this sometimes triggers writing 16 bit PNGs with huge file sizes. This will also include an unused alpha channel in the resulting files if 16 bit is used."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "screenshot-jpeg-source-chroma"
file = "mpv"
default = "yes"
filter = "Screen"
help = "Write JPEG files with the same chroma subsampling as the video. If disabled, the libjpeg default is used."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "screenshot-template"
file = "mpv"
filter = "Screen"
type = "string"
help = "Specify the filename template used to save screenshots. The template specifies the filename without file extension, and can contain format specifiers, which will be substituted when taking a screenshot. By default, the template is mpv-shot%n, which results in filenames like mpv-shot0012.png for example.\n\nFind the full documentation here:"
url = "https://mpv.io/manual/master/#options-screenshot-template"
[[settings]]
name = "screenshot-jpeg-quality"
file = "mpv"
filter = "Screen"
help = "<0-100> Set the JPEG quality level. Higher means better quality. The default is 90."
[[settings]]
name = "screenshot-png-compression"
file = "mpv"
filter = "Screen"
help = "<0-9> Set the PNG compression level. Higher means better compression. This will affect the file size of the written screenshot file and the time it takes to write a screenshot. Too high compression might occupy enough CPU time to interrupt playback. The default is 7."
[[settings]]
name = "screenshot-png-filter"
file = "mpv"
filter = "Screen"
help = "<0-5> Set the filter applied prior to PNG compression. 0 is none, 1 is 'sub', 2 is 'up', 3 is 'average', 4 is 'Paeth', and 5 is 'mixed'. This affects the level of compression that can be achieved. For most images, 'mixed' achieves the best compression ratio, hence it is the default."
[[settings]]
name = "taskbar-progress"
file = "mpv"
default = "yes"
filter = "Playback"
help = "Show progress in taskbar."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "keep-open-pause"
file = "mpv"
default = "yes"
filter = "Playback"
help = "If set to no, instead of pausing when keep-open is active, just stop at end of file and continue playing forward when you seek backwards until end where it stops again."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "keep-open"
file = "mpv"
default = "no"
filter = "Playback"
help = "Using no, mpv would terminate after the last file but mpv.net never terminates automatically."
options = [{ name = "yes", help = "If the current file ends, go to the next file, keep the last file open."},
{ name = "no", help = "If the current file ends, go to the next file." },
{ name = "always", help = "Playback will never automatically advance to the next file."}]
[[settings]]
name = "loop-file"
file = "mpv"
filter = "Playback"
help = "<N|inf|no> Loop a single file N times. inf means forever, no means normal playback.\n\nThe difference to loop-playlist is that this doesn't loop the playlist, just the file itself. If the playlist contains only a single file, the difference between the two option is that this option performs a seek on loop, instead of reloading the file. loop is an alias for this option."
[[settings]]
name = "save-position-on-quit"
file = "mpv"
default = "no"
filter = "Playback"
help = "Always save the current playback position on quit. When this file is played again later, the player will seek to the old playback position on start. This does not happen if playback of a file is stopped in any other way than quitting. For example, going to the next file in the playlist will not save the position, and start playback at beginning the next time the file is played.\n\nThis behavior is disabled by default, but is always available when quitting the player with Shift+Q."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "hr-seek"
file = "mpv"
default = "absolute"
filter = "Playback"
help = "Select when to use precise seeks that are not limited to keyframes. Such seeks require decoding video from the previous keyframe up to the target position and so can take some time depending on decoding performance. For some video formats, precise seeks are disabled. This option selects the default choice to use for seeks; it is possible to explicitly override that default in the definition of key bindings and in input commands."
options = [{ name = "yes", help = "Use precise seeks whenever possible." },
{ name = "no", help = "Never use precise seeks." },
{ name = "absolute", help = "Use precise seeks if the seek is to an absolute position in the file, such as a chapter seek, but not for relative seeks like the default behavior of arrow keys." },
{ name = "always", help = "Same as yes (for compatibility)." }]
[[settings]]
name = "track-auto-selection"
file = "mpv"
default = "yes"
filter = "Playback"
help = "Enable the default track auto-selection. Enabling this will make the player select streams according to aid, alang, and others. If it is disabled, no tracks are selected. In addition, the player will not exit if no tracks are selected, and wait instead (this wait mode is similar to pausing, but the pause option is not set).\n\nThis is useful with lavfi-complex: you can start playback in this mode, and then set select tracks at runtime by setting the filter graph. Note that if lavfi-complex is set before playback is started, the referenced tracks are always selected."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "loop-playlist"
file = "mpv"
filter = "Playback"
help = "<N|inf|force|no> Loops playback N times. A value of 1 plays it one time (default), 2 two times, etc. inf means forever. no is the same as 1 and disables looping. If several files are specified on command line, the entire playlist is looped. The force mode is like inf, but does not skip playlist entries which have been marked as failing. This means the player might waste CPU time trying to loop a file that doesn't exist. But it might be useful for playing webradios under very bad network conditions."
[[settings]]
name = "auto-load-folder"
file = "mpvnet"
default = "yes"
filter = "Playback"
help = "For single files automatically load the entire directory into the playlist. Can be suppressed via shift key. (mpv.net specific setting)"
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"
filter = "Input"
help = "Delay in milliseconds before we start to autorepeat a key (0 to disable)."
[[settings]]
name = "input-ar-rate"
file = "mpv"
filter = "Input"
help = "Number of key presses to generate per second on autorepeat."
[[settings]]
name = "update-check"
file = "mpvnet"
default = "no"
filter = "General"
help = "Daily check for new version. (requires PowerShell 5 and curl. mpv.net specific setting)"
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "process-instance"
file = "mpvnet"
default = "single"
filter = "General"
help = "Defines if more then one mpv.net process is allowed. (mpv.net specific setting)\n\nTip: Whenever the control key is pressed when files or URLs are opened, the playlist is not cleared but the files or URLs are appended to the playlist. This not only works on process startup but in all mpv.net features that open files and URLs."
options = [{ name = "multi", help = "Create a new process everytime the shell starts mpv.net" },
{ name = "single", help = "Force a single process everytime the shell starts mpv.net" },
{ name = "queue", help = "Force a single process and add files to playlist" }]
[[settings]]
name = "recent-count"
file = "mpvnet"
filter = "General"
help = "<int> Amount of recent files to be remembered. Default: 15 (mpv.net specific setting)"
[[settings]]
name = "video-file-extensions"
file = "mpvnet"
filter = "General"
width = 500
help = "Video file extensions used to create file associations and used by the auto-load-folder feature. (mpv.net specific setting)"
[[settings]]
name = "audio-file-extensions"
file = "mpvnet"
filter = "General"
width = 500
help = "Audio file extensions used to create file associations and used by the auto-load-folder feature. (mpv.net specific setting)"
[[settings]]
name = "image-file-extensions"
file = "mpvnet"
filter = "General"
width = 500
help = "Image file extensions used to create file associations and used by the auto-load-folder feature. (mpv.net specific setting)"
[[settings]]
name = "debug-mode"
file = "mpvnet"
default = "no"
filter = "General"
help = "Enable this only when a developer asks for it. (mpv.net specific setting)"
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "dark-mode"
file = "mpvnet"
default = "always"
filter = "UI"
help = "Enables a dark theme. (mpv.net specific setting)"
options = [{ name = "always" },
{ name = "system" , help = "Available on Windows 10 or higher" },
{ name = "never" }]
[[settings]]
name = "dark-theme"
file = "mpvnet"
filter = "UI"
url = "https://github.com/stax76/mpv.net/blob/master/Manual.md#color-theme"
help = "Color theme used in dark mode. Default: dark"
[[settings]]
name = "light-theme"
file = "mpvnet"
filter = "UI"
url = "https://github.com/stax76/mpv.net/blob/master/Manual.md#color-theme"
help = "Color theme used in light mode. Default: light"

View File

@@ -0,0 +1,215 @@
# This file defines the key and mouse bindings and the context menu of mpv.net.
# A input and config editor can be found in the context menu under 'Settings'.
# The mpv.conf defaults of mpv.net contain: 'input-default-bindings = no'
# which disables the input defaults of mpv.
# Every line in this file begins with a space character to make search easier,
# if you want to know if 'o' has already a binding you can search for ' o '.
# input test mode:
# mpvnet --input-test
# The input key list can be found in the context menu under: View > Show Keys
# mpv.net input.conf defaults:
# https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/input.conf.txt
# mpv input.conf defaults:
# https://github.com/mpv-player/mpv/blob/master/etc/input.conf
# mpv input commands:
# https://mpv.io/manual/master/#list-of-input-commands
# mpv input options:
# https://mpv.io/manual/master/#input
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
_ script-message mpv.net open-optical-media #menu: Open > Open DVD/Blu-ray Drive/Folder...
_ ignore #menu: Open > -
Alt+a script-message mpv.net load-audio #menu: Open > Load external audio files...
Alt+s script-message mpv.net load-sub #menu: Open > Load external subtitle files...
_ ignore #menu: Open > -
_ script-message mpv.net open-files append #menu: Open > Add files to playlist...
F3 script-message mpv.net show-media-search #menu: Open > Show media search...
_ ignore #menu: Open > -
_ ignore #menu: Open > Recent
_ ignore #menu: -
Space cycle pause #menu: Play/Pause
s stop #menu: Stop
_ ignore #menu: -
Enter cycle fullscreen #menu: Toggle Fullscreen
F11 playlist-prev #menu: Navigate > Previous File
F12 playlist-next #menu: Navigate > Next File
_ ignore #menu: Navigate > -
Home script-message mpv.net playlist-first #menu: Navigate > First File
End script-message mpv.net playlist-last #menu: Navigate > Last File
_ ignore #menu: Navigate > -
PGUP add chapter 1 #menu: Navigate > Next Chapter
PGDWN add chapter -1 #menu: Navigate > Previous Chapter
_ ignore #menu: Navigate > -
. frame-step #menu: Navigate > Jump Next Frame
, frame-back-step #menu: Navigate > Jump Previous Frame
_ ignore #menu: Navigate > -
Right seek 5 #menu: Navigate > Jump 5 sec forward
Left seek -5 #menu: Navigate > Jump 5 sec backward
_ ignore #menu: Navigate > -
Up seek 30 #menu: Navigate > Jump 30 sec forward
Down seek -30 #menu: Navigate > Jump 30 sec backward
_ ignore #menu: Navigate > -
Ctrl+Right seek 300 #menu: Navigate > Jump 5 min forward
Ctrl+Left seek -300 #menu: Navigate > Jump 5 min backward
_ ignore #menu: Navigate > -
_ ignore #menu: Navigate > Titles
_ ignore #menu: Navigate > Chapters
Ctrl++ add video-zoom 0.1 #menu: Pan & Scan > Increase Size
Ctrl+- add video-zoom -0.1 #menu: Pan & Scan > Decrease Size
_ ignore #menu: Pan & Scan > -
Ctrl+KP4 add video-pan-x -0.01 #menu: Pan & Scan > Move Left
Ctrl+KP6 add video-pan-x 0.01 #menu: Pan & Scan > Move Right
_ ignore #menu: Pan & Scan > -
Ctrl+KP8 add video-pan-y -0.01 #menu: Pan & Scan > Move Up
Ctrl+KP2 add video-pan-y 0.01 #menu: Pan & Scan > Move Down
_ ignore #menu: Pan & Scan > -
w add panscan -0.1 #menu: Pan & Scan > Decrease Height
W add panscan 0.1 #menu: Pan & Scan > Increase Height
_ ignore #menu: Pan & Scan > -
Ctrl+BS set video-zoom 0; set video-pan-x 0; set video-pan-y 0 #menu: Pan & Scan > Reset
Ctrl+1 add contrast -1 #menu: Video > Decrease Contrast
Ctrl+2 add contrast 1 #menu: Video > Increase Contrast
_ ignore #menu: Video > -
Ctrl+3 add brightness -1 #menu: Video > Decrease Brightness
Ctrl+4 add brightness 1 #menu: Video > Increase Brightness
_ ignore #menu: Video > -
Ctrl+5 add gamma -1 #menu: Video > Decrease Gamma
Ctrl+6 add gamma 1 #menu: Video > Increase Gamma
_ ignore #menu: Video > -
Ctrl+7 add saturation -1 #menu: Video > Decrease Saturation
Ctrl+8 add saturation 1 #menu: Video > Increase Saturation
_ ignore #menu: Video > -
Ctrl+s async screenshot #menu: Video > Take Screenshot
d cycle deinterlace #menu: Video > Toggle Deinterlace
a cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1" #menu: Video > Cycle Aspect Ratio
KP7 script-message mpv.net cycle-audio #menu: Audio > Cycle/Next
_ ignore #menu: Audio > -
KP6 add audio-delay 0.1 #menu: Audio > Delay +0.1
KP9 add audio-delay -0.1 #menu: Audio > Delay -0.1
KP8 cycle sub #menu: Subtitle > Cycle/Next
v cycle sub-visibility #menu: Subtitle > Toggle Visibility
_ ignore #menu: Subtitle > -
z add sub-delay -0.1 #menu: Subtitle > Delay -0.1
Z add sub-delay 0.1 #menu: Subtitle > Delay +0.1
_ ignore #menu: Subtitle > -
r add sub-pos -1 #menu: Subtitle > Move Up
R add sub-pos +1 #menu: Subtitle > Move Down
_ ignore #menu: Subtitle > -
_ add sub-scale -0.1 #menu: Subtitle > Decrease Subtitle Font Size
_ add sub-scale 0.1 #menu: Subtitle > Increase Subtitle Font Size
_ ignore #menu: Track
+ add volume 10 #menu: Volume > Up
- add volume -10 #menu: Volume > Down
_ ignore #menu: Volume > -
m cycle mute #menu: Volume > Mute
[ multiply speed 1/1.1 #menu: Speed > -10%
] multiply speed 1.1 #menu: Speed > +10%
_ ignore #menu: Speed > -
{ multiply speed 0.5 #menu: Speed > Half
} multiply speed 2.0 #menu: Speed > Double
_ ignore #menu: Speed > -
BS set speed 1 #menu: Speed > Reset
KP0 script-message rate-file 0 #menu: Extensions > Rating > 0stars
KP1 script-message rate-file 1 #menu: Extensions > Rating > 1stars
KP2 script-message rate-file 2 #menu: Extensions > Rating > 2stars
KP3 script-message rate-file 3 #menu: Extensions > Rating > 3stars
KP4 script-message rate-file 4 #menu: Extensions > Rating > 4stars
KP5 script-message rate-file 5 #menu: Extensions > Rating > 5stars
_ ignore #menu: Extensions > Rating > -
_ script-message rate-file about #menu: Extensions > Rating > About
Ctrl+t set ontop yes #menu: View > On Top > Enable
Ctrl+T set ontop no #menu: View > On Top > Disable
Alt++ script-message mpv.net scale-window 1.2 #menu: View > Zoom > Enlarge
Alt+- script-message mpv.net scale-window 0.8 #menu: View > Zoom > Shrink
_ ignore #menu: View > Zoom > -
Alt+0 set window-scale 0.5 #menu: View > Zoom > 50 %
Alt+1 set window-scale 1.0 #menu: View > Zoom > 100 %
Alt+2 set window-scale 2.0 #menu: View > Zoom > 200 %
b cycle border #menu: View > Toggle Border
i script-message mpv.net show-info #menu: View > File/Stream Info
t script-binding stats/display-stats #menu: View > Show Statistics
T script-binding stats/display-stats-toggle #menu: View > Toggle Statistics
Del script-binding osc/visibility #menu: View > Toggle OSC Visibility
_ script-message mpv.net show-audio-devices #menu: View > Show Audio Devices
Shift+c script-message mpv.net show-commands #menu: View > Show Commands
` script-binding console/enable #menu: View > Show Console
_ script-message mpv.net show-decoders #menu: View > Show Decoders
_ script-message mpv.net show-demuxers #menu: View > Show Demuxers
_ script-message mpv.net show-keys #menu: View > Show Keys
F8 script-message mpv.net show-playlist #menu: View > Show Playlist
Ctrl+p script-message mpv.net show-profiles #menu: View > Show Profiles
p show-progress #menu: View > Show Progress
Shift+p script-message mpv.net show-properties #menu: View > Show Properties
_ script-message mpv.net show-protocols #menu: View > Show Protocols
F9 show-text ${track-list} 5000 #menu: View > Show Tracks
c script-message mpv.net show-conf-editor #menu: Settings > Show Config Editor
Ctrl+i script-message mpv.net show-input-editor #menu: Settings > Show Input Editor
Ctrl+f script-message mpv.net open-conf-folder #menu: Settings > Open Config Folder
F1 script-message mpv.net show-command-palette #menu: Tools > Show All Commands
h script-message mpv.net show-history #menu: Tools > Show History
l ab-loop #menu: Tools > Set/clear A-B loop points
L cycle-values loop-file "inf" "no" #menu: Tools > Toggle infinite file looping
_ playlist-shuffle #menu: Tools > Shuffle Playlist
Ctrl+h cycle-values hwdec "auto" "no" #menu: Tools > Toggle Hardware Decoding
_ script-message mpv.net show-setup-dialog #menu: Tools > Setup...
_ script-message mpv.net shell-execute https://mpv.io #menu: Help > Website mpv
_ script-message mpv.net shell-execute https://github.com/stax76/mpv.net #menu: Help > Website mpv.net
_ ignore #menu: Help > -
_ script-message mpv.net shell-execute https://mpv.io/manual/stable/ #menu: Help > Manual mpv
_ script-message mpv.net shell-execute https://github.com/stax76/mpv.net/blob/master/Manual.md #menu: Help > Manual mpv.net
_ ignore #menu: Help > -
_ script-message mpv.net update-check #menu: Help > Check for Updates
_ script-message mpv.net show-about #menu: Help > About mpv.net
_ ignore #menu: -
Esc quit #menu: Exit
Q quit-watch-later #menu: Exit Watch Later
Power quit
Play cycle pause
Pause cycle pause
PlayPause cycle pause
MBTN_Mid cycle pause
Stop stop
Forward seek 60
Rewind seek -60
Wheel_Up add volume 10
Wheel_Down add volume -10
Wheel_Left add volume -10
Wheel_Right add volume 10
Prev playlist-prev
Next playlist-next
MBTN_Forward playlist-next
MBTN_Back playlist-prev
> playlist-next
< playlist-prev
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

@@ -0,0 +1,13 @@
input-default-bindings = no
input-ar-delay = 500
input-ar-rate = 20
keep-open = yes
keep-open-pause = no
osd-duration = 2000
osd-playing-msg = '${filename}'
script-opts = osc-scalewindowed=1.5,osc-hidetimeout=2000,console-scale=1
screenshot-directory = '~~desktop/'
[protocol.https]
osd-playing-msg = '${media-title}'

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
src/Resources/mpvnet.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
src/Resources/mpvnet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

27
src/Resources/theme.txt Normal file
View File

@@ -0,0 +1,27 @@
[dark]
heading = #3C8CC8
foreground = #DDDDDD
foreground2 = #AAAAAA
background = #323232
menu-foreground = #DDDDDD
menu-background = #323232
menu-highlight = #505050
menu-border = #FFFFFF
menu-checked = #5A5A5A
[light]
heading = #0068B2
foreground = #000000
foreground2 = #4C4C4C
background = #F7F7F7
menu-foreground = #000000
menu-background = #DFDFDF
menu-highlight = #BFBFBF
menu-border = #6A6A6A
menu-checked = #AAAAAA

31
src/Setup.iss Normal file
View File

@@ -0,0 +1,31 @@
#define MyAppName "mpv.net"
#define MyAppExeName "mpvnet.exe"
#define MyAppSourceDir "mpv.net\bin"
#define MyAppVersion GetFileVersion("mpv.net\bin\mpvnet.exe")
[Setup]
AppId={{9AA2B100-BEF3-44D0-B819-D8FC3C4D557D}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher=Frank Skare (stax76)
ArchitecturesInstallIn64BitMode=x64
Compression=lzma2
DefaultDirName={commonpf}\{#MyAppName}
OutputBaseFilename=mpv.net-{#MyAppVersion}-setup
OutputDir={#GetEnv('USERPROFILE')}\Desktop
DefaultGroupName={#MyAppName}
SetupIconFile=mpv.net\mpvnet.ico
UninstallDisplayIcon={app}\{#MyAppExeName}
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
[Files]
Source: "{#MyAppSourceDir}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppSourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs;
[UninstallRun]
Filename: "powershell.exe"; Flags: runhidden; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\Setup\remove file associations.ps1"""
Filename: "powershell.exe"; Flags: runhidden; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\Setup\remove start menu shortcut.ps1"""
Filename: "powershell.exe"; Flags: runhidden; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\Setup\remove environment variable.ps1"""

24
src/WPF/AboutWindow.xaml Normal file
View File

@@ -0,0 +1,24 @@
<Window x:Class="mpvnet.AboutWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
Title="About mpv.net"
FontSize="16"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
SizeToContent="WidthAndHeight"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}">
<Grid>
<StackPanel HorizontalAlignment="Center">
<TextBlock FontSize="30" TextAlignment="Center" Margin="10,10,10,0">mpv.net</TextBlock>
<TextBlock Name="ContentBlock" TextAlignment="Center" Margin="20,10,20,20"></TextBlock>
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,18 @@

using System.Windows;
using System.Windows.Input;
namespace mpvnet
{
public partial class AboutWindow : Window
{
public AboutWindow()
{
InitializeComponent();
ContentBlock.Text = App.Version;
}
protected override void OnPreviewKeyDown(KeyEventArgs e) => Close();
protected override void OnMouseDown(MouseButtonEventArgs e) => Close();
}
}

View File

@@ -0,0 +1,60 @@
<Window x:Class="mpvnet.CommandPaletteWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
Title="Command Palette"
Height="295"
Width="400"
FontSize="13"
ResizeMode="NoResize"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Name="FilterTextBox"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
PreviewKeyDown="FilterTextBox_PreviewKeyDown"
TextChanged="FilterTextBox_TextChanged"/>
<ListView Name="ListView"
Grid.Row="1"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
MouseUp="ListView_MouseUp">
<ListView.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Display}"></TextBlock>
<TextBlock Grid.Column="1"
Text="{Binding Input}"
HorizontalAlignment="Right"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>

View File

@@ -0,0 +1,125 @@

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interop;
using static mpvnet.Core;
namespace mpvnet
{
public partial class CommandPaletteWindow : Window
{
ICollectionView CollectionView;
public CommandPaletteWindow()
{
InitializeComponent();
CollectionViewSource collectionViewSource = new CollectionViewSource() { Source = CommandItem.Items };
CollectionView = collectionViewSource.View;
var yourCostumFilter = new Predicate<object>(item => Filter((CommandItem)item));
CollectionView.Filter = yourCostumFilter;
ListView.ItemsSource = CollectionView;
}
bool Filter(CommandItem item)
{
if (item.Command == "")
return false;
string filter = FilterTextBox.Text.ToLower();
if (filter == "")
return true;
if (item.Command.ToLower().Contains(filter) ||
item.Input.ToLower().Contains(filter) ||
item.Path.ToLower().Contains(filter))
return true;
return false;
}
void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(WndProc));
Keyboard.Focus(FilterTextBox);
SelectFirst();
}
void SelectFirst()
{
if (ListView.Items.Count > 0)
ListView.SelectedIndex = 0;
}
IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == 0x200 /*WM_MOUSEMOVE*/ && Mouse.LeftButton != MouseButtonState.Pressed)
handled = true;
return IntPtr.Zero;
}
void FilterTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
{
int index = ListView.SelectedIndex;
index -= 1;
if (index < 0)
index = 0;
ListView.SelectedIndex = index;
ListView.ScrollIntoView(ListView.SelectedItem);
}
break;
case Key.Down:
{
int index = ListView.SelectedIndex;
if (++index > ListView.Items.Count - 1)
index = ListView.Items.Count - 1;
ListView.SelectedIndex = index;
ListView.ScrollIntoView(ListView.SelectedItem);
}
break;
case Key.Escape:
Close();
break;
case Key.Enter:
Execute();
break;
}
}
void Execute()
{
if (ListView.SelectedItem != null)
{
CommandItem item = ListView.SelectedItem as CommandItem;
Close();
core.command(item.Command);
}
}
void ListView_MouseUp(object sender, MouseButtonEventArgs e)
{
Execute();
}
void FilterTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
CollectionView.Refresh();
SelectFirst();
}
}
}

66
src/WPF/ConfWindow.xaml Normal file
View File

@@ -0,0 +1,66 @@
<Window xmlns:Controls="clr-namespace:Controls" x:Name="ConfWindow1" x:Class="mpvnet.ConfWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
Title="Config Editor"
Height="530"
Width="700"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
Loaded="ConfWindow1_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Controls:SearchTextBoxUserControl
x:Name="SearchControl"
HintText="Find a setting"
Width="250"
Margin="0,20,0,0"
Grid.ColumnSpan="2"/>
<ScrollViewer Name="MainScrollViewer"
VerticalScrollBarVisibility="Auto"
Grid.Row="1"
Grid.Column="2"
Margin="0,0,0,10">
<StackPanel x:Name="MainStackPanel"></StackPanel>
</ScrollViewer>
<StackPanel Margin="20,0,0,0" Grid.Row="1">
<ListBox Name="FilterListBox"
ItemsSource="{Binding FilterStrings}"
BorderThickness="0"
SelectionChanged="FilterListBox_SelectionChanged"
Foreground="{x:Static mpvnet:Theme.Heading}"
Background="{x:Static mpvnet:Theme.Background}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="16" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Name="OpenSettingsTextBlock" Margin="0,30,0,0" Cursor="Hand" TextWrapping="WrapWithOverflow" Foreground="{x:Static mpvnet:Theme.Heading}" MouseUp="OpenSettingsTextBlock_MouseUp">Open config folder</TextBlock>
<TextBlock Name="PreviewTextBlock" Margin="0,15,0,0" Cursor="Hand" TextWrapping="WrapWithOverflow" Foreground="{x:Static mpvnet:Theme.Heading}" MouseUp="PreviewTextBlock_MouseUp">Preview mpv.conf</TextBlock>
<TextBlock Name="ShowManualTextBlock" Margin="0,15,0,0" Cursor="Hand" TextWrapping="WrapWithOverflow" Foreground="{x:Static mpvnet:Theme.Heading}" MouseUp="ShowManualTextBlock_MouseUp">Show mpv manual</TextBlock>
<TextBlock Name="SupportTextBlock" Margin="0,15,0,0" Cursor="Hand" TextWrapping="WrapWithOverflow" Foreground="{x:Static mpvnet:Theme.Heading}" MouseUp="SupportTextBlock_MouseUp">Show support forum</TextBlock>
</StackPanel>
</Grid>
</Window>

321
src/WPF/ConfWindow.xaml.cs Normal file
View File

@@ -0,0 +1,321 @@

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using DynamicGUI;
using static mpvnet.Core;
namespace mpvnet
{
public partial class ConfWindow : Window
{
List<SettingBase> SettingsDefinitions = Settings.LoadSettings(Properties.Resources.editor_toml);
List<ConfItem> ConfItems = new List<ConfItem>();
public ObservableCollection<string> FilterStrings { get; } = new ObservableCollection<string>();
string InitialContent;
public ConfWindow()
{
InitializeComponent();
DataContext = this;
SearchControl.SearchTextBox.TextChanged += SearchTextBox_TextChanged;
LoadConf(core.ConfPath);
LoadConf(App.ConfPath);
LoadSettings();
InitialContent = GetCompareString();
SearchControl.Text = RegistryHelp.GetString(App.RegPath, "ConfigEditorSearch");
FilterListBox.SelectedItem = SearchControl.Text.TrimEnd(':');
}
void LoadSettings()
{
foreach (SettingBase setting in SettingsDefinitions)
{
if (!FilterStrings.Contains(setting.Filter))
FilterStrings.Add(setting.Filter);
foreach (ConfItem confItem in ConfItems)
{
if (setting.Name == confItem.Name && confItem.Section == "" && !confItem.IsSectionItem)
{
setting.Value = confItem.Value.Trim('\'', '"');
setting.ConfItem = confItem;
confItem.SettingBase = setting;
continue;
}
}
switch (setting)
{
case StringSetting s:
MainStackPanel.Children.Add(new StringSettingControl(s));
break;
case OptionSetting s:
MainStackPanel.Children.Add(new OptionSettingControl(s));
break;
}
}
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
RegistryHelp.SetValue(App.RegPath, "ConfigEditorSearch", SearchControl.Text);
if (InitialContent == GetCompareString())
return;
File.WriteAllText(core.ConfPath, GetContent("mpv"));
File.WriteAllText(App.ConfPath, GetContent("mpvnet"));
Msg.Show("Changes will be available on next mpv.net startup.");
}
string GetCompareString()
{
return string.Join("", SettingsDefinitions.Select(item => item.Name + item.Value).ToArray());
}
void LoadConf(string file)
{
if (!File.Exists(file))
return;
string comment = "";
string section = "";
bool isSectionItem = false;
foreach (string currentLine in File.ReadAllLines(file))
{
string line = currentLine.Trim();
if (line == "")
{
comment += "\r\n";
}
else if (line.StartsWith("#"))
{
comment += line.Trim() + "\r\n";
}
else if (line.StartsWith("[") && line.Contains("]"))
{
if (!isSectionItem && comment != "" && comment != "\r\n")
ConfItems.Add(new ConfItem() {
Comment = comment, File = Path.GetFileNameWithoutExtension(file)});
section = line.Substring(0, line.IndexOf("]") + 1);
comment = "";
isSectionItem = true;
}
else if (line.Contains("="))
{
ConfItem item = new ConfItem();
item.File = Path.GetFileNameWithoutExtension(file);
item.IsSectionItem = isSectionItem;
item.Comment = comment;
comment = "";
item.Section = section;
section = "";
if (line.Contains("#") && !line.Contains("'") && !line.Contains("\""))
{
item.LineComment = line.Substring(line.IndexOf("#")).Trim();
line = line.Substring(0, line.IndexOf("#")).Trim();
}
int pos = line.IndexOf("=");
string left = line.Substring(0, pos).Trim().ToLower();
string right = line.Substring(pos + 1).Trim();
if (left == "fs")
left = "fullscreen";
if (left == "loop")
left = "loop-file";
item.Name = left;
item.Value = right;
ConfItems.Add(item);
}
}
}
string GetContent(string filename)
{
StringBuilder sb = new StringBuilder();
List<string> namesWritten = new List<string>();
foreach (ConfItem item in ConfItems)
{
if (filename != item.File || item.Section != "" || item.IsSectionItem)
continue;
if (item.Comment != "")
sb.Append(item.Comment);
if (item.SettingBase == null)
{
if (item.Name != "")
{
sb.Append(item.Name + " = " + item.Value);
if (item.LineComment != "")
sb.Append(" " + item.LineComment);
sb.AppendLine();
namesWritten.Add(item.Name);
}
}
else if ((item.SettingBase.Value ?? "") != item.SettingBase.Default)
{
string value = "";
if (item.SettingBase.Type == "string" ||
item.SettingBase.Type == "folder" ||
item.SettingBase.Type == "color")
value = "'" + item.SettingBase.Value + "'";
else
value = item.SettingBase.Value;
sb.Append(item.Name + " = " + value);
if (item.LineComment != "")
sb.Append(" " + item.LineComment);
sb.AppendLine();
namesWritten.Add(item.Name);
}
}
if (!sb.ToString().Contains("# Editor"))
sb.AppendLine("# Editor");
foreach (SettingBase setting in SettingsDefinitions)
{
if (filename != setting.File || namesWritten.Contains(setting.Name))
continue;
if ((setting.Value ?? "") != setting.Default)
{
string value = "";
if (setting.Type == "string" ||
setting.Type == "folder" ||
setting.Type == "color")
value = "'" + setting.Value + "'";
else
value = setting.Value;
sb.AppendLine(setting.Name + " = " + value);
}
}
foreach (ConfItem item in ConfItems)
{
if (filename != item.File || (item.Section == "" && !item.IsSectionItem))
continue;
if (item.Section != "")
{
if (!sb.ToString().EndsWith("\r\n\r\n"))
sb.AppendLine();
sb.AppendLine(item.Section);
}
if (item.Comment != "")
sb.Append(item.Comment);
sb.Append(item.Name + " = " + item.Value);
if (item.LineComment != "")
sb.Append(" " + item.LineComment);
sb.AppendLine();
namesWritten.Add(item.Name);
}
return "\r\n" + sb.ToString().Trim() + "\r\n";
}
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string activeFilter = "";
foreach (var i in FilterStrings)
if (SearchControl.Text == i + ":")
activeFilter = i;
if (activeFilter == "")
{
foreach (UIElement i in MainStackPanel.Children)
if ((i as ISettingControl).Contains(SearchControl.Text))
i.Visibility = Visibility.Visible;
else
i.Visibility = Visibility.Collapsed;
FilterListBox.SelectedItem = null;
}
else
foreach (UIElement i in MainStackPanel.Children)
if ((i as ISettingControl).SettingBase.Filter == activeFilter)
i.Visibility = Visibility.Visible;
else
i.Visibility = Visibility.Collapsed;
MainScrollViewer.ScrollToTop();
}
void ConfWindow1_Loaded(object sender, RoutedEventArgs e)
{
SearchControl.SearchTextBox.SelectAll();
Keyboard.Focus(SearchControl.SearchTextBox);
foreach (var i in MainStackPanel.Children.OfType<StringSettingControl>())
i.Update();
}
void FilterListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
SearchControl.Text = e.AddedItems[0] + ":";
}
void OpenSettingsTextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
ProcessHelp.ShellExecute(Path.GetDirectoryName(core.ConfPath));
}
void PreviewTextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
Msg.Show("mpv.conf Preview", GetContent("mpv"));
}
void ShowManualTextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
ProcessHelp.ShellExecute("https://mpv.io/manual/master/");
}
void SupportTextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
ProcessHelp.ShellExecute("https://github.com/stax76/mpv.net#Support");
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Escape)
Close();
}
}
}

View File

@@ -0,0 +1,44 @@
<Window x:Class="mpvnet.EverythingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
Title="Media File Search"
FontSize="13"
Height="300"
Width="600"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox Name="FilterTextBox"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
PreviewKeyDown="FilterTextBox_PreviewKeyDown"
TextChanged="FilterTextBox_TextChanged"/>
<ListView Name="ListView"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
Grid.Row="1"
MouseUp="ListView_MouseUp"
PreviewKeyDown="ListView_PreviewKeyDown">
<ListView.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</Window>

View File

@@ -0,0 +1,165 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using static mpvnet.Core;
namespace mpvnet
{
public partial class EverythingWindow : Window
{
public EverythingWindow()
{
InitializeComponent();
}
const int EVERYTHING_REQUEST_FILE_NAME = 0x00000001;
const int EVERYTHING_REQUEST_PATH = 0x00000002;
[DllImport("Everything.dll", CharSet = CharSet.Unicode)]
public static extern int Everything_SetSearch(string lpSearchString);
[DllImport("Everything.dll")]
public static extern void Everything_SetRequestFlags(UInt32 dwRequestFlags);
[DllImport("Everything.dll")]
public static extern void Everything_SetSort(UInt32 dwSortType);
[DllImport("Everything.dll", CharSet = CharSet.Unicode)]
public static extern bool Everything_Query(bool bWait);
[DllImport("Everything.dll", CharSet = CharSet.Unicode)]
public static extern void Everything_GetResultFullPathName(UInt32 nIndex, StringBuilder lpString, UInt32 nMaxCount);
[DllImport("Everything.dll")]
public static extern bool Everything_GetResultSize(UInt32 nIndex, out long lpFileSize);
[DllImport("Everything.dll")]
public static extern UInt32 Everything_GetNumResults();
void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(WndProc));
Keyboard.Focus(FilterTextBox);
}
void SelectFirst()
{
if (ListView.Items.Count > 0)
ListView.SelectedIndex = 0;
}
IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == 0x200 /*WM_MOUSEMOVE*/ && Mouse.LeftButton != MouseButtonState.Pressed)
handled = true;
return IntPtr.Zero;
}
void FilterTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
{
int index = ListView.SelectedIndex;
if (--index < 0)
index = 0;
ListView.SelectedIndex = index;
ListView.ScrollIntoView(ListView.SelectedItem);
}
break;
case Key.Down:
{
int index = ListView.SelectedIndex;
if (++index > ListView.Items.Count - 1)
index = ListView.Items.Count - 1;
ListView.SelectedIndex = index;
ListView.ScrollIntoView(ListView.SelectedItem);
}
break;
case Key.Escape: Close(); break;
case Key.Enter: Execute(); break;
}
}
void ListView_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
Close();
if (e.Key == Key.Enter)
Execute();
}
void Execute()
{
if (ListView.SelectedItem != null)
core.LoadFiles(new[] { ListView.SelectedItem as string }, true, Keyboard.Modifiers == ModifierKeys.Control);
Keyboard.Focus(FilterTextBox);
}
void ListView_MouseUp(object sender, MouseButtonEventArgs e) => Execute();
void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string searchtext = FilterTextBox.Text;
App.RunTask(() => Search(searchtext));
}
object LockObject = new object();
void Search(string searchText)
{
lock (LockObject)
{
try
{
List<string> items = new List<string>();
StringBuilder sb = new StringBuilder(500);
Everything_SetSearch(searchText);
Everything_SetRequestFlags(EVERYTHING_REQUEST_FILE_NAME | EVERYTHING_REQUEST_PATH);
Everything_Query(true);
uint count = Everything_GetNumResults();
for (uint i = 0; i < count; i++)
{
Everything_GetResultFullPathName(i, sb, (uint)sb.Capacity);
string ext = sb.ToString().Ext();
if (Core.AudioTypes.Contains(ext) || Core.VideoTypes.Contains(ext) || Core.ImageTypes.Contains(ext))
items.Add(sb.ToString());
if (items.Count > 100)
break;
}
Application.Current.Dispatcher.Invoke(() => {
ListView.ItemsSource = items;
SelectFirst();
});
}
catch (Exception)
{
Msg.ShowError("Search query failed.",
"The search feature depends on [Everything](https://www.voidtools.com) being installed.");
}
}
}
}
}

70
src/WPF/InputWindow.xaml Normal file
View File

@@ -0,0 +1,70 @@
<Window xmlns:Controls="clr-namespace:Controls" x:Class="mpvnet.InputWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
Title="Input Editor"
Height="500"
Width="750"
FontSize="13"
ShowInTaskbar="False"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
Loaded="Window_Loaded"
Closed="Window_Closed">
<Window.Resources>
<Style x:Key="DataGrid_Font_Centering" TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Controls:SearchTextBoxUserControl
HintText="Type ? to get help."
x:Name="SearchControl"
Width="300"
Margin="0,20,0,20"
Grid.ColumnSpan="2" />
<DataGrid x:Name="DataGrid"
Grid.Row="1"
CommandManager.PreviewCanExecute="DataGrid_PreviewCanExecute"
AutoGenerateColumns="False"
CellStyle="{StaticResource DataGrid_Font_Centering}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Menu" Binding="{Binding Path}"/>
<DataGridTemplateColumn Header="Input">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button MinHeight="20" Click="ButtonClick">
<TextBlock Text="{Binding Input}"></TextBlock>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Command" Binding="{Binding Command}" MaxWidth="330" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

149
src/WPF/InputWindow.xaml.cs Normal file
View File

@@ -0,0 +1,149 @@

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using static mpvnet.Core;
namespace mpvnet
{
public partial class InputWindow : Window
{
ICollectionView CollectionView;
string InitialInputConfContent;
public InputWindow()
{
InitializeComponent();
InitialInputConfContent = GetInputConfContent();
SearchControl.SearchTextBox.TextChanged += SearchTextBox_TextChanged;
DataGrid.SelectionMode = DataGridSelectionMode.Single;
CollectionViewSource collectionViewSource = new CollectionViewSource() { Source = CommandItem.Items };
CollectionView = collectionViewSource.View;
var yourCostumFilter = new Predicate<object>(item => Filter((CommandItem)item));
CollectionView.Filter = yourCostumFilter;
DataGrid.ItemsSource = CollectionView;
}
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
CollectionView.Refresh();
if (SearchControl.SearchTextBox.Text == "?")
{
SearchControl.SearchTextBox.Text = "";
Msg.Show("Filtering", "Reduce the filter scope with:\n\ni input\n\nm menu\n\nc command\n\nIf only one character is entered input search is performed.");
}
}
bool Filter(CommandItem item)
{
if (item.Command == "") return false;
string searchText = SearchControl.SearchTextBox.Text.ToLower();
if (searchText == "" || searchText == "?") return true;
if (searchText.Length == 1)
return item.Input.ToLower().Replace("ctrl+", "").Replace("shift+", "").Replace("alt+", "") == searchText.ToLower();
else if (searchText.StartsWith("i ") || searchText.StartsWith("i:") || searchText.Length == 1)
{
if (searchText.Length > 1)
searchText = searchText.Substring(2).Trim();
if (searchText.Length < 3)
return item.Input.ToLower().Replace("ctrl+", "").Replace("shift+", "").Replace("alt+", "").Contains(searchText);
else
return item.Input.ToLower().Contains(searchText);
}
else if (searchText.StartsWith("m ") || searchText.StartsWith("m:"))
return item.Path.ToLower().Contains(searchText.Substring(2).Trim());
else if (searchText.StartsWith("c ") || searchText.StartsWith("c:"))
return item.Command.ToLower().Contains(searchText.Substring(2).Trim());
else if (item.Command.ToLower().Contains(searchText) ||
item.Path.ToLower().Contains(searchText) ||
item.Input.ToLower().Contains(searchText))
{
return true;
}
return false;
}
void ButtonClick(object sender, RoutedEventArgs e)
{
CommandItem item = ((Button)e.Source).DataContext as CommandItem;
if (item is null) return;
LearnWindow w = new LearnWindow();
w.Owner = this;
w.InputItem = item;
w.ShowDialog();
var items = new Dictionary<string, CommandItem>();
foreach (CommandItem i in CommandItem.Items)
if (items.ContainsKey(i.Input) && i.Input != "")
Msg.Show($"Duplicate found:\n\n{i.Input}: {i.Path}\n\n{items[i.Input].Input}: {items[i.Input].Path}\n\nPlease note that you can chain multiple commands in the same line by using a semicolon as separator.", "Duplicate Found");
else
items[i.Input] = i;
}
void Window_Loaded(object sender, RoutedEventArgs e) => Keyboard.Focus(SearchControl.SearchTextBox);
string GetInputConfContent()
{
string text = null;
foreach (string line in Properties.Resources.input_conf.Split(new[] { "\r\n" }, StringSplitOptions.None))
{
string test = line.Trim();
if (test == "" || test.StartsWith("#")) text += test + "\r\n";
}
text = "\r\n" + text.Trim() + "\r\n\r\n";
foreach (CommandItem item in CommandItem.Items)
{
string input = item.Input == "" ? "_" : item.Input;
string line = " " + input.PadRight(10);
if (item.Command.Trim() == "")
line += " ignore";
else
line += " " + item.Command.Trim();
if (item.Path.Trim() != "")
line = line.PadRight(40) + " #menu: " + item.Path;
text += line + "\r\n";
}
return text;
}
void Window_Closed(object sender, EventArgs e)
{
if (InitialInputConfContent == GetInputConfContent()) return;
File.WriteAllText(core.InputConfPath, GetInputConfContent());
Msg.Show("Changes will be available on next mpv.net startup.");
}
void DataGrid_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
DataGrid grid = (DataGrid)sender;
if (e.Command == DataGrid.DeleteCommand)
if (Msg.ShowQuestion($"Confirm to delete: {(grid.SelectedItem as CommandItem).Input} ({(grid.SelectedItem as CommandItem).Path})") != MsgResult.OK)
e.Handled = true;
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Escape)
Close();
}
}
}

49
src/WPF/LearnWindow.xaml Normal file
View File

@@ -0,0 +1,49 @@
<Window x:Class="mpvnet.LearnWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
Title="Learn Input"
Height="200"
Width="400"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Loaded="Window_Loaded"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
MouseWheel="Window_MouseWheel"
MouseUp="Window_MouseUp"
MouseDoubleClick="Window_MouseDoubleClick" PreviewKeyDown="Window_PreviewKeyDown">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="MenuTextBlock"
Grid.ColumnSpan="2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontSize="16"></TextBlock>
<TextBlock x:Name="KeyTextBlock"
Grid.Row="1"
Grid.ColumnSpan="2"
VerticalAlignment="Top"
HorizontalAlignment="Center"
FontSize="16" />
<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>
</Window>

266
src/WPF/LearnWindow.xaml.cs Normal file
View File

@@ -0,0 +1,266 @@

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Forms;
namespace mpvnet
{
public partial class LearnWindow : Window
{
public CommandItem InputItem { get; set; }
string NewKey = "";
uint MAPVK_VK_TO_VSC = 0;
int VK_MENU = 0x12;
int VK_LMENU = 0xA4;
int VK_RMENU = 0xA5;
int VK_CONTROL = 0x11;
int VK_LCONTROL = 0xA2;
int VK_RCONTROL = 0xA3;
public LearnWindow() => InitializeComponent();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern short GetKeyState(int keyCode);
[DllImport("user32.dll")]
static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState,
StringBuilder pwszBuff, int cchBuff, uint wFlags);
[DllImport("user32.dll")]
static extern bool GetKeyboardState(byte[] lpKeyState);
string ToUnicode(uint vk)
{
byte[] keys = new byte[256];
if (!GetKeyboardState(keys))
return "";
if ((keys[VK_CONTROL] & 0x80) != 0 && (keys[VK_MENU] & 0x80) == 0)
keys[VK_LCONTROL] = keys[VK_RCONTROL] = keys[VK_CONTROL] = 0;
uint scanCode = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
string ret = ToUnicode(vk, scanCode, keys);
if (ret.Length == 1 && (int)ret[0] < 32)
return "";
if (ret == "" && (keys[VK_MENU] & 0x80) != 0)
{
keys[VK_LMENU] = keys[VK_RMENU] = keys[VK_MENU] = 0;
ret = ToUnicode(vk, scanCode, keys);
}
return ret;
}
public string ToUnicode(uint vk, uint scanCode, byte[] keys)
{
StringBuilder sb = new StringBuilder(10);
ToUnicode(vk, scanCode, keys, sb, sb.Capacity, 0);
return sb.ToString();
}
IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
Message m = new Message();
m.HWnd = hwnd;
m.Msg = msg;
m.WParam = wParam;
m.LParam = lParam;
ProcessKeyEventArgs(ref m);
return m.Result;
}
void OnKeyDown(uint vk)
{
Keys key = (Keys)vk;
if (key == Keys.ControlKey || key == Keys.ShiftKey ||
key == Keys.Menu || key == Keys.None)
return;
string text = ToUnicode(vk);
if ((int)key > 111 && (int)key < 136)
text = "F" + ((int)key - 111);
if ((int)key > 95 && (int)key < 106)
text = "KP" + ((int)key - 96);
switch (text)
{
case "#": text = "SHARP"; break;
case "´´": text = "´"; break;
case "``": text = "`"; break;
case "^^": text = "^"; break;
}
switch (key)
{
case Keys.Left: text = "LEFT"; break;
case Keys.Up: text = "UP"; break;
case Keys.Right: text = "RIGHT"; break;
case Keys.Down: text = "DOWN"; break;
case Keys.Space: text = "SPACE"; break;
case Keys.Enter: text = "ENTER"; break;
case Keys.Tab: text = "TAB"; break;
case Keys.Back: text = "BS"; break;
case Keys.Delete: text = "DEL"; break;
case Keys.Insert: text = "INS"; break;
case Keys.Home: text = "HOME"; break;
case Keys.End: text = "END"; break;
case Keys.PageUp: text = "PGUP"; break;
case Keys.PageDown: text = "PGDWN"; break;
case Keys.Escape: text = "ESC"; break;
case Keys.Sleep: text = "SLEEP"; break;
case Keys.Cancel: text = "CANCEL"; break;
case Keys.PrintScreen: text = "PRINT"; break;
case Keys.BrowserFavorites: text = "FAVORITES"; break;
case Keys.BrowserSearch: text = "SEARCH"; break;
case Keys.BrowserHome: text = "HOMEPAGE"; break;
case Keys.LaunchMail: text = "MAIL"; break;
case Keys.Play: text = "PLAY"; break;
case Keys.Pause: text = "PAUSE"; break;
case Keys.MediaPlayPause: text = "PLAYPAUSE"; break;
case Keys.MediaStop: text = "STOP"; break;
case Keys.MediaNextTrack: text = "NEXT"; break;
case Keys.MediaPreviousTrack: text = "PREV"; break;
case Keys.VolumeUp:
case Keys.VolumeDown:
case Keys.VolumeMute:
text = ""; break;
}
bool isAlt = GetKeyState(18) < 0;
bool isShift = GetKeyState(16) < 0;
bool isCtrl = GetKeyState(17) < 0;
bool isLetter = (int)key > 64 && (int)key < 91;
if (isLetter && isShift)
text = text.ToUpper();
string keyString = ToUnicode(vk);
if (isAlt && !isCtrl)
text = "ALT+" + text;
if (isShift && keyString == "")
text = "SHIFT+" + text;
if (isCtrl && !(keyString != "" && isCtrl && isAlt))
text = "CTRL+" + text;
if (!string.IsNullOrEmpty(text))
SetKey(text);
}
void SetKey(string key)
{
NewKey = key;
MenuTextBlock.Text = InputItem.Path;
KeyTextBlock.Text = key;
}
void ProcessKeyEventArgs(ref Message m)
{
int WM_KEYDOWN = 0x100;
int WM_SYSKEYDOWN = 0x104;
int WM_APPCOMMAND = 0x319;
if (m.Msg == WM_KEYDOWN || m.Msg == WM_SYSKEYDOWN)
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));
if (value != null)
SetKey(value);
}
}
void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(WndProc));
SetKey(InputItem.Input);
}
void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
InputItem.Input = NewKey;
Close();
}
void ClearButton_Click(object sender, RoutedEventArgs e)
{
InputItem.Input = "_";
Close();
}
void Window_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0)
SetKey("WHEEL_UP");
else
SetKey("WHEEL_DOWN");
}
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;
void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
SetKey("MBTN_LEFT_DBL");
BlockMBTN_LEFT = true;
}
}
void Window_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
OnKeyDown((uint)Keys.Tab);
e.Handled = true;
}
}
}
}

120
src/WPF/Resources.xaml Normal file
View File

@@ -0,0 +1,120 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mpvnet="clr-namespace:mpvnet">
<Style TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" TargetName="border" Value="0.56"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="#FF7EB4EA"/>
</Trigger>
<Trigger Property="IsFocused" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{x:Static mpvnet:Theme.Heading}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="RadioButton">
<Setter Property="Padding" Value="6 0 0 0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftCol" Width="18" />
<ColumnDefinition x:Name="RightCol" Width="*" />
</Grid.ColumnDefinitions>
<Grid x:Name="PART_CHECKBOX">
<Ellipse
x:Name="normal"
Width="18"
Height="18"
Fill="{x:Static mpvnet:Theme.Background}"
Stroke="{x:Static mpvnet:Theme.Heading}"
StrokeThickness="2" />
<Ellipse
x:Name="Checked1"
Width="8"
Height="8"
Fill="{TemplateBinding Foreground}"
Opacity="0" />
<Ellipse
x:Name="disabled"
Width="18"
Height="18"
Fill="{DynamicResource SemiTransparentWhiteBrush}"
Opacity="0"
StrokeThickness="{TemplateBinding BorderThickness}" />
</Grid>
<ContentPresenter
x:Name="contentPresenter"
Grid.Column="1"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
RecognizesAccessKey="True" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="contentPresenter"
Storyboard.TargetProperty="(UIElement.Opacity)"
To=".55"
Duration="0" />
<DoubleAnimation
Storyboard.TargetName="disabled"
Storyboard.TargetProperty="(UIElement.Opacity)"
To="1"
Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="Checked1"
Storyboard.TargetProperty="(UIElement.Opacity)"
To="1"
Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked" />
<VisualState x:Name="Indeterminate" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,68 @@
<UserControl x:Class="Controls.SearchTextBoxUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid Name="SearchTextBoxUserControl1"
Background="{x:Static mpvnet:Theme.Background}">
<TextBlock Name="HintTextBlock"
Margin="5,2"
Text="Find a setting"
VerticalAlignment="Center"
Foreground="{x:Static mpvnet:Theme.Foreground2}"
Background="{x:Static mpvnet:Theme.Background}" />
<TextBox Name="SearchTextBox"
Height="25"
Padding="1,2,0,0"
BorderThickness="2"
Background="Transparent"
TextChanged="SearchTextBox_TextChanged"
Foreground="{x:Static mpvnet:Theme.Foreground}"
CaretBrush="{x:Static mpvnet:Theme.Foreground}" />
<Button Name="SearchClearButton"
Background="Transparent"
HorizontalAlignment="Right"
FontFamily="Marlett"
FontSize="10"
Width="17"
Height="17"
Margin="2,0,4,0"
Visibility="Hidden"
Click="SearchClearButton_Click" >r
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{x:Static mpvnet:Theme.Background}"/>
<Setter Property="Foreground" Value="{x:Static mpvnet:Theme.Foreground2}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderThickness="1"
BorderBrush="{TemplateBinding Foreground}"
SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{x:Static mpvnet:Theme.Heading}"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</UserControl>

View File

@@ -0,0 +1,48 @@

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Controls
{
public partial class SearchTextBoxUserControl : UserControl
{
public SearchTextBoxUserControl()
{
InitializeComponent();
}
public string Text { get => SearchTextBox.Text; set => SearchTextBox.Text = value; }
string _HintText;
public string HintText {
get => _HintText;
set {
_HintText = value;
UpdateControls();
}
}
void SearchClearButton_Click(object sender, RoutedEventArgs e)
{
SearchTextBox.Text = "";
Keyboard.Focus(SearchTextBox);
}
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateControls();
}
void UpdateControls()
{
HintTextBlock.Text = SearchTextBox.Text == "" ? HintText : "";
if (SearchTextBox.Text == "")
SearchClearButton.Visibility = Visibility.Hidden;
else
SearchClearButton.Visibility = Visibility.Visible;
}
}
}

69
src/WPF/SetupWindow.xaml Normal file
View File

@@ -0,0 +1,69 @@
<Window x:Class="mpvnet.SetupWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mpvnet="clr-namespace:mpvnet"
mc:Ignorable="d"
Title="mpv.net Setup"
FontSize="13"
Foreground="{x:Static mpvnet:Theme.Foreground}"
Background="{x:Static mpvnet:Theme.Background}"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner" >
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="3"></Setter>
<Setter Property="Height" Value="25"></Setter>
</Style>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="14"></Setter>
<Setter Property="Margin" Value="3,0,0,0"></Setter>
<Setter Property="TextAlignment" Value="Center"></Setter>
</Style>
<ControlTemplate x:Key = "ShieldButtonTemplate" TargetType = "Button">
<Button Margin="0" HorizontalContentAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{x:Static mpvnet:SetupWindow.ShieldIcon}"
Width="18"
Height="18"
Margin="3,0,0,0"/>
<ContentPresenter Grid.Column="1" HorizontalAlignment="Center" />
</Grid>
</Button>
</ControlTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<StackPanel Width="180" Margin="5">
<TextBlock>Start Menu Shortcut</TextBlock>
<Button Name="AddStartMenuShortcut" Click="AddStartMenuShortcut_Click">Add</Button>
<Button Name="RemoveStartMenuShortcut" Click="RemoveStartMenuShortcut_Click">Remove</Button>
</StackPanel>
<StackPanel Width="180" Margin="20,5,5,5">
<TextBlock>File Extensions</TextBlock>
<Button Name="AddVideo" Click="AddVideo_Click" Template="{StaticResource ShieldButtonTemplate}">Add Video</Button>
<Button Name="AddAudio" Click="AddAudio_Click" Template="{StaticResource ShieldButtonTemplate}">Add Audio</Button>
<Button Name="AddImage" Click="AddImage_Click" Template="{StaticResource ShieldButtonTemplate}">Add Image</Button>
<Button Name="RemoveFileAssociations" Margin="3,15,3,3" Click="RemoveFileAssociations_Click" Template="{StaticResource ShieldButtonTemplate}">Remove All</Button>
<Button Name="EditDefaultApp" Click="EditDefaultApp_Click">Edit Default App</Button>
</StackPanel>
<StackPanel Width="180" Margin="20,5,5,5">
<TextBlock>Path Environment Variable</TextBlock>
<Button Name="AddToPathEnvVar" Click="AddToPathEnvVar_Click">Add</Button>
<Button Name="RemoveFromPathEnvVar" Click="RemoveFromPathEnvVar_Click">Remove</Button>
<Button Name="ShowEnvVarEditor" Click="ShowEnvVarEditor_Click">Show Editor</Button>
</StackPanel>
</StackPanel>
</Grid>
</Window>

109
src/WPF/SetupWindow.xaml.cs Normal file
View File

@@ -0,0 +1,109 @@

using System;
using System.Diagnostics;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using System.Windows;
using WinForms = System.Windows.Forms;
using static StockIcon;
namespace mpvnet
{
public partial class SetupWindow : Window
{
public SetupWindow() => InitializeComponent();
static BitmapSource _ShieldIcon;
public static BitmapSource ShieldIcon {
get {
if (_ShieldIcon == null)
{
IntPtr icon = GetIcon(SHSTOCKICONID.Shield, SHSTOCKICONFLAGS.SHGSI_ICON);
_ShieldIcon = Imaging.CreateBitmapSourceFromHIcon(
icon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
DestroyIcon(icon);
}
return _ShieldIcon;
}
}
void RegFileAssoc(string[] extensions)
{
try
{
using (Process proc = new Process())
{
proc.StartInfo.FileName = WinForms.Application.ExecutablePath;
proc.StartInfo.Arguments = "--reg-file-assoc " + String.Join(" ", extensions);
proc.StartInfo.Verb = "runas";
proc.StartInfo.UseShellExecute = true;
proc.Start();
proc.WaitForExit();
if (proc.ExitCode == 0)
Msg.Show("File associations successfully created.");
}
} catch {}
}
void AddVideo_Click(object sender, RoutedEventArgs e) => RegFileAssoc(Core.VideoTypes);
void AddAudio_Click(object sender, RoutedEventArgs e) => RegFileAssoc(Core.AudioTypes);
void AddImage_Click(object sender, RoutedEventArgs e) => RegFileAssoc(Core.ImageTypes);
void RemoveFileAssociations_Click(object sender, RoutedEventArgs e)
{
try
{
using (Process proc = new Process())
{
proc.StartInfo.FileName = "powershell.exe";
proc.StartInfo.Arguments = "-NoLogo -NoExit -NoProfile -ExecutionPolicy Bypass -File \"" +
Folder.Startup + "Setup\\remove file associations.ps1\"";
proc.StartInfo.Verb = "runas";
proc.StartInfo.UseShellExecute = true;
proc.Start();
}
} catch { }
}
void AddToPathEnvVar_Click(object sender, RoutedEventArgs e)
{
ExecutePowerShellScript(Folder.Startup + "Setup\\add environment variable.ps1");
}
void RemoveFromPathEnvVar_Click(object sender, RoutedEventArgs e)
{
ExecutePowerShellScript(Folder.Startup + "Setup\\remove environment variable.ps1");
}
void AddStartMenuShortcut_Click(object sender, RoutedEventArgs e)
{
ExecutePowerShellScript(Folder.Startup + "Setup\\create start menu shortcut.ps1");
}
void RemoveStartMenuShortcut_Click(object sender, RoutedEventArgs e)
{
ExecutePowerShellScript(Folder.Startup + "Setup\\remove start menu shortcut.ps1");
}
void ShowEnvVarEditor_Click(object sender, RoutedEventArgs e)
{
ProcessHelp.Execute("rundll32.exe", "sysdm.cpl,EditEnvironmentVariables");
}
void ExecutePowerShellScript(string file)
{
ProcessHelp.Execute("powershell.exe",
"-NoLogo -NoExit -NoProfile -ExecutionPolicy Bypass -File \"" + file + "\"");
}
void EditDefaultApp_Click(object sender, RoutedEventArgs e)
{
ProcessHelp.ShellExecute("ms-settings:defaultapps");
}
}
}

23
src/WPF/WPF.cs Normal file
View File

@@ -0,0 +1,23 @@

using System;
using System.Windows;
namespace mpvnet
{
public class WPF
{
public static void Init()
{
if (Application.Current == null)
{
new Application();
Application.Current.Resources.MergedDictionaries.Add(
Application.LoadComponent(new Uri("mpvnet;component/WPF/Resources.xaml",
UriKind.Relative)) as ResourceDictionary);
Application.Current.DispatcherUnhandledException += (sender, e) => App.ShowException(e.Exception);
}
}
}
}

68
src/WinForms/MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,68 @@
namespace mpvnet
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.CursorTimer = new System.Windows.Forms.Timer(this.components);
this.ProgressTimer = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// CursorTimer
//
this.CursorTimer.Enabled = true;
this.CursorTimer.Interval = 1000;
this.CursorTimer.Tick += new System.EventHandler(this.CursorTimer_Tick);
//
// ProgressTimer
//
this.ProgressTimer.Tick += new System.EventHandler(this.ProgressTimer_Tick);
//
// MainForm
//
this.AllowDrop = true;
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(348, 0);
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);
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer CursorTimer;
private System.Windows.Forms.Timer ProgressTimer;
}
}

961
src/WinForms/MainForm.cs Normal file
View File

@@ -0,0 +1,961 @@

using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using static mpvnet.Core;
using static WinAPI;
namespace mpvnet
{
public partial class MainForm : Form
{
public static MainForm Instance { get; set; }
public static IntPtr Hwnd { get; set; }
public new ContextMenuStripEx ContextMenu { get; set; }
Point LastCursorPosition;
int LastCursorChanged;
int LastCycleFullscreen;
int LastAppCommand;
int TaskbarButtonCreatedMessage;
int ShownTickCount;
Taskbar Taskbar;
List<string> RecentFiles;
bool WasMaximized;
public MainForm()
{
InitializeComponent();
try
{
object recent = RegistryHelp.GetValue(App.RegPath, "Recent");
if (recent is string[] r)
RecentFiles = new List<string>(r);
else
RecentFiles = new List<string>();
Instance = this;
Hwnd = Handle;
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;
core.FileLoaded += FileLoaded;
core.Idle += Idle;
core.Seek += () => UpdateProgressBar();
core.observe_property("window-maximized", PropChangeWindowMaximized);
core.observe_property("window-minimized", PropChangeWindowMinimized);
core.observe_property_bool("pause", PropChangePause);
core.observe_property_bool("fullscreen", PropChangeFullscreen);
core.observe_property_bool("ontop", PropChangeOnTop);
core.observe_property_bool("border", PropChangeBorder);
core.observe_property_string("sid", PropChangeSid);
core.observe_property_string("aid", PropChangeAid);
core.observe_property_string("vid", PropChangeVid);
core.observe_property_string("title", PropChangeTitle);
core.observe_property_int("edition", PropChangeEdition);
core.observe_property_double("window-scale", PropChangeWindowScale);
if (core.GPUAPI != "vulkan")
core.ProcessCommandLine(false);
AppDomain.CurrentDomain.UnhandledException += (sender, e) => App.ShowException(e.ExceptionObject);
Application.ThreadException += (sender, e) => App.ShowException(e.Exception);
Msg.SupportURL = "https://github.com/stax76/mpv.net#support";
TaskbarButtonCreatedMessage = RegisterWindowMessage("TaskbarButtonCreated");
ContextMenu = new ContextMenuStripEx(components);
ContextMenu.Opened += ContextMenu_Opened;
ContextMenu.Opening += ContextMenu_Opening;
if (core.Screen > -1)
{
int targetIndex = core.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;
}
if (!core.Border)
FormBorderStyle = FormBorderStyle.None;
int posX = RegistryHelp.GetInt(App.RegPath, "PosX");
int posY = RegistryHelp.GetInt(App.RegPath, "PosY");
if (posX != 0 && posY != 0 && App.RememberPosition)
{
Left = posX - Width / 2;
Top = posY - Height / 2;
}
if (core.WindowMaximized)
{
SetFormPosAndSize(1, true);
WindowState = FormWindowState.Maximized;
}
if (core.WindowMinimized)
{
SetFormPosAndSize(1, true);
WindowState = FormWindowState.Minimized;
}
}
catch (Exception ex)
{
Msg.ShowException(ex);
}
}
void ScaleWindow(float value) {
BeginInvoke(new Action(() => {
if (value < 1 && (Width == MinimumSize.Width || Height == MinimumSize.Height))
return;
SetFormPosAndSize(value, false, false, false);
}));
}
void WindowScale(double scale)
{
if (!WasShown())
return;
Size size = new Size((int)(core.VideoSize.Width * scale), (int)(core.VideoSize.Height * scale));
SetSize(size, core.VideoSize, Screen.FromControl(this), false, false);
}
public MenuItem FindMenuItem(string text) => FindMenuItem(text, ContextMenu.Items);
void Shutdown() => BeginInvoke(new Action(() => Close()));
void Idle() => SetTitle();
bool WasShown() => ShownTickCount != 0 && Environment.TickCount > ShownTickCount + 500;
void CM_Popup(object sender, EventArgs e) => CursorHelp.Show();
void VideoSizeChanged() => BeginInvoke(new Action(() => SetFormPosAndSize()));
void PropChangeFullscreen(bool value) => BeginInvoke(new Action(() => CycleFullscreen(value)));
void ContextMenu_Opened(object sender, EventArgs e) => CursorHelp.Show();
bool IsFullscreen => WindowState == FormWindowState.Maximized && FormBorderStyle == FormBorderStyle.None;
bool IsMouseInOSC()
{
Point pos = PointToClient(Control.MousePosition);
float top = 0;
if (FormBorderStyle == FormBorderStyle.None)
top = ClientSize.Height * 0.1f;
return pos.Y > ClientSize.Height * 0.85 || pos.Y < top;
}
void ContextMenu_Opening(object sender, CancelEventArgs e)
{
lock (core.MediaTracks)
{
MenuItem trackMenuItem = FindMenuItem("Track");
if (trackMenuItem != null)
{
trackMenuItem.DropDownItems.Clear();
MediaTrack[] audTracks = core.MediaTracks.Where(track => track.Type == "a").ToArray();
MediaTrack[] subTracks = core.MediaTracks.Where(track => track.Type == "s").ToArray();
MediaTrack[] vidTracks = core.MediaTracks.Where(track => track.Type == "v").ToArray();
MediaTrack[] ediTracks = core.MediaTracks.Where(track => track.Type == "e").ToArray();
foreach (MediaTrack track in vidTracks)
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => core.commandv("set", "vid", track.ID.ToString());
mi.Checked = core.Vid == track.ID.ToString();
trackMenuItem.DropDownItems.Add(mi);
}
if (vidTracks.Length > 0)
trackMenuItem.DropDownItems.Add(new ToolStripSeparator());
foreach (MediaTrack track in audTracks)
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => core.commandv("set", "aid", track.ID.ToString());
mi.Checked = core.Aid == track.ID.ToString();
trackMenuItem.DropDownItems.Add(mi);
}
if (subTracks.Length > 0)
trackMenuItem.DropDownItems.Add(new ToolStripSeparator());
foreach (MediaTrack track in subTracks)
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => core.commandv("set", "sid", track.ID.ToString());
mi.Checked = core.Sid == track.ID.ToString();
trackMenuItem.DropDownItems.Add(mi);
}
if (subTracks.Length > 0)
{
MenuItem mi = new MenuItem("S: No subtitles");
mi.Action = () => core.commandv("set", "sid", "no");
mi.Checked = core.Sid == "no";
trackMenuItem.DropDownItems.Add(mi);
}
if (ediTracks.Length > 0)
trackMenuItem.DropDownItems.Add(new ToolStripSeparator());
foreach (MediaTrack track in ediTracks)
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => core.commandv("set", "edition", track.ID.ToString());
mi.Checked = core.Edition == track.ID;
trackMenuItem.DropDownItems.Add(mi);
}
}
}
lock (core.Chapters)
{
MenuItem chaptersMenuItem = FindMenuItem("Chapters");
if (chaptersMenuItem != null)
{
chaptersMenuItem.DropDownItems.Clear();
foreach (var pair in core.Chapters)
{
MenuItem mi = new MenuItem(pair.Key);
mi.ShortcutKeyDisplayString = TimeSpan.FromSeconds(pair.Value).ToString().Substring(0, 8) + " ";
mi.Action = () => core.commandv("seek", pair.Value.ToString(CultureInfo.InvariantCulture), "absolute");
chaptersMenuItem.DropDownItems.Add(mi);
}
}
}
MenuItem recent = FindMenuItem("Recent");
if (recent != null)
{
recent.DropDownItems.Clear();
foreach (string path in RecentFiles)
MenuItem.Add(recent.DropDownItems, path, () => core.LoadFiles(new[] { path }, true, Control.ModifierKeys.HasFlag(Keys.Control)));
recent.DropDownItems.Add(new ToolStripSeparator());
MenuItem mi = new MenuItem("Clear List");
mi.Action = () => RecentFiles.Clear();
recent.DropDownItems.Add(mi);
}
MenuItem titles = FindMenuItem("Titles");
if (titles != null)
{
titles.DropDownItems.Clear();
lock (core.BluRayTitles)
{
List<(int Index, TimeSpan Len)> items = new List<(int Index, TimeSpan Len)>();
for (int i = 0; i < core.BluRayTitles.Count; i++)
items.Add((i, core.BluRayTitles[i]));
var titleItems = items.OrderByDescending(item => item.Len)
.Take(20).OrderBy(item => item.Index);
foreach (var item in titleItems)
if (item.Len != TimeSpan.Zero)
MenuItem.Add(titles.DropDownItems, $"{item.Len} ({item.Index})",
() => core.SetBluRayTitle(item.Index));
}
}
}
MenuItem FindMenuItem(string text, ToolStripItemCollection items)
{
foreach (var item in items)
{
if (item is MenuItem mi)
{
if (mi.Text.StartsWith(text) && mi.Text.Trim() == text)
return mi;
if (mi.DropDownItems.Count > 0)
{
MenuItem val = FindMenuItem(text, mi.DropDownItems);
if (val != null) return val;
}
}
}
return null;
}
void SetFormPosAndSize(double scale = 1,
bool force = false,
bool checkAutofitSmaller = true,
bool checkAutofitLarger = true)
{
if (!force)
{
if (WindowState != FormWindowState.Normal)
return;
if (core.Fullscreen)
{
CycleFullscreen(true);
return;
}
}
Screen screen = Screen.FromControl(this);
int autoFitHeight = Convert.ToInt32(screen.WorkingArea.Height * core.Autofit);
if (core.VideoSize.Height == 0 || core.VideoSize.Width == 0 ||
core.VideoSize.Width / (float)core.VideoSize.Height < App.MinimumAspectRatio)
core.VideoSize = new Size((int)(autoFitHeight * (16 / 9f)), autoFitHeight);
Size videoSize = core.VideoSize;
int height = videoSize.Height;
if (core.WasInitialSizeSet || scale != 1)
height = ClientSize.Height;
else
{
int savedHeight = RegistryHelp.GetInt(App.RegPath, "Height");
if (App.StartSize == "always" && savedHeight != 0)
height = savedHeight;
else
if (App.StartSize != "video")
height = autoFitHeight;
core.WasInitialSizeSet = true;
}
height = Convert.ToInt32(height * scale);
SetSize(new Size(height * videoSize.Width / videoSize.Height, height),
videoSize, screen, checkAutofitSmaller, checkAutofitLarger);
}
void SetSize(Size size,
Size videoSize,
Screen screen,
bool checkAutofitSmaller = true,
bool checkAutofitLarger = true)
{
int height = size.Height;
int width = size.Height * videoSize.Width / videoSize.Height;
int maxHeight = screen.WorkingArea.Height - (Height - ClientSize.Height);
int maxWidth = screen.WorkingArea.Width - (Width - ClientSize.Width);
if (checkAutofitSmaller && (height < maxHeight * core.AutofitSmaller))
{
height = Convert.ToInt32(maxHeight * core.AutofitSmaller);
width = Convert.ToInt32(height * videoSize.Width / videoSize.Height);
}
float autofitLarger = checkAutofitLarger ? core.AutofitLarger : 1;
if (height > maxHeight * autofitLarger)
{
height = Convert.ToInt32(maxHeight * autofitLarger);
width = Convert.ToInt32(height * videoSize.Width / videoSize.Height);
}
if (width > maxWidth)
{
width = maxWidth;
height = (int)Math.Ceiling(width * videoSize.Height / (double)videoSize.Width);
}
Point middlePos = new Point(Left + Width / 2, Top + Height / 2);
var rect = new 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;
SetWindowPos(Handle, IntPtr.Zero, left, top, rect.Width, rect.Height, 4);
}
public void CycleFullscreen(bool enabled)
{
LastCycleFullscreen = Environment.TickCount;
core.Fullscreen = enabled;
if (enabled)
{
if (WindowState != FormWindowState.Maximized || FormBorderStyle != FormBorderStyle.None)
{
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
if (WasMaximized)
{
Rectangle b = Screen.FromControl(this).Bounds;
uint SWP_SHOWWINDOW = 0x0040;
IntPtr HWND_TOP= IntPtr.Zero;
SetWindowPos(Handle, HWND_TOP, b.X, b.Y, b.Width, b.Height, SWP_SHOWWINDOW);
}
}
}
else
{
if (WindowState == FormWindowState.Maximized && FormBorderStyle == FormBorderStyle.None)
{
if (WasMaximized)
WindowState = FormWindowState.Maximized;
else
WindowState = FormWindowState.Normal;
if (core.Border)
FormBorderStyle = FormBorderStyle.Sizable;
else
FormBorderStyle = FormBorderStyle.None;
SetFormPosAndSize();
SaveWindowProperties();
}
}
}
public void BuildMenu()
{
string content = File.ReadAllText(core.InputConfPath);
var items = CommandItem.GetItems(content);
if (!content.Contains("#menu:"))
{
var defaultItems = CommandItem.GetItems(Properties.Resources.input_conf);
foreach (CommandItem item in items)
foreach (CommandItem defaultItem in defaultItems)
if (item.Command == defaultItem.Command)
defaultItem.Input = item.Input;
items = defaultItems;
}
foreach (CommandItem item in items)
{
if (string.IsNullOrEmpty(item.Path))
continue;
MenuItem menuItem = ContextMenu.Add(item.Path.Replace("&", "&&"), () => {
try {
core.command(item.Command);
} catch (Exception ex) {
Msg.ShowException(ex);
}
});
if (menuItem != null)
menuItem.ShortcutKeyDisplayString = item.Input.Replace("&", "&&") + " ";
}
}
void FileLoaded()
{
string path = core.get_property_string("path");
BeginInvoke(new Action(() => {
Text = core.expand(Title);
int interval = (int)(core.Duration.TotalMilliseconds / 100);
if (interval < 100)
interval = 100;
if (interval > 1000)
interval = 1000;
ProgressTimer.Interval = interval;
UpdateProgressBar();
}));
if (RecentFiles.Contains(path))
RecentFiles.Remove(path);
RecentFiles.Insert(0, path);
while (RecentFiles.Count > App.RecentCount)
RecentFiles.RemoveAt(App.RecentCount);
}
void SetTitle() => BeginInvoke(new Action(() => Text = core.expand(Title)));
void SaveWindowProperties()
{
if (WindowState == FormWindowState.Normal)
{
RegistryHelp.SetValue(App.RegPath, "PosX", Left + Width / 2);
RegistryHelp.SetValue(App.RegPath, "PosY", Top + Height / 2);
RegistryHelp.SetValue(App.RegPath, "Height", ClientSize.Height);
}
}
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.Style |= 0x00020000 /* WS_MINIMIZEBOX */;
return cp;
}
}
string _Title;
public string Title {
get => _Title;
set {
if (string.IsNullOrEmpty(value))
return;
if (value.EndsWith("} - mpv"))
value = value.Replace("} - mpv", "} - mpv.net");
_Title = value;
}
}
protected override void WndProc(ref Message m)
{
//Debug.WriteLine(m);
switch (m.Msg)
{
case 0x100: // WM_KEYDOWN
case 0x101: // WM_KEYUP
case 0x104: // WM_SYSKEYDOWN
case 0x105: // WM_SYSKEYUP
case 0x201: // WM_LBUTTONDOWN
case 0x202: // WM_LBUTTONUP
case 0x207: // WM_MBUTTONDOWN
case 0x208: // WM_MBUTTONUP
case 0x20a: // WM_MOUSEWHEEL
case 0x20e: // WM_MOUSEHWHEEL
case 0x20b: // WM_XBUTTONDOWN
case 0x20c: // WM_XBUTTONUP
{
bool skip = m.Msg == 0x100 && LastAppCommand != 0 &&
(Environment.TickCount - LastAppCommand) < 1000;
if (core.WindowHandle != IntPtr.Zero && !skip)
m.Result = SendMessage(core.WindowHandle, m.Msg, m.WParam, m.LParam);
}
break;
case 0x319: // WM_APPCOMMAND
{
string value = mpvHelp.WM_APPCOMMAND_to_mpv_key((int)(m.LParam.ToInt64() >> 16 & ~0xf000));
if (value != null)
{
core.command("keypress " + value);
m.Result = new IntPtr(1);
LastAppCommand = Environment.TickCount;
return;
}
}
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;
}
break;
case 0x0200: // WM_MOUSEMOVE
if (Environment.TickCount - LastCycleFullscreen > 500)
{
Point pos = PointToClient(Cursor.Position);
core.command($"mouse {pos.X} {pos.Y}");
}
if (CursorHelp.IsPosDifferent(LastCursorPosition))
CursorHelp.Show();
break;
case 0x2a3: // WM_MOUSELEAVE
//osc won't auto hide after mouse left window in borderless mode
core.command($"mouse {ClientSize.Width / 2} {ClientSize.Height / 3}");
break;
case 0x203: // WM_LBUTTONDBLCLK
{
Point pos = PointToClient(Cursor.Position);
core.command($"mouse {pos.X} {pos.Y} 0 double");
}
break;
case 0x02E0: // WM_DPICHANGED
{
if (!WasShown())
break;
RECT rect = Marshal.PtrToStructure<RECT>(m.LParam);
SetWindowPos(Handle, IntPtr.Zero, rect.Left, rect.Top, rect.Width, rect.Height, 0);
}
break;
case 0x0214: // WM_SIZING
{
var rc = Marshal.PtrToStructure<RECT>(m.LParam);
var r = rc;
NativeHelp.SubtractWindowBorders(Handle, ref r);
int c_w = r.Right - r.Left, c_h = r.Bottom - r.Top;
Size s = core.VideoSize;
if (s == Size.Empty)
s = new Size(16, 9);
float aspect = s.Width / (float)s.Height;
int d_w = (int)(c_h * aspect - c_w);
int d_h = (int)(c_w / aspect - c_h);
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];
Marshal.StructureToPtr<RECT>(new RECT(corners[0], corners[1], corners[2], corners[3]), m.LParam, false);
m.Result = new IntPtr(1);
}
return;
case 0x004A: // WM_COPYDATA
{
var copyData = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
string[] files = copyData.lpData.Split('\n');
string mode = files[0];
files = files.Skip(1).ToArray();
switch (mode)
{
case "single":
core.LoadFiles(files, true, Control.ModifierKeys.HasFlag(Keys.Control));
break;
case "queue":
foreach (string file in files)
core.commandv("loadfile", file, "append");
break;
}
Activate();
}
return;
}
if (m.Msg == TaskbarButtonCreatedMessage && core.TaskbarProgress)
{
Taskbar = new Taskbar(Handle);
ProgressTimer.Start();
}
// beep sound when closed using taskbar due to exception
if (!IsDisposed)
base.WndProc(ref m);
}
void CursorTimer_Tick(object sender, EventArgs e)
{
if (CursorHelp.IsPosDifferent(LastCursorPosition))
{
LastCursorPosition = Control.MousePosition;
LastCursorChanged = Environment.TickCount;
}
else if (Environment.TickCount - LastCursorChanged > 1500 &&
!IsMouseInOSC() && ClientRectangle.Contains(PointToClient(MousePosition)) &&
Form.ActiveForm == this && !ContextMenu.Visible)
CursorHelp.Hide();
}
void ProgressTimer_Tick(object sender, EventArgs e) => UpdateProgressBar();
void UpdateProgressBar()
{
if (core.TaskbarProgress && Taskbar != null)
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;
void PropChangeSid(string value) => core.Sid = value;
void PropChangeVid(string value) => core.Vid = value;
void PropChangeTitle(string value) { Title = value; SetTitle(); }
void PropChangeEdition(int value) => core.Edition = value;
void PropChangeWindowScale(double value) => BeginInvoke(new Action(() => WindowScale(value)));
void PropChangeWindowMaximized()
{
if (!WasShown())
return;
BeginInvoke(new Action(() =>
{
core.WindowMaximized = core.get_property_bool("window-maximized");
if (core.WindowMaximized && WindowState != FormWindowState.Maximized)
WindowState = FormWindowState.Maximized;
else if (!core.WindowMaximized && WindowState == FormWindowState.Maximized)
WindowState = FormWindowState.Normal;
}));
}
void PropChangeWindowMinimized()
{
if (!WasShown())
return;
BeginInvoke(new Action(() =>
{
core.WindowMinimized = core.get_property_bool("window-minimized");
if (core.WindowMinimized && WindowState != FormWindowState.Minimized)
WindowState = FormWindowState.Minimized;
else if (!core.WindowMinimized && WindowState == FormWindowState.Minimized)
WindowState = FormWindowState.Normal;
}));
}
void PropChangeBorder(bool enabled) {
core.Border = enabled;
BeginInvoke(new Action(() => {
if (!IsFullscreen)
{
if (core.Border && FormBorderStyle == FormBorderStyle.None)
FormBorderStyle = FormBorderStyle.Sizable;
if (!core.Border && FormBorderStyle == FormBorderStyle.Sizable)
FormBorderStyle = FormBorderStyle.None;
}
}));
}
void PropChangePause(bool enabled)
{
if (Taskbar != null && core.TaskbarProgress)
{
if (enabled)
Taskbar.SetState(TaskbarStates.Paused);
else
Taskbar.SetState(TaskbarStates.Normal);
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (core.GPUAPI != "vulkan")
core.VideoSizeAutoResetEvent.WaitOne(App.StartThreshold);
LastCycleFullscreen = Environment.TickCount;
SetFormPosAndSize();
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
if (WindowState == FormWindowState.Maximized)
core.set_property_bool("window-maximized", true);
if (core.GPUAPI == "vulkan")
core.ProcessCommandLine(false);
ToolStripRendererEx.ForegroundColor = Theme.Current.GetWinFormsColor("menu-foreground");
ToolStripRendererEx.BackgroundColor = Theme.Current.GetWinFormsColor("menu-background");
ToolStripRendererEx.SelectionColor = Theme.Current.GetWinFormsColor("menu-highlight");
ToolStripRendererEx.BorderColor = Theme.Current.GetWinFormsColor("menu-border");
ToolStripRendererEx.CheckedColor = Theme.Current.GetWinFormsColor("menu-checked");
BuildMenu();
ContextMenuStrip = ContextMenu;
WPF.Init();
System.Windows.Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;
Cursor.Position = new Point(Cursor.Position.X + 1, Cursor.Position.Y);
MinimumSize = new Size(FontHeight * 9, FontHeight * 9);
UpdateCheck.DailyCheck();
core.LoadScripts();
App.RunTask(() => App.Extension = new Extension());
CSharpScriptHost.ExecuteScriptsInFolder(core.ConfigFolder + "scripts-cs");
ShownTickCount = Environment.TickCount;
App.ShowSetup();
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
Message m = new Message() { Msg = 0x0202 }; // WM_LBUTTONUP
SendMessage(MainForm.Instance.Handle, m.Msg, m.WParam, m.LParam);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
if (core.IsLogoVisible)
core.ShowLogo();
if (FormBorderStyle != FormBorderStyle.None)
{
if (WindowState == FormWindowState.Maximized)
WasMaximized = true;
else if (WindowState == FormWindowState.Normal)
WasMaximized = false;
}
if (WasShown())
{
if (WindowState == FormWindowState.Minimized)
{
core.set_property_bool("window-minimized", true);
}
else if (WindowState == FormWindowState.Normal)
{
core.set_property_bool("window-maximized", false);
core.set_property_bool("window-minimized", false);
}
else if (WindowState == FormWindowState.Maximized)
{
core.set_property_bool("window-maximized", true);
}
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
SaveWindowProperties();
RegistryHelp.SetValue(App.RegPath, "Recent", RecentFiles.ToArray());
if (core.IsQuitNeeded)
core.commandv("quit");
if (!core.ShutdownAutoResetEvent.WaitOne(10000))
Msg.ShowError("Shutdown thread failed to complete within 10 seconds.");
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (WindowState == FormWindowState.Normal &&
e.Button == MouseButtons.Left && !IsMouseInOSC())
{
var HTCAPTION = new IntPtr(2);
ReleaseCapture();
PostMessage(Handle, 0xA1 /* WM_NCLBUTTONDOWN */, HTCAPTION, IntPtr.Zero);
}
if (Width - e.Location.X < 10 && e.Location.Y < 10)
core.commandv("quit");
}
protected override void OnDragEnter(DragEventArgs e)
{
base.OnDragEnter(e);
if (e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetDataPresent(DataFormats.Text))
e.Effect = DragDropEffects.Copy;
}
protected override void OnDragDrop(DragEventArgs e)
{
base.OnDragDrop(e);
if (e.Data.GetDataPresent(DataFormats.FileDrop))
core.LoadFiles(e.Data.GetData(DataFormats.FileDrop) as String[], true, Control.ModifierKeys.HasFlag(Keys.Control));
if (e.Data.GetDataPresent(DataFormats.Text))
core.LoadFiles(new[] { e.Data.GetData(DataFormats.Text).ToString() }, true, Control.ModifierKeys.HasFlag(Keys.Control));
}
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
CursorHelp.Show();
}
protected override void OnKeyDown(KeyEventArgs e)
{
// prevent annoying beep using alt key
if (ModifierKeys == Keys.Alt)
e.SuppressKeyPress = true;
base.OnKeyDown(e);
}
}
}

560
src/WinForms/MainForm.resx Normal file
View File

@@ -0,0 +1,560 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="CursorTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>30, 12</value>
</metadata>
<metadata name="ProgressTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>321, 12</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAgIAAAAQAgAKgQAABGDgAAMDAAAAEA
IACoJQAA7h4AAAAAAAABACAA3x8AAJZEAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAADDDgAAww4AAAAA
AAAAAAAAAAAAAAAAAAD/lAAA/5QABf+UAEH/lACe/5QA3P+UAPX/lAD1/5QA3P+UAJ7/lABB/5QABf+U
AAAAAAAAAAAAAP+UAAD/lAAA/5QAFP+UAIn/lADr/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA6/+U
AIr/lAAU/5QAAP+UAAD/lAAA/5QAFP+UAKj/lAD+/5QA//+TAP//lgb//5oR//+aEf//lgf//5MA//+U
AP//lAD+/5QAqf+UABT/lAAA/5QABP+UAIn/lAD+/5QA//+WBv//pzX//7ln///Be///wXz//7tq//+p
Ov//lwj//5QA//+UAP7/lACK/5QABP+UAEH/lADp/5QA//+WBv//sVD//8mP///Tpv//yZH//8eM///I
jv//x4v//7NW//+XCP//lAD//5QA6f+UAEH/lACe/5QA//+TAP//qDf//8aI///Sov//9+///+vX///T
pP//x4z//8eM///Hi///qj7//5QA//+UAP//lACe/5QA2/+UAP//lgb//7pp///Hjf//06T///Hh///h
wv//8eL//+TH///NmP//x43//7xv//+XCf//lAD//5QA2/+UAPT/lAD//5oR///BfP//x4z//9Ok///u
3P//yZD//8+c///q1f//8+f//9Gh///Cf///nBb//5MA//+UAPT/lAD0/5QA//+aEf//wXv//8eM///T
pP//7tz//8mR///Up///7tz//+rU///Nmf//wn///5wW//+TAP//lAD0/5QA2/+UAP//lgb//7ln///H
jf//06T///Hj///lyv//8eL//9y3///Jj///x43//7xt//+XCP//lAD//5QA2/+UAJ3/lAD//5MA//+m
M///xYf//9Gi///37///6dH//8+d///Gi///x4z//8aK//+pOf//lAD//5QA//+UAJ3/lABB/5QA6f+U
AP//lQT//69J///IjP//06T//8mQ///HjP//yI7//8aJ//+xT///lgb//5QA//+UAOn/lABB/5QABP+U
AIn/lAD+/5QA//+VBP//pS7//7dg//+/dv//v3f//7hj//+mMv//lgX//5QA//+UAP7/lACJ/5QABP+U
AAD/lAAU/5QAqP+UAP7/lAD//5MA//+VBP//mQ3//5kN//+VBP//kwD//5QA//+UAP7/lACo/5QAFP+U
AAD/lAAA/5QAAP+UABT/lACJ/5QA6/+UAP//lAD//5QA//+UAP//lAD//5QA//+UAOv/lACJ/5QAFP+U
AAD/lAAAAAAAAAAAAAD/lAAA/5QABf+UAEH/lACe/5QA3P+UAPX/lAD1/5QA3P+UAJ7/lABB/5QABf+U
AAAAAAAAAAAAAOAHAADAAwAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACAAQAAwAMAAOAHAAAoAAAAGAAAADAAAAABACAAAAAAAAAJAADDDgAAww4AAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD/lAAA/5QABP+UADD/lAB8/5QAvf+UAOP/lADz/5QA8/+UAOP/lAC9/5QAfP+U
ADD/lAAE/5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAo/5QAk/+U
AOP/lAD9/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA/f+UAOP/lACT/5QAKf+UAAD/lAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD/lAAA/5QAA/+UAFf/lADa/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA2v+UAFj/lAAD/5QAAAAAAAAAAAAAAAAAAP+UAAD/lAAD/5QAa/+U
AO//lAD//5QA//+UAP//lAD//5MA//+TAP//kwD//5MA//+TAP//kwD//5QA//+UAP//lAD//5QA//+U
APD/lABr/5QAA/+UAAAAAAAA/5QAAP+UAAD/lABY/5QA7/+UAP//lAD//5QA//+TAP//lQT//54c//+p
Of//r0r//69L//+qPP//nx///5YG//+TAP//lAD//5QA//+UAP//lADv/5QAWP+UAAD/lAAA/5QAAP+U
ACn/lADZ/5QA//+UAP//lAD//5QA//+eG///tVz//8OC///HjP//yI7//8iO///Hjf//xIT//7dh//+g
IP//lAH//5QA//+UAP//lAD//5QA2f+UACn/lAAA/5QAA/+UAJP/lAD//5QA//+UAP//lAD//6Mq///A
ef//yI///8qS///HjP//x4v//8eM///HjP//x43//8iO///Cfv//pjL//5QA//+UAP//lAD//5QA//+U
AJP/lAAD/5QAMf+UAOL/lAD//5QA//+TAP//nhz//8B6///Hjf//0J7///Dg///ixP//zJf//8aK///H
jP//x4z//8eM///Ijv//wn///6Ek//+TAP//lAD//5QA//+UAOL/lAAx/5QAfP+UAP3/lAD//5QA//+V
BP//tlz//8iO///Giv//1Kj///36///+/P//9Oj//9qz///JkP//xov//8eM///HjP//yI7//7lm//+X
CP//lAD//5QA//+UAP3/lAB8/5QAvf+UAP//lAD//5MA//+fHv//xIP//8eN///Giv//1Kj///v3///i
xP//7tz///z5///t2f//06T//8eM///Hi///x4z//8WH//+iJ///kwD//5QA//+UAP//lAC8/5QA4v+U
AP//lAD//5MA//+qO///x43//8eM///Giv//1aj///v3///Vqf//yY///9y3///27P//+vT//+TH///N
mP//x4z//8iO//+uR///kwD//5QA//+UAP//lADi/5QA8v+UAP//lAD//5MA//+vS///yI7//8eM///G
iv//1aj///v3///Wq///xor//8aK///NmP//6dL////////w3///ypP//8iO//+zVv//lAD//5QA//+U
AP//lADy/5QA8v+UAP//lAD//5MA//+vSf//yI7//8eM///Giv//1aj///v3///Wq///xon//8eN///W
q///8uX///r2///hwf//yI///8iO//+zVf//lAD//5QA//+UAP//lADy/5QA4v+UAP//lAD//5MA//+p
OP//x4z//8eM///Giv//1aj///v3///Vqf//zJb//+bL///79///8eP//9Wp///HjP//x4z//8iO//+t
RP//kwD//5QA//+UAP//lADi/5QAvP+UAP//lAD//5MA//+eG///w4H//8eN///Giv//1Kj///v3///m
y///8+f///r1///ixP//y5T//8aK///HjP//x4z//8WG//+hJP//kwD//5QA//+UAP//lAC8/5QAe/+U
AP3/lAD//5QA//+VA///s1f//8iO///Giv//1Kf///36///+/f//79///9Om///HjP//x4v//8eM///H
jP//yI7//7dh//+WBv//lAD//5QA//+UAP3/lAB7/5QAMf+UAOL/lAD//5QA//+UAP//nBf//750///H
jf//z5z//+7d///gv///ypP//8aL///HjP//x4z//8eM///Ijv//wXr//58e//+TAP//lAD//5QA//+U
AOL/lAAx/5QAA/+UAJL/lAD//5QA//+UAP//lAD//6Ah//++cv//yI7//8mR///HjP//x4v//8eM///H
jP//x43//8iO///AeP//oyn//5QA//+UAP//lAD//5QA//+UAJL/lAAD/5QAAP+UACj/lADY/5QA//+U
AP//lAD//5QA//+bFP//sVD//8F7///Giv//x43//8eN///Hi///wn7//7NW//+dGP//lAD//5QA//+U
AP//lAD//5QA2f+UACn/lAAA/5QAAP+UAAD/lABX/5QA7/+UAP//lAD//5QA//+UAP//lAL//5sU//+l
Lv//qz7//6s///+mMf//nBb//5UD//+TAP//lAD//5QA//+UAP//lADv/5QAWP+UAAD/lAAAAAAAAP+U
AAD/lAAD/5QAav+UAO//lAD//5QA//+UAP//lAD//5QA//+TAP//kwD//5MA//+TAP//lAD//5QA//+U
AP//lAD//5QA//+UAPD/lABr/5QAA/+UAAAAAAAAAAAAAAAAAAD/lAAA/5QAA/+UAFf/lADZ/5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA2v+UAFj/lAAD/5QAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAo/5QAk/+UAOP/lAD9/5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA/f+UAOP/lACT/5QAKP+UAAD/lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD/lAAA/5QABP+UADD/lAB8/5QAvf+UAOP/lADz/5QA8/+UAOP/lAC9/5QAfP+UADD/lAAE/5QAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA/AA/APgAHwDgAAcAwAADAMAAAwCAAAEAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAABAMAAAwDAAAMA4AAHAPgAHwD8AD8AKAAAACAA
AABAAAAAAQAgAAAAAAAAEAAAww4AAMMOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA/5QAAP+UAAL/lAAd/5QAWv+UAJr/lADI/5QA5P+UAO//lADv/5QA5P+UAMj/lACa/5QAWv+U
AB7/lAAC/5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA/5QAAP+UAAH/lAAq/5QAh/+UANT/lAD3/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD4/5QA1P+UAIf/lAAr/5QAAv+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA/5QAAP+UAAD/lAAS/5QAeP+UAOH/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAOH/lAB5/5QAEv+UAAD/lAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP+UAAD/lAAA/5QAKf+UALL/lAD8/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAPz/lACz/5QAKv+UAAD/lAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD/lAAA/5QAAP+UADT/lADM/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5MA//+TAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lADN/5QANP+U
AAD/lAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAq/5QAzP+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//kwD//5UE//+ZDv//nBb//5wW//+aEP//lgX//5MA//+TAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lADN/5QAKv+UAAD/lAAAAAAAAAAAAAD/lAAA/5QAEv+UALL/lAD//5QA//+UAP//lAD//5QA//+U
AP//lAH//5wV//+rP///uGP//793///Cf///wn///8B5//+5Z///rUT//50a//+UAv//lAD//5QA//+U
AP//lAD//5QA//+UAP//lACz/5QAEv+UAAAAAAAA/5QAAP+UAAD/lAB4/5QA/f+UAP//lAD//5QA//+U
AP//lAD//5cI//+rPv//wHr//8eN///Ijv//x43//8eN///Hjf//x43//8iO///Hjf//wn7//65H//+Y
DP//kwD//5QA//+UAP//lAD//5QA//+UAP3/lAB5/5QAAP+UAAD/lAAA/5QALP+UAOD/lAD//5QA//+U
AP//lAD//5QA//+ZDv//tVr//8eL///Hjf//xov//8aL///HjP//x4z//8eM///HjP//x4z//8eM///H
jf//x4z//7hk//+bFf//kwD//5QA//+UAP//lAD//5QA//+UAOD/lAAs/5QAAP+UAAD/lACH/5QA//+U
AP//lAD//5QA//+UAP//lwj//7Va///Hjf//x4z//8uU///gv///2K///8mP///Gi///x4z//8eM///H
jP//x4z//8eM///HjP//yI7//7lm//+ZDv//lAD//5QA//+UAP//lAD//5QA//+UAIf/lAAA/5QAHv+U
ANP/lAD//5QA//+UAP//lAD//5QA//+rQP//x4v//8eM///Giv//1qr///37///9+v//69b//9Kj///H
jP//x4v//8eM///HjP//x4z//8eM///HjP//x43//7BO//+VAv//lAD//5QA//+UAP//lAD//5QA0/+U
AB7/lABa/5QA9/+UAP//lAD//5QA//+UAP//nBb//8B6///Hjf//x4z//8aK///Xrf///v3///79////
/v//+fP//+PF///Nl///xor//8eM///HjP//x4z//8eM///Hjf//w4L//6Ah//+TAP//lAD//5QA//+U
AP//lAD3/5QAWv+UAJr/lAD//5QA//+UAP//lAD//5MA//+sQf//x43//8eM///HjP//xor//9et////
/v//7tz//+PF///69P////////To///atP//yZD//8aL///HjP//x4z//8eM///Ijv//slH//5QA//+U
AP//lAD//5QA//+UAP//lACa/5QAyP+UAP//lAD//5QA//+UAP//lgX//7lm///Ijv//x4z//8eM///G
iv//163////////p0v//x4v//9Kj///s1////fv///38///s2f//06X//8eM///Hi///x4z//8iO//+9
cv//mAv//5QA//+UAP//lAD//5QA//+UAMj/lADj/5QA//+UAP//lAD//5QA//+aEP//wHn//8eN///H
jP//x4z//8aK///Xrf///////+nS///HjP//xov//8mP///asv//8+b////+///69P//5Mj//8yX///H
jP//x43//8OB//+dGv//kwD//5QA//+UAP//lAD//5QA4/+UAO//lAD//5QA//+UAP//kwD//5wW///C
f///x43//8eM///HjP//xor//9et////////6dL//8eM///HjP//x4z//8aK///Llf//587///7+////
////5cr//8eL///HjP//xYb//6Ah//+TAP//lAD//5QA//+UAP//lADv/5QA7/+UAP//lAD//5QA//+U
AP//nBX//8J////Hjf//x4z//8eM///Giv//163////////p0v//x4z//8eM///Gi///yI7//9iw///0
5/////////bs///WrP//x4v//8eM///Fhv//oCH//5MA//+UAP//lAD//5QA//+UAO//lADj/5QA//+U
AP//lAD//5QA//+ZDv//v3f//8eN///HjP//x4z//8aK///Xrf///////+nS///Hi///xov//86b///o
0P///Pr///37///q1P//z53//8eL///HjP//x43//8OA//+dGP//kwD//5QA//+UAP//lAD//5QA4/+U
AMj/lAD//5QA//+UAP//lAD//5UE//+4Yv//yI7//8eM///HjP//xor//9et////////6dH//8iP///b
tf//9ev////////27P//27X//8mP///Gi///x4z//8eM///Ijv//vG///5cJ//+UAP//lAD//5QA//+U
AP//lADI/5QAmv+UAP//lAD//5QA//+UAP//kwD//6o8///HjP//x4z//8eM///Giv//163///7+///w
4P//6tP///37///9+v//6NH//8+c///Gi///x4z//8eM///HjP//x4z//8iO//+vS///lAD//5QA//+U
AP//lAD//5QA//+UAJr/lABa/5QA9/+UAP//lAD//5QA//+UAP//mhH//791///Ijv//x4z//8aK///X
rf///v3////+////////9On//9mx///Ijv//xov//8eM///HjP//x4z//8eM///Hjf//wn7//54b//+T
AP//lAD//5QA//+UAP//lAD3/5QAWv+UAB7/lADT/5QA//+UAP//lAD//5QA//+UAP//qDf//8aI///H
jf//xor//9Wp///9+////Pj//+bM///Nmf//xov//8eM///HjP//x4z//8eM///HjP//x4z//8eL//+t
Rf//lAH//5QA//+UAP//lAD//5QA//+UANP/lAAe/5QAAP+UAIb/lAD//5QA//+UAP//lAD//5QA//+V
Bf//sU///8eM///HjP//ypL//967///XrP//yI3//8eL///HjP//x4z//8eM///HjP//x4z//8eM///H
jf//tVv//5cJ//+UAP//lAD//5QA//+UAP//lAD//5QAhv+UAAD/lAAA/5QAK/+UAOD/lAD//5QA//+U
AP//lAD//5QA//+XCf//sEz//8WH///Hjf//xov//8aL///HjP//x4z//8eM///HjP//x4z//8eM///I
jv//xor//7RX//+ZDf//lAD//5QA//+UAP//lAD//5QA//+UAOD/lAAr/5QAAP+UAAD/lAAA/5QAd/+U
APz/lAD//5QA//+UAP//lAD//5QA//+VBP//pjD//7xv///Giv//yI7//8iO///Ijf//yI3//8iO///I
jv//x4v//750//+pOf//lgf//5QA//+UAP//lAD//5QA//+UAP//lAD9/5QAeP+UAAD/lAAAAAAAAP+U
AAD/lAAS/5QAsf+UAP//lAD//5QA//+UAP//lAD//5QA//+TAP//mAz//6Uv//+zVf//u2z//791//+/
dv//vG7//7RZ//+nNf//mg///5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UALL/lAAS/5QAAAAA
AAAAAAAA/5QAAP+UAAD/lAAp/5QAy/+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//kwD//5QB//+X
B///mAz//5kN//+XCP//lAH//5MA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lADM/5QAKf+U
AAD/lAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAz/5QAy/+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QAzP+U
ADT/lAAA/5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAo/5QAsv+UAPz/lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA/P+U
ALP/lAAp/5QAAP+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAR/5QAd/+U
AOH/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AOH/lAB4/5QAEv+UAAD/lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+U
AAD/lAAB/5QAKv+UAIf/lADU/5QA9/+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA+P+U
ANT/lACH/5QAK/+UAAH/lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD/lAAA/5QAAv+UAB3/lABa/5QAmv+UAMj/lADk/5QA7/+UAO//lADk/5QAyP+U
AJr/lABa/5QAHv+UAAL/lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAH//gAAf/wA
AD/4AAAf8AAAD+AAAAfAAAADwAAAA4AAAAGAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAgAAAAYAAAAHAAAADwAAAA+AAAAfwAAAP+AAAH/wAAD/+AAB//4AB/ygA
AAAwAAAAYAAAAAEAIAAAAAAAACQAAMMOAADDDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+UAAD/lAAA/5QABf+UACL/lABU/5QAif+U
AK//lADP/5QA3/+UAOn/lADp/5QA3/+UAND/lACw/5QAif+UAFT/lAAj/5QABv+UAAD/lAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/lAAA/5QAAP+UABr/lABZ/5QApf+U
ANv/lAD2/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAPb/lADb/5QApv+U
AFn/lAAa/5QAAP+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAe/5QAdv+U
ANL/lAD7/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAPv/lADS/5QAdv+UAB7/lAAA/5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+UAAD/lAAA/5QADP+U
AGH/lADS/5QA/f+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA/f+UANP/lABi/5QADf+UAAD/lAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+U
AAD/lAAn/5QAqP+UAPj/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD5/5QAqf+U
ACf/lAAA/5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD/lAAA/5QAAP+UAEb/lADR/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UANL/lABH/5QAAf+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP+UAAD/lAAC/5QAV/+UAOT/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lADl/5QAWf+UAAL/lAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lABX/5QA6v+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+TAP//kwD//5MA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA6/+UAFn/lAAA/5QAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/lAAA/5QAAP+UAEf/lADk/5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//kwD//5MA//+VAv//mAr//5wW//+dGf//nRn//5wX//+Z
DP//lQT//5MA//+TAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AOX/lABH/5QAAP+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+UAAD/lAAA/5QAJ/+UANH/lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5MA//+VA///nRn//6o8//+2Xv//vXL//8J+///D
gf//w4H//8J///++dP//uGL//6xD//+fHv//lgb//5MA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lADS/5QAKP+UAAD/lAAAAAAAAAAAAAAAAAAAAAAAAP+UAAD/lAAM/5QAp/+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAH//58f//+0WP//wn///8eM///I
jv//yI7//8eN///Hjf//x43//8eN///Ijf//yI7//8eN///Eg///uGL//6Mp//+VBP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QAqf+UAA3/lAAAAAAAAAAAAAAAAAAA/5QAAP+U
AAD/lABi/5QA+P+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5MA//+XCv//rUT//8J////I
jv//x43//8eM///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///Hjf//yI7//8SF//+y
Uv//mhH//5MA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA+f+UAGP/lAAA/5QAAAAA
AAAAAAAA/5QAAP+UAB//lADS/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5sU//+3
YP//x4z//8eN///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///H
jP//x4z//8eN///Hjf//vG3//58g//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
ANL/lAAf/5QAAAAAAAD/lAAA/5QAAP+UAHb/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+T
AP//mxX//7tr///Ijv//x4z//8eM///Hi///yI7//8iO///Gi///x4z//8eM///HjP//x4z//8eM///H
jP//x4z//8eM///HjP//x4z//8eM///HjP//yI7//8B4//+gIv//kwD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAB2/5QAAP+UAAD/lAAA/5QAGv+UANH/lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+YCv//t2D//8iO///HjP//x4z//8eL///NmP//6tT//+3a///YsP//yI///8aL///H
jP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8iO//+9cP//mxX//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lADR/5QAGv+UAAD/lAAA/5QAWf+UAPv/lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QB//+uSP//x4z//8eM///HjP//x4z//8aK///btP////7////////8
+v//69X//9Ki///HjP//x4v//8eM///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///I
jv//tVz//5YG//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD7/5QAWf+UAAD/lAAF/5QApf+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//kwD//6Ag///DgP//x43//8eM///HjP//x4z//8aK///d
uf////////////////////////ny///ixP//zJf//8aL///HjP//x4z//8eM///HjP//x4z//8eM///H
jP//x4z//8eM///HjP//xoj//6cz//+TAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QApf+U
AAX/lAAj/5QA2/+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lQP//7RZ///Ijv//x4z//8eM///H
jP//x4z//8aK///duf/////////////9/P////7//////////v//8+f//9qz///JkP//xov//8eM///H
jP//x4z//8eM///HjP//x4z//8eM///HjP//yI7//7xt//+YC///lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA2/+UACP/lABU/5QA9v+UAP//lAD//5QA//+UAP//lAD//5QA//+TAP//nhv//8OA///H
jf//x4z//8eM///HjP//x4z//8aK///duf/////////////nz///587///v4//////////////37///s
2P//06T//8eM///Hi///x4z//8eM///HjP//x4z//8eM///HjP//x4z//8aJ//+lMP//kwD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA9v+UAFT/lACI/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+T
AP//rEH//8eN///HjP//x4z//8eM///HjP//x4z//8aK///duf/////////////fvv//x4z//9ar///w
4P///v3/////////////+fT//+TH///NmP//xov//8eM///HjP//x4z//8eM///HjP//x4z//8iO//+0
V///lAH//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAIj/lACv/5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+VA///t2D//8iO///HjP//x4z//8eM///HjP//x4z//8aK///duf/////////////g
v///xor//8aK///Kk///3rv///bt///////////////+///06f//27b//8mR///Gi///x4z//8eM///H
jP//x4z//8iO//++c///mAz//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAK//lADP/5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+YDP//vnT//8iN///HjP//x4z//8eM///HjP//x4z//8aK///d
uf/////////////gv///xor//8eM///Hi///xov//8+c///mzf//+/f//////////////fv//+3a///U
pv//x43//8eM///HjP//x4z//8eN///Dgv//nhv//5MA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AM//lADf/5QA//+UAP//lAD//5QA//+UAP//lAD//5MA//+cF///wn///8eN///HjP//x4z//8eM///H
jP//x4z//8aK///duf/////////////gv///xor//8eM///HjP//x4z//8aL///Hjf//1an//+7d///9
/P/////////////69f//4MD//8iO///HjP//x4z//8eM///Gif//oyn//5MA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAN//lADp/5QA//+UAP//lAD//5QA//+UAP//lAD//5MA//+dGf//w4H//8eN///H
jP//x4z//8eM///HjP//x4z//8aK///duf/////////////gv///xor//8eM///HjP//x4z//8eM///H
jP//xor//8mR///my////v7/////////////9u3//8yX///Hi///x4z//8eM///Gif//pCv//5MA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAOn/lADp/5QA//+UAP//lAD//5QA//+UAP//lAD//5MA//+d
Gf//w4H//8eN///HjP//x4z//8eM///HjP//x4z//8aK///duf/////////////gv///xor//8eM///H
jP//x4z//8eM///Gi///ypH//967///37v/////////////8+v//48b//8iP///HjP//x4z//8eM///G
if//pCz//5MA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAOn/lADf/5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+bFP//wX3//8eN///HjP//x4z//8eM///HjP//x4z//8aK///duf/////////////g
v///xor//8eM///HjP//x4v//8eL///So///7dr///78//////////////bt///atP//yI7//8eM///H
jP//x4z//8eM///FiP//oif//5MA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAN//lADP/5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+YCv//vXH//8iO///HjP//x4z//8eM///HjP//x4z//8aK///d
uf/////////////gv///xor//8eM///Gi///ypP//+C////58v/////////////9+///6tT//8+d///G
i///x4z//8eM///HjP//x4z//8eN///DgP//nRj//5MA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AM//lACv/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+VAv//tVv//8iO///HjP//x4z//8eM///H
jP//x4z//8aK///duf/////////////gv///xon//8eM///Upv//797///79//////////////Xr///b
tf//yY///8aL///HjP//x4z//8eM///HjP//x4z//8iO//+8bv//lwn//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAK//lACI/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+TAP//qTr//8eM///H
jP//x4z//8eM///HjP//x4z//8aK///duf/////////////fvv//ypP//+LE///69P/////////////8
+f//6ND//8+c///Gi///x4z//8eM///HjP//x4z//8eM///HjP//x4z//8iO//+xUP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAIj/lABU/5QA9v+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//mxX//8F7///Hjf//x4z//8eM///HjP//x4z//8aK///duf/////////////s2P//8OD///7+////
//////////To///Zsf//yI7//8aL///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8WG//+i
Jv//kwD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA9v+UAFT/lAAj/5QA2v+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAH//7BO///Ijf//x4z//8eM///HjP//x4z//8aK///duf//////////////
/v/////////////7+P//5sz//82Z///Gi///x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///H
jP//yI7//7hj//+WB///lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA2v+UACP/lAAF/5QApf+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5wX///Aef//yI3//8eM///HjP//x4z//8aK///d
uf////////////////////7///Ll///Xrf//yI3//8eL///HjP//x4z//8eM///HjP//x4z//8eM///H
jP//x4z//8eM///Hjf//xIT//6Io//+TAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QApP+U
AAX/lAAA/5QAWf+UAPv/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+pOv//xon//8eN///H
jP//x4z//8aK///Zsv///v3////////79v//5Mj//8yX///Giv//x4z//8eM///HjP//x4z//8eM///H
jP//x4z//8eM///HjP//x4z//8eM///HjP//sE7//5UD//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD7/5QAWf+UAAD/lAAA/5QAGv+UANH/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+W
Bf//sU///8eM///HjP//x4z//8eL///LlP//5cv//+zX///Vqv//x4z//8eL///HjP//x4z//8eM///H
jP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8iO//+3Yf//mAz//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lADR/5QAGv+UAAD/lAAA/5QAAP+UAHb/lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//mAv//7Va///Hi///x43//8eM///HjP//x4z//8iN///Gi///x4z//8eM///H
jP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x43//7pp//+bFf//kwD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP7/lAB2/5QAAP+UAAAAAAAA/5QAAP+UAB//lADR/5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5cJ//+wTP//xYb//8iO///HjP//x4z//8eM///H
jP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eN///Giv//tVv//5oR//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UANH/lAAf/5QAAAAAAAAAAAAA/5QAAP+U
AAD/lABg/5QA+P+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+VBP//pTD//71x///H
jP//yI7//8eM///HjP//x4z//8eM///HjP//x4z//8eM///HjP//x4z//8eM///Ijf//yI3//8B5//+q
Pf//lwj//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA+P+UAGL/lAAA/5QAAAAA
AAAAAAAAAAAAAP+UAAD/lAAL/5QApv+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//kwD//5oQ//+sQf//vG7//8WH///Ijv//yI7//8iO///Ijv//yI7//8iO///Ijv//yI7//8aJ//+/
df//r0v//5wX//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QAqP+U
AAz/lAAAAAAAAAAAAAAAAAAAAAAAAP+UAAD/lAAA/5QAJv+UAND/lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//mAv//6In//+uR///tl7//7xv//++c///vnP//71x//+4
Yv//sEz//6Qt//+ZD///lAH//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lADR/5QAJ/+UAAD/lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/lAAA/5QAAP+UAEX/lADj/5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5MA//+TAP//lQL//5cJ//+Y
C///mAv//5gK//+VA///lAD//5MA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAOP/lABG/5QAAP+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+U
AAD/lABV/5QA6f+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA6v+UAFf/lAAA/5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP+UAAD/lAAB/5QAVf+UAOP/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lADk/5QAV/+UAAL/lAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/lAAA/5QAAP+UAEX/lADR/5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UANL/lABH/5QAAf+UAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAm/5QApv+U
APj/lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD4/5QAqP+UACf/lAAA/5QAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+U
AAD/lAAA/5QAC/+UAGD/lADS/5QA/f+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA/f+UANP/lABh/5QADP+U
AAD/lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA/5QAAP+UAAD/lAAe/5QAdv+UANL/lAD7/5QA//+UAP//lAD//5QA//+U
AP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAPv/lADS/5QAdv+U
AB7/lAAA/5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/lAAA/5QAAP+UABr/lABZ/5QApf+U
ANv/lAD2/5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAP//lAD//5QA//+UAPb/lADb/5QApv+U
AFn/lAAa/5QAAP+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+U
AAD/lAAA/5QABf+UACL/lABU/5QAif+UAK//lADP/5QA3/+UAOn/lADp/5QA3/+UAND/lACw/5QAif+U
AFT/lAAj/5QABv+UAAD/lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD//wAA//8AAP/8AAA//wAA//AAAA//AAD/wAAAA/8AAP+AAAAB/wAA/wAAAAB/
AAD8AAAAAD8AAPwAAAAAPwAA+AAAAAAfAADwAAAAAA8AAOAAAAAABwAA4AAAAAAHAADAAAAAAAMAAMAA
AAAAAwAAgAAAAAABAACAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAABAACAAAAAAAEAAMAAAAAAAwAAwAAAAAAD
AADgAAAAAAcAAOAAAAAABwAA8AAAAAAPAAD4AAAAAB8AAPwAAAAAPwAA/AAAAAA/AAD/AAAAAH8AAP+A
AAAB/wAA/8AAAAP/AAD/8AAAD/8AAP/8AAA//wAA//8AAP//AACJUE5HDQoaCgAAAA1JSERSAAABAAAA
AQAIBgAAAFxyqGYAAB+mSURBVHja7Z1/bFXXle8/xxiuCb9sfAEDJoFAiJMQfjSkDdNkcBuqhCozIlXa
SSbpJFNNNEW3fW+qTKWpqlFVVVU76osaveqqkaYzmSjJCzPJzPDaqCQT0pgJHZj8KMaBxEkNOMSAAwYb
uMC9xubMH/tcc2384/445+yz91kfyQIsfO86x3d9z9pr77WW47ougp04aWqABu+fjUA1UOt9VXvfA+gC
BoA+72vA+x5At5siq/tahGBwRADMw0lTDSwr+Lq+4O8ASWC6z2+bAXq8v3d4XweA9vy/3RQDuu+NUBoi
ABHGSdMINHGlky9DPcGjxpAYcFkc2t3UUDQhRAwRgAjhpFkLbADWA81AjW6bfCID7AR2AC1uit26DRIU
IgAacdI0oRz9Lu/PWt02hUQf0AK8Bmx3U7TrNiiuiACEiBfS55/wd3M5QRd3ulGC8AoqQujUbVBcEAEI
GO8p/yBwH2o9L0xMJ/Ai8JybolW3MTYjAhAATpoG4H7gYWC1bnsMpxV4DtgiyUT/EQHwCW/P/T7U034D
0czSm8wAapnwNLDVTZHRbZANiABUgLcf34x60m/C/713YXQywFaUGLTI+YPyEQEoAy/EfwwV5jdW+HJC
ZXQBW4DH3RTduo0xDRGAEnDSLEM5/iPYs0dvC1ngWeDv3BQduo0xBRGAIvAc/7vAQ8jaPuoMoITgcTfF
Pt3GRB0RgHFw0qwGvgfcgzi+iWwFvi9biWMjAjAKBY6/Sbctgi+8hIoIWnQbEjVEAApw0jSjHL9Zty1C
ILSgIoIW3YZEBREAho7o/gSV1Rfs5yXgm3LkOOYC4O3jfwP11K/VbY8QKlngh8CP43yOILYC4KS5Dfg5
clQ37rQDm+O6LIidADhpaoEfAV/XbYsQKZ4Fvh23w0SxEgAnzSOotX5Sty1CJOkDvgP8Ii7LglgIgFeS
+/fA7bptEYxgNypJ+LZuQ4LGagHwknx/g0ryyUEeoRQGgCeA79gcDVgrAF7BzjOo0lxBKJe3gS/bumVY
pduAIHDSbAD2IM4vVM5aYI+TtvNUqFURgBfy/wAV9guC3zyB2imwZklgjQB4p/meRxJ9QrBYtSSwYgng
pLkbFfKL8wtBk18SWHFs3OgIQEJ+QTNPAt8yeXaisQLgZflfQJ76gl5aUUsCI7sQGSkA3sGebcBi3bYI
AmqwyRdM7EBkXA7AK+J5HXF+ITo0ALuctHnRqFEC4O3vv4qM1BKix3TgVSfNfboNKQVjBMC7sduQ3vtC
dKkBnnfS5lSaGiEATpq/Qu3xy3l+IepUAz930mbsTEVeAJw0PwB+iji/YBY/ctL8VLcRExHZXQBvj/9n
SOMOwWxeBB6I6vHhSAqA5/zPg1kJFUEYg+3AvVEcaBo5ARDnFyxlO7AxapFAFHMAP0OcX7CPDagHW6SI
lAB4CT9Z8wu2cl/UEoORWQJ4W32RujmCEBDfdlP8H91GQEQEwDvkI/v8Qpz4qpviWd1GaBcA73jvNsT5
hXgxgNoZeEmnEVoFwCvseRU53ivEkwxqZ2CnLgO0CYBX0vs6UtgjxJtu4HNuinYdb65FALxmHruQkl5B
AOgE1ukYSxb6NqB30OcFxPkFIc9i4Feeb4SKjsTbD5A2XoGTTMDKOv9er60XenK6r8pq1qLmVn4rzDcN
dQngde/dFuYF2s5IR08moD4B86fCVT7K+/kBOHYBTuaGC4EIg+/c66bYGtabhSYAXt/+Pchk3rIpdPYp
VdAwFZbNgJpJ+mzKDkLHWei+AP2X1PdEFCqiD1gT1tyBUATAW9u8joT+JZN3+ilVsKoO5tTotmhiTmRh
b68SBBGDsngblRQMvHAorByArPtLIO/0s6fAzXUwzbAjUnNqYMN89ff18+DdXjjVL2JQAqHlAwKPAAoa
eQrjkHf6GZPhU7PNc/piODcAvzsFZy+KGBRJ4PmAQAXA2+/fgxz2GZO8499ab0Z47xcnsvDWSRGCCegj
4HxAYALgrfu3ISO6r6DwaX9rvd4knm5yg/DmSYkKxiHQfECQgebfIM4/jLzjL5sBy2fqtiYaJCbBHXPV
35fNUDsKIgTDCDQfEEgE4J3zfxep8AMuO/71M2HpDN3WRJ8DZ+GDMyIEI1jnptjt94sGJQBvIFl/QDn/
pkXQNEu3JebRfhq2fiwi4BHIUsB3AXDSPAI8Fd59iSbJBHw6CZ+do8JcoTxyg7DrBOzuESEANrspnvTz
BX0VACdNLfB7YnzaLx/u/8EcqJ2i2xp7OHMRdh6P/bKgD7jBz6pBv6sBf0TMnX/TIvjiQnF+v5k5Wd3X
TYvUfY4ptaiEoG/4FgF43X12hX9P9JN/6q+fZ+cBnqhxfgBaPol1NHCHX12EfBEAb8//LWC13vsSPpLk
00eMk4T7UAeEKk4I+rUE+AYxc/5kAj7fAI9eJ86vi6ZZ8JfL1e8hZsuCFcBf+/FCFUcAXpnvu6j1SSxI
JuAL8+GWet2WCHneOQmvHotVNJBBJQS7KnkRPyKAnxAz59+0SJw/atxSH7sE4XR8SAhWFAE4aZpRdf6x
IJmAB5fAomm6LRHG4sh5eOZgrCKBz7kpWsr94UojgO/pvvqwSCbg68vF+aPOwqtg8/JYRQIV+WDZEYCT
ZjWq1Ndq8lt8dzbIiT6TyA3Ca92x2Spc46ZoLecHK4kArH/6JxNqb/+LC8X5TSMxSf3e1s+LRTRQti+W
FQHE4emfd/51c3RbIlTKrhOw4xPrI4GyooByIwCrn/7i/Haxbk4sIoHvlvNDJUcATpplwPtYWusvzm8v
lkcCA6hzAR2l/FA5EcB3sdT5QSX8xPntZN0cf6clRYxqyogCShIA7+n/kO4rDYpkAjZobl+amAQzE+pr
qrUyq48vzLd6KfCQ56NFU+pH7LEyfsYIkgn4ZhNM0ZTtnzcN1jRAUz1M8mT5kgsHeqHtE+g8rfsO2cHk
KvhfTfB/261cClSjfHRzsT9QdA7Aa/F9CLCueXUyAY8sVaO2dLBxKdw4wbLj6Fl45QCcyuqx0TZOZOEf
OqwUgSywpNimIaUsAR7DUuf/ymJ9zv8nN07s/AALZsDDq2BdIzh6TLWKOTXwwGIrlwM1KF8tiqIiAK/e
/xDQqPvq/CSZgI0L1cw9HdyxCD69sPSfO34Oth+CYxk9dtvE3l7YdsS6SKALFQVM2C+g2AigGcucH1RG
WJfzT62GW+aX97Nzp8H9N8EfXg2TJByoiFV1Vu4MNKJ8dkKKFYCHdV+R3+T3+3XxmQWXk33lUOXArQvg
qyuhUYaMVMTn7Gwo8mAx/2nCj6CTpgbYpPtq/CSZgD9dord/X4NPA0Lqp6o8wucXqwy3UDpTJ6kyb8tE
4D7Pd8elmI/MfajmA1aQTMBdC+BqzWW9dT5/2NY0wJ+vgmtr9V6XqSyapvJBFonAdJTvjksxAlBUKGEK
K+tgzWzdVsBVAbQNn5GAe5vgrmulerEcLMwHTOi74wqAt/dvzYDPZEJf0i9MVsyFr62G6yIgdKaxZrZV
UcAGz4fHZKII4H4sOvm3tl51jIkDV02GP14Of3QdTJchJUUzf6r6nFhCNcqHx2QiAbAm+59MwGfn6rYi
fJbXw8Mr4SYpcCqa2+daFQWMuwwYUwC8Ed+rdVvvB8kE/HGjyvbGkZpquHspfKlJFRkJ41MzCb50tTUi
sNbz5VEZLwKwJvm3sg5urNVthX6W1KpoYLXG8w+msHymVQnBMX15PAGYcAvBBJIJuLlWtxXRYcokuHMJ
PHATzLaussNfPmVPQnBMXx5VALxpP2OGDSaxsk5aeY/GghnqFOHa+VJcNBYNU62JApo8n76CsSIAK7b+
kgm41Z6Mru9UV8H6a+Chm2FOTHZHSmXdHGuigFF9eiwBWK/bWj9YWafKPoXxmTtNicBnG6W4aCR1U6yJ
Akb16bEE4G7d1laKPP1Lo8qB2xrVsmC+NQe//cGSKKC4CMDbMtDcGa9y5OlfHvVTValx8zVSXJTHkiig
cbTtwNF+xc26La0U2zP/2QnbPFRGlaN6FTwspcZDWHJEuHnkN0YTgLt0W1kptmf+n25TzUKDZlaNKjWW
4iJ1RNiCKOAK37YyApgxWbcFwZLph60fwK874PzF4N9vxVwVDcS91Hi2+TUVzSO/MUwAnDRrgVrdVlZC
nJJ/7/fAP+2FD08G/175UuM/ui6+8wosOBhU6/n4ECMjAOP3/1fWqbPcceHCAPzq9/Dv7XA2hMaWy+vh
kVVwU1L3lYfPlElWLAOG+fhIATB6/z8u9f6jcbBP5Qb2HQ/+va6aDHcvg03Xx6/U2IJk4DAfHykAzbqt
q4SVdfGp9x+N3CC8chD++T04HcIAkaV1KjewMkZl1hYkA28v/MeQAHhnhY3eOZ8i+9YAdJ1R0cA7x9R4
sSCpqYYvXAtfuSE+pcaGf86mF9YFFF6K0cU/yQSsqNVtRXS4eAlaPoIt++HkheDfb9Es1ZQ0DsVFq81f
Bgz5eqEAlDRVNGqsrNM33ivKHMvAM22wuyv4aCBfXHS/5aXGyYTxy4AhXy8UgOt1WyUEw6ALv+2CZ99V
Y8WCRuYYRp4hX7cmAohz8q9YTpxXIrDjIxi4FOx7VTnwB42qytDG4iLDP2+jRgDGCkAyoVo4CRPjAm8f
U8uCo2eDf7/COYZm586G0zTT6DzAcAHwpv8aKwAr6+Lb8LNcTmXh+f3w2iHoHwz2vfJzDP9slT3FRQmz
DwUt83x+SJSXYVH/f6F4Wj9RW4aH+oJ/r8I5htWSHNDJ0AO/UACMpcHijHMYnMnBv7XDyweCLzUGNcfw
a6thySzdV14ZNuQBjBeAZAKW+jRpN+7sP6GigbCKi750gyo1NrXxyNIZ5ucB8rfe2C3AlXUwK2bn0YMk
06+Ki375YXilxn+xxsxS4+nVRucBrgcLIgAhGH5/Cv6xNbzionyp8RRJ5oaFHUsAITjyxUVhlho/ugau
NfepahLDBMBYZlre/ScKHOyDp/bCnu7g36umGu69Xk02rjbg02n65y9/i41t72BwEsYoLl6C33SqUuMw
iouumw2bb4FFET83YPDnLwmXBcDYw5q1kgAMla4z6hThW0eDLy6aMgm+ciNsXBrdgSWLphkrAtMBqpy0
uT0AkgmoN/PmG82gC/95WJUah1FcdOMc+Pot0RxfNs3gnQAnTU0VBg8BWVln7h6yDRzLqOKi/wqh1Lim
Gv5sJWxYoo4WC77QIO4jVIQL7OqCp/eGU1y0ah58bVW8Gr8GSRWMPjZYEErhVFYtCcIoNZ5VA5vXqp6E
QkU0ViFFQIJP5EuNn9oLH58O9r2qHNWV+EtNZmwXRpTqKgwfBCJEjzM5+Jf34dWDwRcXLalVS4Lphu/H
a6JWBEAIjLbj4cwxnJGAP18NSekJWSq1sgQQAiWsOYZTJqk+hI3GnmjRQrUkAYVQCGuO4eeX6L5So2iU
9IkQGmHMMZwzLX7jyiqhCgihxEMQLpOfYxhUNBD1+oEI0VUFhDBFThCGM3+6eloHQdDnECxioBrI6LZC
iA8zpsDnFqtqv6C4EEJfQ0voqwZ6dFsh2E8V8JlG+PSCYA/uDFyCUyGUK1tCn2wBCoGzrA6ar1FHeINm
/4lwehlawkA10KnbCsFOahNqW25JbTjv1z8Ibx/VfdVG0SURgOA71Q7c1qimAYVVuts/qGYb9IXQu9Am
JAcg+Mr19bD+anU8NyzO5uBf28NpVWYZ3dVuioyT1m2HYDr1U+HOxbAoxGk/l1zVmuy/j6iehUJpuCmy
+SVAFsxrDdbWC+vnqbZMgh4Sk2Bdoxr3FWannkN98JtD+kP+cwPqc2ggGbhcCNQNLNZtUan05ODjc9Bk
+Iw5U7lpDtyxCKaFePT2dBZaPoKOiDjdkfPqc2ggPWBBJaChN99o5k1TE34XhDiTceASvHkU3jyimpJG
hVOGf/7yAtCJgREAwBnZ8w2NxCS442q4eW644f7vT6mn/pkIOtupft0WlE0HXBaAdqBZt0VCNHFQTn/H
1ao7b1j0ZtU6vzPg9mIxZZgAHNBtjRBN5k9X4X5DiI02+gdVZv+to6rPoBAIB2B4BGAkbb3wh/PUqGbB
P66arBJ8K+aG+77tPbDjsOokFHUy5u4AgOfzebfp0G1NufTk4MBZWCUton3BQW3prWsMN9zvOQ+vdarR
Y6Zw8KzRSehhSwBjBQDUVowIQOUsnKEm7yRDHMGVHVCDRfZ0mxfud53XbUFFXBYAN8WAk6YdaNJtlRA+
06eo47tNIc+I3ncc3vhYqvc00O6mGIDh5wA6MFQA2nrhzga1TSUUj4Mq2PnMQtVVNyy6M2rU+DGDW9Fk
B41e/w9F/NWjfdM0enLQfkaWAaWweBY0L1Zn+MMiOwBvHIZ3j5sX7o+kw4L1PwwXAKO3AiUPUBwzE6o5
R5AtuUZyyVVO/8ZhyA3qvgP+cDiEsegBMuTrhQJg7FagMDGTHFi7AG5bGO4svaNnVbj/idkOYxtDvm6N
ALT1wqeTkAyxDt0UwmzJledcP/z2Y3j3hO6r95+enNHrfxhNANwUXU7azLJgUL+U1lOwYb5uS6JD2C25
QIX7e7rV1p4t4f5I2nqNXv9n3BRd+X+MPOrRAtyt28Jy6ZemEABMrlKZ/Vvmhxvud52B7Yfs78yTNVvY
dhb+Y6QA7MBgAWjrhU/NhoYYT4nV1ZJrx2H4IOC5f1Gg+4Lx4f+Own+MFgEYS08OfncKvrhQtyXhUz9V
Ddy4RlpyBcrvThkd/sMIHx8mAG6K3U6aPqBWt5XlErdDQTpbcrV0wikZLGcSfW6K3YXfGK3cowXYpNvS
cunJwVsn4faQq9h0cENShftxbskVJh+eMT78bxn5jdEE4DUMFgCwv0vQvGlqW68xxCm4A5fU0I3dEWvJ
FSaGn/4D5dvDGE0Atuu2slLaetWpwIUhVrWFyZ+ukJZcYWPB3j+M4ttXbBK5KdpRXYKNpScHe83/ZY1J
WM7fm4V/fR9++WG8nR/gzR7jn/7dnm8PY6xd4hbd1lZKWy+cNPsXpo3+QXVu/+m90o8PoK/fiqd/y2jf
HEsAXtFtbaX05OC/ZehZybT3wFN7VQvuuK71R/JfJ4x/+sMYPj1W06cW3db6QVsvfCYJ9VIfMCEnL6gO
vIcNaskVBqftePpDKRGAm6ITC8aGSxQwMdkBeL1Thfvi/FfyWzue/p2eT1/BeCfFX9RttR+09cIxy8+m
l8t7J1S4/zsD+/GFwYmsNU//MX15PAF4TrfVftCTgz2ndFsRLbozsGU/bDsg/fjG462TVjz9YRxfHlMA
3BStQKtuy/2grVed4oo72QF49SD8v31w5Kxua6LNgbPWPP1bPV8elYmKRa2JAv7tsPFlnGVzyYW24/CP
repPCffH5+IleOEj+5/+MLEAbAHVPth0enKw87huK8Ln6Fn1xH/1IFyw4jcZPG8ct8b5B1A+PCbjCoDX
OaRF91X4xdsn45MQPH8R/uMAPL9f+vGVQk9OnfqzhJbC7j+jUUy/mKd1X4VfxCEheMmFd46pcN/GfnxB
Y8GR30Im9N1ipr9tBTJAiPNhg6OtVxUJ6W4hfv6iGsDpJ3FpyRUUe3utSfyB8tmtE/2nCSMAN1XcC5lC
Tw62HVFzBHTi51Scszn4dQf883vi/OXSfUF9Lix6+m/1fHdcim0Zac0yANQv+ZmDervW9vggQJdc1Y7r
qb3wvj3r1tDJDcI/HbDK+aFIny1WAFpg/GSCafTk4DWNRc+tn6h9+XL56LQ6vrvz4/j04wuK17qtc/6i
k/dFCYA3SXRLMf/XJNp6YZ+mNV+mXzXZKJXTWfj/H8CL70s/Pj94r8+qdX+eLfnpvxNRTBIwz+PANzB0
cMho9OTgpSMwYzJcoyHFuf8E1NWoCb0TNfk4k4PdXfBej5Tp+sXhc/DLLuue/lmUrxaF47rFf5qcNH8P
/IXuK/SbZAIevU5f2fD86bBxmRKDkZzOqTLdg31ab5F19PXDkx9a5/wAv3BTPFrsfy5VAJYB71Na5GAE
yQT87xtgqsZ24tOnQMM0mD0VTl2A7nNqqSD4S3YQnnjfSucfAG5wU5fHf09ESYOjvBd+VvdVBkFPDl7X
3Akx06/abb95VP0pzh8Mv7Ev6Zfn2VKcH0oUAI+i1xem0dYLu+T0nNXsOmFl0i9Pyb5ZsgC4KfZh0cGg
QnpysOMTEQFb2XVC/X4tffpv9XyzJMqdHft93VcbFHkReCcGgy7jxDsnrXZ+KNMnyxIAr8HAS7qvOCh6
cvDqMREBW3jnpPp9Wuz8L43X9GM8Kpkeb20uAC6LgCwHzGbXCeudHyrwxbIFwE3RgkW9AkZDcgJmY/ma
P0+L54tlUUkEABbnAvLkReDXRyAjHXWM4NyA+n3FwPmhQh8s6SDQqC+Q5lfAPbrvQhgkE/DIUmiYqtsS
YSxOZOEfOmLh+AAvuyk2VvIClUYAAN9EnT+2np6cKhs9IB11I8nBTKycPwtsrvRFKhYAb+LID3XfjbDo
yamOsba3FjONvb3wL52xcX6AH4417acUKl4CADhpqoF3gSbddyUskglYWQefnQOzpui2Jr6cuai6Pbf1
xsr5O4Cb3VTlkbcvAgDgpGkGXtd7X8InmYAvXwNLZ+i2JH4czMTuqZ9no5viZT9eyDcBAHDSPAM8pOuu
6CIfDdzZAAmN1YRxoX8QtnfH7qmfZ4ub4gG/XsxvAWhAlQvXhn9f9CPRQPDE+KkPqtPvDRP1+i8FP3YB
hnBTdAPfCfuuRIV8gvDXR+C8nBnwlfPe3n6MnR9U4s/X3py+RgAwlBB8A7gtxBsTOZIJuGchrNA8f8AG
9vWq1m0xdnyAfcCaYnv9FYvvAgDgpFkL7MLCzkGlkM8NLJsBy2fqtsY8DpyFD87Edq0/kjvcFDv9ftFA
BADASfNT4K8CvilGkBeCm2bpaT5qGofPwb4+cfwCnnRTlR/6GY0gBaAaFQWsDfDGGEVeCJpmwbUiBFfw
UQb2nxbHH0ErsM6PPf/RCEwAAJw0i4E9xHRXYCzyQnDtdCUGcaf9tMrui+NfQQa17i+pz18pBCoAAE6a
TcC/B/omhpIXgilVcGt9vE4Unu6Ht05C/yVx/HF4wE0FO5AncAEAyQcUQ14Mkgk1udjGA0W5QXVmvycn
Tl8Ega37CwlLACQfUAJ5MZhbAzfV6p1VUCkXBmF/HxzPitOXQCsBrvsLCUUAQPIB5ZIXg5pJKjLQNb2o
FHqy0NanBnCI05dM4Ov+QkITAAAnzf3A86G9oWXkxQCUICyYCkum610uZAfhUAaOXVB/B3H6Cgl83V9I
qAIA4KT5OfD1UN/UYgpFAaChBuoSqmuRn0uHC4PQfQF6c9BdEJiKs/tKKOv+QnQIQA0qH7A61DeOGSOF
oVLE0QOnlZDW/YWELgAwNGT0DaAh9DcXhOjRjTrqG8q6vxAtAgDgpFmBigTkTJwQZzIo52/V8ea+lgOX
gjfHbCMxaSgqCKMwgOru06rLAG0CAOBVN33VuxGCECcGUBl/3yv8SkGrAAC4KV5EtRYXhDjxbe+zrxXt
AgDgpniSGHcSEmLHj90UT+g2AjQmAUc1RmoGBPv5hZviUd1G5IlEBJDHTfEt0B8WCUJAbMWHaT5+EikB
8HgA2K7bCEHwmZ3AV/3u6VcpkRMA7wbdi4iAYA87Udt9Gd2GjCRyAgDg3aiNyHJAMJ+tRNT5IWJJwFEN
lMSgYC6/ADZHLewvJPICAOCk+WvgJ7rtEIQS+LGbiv7WthECAOCkeQh4ipjPGhAizwDqkM8Tug0pBmME
AMBJcw+qoYgUEAlRJH+815jclVECAOCkuR14ASklFqJFBpXs03q2v1SMEwAAJ00TsA1YrNsWQUDV82ut
6iuXSG4DToSboh1YB7yt2xYh9rSisZ6/UowUABgaRb4OzEi2CFbyJKqNV+idfPzCyCXAFRehpg89hbQc
F8IhAzwaZvfeoLBCAGBo7sALyPARIVhagS+b/NQvxNglwEjcFJ3IkkAIFuND/pFYEwEMuyhZEgj+Yk3I
PxIrBQBkSSD4RisWhfwjsWYJMBJZEgg+YF3IPxJrI4BhF5nmNuBnSDQgFMc+VBWfUaf6ysHaCKAQN8Vu
VDSwGejTbY8QWTKo5rRr4uD8EJMIYNgFp2lAlRY/pNsWIVJsQVXxdek2JExiJwBDF66Kin4OrNBti6CV
DuCbboqXdRuig1gsAUbDC/HWoEK+SLZrEgIlC/wtcHNcnR9iHAEMuwlpGlHLgvt12yKEwsuoJF+nbkN0
IwJQgJOmGfge0KzbFiEQWoDvuyladBsSFUQARsFJsxolBJt02yL4wkvA4+L4VyICMA4FQnAP0ovQRLai
nvitug2JKiIAReCkWQZ8F7V1KEIQbQaAZ1FP/H26jYk6IgAl4AnBY8AjQI1ue4RhZFGO/3c2H931GxGA
MvAOEz2G2jVo1G1PzOlCHeJ53OsSJZSACEAFOGmqUTsGD6MShtKuPBwyqPX900BLlCfvRB0RAJ9w0tQA
9wEPAhuQXIHfDKC28Z4GtkZ11p5piAAEgLdEuB8VGazWbY/htALPAVvidk4/DEQAAsabYfAgKjpo0m2P
IXSiJkM/J1t4wSICECLekeMNwHrgbmS6UZ5uVHj/CmpN36nboLggAqARLzpoBu7y/qzVbVNI9KEc/jVg
uzfoRdCACECEcNKs5XKE0Iw9Zw0ywE5gB+oJv1u3QYJCBCDCeEuGJmAZcL33Z/4rirsM7aj6+g7ggPfv
dkneRRcRAAPxzh8UikGhOAAk8f9MQgbo8f4+0sk7gA7ZjzcPEQCL8c4m5BONjaioodb7qubyKcYu1D57
n/c14H0PoNtNkdV9LUIw/A9S7C5c0k6ZrgAAAABJRU5ErkJggg==
</value>
</data>
</root>

396
src/WinForms/Menu.cs Normal file
View File

@@ -0,0 +1,396 @@

using System;
using System.Linq;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Windows.Forms;
using System.Drawing;
public class ContextMenuStripEx : ContextMenuStrip
{
public ContextMenuStripEx()
{
}
public ContextMenuStripEx(IContainer container) : base(container)
{
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
Renderer = new ToolStripRendererEx();
}
public MenuItem Add(string path)
{
return Add(path, null);
}
public MenuItem Add(string path, Action action, bool enabled = true)
{
MenuItem ret = MenuItem.Add(Items, path, action);
if (ret == null) return null;
ret.Enabled = enabled;
return ret;
}
}
public class MenuItem : ToolStripMenuItem
{
public Action Action { get; set; }
public MenuItem()
{
}
public MenuItem(string text) : base(text)
{
}
public MenuItem(string text, Action action) : base(text)
{
Action = action;
}
protected override void OnClick(EventArgs e)
{
Application.DoEvents();
Action?.Invoke();
base.OnClick(e);
}
public static MenuItem Add(ToolStripItemCollection items, string path, Action action)
{
string[] a = path.Split(new[] { " > ", " | " }, StringSplitOptions.RemoveEmptyEntries);
var itemsCollection = items;
for (int x = 0; x < a.Length; x++)
{
bool found = false;
foreach (var i in itemsCollection.OfType<ToolStripMenuItem>())
{
if (x < a.Length - 1)
{
if (i.Text == a[x] + " ")
{
found = true;
itemsCollection = i.DropDownItems;
}
}
}
if (!found)
{
if (x == a.Length - 1)
{
if (a[x] == "-")
itemsCollection.Add(new ToolStripSeparator());
else
{
MenuItem item = new MenuItem(a[x] + " ", action);
itemsCollection.Add(item);
itemsCollection = item.DropDownItems;
return item;
}
}
else
{
MenuItem item = new MenuItem();
item.Text = a[x] + " ";
itemsCollection.Add(item);
itemsCollection = item.DropDownItems;
}
}
}
return null;
}
public override Size GetPreferredSize(Size constrainingSize)
{
Size size = base.GetPreferredSize(constrainingSize);
size.Height = Convert.ToInt32(Font.Height * 1.4);
return size;
}
public void CloseAll(object item)
{
if (item is ToolStripItem)
CloseAll(((ToolStripItem)item).Owner);
if (item is ToolStripDropDown)
{
var d = (ToolStripDropDown)item;
d.Close();
CloseAll(d.OwnerItem);
}
}
}
public class ToolStripRendererEx : ToolStripSystemRenderer
{
public static Color ForegroundColor { get; set; }
public static Color BackgroundColor { get; set; }
public static Color SelectionColor { get; set; }
public static Color CheckedColor { get; set; }
public static Color BorderColor { get; set; }
int TextOffset;
public static void SetDefaultColors()
{
ForegroundColor = Color.FromArgb(unchecked((int)0xFF000000));
BackgroundColor = Color.FromArgb(unchecked((int)0xFFDFDFDF));
SelectionColor = Color.FromArgb(unchecked((int)0xFFBFBFBF));
CheckedColor = Color.FromArgb(unchecked((int)0xFFAAAAAA));
BorderColor = Color.FromArgb(unchecked((int)0xFF6A6A6A));
}
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
{
Rectangle r = e.AffectedBounds;
r.Inflate(-1, -1);
ControlPaint.DrawBorder(e.Graphics, r, BackgroundColor, ButtonBorderStyle.Solid);
ControlPaint.DrawBorder(e.Graphics, e.AffectedBounds, BorderColor, ButtonBorderStyle.Solid);
}
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
if (e.Item is ToolStripMenuItem && !(e.Item.Owner is MenuStrip))
{
Rectangle rect = e.TextRectangle;
var dropDown = e.ToolStrip as ToolStripDropDownMenu;
if (dropDown == null || dropDown.ShowImageMargin || dropDown.ShowCheckMargin)
TextOffset = Convert.ToInt32(e.Item.Height * 1.1);
else
TextOffset = Convert.ToInt32(e.Item.Height * 0.2);
e.TextColor = ForegroundColor;
e.TextRectangle = new Rectangle(TextOffset, Convert.ToInt32((e.Item.Height - rect.Height) / 2.0), rect.Width, rect.Height);
}
base.OnRenderItemText(e);
}
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
{
Rectangle rect = new Rectangle(Point.Empty, e.Item.Size);
if (!(e.Item.Owner is MenuStrip))
e.Graphics.Clear(BackgroundColor);
if (e.Item.Selected && e.Item.Enabled)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
rect = new Rectangle(rect.X + 2, rect.Y, rect.Width - 4, rect.Height - 1);
rect.Inflate(-1, -1);
using (SolidBrush b = new SolidBrush(SelectionColor))
e.Graphics.FillRectangle(b, rect);
}
}
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
{
if (e.Direction == ArrowDirection.Down) throw new NotImplementedException();
float x1 = e.Item.Width - e.Item.Height * 0.6f;
float y1 = e.Item.Height * 0.25f;
float x2 = x1 + e.Item.Height * 0.25f;
float y2 = e.Item.Height / 2f;
float x3 = x1;
float y3 = e.Item.Height * 0.75f;
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
using (Brush b = new SolidBrush(ForegroundColor))
{
using (Pen p = new Pen(b, Control.DefaultFont.Height / 20f))
{
e.Graphics.DrawLine(p, x1, y1, x2, y2);
e.Graphics.DrawLine(p, x2, y2, x3, y3);
}
}
}
protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
{
if (e.Item.GetType() != typeof(MenuItem))
return;
MenuItem item = e.Item as MenuItem;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
if (!item.Checked)
return;
Rectangle rect = new Rectangle(Point.Empty, e.Item.Size);
rect = new Rectangle(rect.X + 2, rect.Y, rect.Height - 1, rect.Height - 1);
rect.Inflate(-1, -1);
using (Brush brush = new SolidBrush(CheckedColor))
e.Graphics.FillRectangle(brush, rect);
float ellipseWidth = rect.Height / 3f;
RectangleF rectF = new RectangleF(rect.X + rect.Height / 2f - ellipseWidth / 2f,
rect.Y + rect.Height / 2f - ellipseWidth / 2f,
ellipseWidth, ellipseWidth);
using (Brush brush = new SolidBrush(ForegroundColor))
e.Graphics.FillEllipse(brush, rectF);
}
protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e)
{
e.Graphics.Clear(BackgroundColor);
int top = e.Item.Height / 2;
top -= 1;
int offset = Convert.ToInt32(e.Item.Font.Height * 0.7);
using (Pen p = new Pen(BorderColor))
e.Graphics.DrawLine(p,
new Point(offset, top),
new Point(e.Item.Width - offset, top));
}
}
public struct HSLColor
{
public HSLColor(Color color) : this()
{
SetRGB(color.R, color.G, color.B);
}
public HSLColor(int h, int s, int l) : this()
{
Hue = h;
Saturation = s;
Luminosity = l;
}
double _Hue;
public int Hue {
get => System.Convert.ToInt32(_Hue * 240);
set => _Hue = CheckRange(value / 240.0);
}
double _Saturation;
public int Saturation {
get => System.Convert.ToInt32(_Saturation * 240);
set => _Saturation = CheckRange(value / 240.0);
}
double _Luminosity;
public int Luminosity {
get => System.Convert.ToInt32(_Luminosity * 240);
set => _Luminosity = CheckRange(value / 240.0);
}
double CheckRange(double value)
{
if (value < 0)
value = 0;
else if (value > 1)
value = 1;
return value;
}
public Color ToColorAddLuminosity(int luminosity)
{
Luminosity += luminosity;
return ToColor();
}
public Color ToColorSetLuminosity(int luminosity)
{
Luminosity = luminosity;
return ToColor();
}
public Color ToColor()
{
double r = 0, g = 0, b = 0;
if (_Luminosity != 0)
{
if (_Saturation == 0)
{
b = _Luminosity;
g = _Luminosity;
r = _Luminosity;
}
else
{
double temp2 = GetTemp2(this);
double temp1 = 2.0 * _Luminosity - temp2;
r = GetColorComponent(temp1, temp2, _Hue + 1.0 / 3.0);
g = GetColorComponent(temp1, temp2, _Hue);
b = GetColorComponent(temp1, temp2, _Hue - 1.0 / 3.0);
}
}
return Color.FromArgb(
System.Convert.ToInt32(255 * r),
System.Convert.ToInt32(255 * g),
System.Convert.ToInt32(255 * b));
}
static double GetColorComponent(double temp1, double temp2, double temp3)
{
temp3 = MoveIntoRange(temp3);
if (temp3 < 1 / 6.0)
return temp1 + (temp2 - temp1) * 6.0 * temp3;
else if (temp3 < 0.5)
return temp2;
else if (temp3 < 2 / 3.0)
return temp1 + ((temp2 - temp1) * (2 / 3.0 - temp3) * 6);
else
return temp1;
}
static double MoveIntoRange(double temp3)
{
if (temp3 < 0)
temp3++;
else if (temp3 > 1)
temp3--;
return temp3;
}
static double GetTemp2(HSLColor hslColor)
{
double temp2;
if (hslColor._Luminosity < 0.5)
temp2 = hslColor._Luminosity * (1.0 + hslColor._Saturation);
else
temp2 = hslColor._Luminosity + hslColor._Saturation - (hslColor._Luminosity * hslColor._Saturation);
return temp2;
}
public static HSLColor Convert(Color c)
{
HSLColor r = new HSLColor();
r._Hue = c.GetHue() / 360.0;
r._Luminosity = c.GetBrightness();
r._Saturation = c.GetSaturation();
return r;
}
public void SetRGB(int red, int green, int blue)
{
HSLColor hc = HSLColor.Convert(Color.FromArgb(red, green, blue));
_Hue = hc._Hue;
_Saturation = hc._Saturation;
_Luminosity = hc._Luminosity;
}
}

46
src/app.manifest Normal file
View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly
manifestVersion="1.0"
xmlns="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RatingExtension")]
[assembly: AssemblyDescription("RatingExtension")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Frank Skare (stax76)")]
[assembly: AssemblyProduct("RatingExtension")]
[assembly: AssemblyCopyright("Copyright (C) 2017-2020 Frank Skare (stax76)")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("55c88710-539d-4402-84c8-31694841c731")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,119 @@
// This extension writes a rating to the filename of rated videos when mpv.net shuts down.
// The input.conf defaults contain key bindings for this extension to set ratings.
using System;
using System.ComponentModel.Composition;
using System.Collections.Generic;
using System.IO;
using Microsoft.VisualBasic.FileIO;
using mpvnet;
using static mpvnet.Core;
using System.Threading;
namespace RatingExtension // the assembly name must end with 'Extension'
{
[Export(typeof(IExtension))]
public class RatingExtension : IExtension
{
// dictionory to store the filename and the rating
Dictionary<string, int> Dic = new Dictionary<string, int>();
string FileToDelete;
DateTime DeleteTime;
public RatingExtension() // plugin initialization
{
core.ClientMessage += ClientMessage; //handles keys defined in input.conf
core.Shutdown += Shutdown; // handles MPV_EVENT_SHUTDOWN
}
// handles MPV_EVENT_SHUTDOWN
void Shutdown()
{
foreach (var i in Dic)
{
string filepath = i.Key;
int rating = i.Value;
if (String.IsNullOrEmpty(filepath) || !File.Exists(filepath))
return;
string basename = Path.GetFileNameWithoutExtension(filepath);
for (int x = 0; x < 6; x++)
if (basename.Contains(" (" + x + "stars)"))
basename = basename.Replace(" (" + x + "stars)", "");
basename += $" ({rating}stars)";
string newPath = Path.Combine(Path.GetDirectoryName(filepath),
basename + Path.GetExtension(filepath));
if (filepath.ToLower() != newPath.ToLower())
File.Move(filepath, newPath);
File.SetLastWriteTime(newPath, DateTime.Now);
}
}
//handles keys defined in input.conf
void ClientMessage(string[] args)
{
if (args[0] != "rate-file")
return;
if (int.TryParse(args[1], out int rating))
{
string path = core.get_property_string("path");
if (!File.Exists(path))
return;
if (rating == 0 || rating == 1)
Delete(rating);
else
{
Dic[path] = rating;
core.commandv("show-text", $"Rating: {rating}");
}
}
else if (args[1] == "about")
Msg.Show("Rating Extension", "This extension writes a rating to the filename of rated videos when mpv.net shuts down.\n\nThe input.conf defaults contain key bindings for this extension to set ratings.");
}
void Delete(int rating)
{
if (rating == 0)
{
FileToDelete = core.get_property_string("path");
DeleteTime = DateTime.Now;
core.commandv("show-text", "Press 1 to delete file", "5000");
}
else
{
TimeSpan ts = DateTime.Now - DeleteTime;
string path = core.get_property_string("path");
if (FileToDelete == path && ts.TotalSeconds < 5 && File.Exists(FileToDelete))
{
core.command("playlist-remove current");
int pos = core.get_property_int("playlist-pos");
if (pos == -1)
{
int count = core.get_property_int("playlist-count");
if (count > 0)
core.set_property_int("playlist-pos", count - 1);
}
Thread.Sleep(2000);
FileSystem.DeleteFile(FileToDelete, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{55C88710-539D-4402-84C8-31694841C731}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RatingExtension</RootNamespace>
<AssemblyName>RatingExtension</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>C:\Users\frank\OneDrive\Settings\mpv.net\extensions\RatingExtension\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<OutputPath>C:\Users\frank\OneDrive\Settings\mpv.net\extensions\RatingExtension\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="RatingExtension.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\mpv.net.csproj">
<Project>{1751f378-8edf-4b62-be6d-304c7c287089}</Project>
<Name>mpv.net</Name>
<Private>False</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

237
src/mpv.net.csproj Normal file
View File

@@ -0,0 +1,237 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1751F378-8EDF-4B62-BE6D-304C7C287089}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>mpvnet</RootNamespace>
<AssemblyName>mpvnet</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup />
<PropertyGroup />
<PropertyGroup>
<StartupObject>mpvnet.Program</StartupObject>
</PropertyGroup>
<PropertyGroup />
<PropertyGroup />
<PropertyGroup />
<PropertyGroup />
<PropertyGroup />
<PropertyGroup />
<PropertyGroup />
<PropertyGroup />
<PropertyGroup>
<ApplicationIcon>mpvnet.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.PowerShell.5.ReferenceAssemblies.1.1.0\lib\net4\System.Management.Automation.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Misc\App.cs" />
<Compile Include="Misc\CSharpScriptHost.cs" />
<Compile Include="Misc\Extension.cs" />
<None Include="Resources\mpvnet-santa.png" />
<Content Include="Resources\theme.txt" />
<Page Include="WPF\SearchTextBoxUserControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WPF\SetupWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WPF\AboutWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WPF\EverythingWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WPF\CommandPaletteWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WPF\Resources.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Compile Include="Misc\Help.cs" />
<Compile Include="Misc\UpdateCheck.cs" />
<Compile Include="Misc\Theme.cs" />
<Compile Include="Misc\PowerShell.cs" />
<Compile Include="Native\StockIcon.cs" />
<Compile Include="WPF\SearchTextBoxUserControl.xaml.cs">
<DependentUpon>SearchTextBoxUserControl.xaml</DependentUpon>
</Compile>
<Compile Include="DynamicGUI\DynamicGUI.cs" />
<Compile Include="DynamicGUI\OptionSettingControl.xaml.cs">
<DependentUpon>OptionSettingControl.xaml</DependentUpon>
</Compile>
<Compile Include="DynamicGUI\StringSettingControl.xaml.cs">
<DependentUpon>StringSettingControl.xaml</DependentUpon>
</Compile>
<Compile Include="DynamicGUI\Tommy.cs" />
<Compile Include="Misc\ExtensionMethods.cs" />
<Compile Include="Native\MediaInfo.cs" />
<Compile Include="Native\Taskbar.cs" />
<Compile Include="WinForms\Menu.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="mpv\libmpv.cs" />
<Compile Include="WinForms\MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="WinForms\MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Misc\Misc.cs" />
<Compile Include="mpv\core.cs" />
<Compile Include="Misc\Commands.cs" />
<Compile Include="Native\Native.cs" />
<Compile Include="Native\NativeHelp.cs" />
<Compile Include="Misc\Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Native\TaskDialog.cs" />
<Compile Include="WPF\SetupWindow.xaml.cs">
<DependentUpon>SetupWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\EverythingWindow.xaml.cs">
<DependentUpon>EverythingWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\CommandPaletteWindow.xaml.cs">
<DependentUpon>CommandPaletteWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\ConfWindow.xaml.cs">
<DependentUpon>ConfWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\AboutWindow.xaml.cs">
<DependentUpon>AboutWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\LearnWindow.xaml.cs">
<DependentUpon>LearnWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\InputWindow.xaml.cs">
<DependentUpon>InputWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\WPF.cs" />
<EmbeddedResource Include="WinForms\MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="..\README.md">
<Link>README.md</Link>
</None>
<None Include="app.manifest" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Content Include="mpvnet.ico" />
<Content Include="Resources\mpv.conf.txt" />
<Content Include="Resources\editor.toml.txt" />
<None Include="Resources\mpvnet.ico" />
<None Include="Resources\mpvnet.png" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Resources\input.conf.txt" />
</ItemGroup>
<ItemGroup>
<Page Include="DynamicGUI\OptionSettingControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="DynamicGUI\StringSettingControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WPF\ConfWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WPF\LearnWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WPF\InputWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

31
src/mpv.net.sln Normal file
View File

@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28729.10
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mpv.net", "mpv.net.csproj", "{1751F378-8EDF-4B62-BE6D-304C7C287089}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RatingExtension", "extensions\RatingExtension\RatingExtension.csproj", "{55C88710-539D-4402-84C8-31694841C731}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1751F378-8EDF-4B62-BE6D-304C7C287089}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1751F378-8EDF-4B62-BE6D-304C7C287089}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1751F378-8EDF-4B62-BE6D-304C7C287089}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1751F378-8EDF-4B62-BE6D-304C7C287089}.Release|Any CPU.Build.0 = Release|Any CPU
{55C88710-539D-4402-84C8-31694841C731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55C88710-539D-4402-84C8-31694841C731}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55C88710-539D-4402-84C8-31694841C731}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55C88710-539D-4402-84C8-31694841C731}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8DE9E796-619E-44B8-9576-F4DE028701BF}
EndGlobalSection
EndGlobal

1432
src/mpv/Core.cs Normal file

File diff suppressed because it is too large Load Diff

237
src/mpv/libmpv.cs Normal file
View File

@@ -0,0 +1,237 @@

using System;
using System.Runtime.InteropServices;
using System.Text;
public class libmpv
{
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mpv_create();
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_initialize(IntPtr mpvHandle);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_command(IntPtr mpvHandle, IntPtr strings);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_command_string(IntPtr mpvHandle, [MarshalAs(UnmanagedType.LPUTF8Str)] string command);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_command_ret(IntPtr mpvHandle, IntPtr strings, IntPtr node);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void mpv_free_node_contents(IntPtr node);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mpv_error_string(mpv_error error);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mpv_terminate_destroy(IntPtr mpvHandle);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_request_log_messages(IntPtr mpvHandle, [MarshalAs(UnmanagedType.LPUTF8Str)] string min_level);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mpv_set_option(IntPtr mpvHandle, byte[] name, mpv_format format, ref long data);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mpv_set_option_string(IntPtr mpvHandle, byte[] name, byte[] value);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_get_property(IntPtr mpvHandle, byte[] name, mpv_format format, out IntPtr data);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_get_property(IntPtr mpvHandle, byte[] name, mpv_format format, out double data);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_set_property(IntPtr mpvHandle, byte[] name, mpv_format format, ref byte[] data);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_set_property(IntPtr mpvHandle, byte[] name, mpv_format format, ref long data);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_set_property(IntPtr mpvHandle, byte[] name, mpv_format format, ref double data);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern mpv_error mpv_observe_property(IntPtr mpvHandle, ulong reply_userdata, [MarshalAs(UnmanagedType.LPUTF8Str)] string name, mpv_format format);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mpv_unobserve_property(IntPtr mpvHandle, ulong registered_reply_userdata);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void mpv_free(IntPtr data);
[DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mpv_wait_event(IntPtr mpvHandle, double timeout);
public enum mpv_error
{
MPV_ERROR_SUCCESS = 0,
MPV_ERROR_EVENT_QUEUE_FULL = -1,
MPV_ERROR_NOMEM = -2,
MPV_ERROR_UNINITIALIZED = -3,
MPV_ERROR_INVALID_PARAMETER = -4,
MPV_ERROR_OPTION_NOT_FOUND = -5,
MPV_ERROR_OPTION_FORMAT = -6,
MPV_ERROR_OPTION_ERROR = -7,
MPV_ERROR_PROPERTY_NOT_FOUND = -8,
MPV_ERROR_PROPERTY_FORMAT = -9,
MPV_ERROR_PROPERTY_UNAVAILABLE = -10,
MPV_ERROR_PROPERTY_ERROR = -11,
MPV_ERROR_COMMAND = -12,
MPV_ERROR_LOADING_FAILED = -13,
MPV_ERROR_AO_INIT_FAILED = -14,
MPV_ERROR_VO_INIT_FAILED = -15,
MPV_ERROR_NOTHING_TO_PLAY = -16,
MPV_ERROR_UNKNOWN_FORMAT = -17,
MPV_ERROR_UNSUPPORTED = -18,
MPV_ERROR_NOT_IMPLEMENTED = -19,
MPV_ERROR_GENERIC = -20
}
public enum mpv_event_id
{
MPV_EVENT_NONE = 0,
MPV_EVENT_SHUTDOWN = 1,
MPV_EVENT_LOG_MESSAGE = 2,
MPV_EVENT_GET_PROPERTY_REPLY = 3,
MPV_EVENT_SET_PROPERTY_REPLY = 4,
MPV_EVENT_COMMAND_REPLY = 5,
MPV_EVENT_START_FILE = 6,
MPV_EVENT_END_FILE = 7,
MPV_EVENT_FILE_LOADED = 8,
MPV_EVENT_TRACKS_CHANGED = 9,
MPV_EVENT_TRACK_SWITCHED = 10,
MPV_EVENT_IDLE = 11,
MPV_EVENT_PAUSE = 12,
MPV_EVENT_UNPAUSE = 13,
MPV_EVENT_TICK = 14,
MPV_EVENT_SCRIPT_INPUT_DISPATCH = 15,
MPV_EVENT_CLIENT_MESSAGE = 16,
MPV_EVENT_VIDEO_RECONFIG = 17,
MPV_EVENT_AUDIO_RECONFIG = 18,
MPV_EVENT_METADATA_UPDATE = 19,
MPV_EVENT_SEEK = 20,
MPV_EVENT_PLAYBACK_RESTART = 21,
MPV_EVENT_PROPERTY_CHANGE = 22,
MPV_EVENT_CHAPTER_CHANGE = 23,
MPV_EVENT_QUEUE_OVERFLOW = 24,
MPV_EVENT_HOOK = 25
}
public enum mpv_format
{
MPV_FORMAT_NONE = 0,
MPV_FORMAT_STRING = 1,
MPV_FORMAT_OSD_STRING = 2,
MPV_FORMAT_FLAG = 3,
MPV_FORMAT_INT64 = 4,
MPV_FORMAT_DOUBLE = 5,
MPV_FORMAT_NODE = 6,
MPV_FORMAT_NODE_ARRAY = 7,
MPV_FORMAT_NODE_MAP = 8,
MPV_FORMAT_BYTE_ARRAY = 9
}
public enum mpv_log_level
{
MPV_LOG_LEVEL_NONE = 0,
MPV_LOG_LEVEL_FATAL = 10,
MPV_LOG_LEVEL_ERROR = 20,
MPV_LOG_LEVEL_WARN = 30,
MPV_LOG_LEVEL_INFO = 40,
MPV_LOG_LEVEL_V = 50,
MPV_LOG_LEVEL_DEBUG = 60,
MPV_LOG_LEVEL_TRACE = 70,
}
public enum mpv_end_file_reason
{
MPV_END_FILE_REASON_EOF = 0,
MPV_END_FILE_REASON_STOP = 2,
MPV_END_FILE_REASON_QUIT = 3,
MPV_END_FILE_REASON_ERROR = 4,
MPV_END_FILE_REASON_REDIRECT = 5
}
[StructLayout(LayoutKind.Sequential)]
public struct mpv_event_log_message
{
public IntPtr prefix;
public IntPtr level;
public IntPtr text;
public mpv_log_level log_level;
}
[StructLayout(LayoutKind.Sequential)]
public struct mpv_event
{
public mpv_event_id event_id;
public int error;
public ulong reply_userdata;
public IntPtr data;
}
[StructLayout(LayoutKind.Sequential)]
public struct mpv_event_client_message
{
public int num_args;
public IntPtr args;
}
[StructLayout(LayoutKind.Sequential)]
public struct mpv_event_property
{
public string name;
public mpv_format format;
public IntPtr data;
}
[StructLayout(LayoutKind.Sequential)]
public struct mpv_event_end_file
{
public int reason;
public int error;
}
[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct mpv_node
{
[FieldOffset(0)] public IntPtr str;
[FieldOffset(0)] public int flag;
[FieldOffset(0)] public long int64;
[FieldOffset(0)] public double dbl;
[FieldOffset(0)] public IntPtr list;
[FieldOffset(0)] public IntPtr ba;
[FieldOffset(8)] public mpv_format format;
}
public static string[] ConvertFromUtf8Strings(IntPtr utf8StringArray, int stringCount)
{
IntPtr[] intPtrArray = new IntPtr[stringCount];
string[] stringArray = new string[stringCount];
Marshal.Copy(utf8StringArray, intPtrArray, 0, stringCount);
for (int i = 0; i < stringCount; i++)
stringArray[i] = ConvertFromUtf8(intPtrArray[i]);
return stringArray;
}
public static string ConvertFromUtf8(IntPtr nativeUtf8)
{
int len = 0;
while (Marshal.ReadByte(nativeUtf8, len) != 0)
++len;
byte[] buffer = new byte[len];
Marshal.Copy(nativeUtf8, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
}
public static string GetError(mpv_error err) => ConvertFromUtf8(mpv_error_string(err));
public static byte[] GetUtf8Bytes(string s) => Encoding.UTF8.GetBytes(s + "\0");
}

BIN
src/mpvnet.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

4
src/packages.config Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.PowerShell.5.ReferenceAssemblies" version="1.1.0" targetFramework="net472" />
</packages>

View File

@@ -0,0 +1,39 @@
// This script creates context menu items dynamically.
using mpvnet;
using System.ComponentModel;
using System.Linq;
class Script
{
MainForm MainForm;
Core core;
public Script()
{
core = Core.core;
MainForm = mpvnet.MainForm.Instance;
MainForm.ContextMenu.Opening += ContextMenu_Opening;
}
void ContextMenu_Opening(object sender, CancelEventArgs e)
{
// edit input.conf and add 'Edition' menu item there
MenuItem menuItem = MainForm.FindMenuItem("Edition");
if (menuItem == null)
return;
menuItem.DropDownItems.Clear();
var editionTracks = core.MediaTracks.Where(track => track.Type == "e");
foreach (MediaTrack track in editionTracks)
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => { core.commandv("set", "edition", track.ID.ToString()); };
mi.Checked = core.Edition == track.ID;
menuItem.DropDownItems.Add(mi);
}
}
}

View File

@@ -0,0 +1,29 @@
// This script adds a key binding.
using System.Reflection;
using mpvnet;
class Script
{
public Script()
{
string content = "ctrl+w script-message my-message-1 my-argument-1";
string sectionName = Assembly.GetExecutingAssembly().GetName().Name;
Core core = Core.core;
core.commandv("define-section", sectionName, content, "force");
core.commandv("enable-section", sectionName);
core.ClientMessage += ClientMessage;
}
void ClientMessage(string[] args)
{
switch (args[0])
{
case "my-message-1":
Msg.Show(args[1]);
break;
}
}
}

View File

@@ -0,0 +1,21 @@
// This script observes the fullscreen property and
// draws text on screen when the property changes.
using mpvnet;
class Script
{
Core core;
public Script()
{
core = Core.core;
core.observe_property_bool("fullscreen", FullscreenChange);
}
void FullscreenChange(bool value)
{
core.commandv("show-text", "fullscreen: " + value);
}
}

View File

@@ -0,0 +1,45 @@
// Pauses playback when window is minimized and resumes afterwards.
using System;
using System.Windows.Forms;
using mpvnet;
class Script
{
MainForm Form;
Core core;
bool WasPlaying;
bool WasPaused;
public Script()
{
core = Core.core;
Form = MainForm.Instance;
Form.Resize += Form_Resize;
}
private void Form_Resize(object sender, EventArgs e)
{
if (Form.WindowState == FormWindowState.Minimized)
{
WasPlaying = !core.get_property_bool("pause");
if (WasPlaying)
{
core.set_property_bool("pause", true, true);
WasPaused = true;
}
}
else
{
if (WasPaused)
{
core.set_property_bool("pause", false, true);
WasPaused = false;
}
}
}
}

View File

@@ -0,0 +1,35 @@
// When seeking displays position and duration like so: 70:00 / 80:00
// Which is different from most players which use: 01:10:00 / 01:20:00
// In input.conf set the input command prefix no-osd infront of the seek command.
function add_zero(val)
{
val = Math.round(val);
return val > 9 ? "" + val : "0" + val;
}
function format(val)
{
var sec = Math.round(val);
if (sec < 0)
sec = 0;
pos_min_floor = Math.floor(sec / 60);
sec_rest = sec - pos_min_floor * 60;
return add_zero(pos_min_floor) + ":" + add_zero(sec_rest);
}
function on_seek(_)
{
pos = mp.get_property_number("time-pos");
dur = mp.get_property_number("duration");
if (pos > dur)
pos = dur;
mp.commandv("show-text", format(pos) + " / " + format(dur));
}
mp.register_event("seek", on_seek);

View File

@@ -0,0 +1,26 @@
// This script shows the playlist.
function showPlaylist()
{
// set font size
mp.set_property_number("osd-font-size", 40);
// show playlist for 5 seconds
mp.command("show-text ${playlist} 5000");
// restore original font size in 6 seconds
setTimeout(resetFontSize, 6000);
}
// restore original font size
function resetFontSize()
{
mp.set_property_number("osd-font-size", size);
}
// save original font size
var size = mp.get_property_number("osd-font-size");
// input.conf: key script-binding show-playlist
mp.add_key_binding(null, "show-playlist", showPlaylist);

View File

@@ -0,0 +1,23 @@
-- https://github.com/mpv-player/mpv/blob/master/TOOLS/lua/pause-when-minimize.lua
-- This script pauses playback when minimizing the window, and resumes playback
-- if it's brought back again. If the player was already paused when minimizing,
-- then try not to mess with the pause state.
local did_minimize = false
mp.observe_property("window-minimized", "bool", function(name, value)
local pause = mp.get_property_native("pause")
if value == true then
if pause == false then
mp.set_property_native("pause", true)
did_minimize = true
end
elseif value == false then
if did_minimize and (pause == true) then
mp.set_property_native("pause", false)
end
did_minimize = false
end
end)

View File

@@ -0,0 +1,20 @@
# Shows the Open File dialog to open a file without loading its folder into the playlist.
# In input.conf add: <key> script-message load-without-folder
$code = {
if ($args[0] -eq 'load-without-folder')
{
$dialog = New-Object Windows.Forms.OpenFileDialog
if ($dialog.ShowDialog() -eq 'OK')
{
$core.LoadFiles($dialog.FileNames, $false, $false);
}
$dialog.Dispose()
}
}
$mp.register_event("client-message", $code)

View File

@@ -0,0 +1,25 @@
$code = {
$isMinimized = $args[0]
$isPaused = $mp.get_property_bool('pause')
if ($isMinimized)
{
if (-not $isPaused)
{
$mp.set_property_bool('pause', $true)
$script:wasPaused = $true
}
}
else
{
if ($script:wasPaused -and $isPaused)
{
$mp.set_property_bool('pause', $false)
}
$script:wasPaused = $false
}
}
$mp.observe_property('window-minimized', 'bool', $code)

View File

@@ -0,0 +1,14 @@
# Shows the current file in File Explorer
# In input.conf add: <key> script-message show-in-file-explorer
$code = {
if ($args[0] -eq 'show-in-file-explorer')
{
# probably works only with shell execute for which powershell has no built-in support
[Diagnostics.Process]::Start('explorer.exe', '/n, /select, "' + $mp.get_property_string('path') + '"')
}
}
$mp.register_event("client-message", $code)