Compare commits

..

22 Commits

Author SHA1 Message Date
stax76
a3b9c653fa v7.0.0.4 Beta 2023-12-19 09:37:46 +01:00
stax76
ed48f5c559 Command line parser supports list options with -add suffix. Fixex #619. 2023-12-19 03:18:55 +01:00
stax76
d328f6b7ec Main window: Limited geometry support 2023-12-17 19:08:55 +01:00
stax76
16ba94d67d updated manual and new libplacebo GUI options 2023-12-16 13:48:27 +01:00
stax76
7978170133 Merge pull request #617 from FantasqueX/fix-typo-1
fix typo in changelog
2023-12-16 08:44:03 +01:00
stax76
ab313eb442 new libplacebo options 2023-12-16 08:43:22 +01:00
Letu Ren
f40008d94a fix typo in changelog 2023-12-16 15:31:50 +08:00
stax76
75e19d8d18 newly developed combo box control 2023-12-16 04:57:49 +01:00
stax76
9bb978f612 new combo box control 2023-12-16 04:52:16 +01:00
stax76
7ea3fbc917 v7.0.0.3 2023-12-15 15:21:35 +01:00
stax76
3af5b458ba misc 2023-12-15 14:58:32 +01:00
stax76
b23542d681 Conf editor crash fixed 2023-12-15 08:35:01 +01:00
stax76
e0616dee76 misc 2023-12-15 01:24:43 +01:00
stax76
cd54e67b87 new menu items and bindings to open mpv.conf and input.conf with a text editor 2023-12-14 13:48:23 +01:00
stax76
9d4779fd96 Conf editor support for: reset-on-next-file, input-ipc-server, background, title 2023-12-14 11:08:36 +01:00
stax76
d4d147e5fc Improved conf file reader/writer. 2023-12-14 07:31:39 +01:00
stax76
35b17bc620 New menu item added to add mpv.net to the path environment variable. 2023-12-13 09:46:17 +01:00
stax76
3eb4af5e75 v7.0.0.2 2023-12-13 08:45:20 +01:00
stax76
49f22a1f81 misc 2023-12-13 03:38:23 +01:00
stax76
7cd5686488 Trying getting decent menu input display 2023-12-13 00:57:59 +01:00
stax76
0d63feec57 Remove MS Store package from solution 2023-12-12 18:29:35 +01:00
stax76
0ee8318ca4 some bug fixes 2023-12-12 18:15:20 +01:00
52 changed files with 1833 additions and 916 deletions

View File

@@ -1,4 +1,52 @@
# v7.0.0.4 Beta (2023-12-19)
- When mpv.net is started for the first time from a new startup location,
it asks if file associations should be registered.
- Setup supports installing per user in non admin mode.
- Command line parser supports list options with `-add` suffix.
- Fix window sometimes shown with wrong size.
- Limited support for the mpv option `geometry`, it supports location in percent,
for size use `autofit`. Read the instructions in the mpv.net manual or in the conf editor.
- Improved manual.
- Improved bindings.
- Conf editor reorganized according to options categories used in mpv manual.
- mpv.net is available via command line package manager winget.
- New libplacebo config editor options added.
- The conf editor uses a newly developed combo box control (dropdown menu)
instead of radio buttons whenever an option has more than 3 items,
this improves the look and feel, usability and performance.
The navigation tree view was improved.
- New zhongfly libmpv build.
# v7.0.0.3 Beta (2023-12-15)
- New conf editor option `Video/libplacebo/preset`.
- New conf editor option `Video/libplacebo/Scaling/upscaler`.
- New menu item `Settings/Setup/Add mpv.net to Path environment variable' added.
- New menu item `Settings/Edit mpv.conf` added for opening mpv.conf with a text editor. Default binding `c`.
- New menu item `Settings/Edit input.conf` added for opening input.conf with a text editor. Default binding `k`.
- mpv.net can no longer be downloaded from the Microsoft store due
to a general very poor experience with the package creation and submission.
I've submitted mpv.net to the winget package repository, it's not yet processed.
- Improved conf file reader/writer.
- Conf editor support added for the mpv options:
`reset-on-next-file`, `input-ipc-server`, `background`, `title`
- Conf editor crash fixed.
- When mpv.net is started for the first time from a new startup location,
it asks if mpv.net should be added to the Path environment variable.
# v7.0.0.2 Beta (2023-12-13)
- Besides a portable download there is now again a setup installer.
- Fix dynamic menu items missing in context menu.
- Fix certain binding setups shown poorly or incorrectly in the main menu.
- Fix conf editor not remembering the search text.
- Fix quit-watch-later not working.
- New option `menu-syntax`. Default: `#menu:`
- New zhongfly libmpv build.
# v7.0.0.1 Beta (2023-12-11)
- [.NET 6 is a new requirement](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
@@ -12,7 +60,7 @@
- Fix message box exceding working area size.
- C# and PowerShell scripting was removed because of a compatibility problem
with the .NET 6 platform. .NET extensions are supported with a new host
(not backward compatible). A example extension is available under \src\MpvNet.Extension\ExampleExtension
(not backward compatible). An example extension is available under \src\MpvNet.Extension\ExampleExtension
- Redesigned bindings and context menu.
- auto-play option removed, mpv supports it with the option reset-on-next-file.
- Dark mode title bar enabled on Windows 10.0.18985 or higher.

View File

@@ -25,6 +25,7 @@ Table of contents
* [Hidden Features](#hidden-features)
* [Differences compared to mpv](#differences-compared-to-mpv)
* [Environment Variables](#environment-variables)
* [user-data](#user-data)
* [Context Menu Commands](#context-menu)
@@ -42,13 +43,9 @@ differences are documented in this manual under [Differences compared to mpv](#d
Download
--------
1. [Stable via Microsoft Store](https://www.microsoft.com/store/productId/9N64SQZTB3LM)
2. [Stable and beta portable via GitHub download](../../../releases)
3. Stable via command line from Microsoft Store: `winget install mpv.net`
4. [Automated nightly portable builds](https://github.com/mpvnet-player/mpv.net/actions)
1. [Stable and beta portable and setup via GitHub download](../../../releases)
2. Stable via command line with winget: `winget install mpv.net`
3. [Automated nightly portable builds](https://github.com/mpvnet-player/mpv.net/actions)
[Changelog](changelog.md)
@@ -56,10 +53,8 @@ Download
Installation
------------
1. Windows 7 or higher is required (Windows 10 or higher is recommended).
2. mpv.net since version 7.0 requires the
[.NET Desktop Runtime 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
being installed. mpv.net before version 7.0 requires .NET Framework 4.8.
1. Windows 10 or higher.
2. [.NET Desktop Runtime 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
Internet streaming requires:
@@ -71,8 +66,8 @@ Internet streaming requires:
File Associations can be registered using the context menu under 'Settings > Setup'.
After the file associations were registered, it might be necessary to change the
default app in the Windows settings (Win+I, ms-settings:defaultapps).
After the file associations were registered, it might still be necessary to change the
default app in the Windows settings.
Another way to register file associations is using Windows File Explorer,
select a media file and select 'Open with > Choose another app' in the context menu.
@@ -84,6 +79,11 @@ to get menu items for [Play with mpv.net](https://github.com/stax76/OpenWithPlus
When multiple files are selected in File Explorer and enter is pressed then
the files are opened in mpv.net in random order, this works with maximum 15 files.
#### Path environment variable
In order to use mpv.net in a terminal for advanced use cases,
mpv.net must be added to the Path environment variable,
this can be achieved with the context menu (Settings/Setup).
Support
-------
@@ -110,8 +110,9 @@ Settings
mpv.net searches the config folder at:
1. startup\portable_config
2. %APPDATA%\mpv.net (`C:\Users\%USERNAME%\AppData\Roaming\mpv.net`)
1. Folder defined via MPVNET_HOME environment variable.
2. startup\portable_config (startup means the directory containing mpvnet.exe)
3. `%APPDATA%\mpv.net` (`C:\Users\Username\AppData\Roaming\mpv.net`)
mpv options are stored in the file mpv.conf,
mpv.net options are stored in the file mpvnet.conf,
@@ -121,7 +122,7 @@ mpv.net options are documented [here](#mpvnet-specific-options).
Input and context menu
----------------------
Global keyboard shortcuts are supported via global-input.conf file.
Global keyboard shortcuts are supported via `global-input.conf` file.
The config folder can be opened from the context menu: `Settings > Open Config Folder`
@@ -141,24 +142,31 @@ mpv input options:
https://mpv.io/manual/master/#input
Before version v7 all bindings and the context menu definition
was contained in the input.conf file, which mpv.net created
were defined in the input.conf file, which mpv.net created
in case it didn't exist. This had the disadvantage that mpv.net
lost control over all default bindings and the context menu
defaults. This was unfortunate, v7 introduces a new design
fixing it.
lost control over all default bindings and context menu
defaults. This was unfortunate, v7 introduces a new bindings
and context menu design fixing it.
In v7 no input.conf file is created, the default bindings and
context menu is defined internally. input.conf only contains
what is different from the internally defined defaults,
so it's the same how mpv is used.
so it works the same it work with mpv.
For backward compatibility the old input.conf format with the
menu definition using `#menu: ` is still supported. The new
design also allows for a menu customization, in a sub section
called `Custom`. In input.conf it can be defined like so:
For backward compatibility the old input.conf context menu
format with the menu definition using `#menu: ` is still
supported. The new design also allows for a menu customization,
in a sub section called `Custom`. In input.conf it can be
defined like so:
`Ctrl+a show-text Test #custom-menu: Test > Test`
Users that have their bindings and context menu customized
before v7 can easily migrate to the new design by deleting
bindings they don't use and remember the shortcut and remove
`#menu:` everywhere, it's important to remove `#menu:`
everywhere in order to enable the new mode/design.
Command Line Interface
----------------------
@@ -194,8 +202,6 @@ Terminal
When mpv.net is started from a terminal it will output status,
error and debug messages to the terminal and accept input keys from the terminal.
A common task for the terminal is debugging scripts.
mpv.net specific commands
-------------------------
@@ -204,6 +210,12 @@ mpv.net specific commands
mpv.net commands are used when mpv commands don't exist or lack a feature.
### add-to-path
Adds mpv.net to the Path environment variable.
### edit-conf-file [mpv.conf|input.conf]
Opens mpv.conf or input.conf in a text editor.
### load-audio
Shows a file browser dialog to open external audio files.
@@ -258,12 +270,6 @@ Shows available [mpv input commands](https://mpv.io/manual/master/#list-of-input
### show-conf-editor
Shows the conf editor.
### show-decoders
Shows available decoders.
### show-demuxers
Shows available demuxers.
### show-input-editor
Shows the input editor.
@@ -390,6 +396,10 @@ For single files automatically load the entire directory into the playlist.
### General
#### --menu-syntax=\<value\>
Used menu syntax for defining the context menu in input.conf.\nmpv.net by default uses `#menu:`, uosc uses `#!` by default.
#### --process-instance=\<value\>
Defines if more then one mpv.net process is allowed.
@@ -508,7 +518,8 @@ Scripting
#### Lua
A very large collection of Lua user scripts can be found in the mpv wiki [here](https://github.com/mpv-player/mpv/wiki/User-Scripts).
A very large collection of user scripts can be found in the GitHub repository
[awesome-mpv](https://github.com/stax76/awesome-mpv).
Lua scripting is documented in the mpv.net wiki [here](https://github.com/mpvnet-player/mpv.net/wiki/Extending-mpv-and-mpv.net-via-Lua-scripting).
@@ -517,10 +528,10 @@ Lua scripting is documented in the mpv.net wiki [here](https://github.com/mpvnet
[mpv JavaScript documentation](https://mpv.io/manual/master/#javascript)
Extensions
----------
.NET Extensions
---------------
Extensions are located in a subfolder _extensions_ in the config folder,
.NET Extensions are located in a subfolder _extensions_ in the config folder,
the filename must have the same name as the directory:
```Text
@@ -620,14 +631,32 @@ https://mpv.io/manual/master/#window
- [window-scale](https://mpv.io/manual/master/#options-window-scale)
**Partly implemented are:**
**Partly implemented or modified:**
- [autofit-larger](https://mpv.io/manual/master/#options-autofit-larger)
Supported is a single integer value in the range 0-100.
- [autofit-smaller](https://mpv.io/manual/master/#options-autofit-smaller)
Supported is a single integer value in the range 0-100.
- [autofit](https://mpv.io/manual/master/#options-autofit)
Supported is a single integer value in the range 0-100.
#### --autofit=\<int\>
\<int\> Initial window height in percent. Default: 60
#### --autofit-smaller=\<int\>
\<int\> Minimum window height in percent. Default: 10
#### --autofit-larger=\<int\>
\<int\> Maximum window height in percent. Default: 80
#### --geometry\<x:y\>
Initial window location in percent. Default: 50:50 (centered)
x=0 docks the window to the left side.
x=100 docks the window to the right side.
y=0 docks the window to the top side.
y=100 docks the window to the bottom side.
**mpv.net specific window features:**
mpv.net specific window features are documented in the [screen section](#screen).
@@ -672,10 +701,18 @@ Environment Variables
Directory where mpv.net looks for user settings.
### MPVNET_VERSION
Returns the version of mpv.net.
user-data
---------
Script authors can access the following
[user-data](https://mpv.io/manual/master/#command-interface-user-data) properties:
```
user-data/frontend/name
user-data/frontend/version
user-data/frontend/process-path
```
Context Menu Commands
---------------------

View File

@@ -23,3 +23,12 @@ dotnet_diagnostic.IDE0044.severity = silent
# Member does not access instance data and can be marked as static
dotnet_diagnostic.CA1822.severity = none
# IDE0057: Use range operator
csharp_style_prefer_range_operator = false
# CA1401: P/Invokes should not be visible
dotnet_diagnostic.CA1401.severity = none
# IDE0017: Simplify object initialization
dotnet_style_object_initializer = false

View File

@@ -53,6 +53,7 @@ public class Conf
if (section.HasName("help")) baseSetting.Help = section.GetValue("help");
if (section.HasName("url")) baseSetting.URL = section.GetValue("url");
if (section.HasName("width")) baseSetting.Width = Convert.ToInt32(section.GetValue("width"));
if (section.HasName("option-name-width")) baseSetting.OptionNameWidth = Convert.ToInt32(section.GetValue("option-name-width"));
if (section.HasName("type")) baseSetting.Type = section.GetValue("type");
if (baseSetting.Help.ContainsEx("\\n"))
@@ -90,6 +91,9 @@ public class ConfParser
{
string line = it.Trim();
if (line.StartsWith('#'))
continue;
if (line == "")
{
currentGroup = new ConfSection();

View File

@@ -11,6 +11,9 @@ using MpvNet.Windows.WinForms;
using MpvNet.Windows.WPF.Views;
using MpvNet.Windows.WPF;
using MpvNet.Windows.WPF.MsgBox;
using MpvNet.Help;
using System.Text.Json;
using MpvNet.Windows.Help;
namespace MpvNet;
@@ -45,6 +48,9 @@ public class GuiCommand
["show-menu"] = args => ShowMenu?.Invoke(),
["show-bindings"] = args => ShowBindings(),
["show-playlist"] = args => ShowPlaylist(),
["add-to-path"] = args => AddToPath(),
["edit-conf-file"] = EditCongFile,
["show-commands"] = args => ShowCommands(),
// deprecated
@@ -98,6 +104,53 @@ public class GuiCommand
Player.LoadDiskFolder(dialog.SelectedPath);
}
public void EditCongFile(IList<string> args)
{
string file = Player.ConfigFolder + args[0];
if (File.Exists(file))
ProcessHelp.ShellExecute(WinApiHelp.GetAppPathForExtension("txt"), "\"" + file + "\"");
}
public static void ShowTextWithEditor(string name, string text)
{
string file = Path.Combine(Path.GetTempPath(), name + ".txt");
App.TempFiles.Add(file);
File.WriteAllText(file, BR + text.Trim() + BR);
ProcessHelp.ShellExecute(WinApiHelp.GetAppPathForExtension("txt"), "\"" + file + "\"");
}
public static void ShowCommands()
{
string json = Core.GetPropertyString("command-list");
var enumerator = JsonDocument.Parse(json).RootElement.EnumerateArray();
var commands = enumerator.OrderBy(it => it.GetProperty("name").GetString());
StringBuilder sb = new StringBuilder();
foreach (var cmd in commands)
{
sb.AppendLine();
sb.AppendLine(cmd.GetProperty("name").GetString());
foreach (var args in cmd.GetProperty("args").EnumerateArray())
{
string value = args.GetProperty("name").GetString() + " <" +
args.GetProperty("type").GetString()!.ToLower() + ">";
if (args.GetProperty("optional").GetBoolean())
value = "[" + value + "]";
sb.AppendLine(" " + value);
}
}
string header = BR +
"https://mpv.io/manual/master/#list-of-input-commands" + BR2 +
"https://github.com/stax76/mpv-scripts#command_palette" + BR;
ShowTextWithEditor("Input Commands", header + sb.ToString());
}
public void OpenFromClipboard(IList<string> args)
{
if (System.Windows.Forms.Clipboard.ContainsFileDropList())
@@ -248,7 +301,7 @@ public class GuiCommand
text = text.TrimEx();
if (editor)
Command.ShowTextWithEditor("media-info", text);
ShowTextWithEditor("media-info", text);
else if (osd)
Command.ShowText(text.Replace("\r", ""), 5000, 16);
else
@@ -261,7 +314,24 @@ public class GuiCommand
public static string FormatTime(double value) => ((int)value).ToString("00");
public void ShowBindings() => Command.ShowTextWithEditor("Bindings", Player.UsedInputConfContent);
public void ShowBindings() => ShowTextWithEditor("Bindings", Player.UsedInputConfContent);
public void AddToPath()
{
string path = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.User)!;
if (path.ToLower().Contains(Folder.Startup.TrimEnd(Path.DirectorySeparatorChar).ToLower()))
{
Msg.ShowWarning("mpv.net is already in Path.");
return;
}
Environment.SetEnvironmentVariable("Path",
Folder.Startup.TrimEnd(Path.DirectorySeparatorChar) + ";" + path,
EnvironmentVariableTarget.User);
Msg.ShowInfo("mpv.net successfully was added to Path.");
}
public void ShowPlaylist()
{

View File

@@ -0,0 +1,126 @@

using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using static MpvNet.Windows.Native.WinApi;
namespace MpvNet.Windows.Help;
public class WinApiHelp
{
public static Version WindowsTen1607 { get; } = new Version(10, 0, 14393); // Windows 10 1607
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, int dpi)
{
Rect r = new Rect(0, 0, 0, 0);
AddWindowBorders(hwnd, ref r, dpi);
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, int dpi)
{
uint windowStyle = (uint)GetWindowLong(hwnd, -16); // GWL_STYLE
uint windowStyleEx = (uint)GetWindowLong(hwnd, -20); // GWL_EXSTYLE
if (Environment.OSVersion.Version >= WindowsTen1607)
AdjustWindowRectExForDpi(ref rc, windowStyle, false, windowStyleEx, (uint)dpi);
else
AdjustWindowRect(ref rc, windowStyle, false);
}
public static Rectangle GetWorkingArea(IntPtr handle, Rectangle workingArea)
{
if (handle != IntPtr.Zero && GetDwmWindowRect(handle, out Rect dwmRect) &&
GetWindowRect(handle, out Rect rect))
{
int left = workingArea.Left;
int top = workingArea.Top;
int right = workingArea.Right;
int bottom = workingArea.Bottom;
left += rect.Left - dwmRect.Left;
top -= rect.Top - dwmRect.Top;
right -= dwmRect.Right - rect.Right;
bottom -= dwmRect.Bottom - rect.Bottom;
return new Rectangle(left, top, right - left, bottom - top);
}
return workingArea;
}
public static bool GetDwmWindowRect(IntPtr handle, out Rect rect)
{
const uint DWMWA_EXTENDED_FRAME_BOUNDS = 9;
return 0 == DwmGetWindowAttribute(handle, DWMWA_EXTENDED_FRAME_BOUNDS,
out rect, (uint)Marshal.SizeOf<Rect>());
}
public static IntPtr GetWindowLong(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLongPtr(hWnd, nIndex);
else
return GetWindowLong32(hWnd, nIndex);
}
public static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong)
{
if (IntPtr.Size == 8)
return SetWindowLongPtr(hWnd, nIndex, dwNewLong);
else
return SetWindowLong32(hWnd, nIndex, dwNewLong);
}
public static string GetAppPathForExtension(params string[] extensions)
{
foreach (string it in extensions)
{
string extension = it;
if (!extension.StartsWith("."))
extension = "." + extension;
uint c = 0U;
if (AssocQueryString(0x40, 2, extension, null, null, ref c) == 1)
{
if (c > 0L)
{
var sb = new StringBuilder((int)c);
if (0 == AssocQueryString(0x40, 2, extension, default, sb, ref c))
{
string ret = sb.ToString();
if (File.Exists(ret))
return ret;
}
}
}
}
return "";
}
}

View File

@@ -1,16 +1,58 @@

using MpvNet.ExtensionMethod;
using System.Windows;
using MpvNet.Windows.WPF;
namespace MpvNet.Windows.Help;
public class WinMpvHelp
{
public static void CopyMpvNetCom()
public static void Setup()
{
string dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).AddSep() +
"Microsoft\\WindowsApps\\";
string dir = RegistryHelp.GetString("PathEnvVarCheck"); // backward compatibility
if (File.Exists(dir + "mpvnet.exe") && !File.Exists(dir + "mpvnet.com"))
File.Copy(Folder.Startup + "mpvnet.com", dir + "mpvnet.com");
if (dir == Folder.Startup)
return;
dir = RegistryHelp.GetString("Setup");
if (dir == Folder.Startup)
return;
string path = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.User)!;
if (!path.ToLower().Contains(Folder.Startup.TrimEnd(Path.DirectorySeparatorChar).ToLower()))
{
var result = Msg.ShowQuestion("Would you like to add mpv.net to the Path environment variable?" + BR2 +
"This will allow using mpv.net in a console/terminal.", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
Environment.SetEnvironmentVariable("Path",
Folder.Startup.TrimEnd(Path.DirectorySeparatorChar) + ";" + path,
EnvironmentVariableTarget.User);
Msg.ShowInfo("mpv.net was added successfully to Path.");
}
else
Msg.ShowInfo("If you want to add mpv.net to the Path environment variable later," + BR +
"you can do so with the context menu (Settings/Setup)");
}
var result2 = Msg.ShowQuestion("Would you like to register video file associations?", MessageBoxButton.YesNo);
if (result2 == MessageBoxResult.Yes)
{
Player.Command("script-message-to mpvnet reg-file-assoc video");
result2 = Msg.ShowQuestion("Would you like to register audio file associations?", MessageBoxButton.YesNo);
if (result2 == MessageBoxResult.Yes)
Player.Command("script-message-to mpvnet reg-file-assoc audio");
}
else
Msg.ShowInfo("If you want to register file associations later," + BR +
"you can do so with the context menu (Settings/Setup)");
RegistryHelp.SetString("Setup", Folder.Startup);
}
}

View File

@@ -11,11 +11,19 @@
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>mpv-icon.ico</ApplicationIcon>
<Product>mpv.net</Product>
<AssemblyVersion>7.0.0.1</AssemblyVersion>
<FileVersion>7.0.0.1</FileVersion>
<FileVersion>7.0.0.4</FileVersion>
<AssemblyVersion>7.0.0.4</AssemblyVersion>
<InformationalVersion>7.0.0.4</InformationalVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Misc\**" />
<EmbeddedResource Remove="Misc\**" />
<None Remove="Misc\**" />
<Page Remove="Misc\**" />
</ItemGroup>
<ItemGroup>
<Content Include="mpv-icon.ico" />
</ItemGroup>
@@ -38,8 +46,4 @@
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
</ItemGroup>
<ItemGroup>
<Folder Include="Misc\" />
</ItemGroup>
</Project>

View File

@@ -1,13 +1,12 @@

using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
namespace MpvNet.Windows.Native;
public static class WinApi
{
public static Version WindowsTen1607 { get; } = new Version(10, 0, 14393); // Windows 10 1607
[DllImport("kernel32.dll")]
public static extern bool AttachConsole(int dwProcessId);
@@ -60,18 +59,10 @@ public static class WinApi
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);
public static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
public static IntPtr GetWindowLong(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLongPtr(hWnd, nIndex);
else
return GetWindowLong32(hWnd, nIndex);
}
public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
public static extern IntPtr SetWindowLong32(IntPtr hWnd, int nIndex, uint dwNewLong);
@@ -79,50 +70,17 @@ public static class WinApi
[DllImport("user32.dll")]
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, uint dwNewLong);
public static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong)
{
if (IntPtr.Size == 8)
return SetWindowLongPtr(hWnd, nIndex, dwNewLong);
else
return SetWindowLong32(hWnd, nIndex, dwNewLong);
}
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("shlwapi", CharSet = CharSet.Auto)]
public static extern uint AssocQueryString(
uint flags, uint str, string? pszAssoc, string? pszExtra, [Out] StringBuilder? pszOut, ref uint pcchOut);
[DllImport("dwmapi.dll")]
public static extern int DwmGetWindowAttribute(
IntPtr hwnd, uint dwAttribute, out Rect pvAttribute, uint cbAttribute);
public static bool GetDwmWindowRect(IntPtr handle, out Rect rect)
{
const uint DWMWA_EXTENDED_FRAME_BOUNDS = 9;
return 0 == DwmGetWindowAttribute(handle, DWMWA_EXTENDED_FRAME_BOUNDS,
out rect, (uint)Marshal.SizeOf<Rect>());
}
public static Rectangle GetWorkingArea(IntPtr handle, Rectangle workingArea)
{
if (handle != IntPtr.Zero && GetDwmWindowRect(handle, out Rect dwmRect) &&
GetWindowRect(handle, out Rect rect))
{
int left = workingArea.Left;
int top = workingArea.Top;
int right = workingArea.Right;
int bottom = workingArea.Bottom;
left += rect.Left - dwmRect.Left;
top -= rect.Top - dwmRect.Top;
right -= dwmRect.Right - rect.Right;
bottom -= dwmRect.Bottom - rect.Bottom;
return new Rectangle(left, top, right - left, bottom - top);
}
return workingArea;
}
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
@@ -171,41 +129,4 @@ public static class WinApi
[MarshalAs(UnmanagedType.LPTStr)]
public string lpData;
}
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, int dpi)
{
Rect r = new Rect(0, 0, 0, 0);
AddWindowBorders(hwnd, ref r, dpi);
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, int dpi)
{
uint windowStyle = (uint)GetWindowLong(hwnd, -16); // GWL_STYLE
uint windowStyleEx = (uint)GetWindowLong(hwnd, -20); // GWL_EXSTYLE
if (Environment.OSVersion.Version >= WindowsTen1607)
AdjustWindowRectExForDpi(ref rc, windowStyle, false, windowStyleEx, (uint)dpi);
else
AdjustWindowRect(ref rc, windowStyle, false);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,137 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity
Name="5664FrankSkare.mpv.net"
Publisher="CN=6A1A1E69-736C-4C77-B310-7B6D38E32617"
Version="6.0.3.0" />
<Properties>
<DisplayName>mpv.net</DisplayName>
<PublisherDisplayName>Frank Skare</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="mpv.net"
Description="mpv.net is a modern media player based on the popular mpv player."
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
<uap:SplashScreen Image="Images\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<uap3:Extension Category="windows.appExecutionAlias">
<uap3:AppExecutionAlias>
<desktop:ExecutionAlias Alias="MpvNet.exe" />
</uap3:AppExecutionAlias>
</uap3:Extension>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="videotypes">
<uap:SupportedFileTypes>
<uap:FileType>.264</uap:FileType>
<uap:FileType>.265</uap:FileType>
<uap:FileType>.asf</uap:FileType>
<uap:FileType>.avc</uap:FileType>
<uap:FileType>.avi</uap:FileType>
<uap:FileType>.avs</uap:FileType>
<uap:FileType>.dav</uap:FileType>
<uap:FileType>.flv</uap:FileType>
<uap:FileType>.h264</uap:FileType>
<uap:FileType>.h265</uap:FileType>
<uap:FileType>.hevc</uap:FileType>
<uap:FileType>.m2t</uap:FileType>
<uap:FileType>.m2ts</uap:FileType>
<uap:FileType>.m2v</uap:FileType>
<uap:FileType>.m4v</uap:FileType>
<uap:FileType>.mkv</uap:FileType>
<uap:FileType>.mov</uap:FileType>
<uap:FileType>.mp4</uap:FileType>
<uap:FileType>.mpeg</uap:FileType>
<uap:FileType>.mpg</uap:FileType>
<uap:FileType>.mpv</uap:FileType>
<uap:FileType>.mts</uap:FileType>
<uap:FileType>.ts</uap:FileType>
<uap:FileType>.vob</uap:FileType>
<uap:FileType>.vpy</uap:FileType>
<uap:FileType>.webm</uap:FileType>
<uap:FileType>.wmv</uap:FileType>
<uap:FileType>.y4m</uap:FileType>
</uap:SupportedFileTypes>
</uap:FileTypeAssociation>
</uap:Extension>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="audiotypes">
<uap:SupportedFileTypes>
<uap:FileType>.aac</uap:FileType>
<uap:FileType>.ac3</uap:FileType>
<uap:FileType>.dts</uap:FileType>
<uap:FileType>.dtshd</uap:FileType>
<uap:FileType>.dtshr</uap:FileType>
<uap:FileType>.dtsma</uap:FileType>
<uap:FileType>.eac3</uap:FileType>
<uap:FileType>.flac</uap:FileType>
<uap:FileType>.m4a</uap:FileType>
<uap:FileType>.mka</uap:FileType>
<uap:FileType>.mp2</uap:FileType>
<uap:FileType>.mp3</uap:FileType>
<uap:FileType>.mpa</uap:FileType>
<uap:FileType>.mpc</uap:FileType>
<uap:FileType>.ogg</uap:FileType>
<uap:FileType>.opus</uap:FileType>
<uap:FileType>.thd</uap:FileType>
<uap:FileType>.w64</uap:FileType>
<uap:FileType>.wav</uap:FileType>
</uap:SupportedFileTypes>
</uap:FileTypeAssociation>
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="ytdl" />
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="rtsp" />
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="srt" />
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="srtp" />
</uap:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'">
<VisualStudioVersion>15.0</VisualStudioVersion>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
</PropertyGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
<PropertyGroup>
<ProjectGuid>81daee3a-76ff-4494-9384-d28a651d70bb</ProjectGuid>
<TargetPlatformVersion>10.0.22000.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.14393.0</TargetPlatformMinVersion>
<DefaultLanguage>en-US</DefaultLanguage>
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\mpv.net.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundlePlatforms>x64</AppxBundlePlatforms>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<Content Include="..\bin\MediaInfo.dll">
<Link>mpv.net\MediaInfo.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\bin\Microsoft.Management.Infrastructure.dll">
<Link>mpv.net\Microsoft.Management.Infrastructure.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\bin\libmpv-2.dll">
<Link>mpv.net\libmpv-2.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\bin\mpvnet.com">
<Link>mpv.net\mpvnet.com</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\SplashScreen.scale-200.png" />
<Content Include="Images\LockScreenLogo.scale-200.png" />
<Content Include="Images\Square150x150Logo.scale-200.png" />
<Content Include="Images\Square44x44Logo.scale-200.png" />
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Images\StoreLogo.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.19041.8" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\mpv.net.csproj" />
</ItemGroup>
</Project>

View File

@@ -7,7 +7,6 @@ using MpvNet.Help;
using MpvNet.Windows.UI;
using MpvNet.Windows.Help;
using MpvNet.Windows.WPF;
using System.Diagnostics;
namespace MpvNet.Windows;
@@ -97,10 +96,13 @@ static class Program
return;
}
if (App.CommandLine.Contains("--o="))
if (ProcessCommandLineArguments())
Environment.GetCommandLineArgs();
else if (App.CommandLine.Contains("--o="))
{
App.AutoLoadFolder = false;
Player.Init(IntPtr.Zero);
Player.ProcessCommandLineArgsPost();
Player.ProcessCommandLineFiles();
Player.SetPropertyString("idle", "no");
Player.EventLoop();
@@ -119,4 +121,48 @@ static class Program
Terminal.WriteError(ex);
}
}
static bool ProcessCommandLineArguments()
{
foreach (string arg in Environment.GetCommandLineArgs().Skip(1))
{
if (arg == "--profile=help")
{
Player.Init(IntPtr.Zero, false);
Console.WriteLine(Player.GetProfiles());
Player.Destroy();
return true;
}
else if (arg == "--vd=help" || arg == "--ad=help")
{
Player.Init(IntPtr.Zero, false);
Console.WriteLine(Player.GetDecoders());
Player.Destroy();
return true;
}
else if (arg == "--audio-device=help")
{
Player.Init(IntPtr.Zero, false);
Console.WriteLine(Player.GetPropertyOsdString("audio-device-list"));
Player.Destroy();
return true;
}
else if (arg == "--input-keylist")
{
Player.Init(IntPtr.Zero, false);
Console.WriteLine(Player.GetPropertyString("input-key-list").Replace(",", BR));
Player.Destroy();
return true;
}
else if (arg == "--version")
{
Player.Init(IntPtr.Zero, false);
Console.WriteLine(AppClass.About);
Player.Destroy();
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,7 @@
{
"profiles": {
"MpvNet.Windows": {
"commandName": "Project"
}
}
}

View File

@@ -3,7 +3,7 @@ name = process-instance
file = mpvnet
default = single
directory = General
help = Defines if more then one mpv.net process is allowed. (mpv.net option)\n\nMulti can alternatively be enabled by pressing the SHIFT key.
help = Defines if more then one mpv.net process is allowed.\nMulti can alternatively be enabled by pressing the SHIFT key. (mpv.net option)
option = multi Create a new process everytime the shell starts mpv.net
option = single Force a single process everytime the shell starts mpv.net
option = queue Force a single process and add files to playlist
@@ -39,6 +39,26 @@ directory = General
width = 500
help = Image file extensions used to create file associations and used by the auto-load-folder feature. (mpv.net option)
name = menu-syntax
file = mpvnet
directory = General
help = Used menu syntax for defining the context menu in input.conf.\nmpv.net by default uses '#menu:', uosc uses '#!' by default.
name = video-sync
file = mpv
default = audio
directory = General
help = How the player synchronizes audio and video.\n\nFor more information visit:
url = https://mpv.io/manual/master/#options-video-sync
option = audio
option = display-resample
option = display-resample-vdrop
option = display-resample-desync
option = display-vdrop
option = display-adrop
option = display-desync
option = desync
name = debug-mode
file = mpvnet
default = no
@@ -68,20 +88,20 @@ option = auto enable best hw decoder
option = yes exactly the same as auto
option = auto-copy enable best hw decoder with copy-back
option = auto-safe enable any whitelisted hw decoder
option = dxva2 requires vo=gpu with gpu-context=d3d11, gpu-context=angle or gpu-context=dxinterop (Windows only)
option = dxva2-copy copies video back to system RAM (Windows only)
option = d3d11va requires vo=gpu with gpu-context=d3d11 or gpu-context=angle (Windows 8+ only)
option = d3d11va-copy copies video back to system RAM (Windows 8+ only)
option = dxva2 requires vo=gpu with gpu-context=d3d11, gpu-context=angle or gpu-context=dxinterop
option = dxva2-copy copies video back to system RAM
option = d3d11va requires vo=gpu with gpu-context=d3d11 or gpu-context=angle
option = d3d11va-copy copies video back to system RAM
option = cuda requires vo=gpu (Any platform CUDA is available)
option = cuda-copy copies video back to system RAM (Any platform CUDA is available)
option = cuda-copy copies video back to system RAM
option = nvdec requires vo=gpu (Any platform CUDA is available)
option = nvdec-copy copies video back to system RAM (Any platform CUDA is available)
option = nvdec-copy copies video back to system RAM
name = gpu-api
file = mpv
default = auto
directory = 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.
directory = Video/Render Options
help = Controls which type of graphics APIs will be accepted. On Windows this defaults to d3d11 and should not be changed without a good reason.
option = auto Use any available API
option = d3d11 Allow only gpu-context=d3d11
option = opengl Allow only OpenGL (requires OpenGL 2.1+ or GLES 2.0+)
@@ -90,7 +110,7 @@ option = vulkan Allow only Vulkan
name = gpu-context
file = mpv
default = auto
directory = Video
directory = Video/Render Options
option = auto auto-select
option = d3d11 Win32, with native Direct3D 11 rendering.
option = angle Direct3D11 through the OpenGL ES translation layer ANGLE. This supports almost everything the win backend does (if the ANGLE build is new enough).
@@ -98,74 +118,52 @@ option = win Win32/WGL
option = dxinterop (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.
option = winvk VK_KHR_win32_surface
name = video-sync
file = mpv
default = audio
directory = Video
help = How the player synchronizes audio and video.\n\nFor more information visit:
url = https://mpv.io/manual/master/#options-video-sync
option = audio
option = display-resample
option = display-resample-vdrop
option = display-resample-desync
option = display-vdrop
option = display-adrop
option = display-desync
option = desync
name = scale
file = mpv
default = bilinear
directory = Video
default = lanczos
directory = Video/Render Options/Scaling
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
option = bilinear Bilinear hardware texture filtering (fastest, very low quality).
option = spline36 Mid quality and speed. This is the default when using gpu-hq.
option = lanczos 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)
option = ewa_lanczos 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)
option = ewa_lanczossharp 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.
option = mitchell 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).
option = oversample 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).
url = https://mpv.io/manual/master/#options-scale
option = bilinear
option = spline36
option = lanczos
option = ewa_lanczos
option = ewa_lanczossharp
option = mitchell
option = oversample
name = cscale
file = mpv
default = bilinear
directory = Video
directory = Video/Render Options/Scaling
help = As scale, but for interpolating chroma information. If the image is not subsampled, this option is ignored entirely.
option = bilinear Bilinear hardware texture filtering (fastest, very low quality).
option = spline36 Mid quality and speed. This is the default when using gpu-hq.
option = lanczos 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)
option = ewa_lanczos 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)
option = ewa_lanczossharp 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.
option = mitchell 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).
option = oversample 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).
url = https://mpv.io/manual/master/#options-cscale
option = bilinear
option = spline36
option = lanczos
option = ewa_lanczos
option = ewa_lanczossharp
option = mitchell
option = oversample
name = dscale
file = mpv
default =
directory = Video
default = lanczos
directory = Video/Render Options/Scaling
help = Like scale, but apply these filters on downscaling instead. \nIf no option is selected, it will keep the same with the upscaler.
option = bilinear Bilinear hardware texture filtering (fastest, very low quality).
option = spline36 Mid quality and speed. This is the default when using gpu-hq.
option = lanczos 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)
option = ewa_lanczos 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)
option = ewa_lanczossharp 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.
option = mitchell 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).
option = oversample 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 = dither-depth
file = mpv
default = no
directory = 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.
option = no Disable any dithering done by mpv.
option = auto Automatic selection. If output bit depth cannot be detected, 8 bits per component are assumed.
option = 8 Dither to 8 bit output.
option = 10 Dither to 10 bit output.
url = https://mpv.io/manual/master/#options-dscale
option = bilinear
option = spline36
option = lanczos
option = ewa_lanczos
option = ewa_lanczossharp
option = mitchell
option = oversample
name = correct-downscaling
file = mpv
default = no
directory = Video
directory = Video/Render Options/Scaling
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.
option = yes
option = no
@@ -173,32 +171,34 @@ option = no
name = sigmoid-upscaling
file = mpv
default = no
directory = Video
directory = Video/Render Options/Scaling
help = When upscaling, use a sigmoidal color transform to avoid emphasizing ringing artifacts. This also implies linear-scaling.
option = yes
option = no
name = dither-depth
file = mpv
default = no
directory = Video/Render Options
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.
option = no Disable any dithering done by mpv.
option = auto Automatic selection. If output bit depth cannot be detected, 8 bits per component are assumed.
option = 8 Dither to 8 bit output.
option = 10 Dither to 10 bit output.
name = deband
file = mpv
default = no
directory = Video
directory = Video/Render Options
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.
option = yes
option = no
name = d3d11va-zero-copy
file = mpv
default = no
directory = 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.)
option = yes
option = no
name = hdr-compute-peak
file = mpv
default = auto
directory = 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.
directory = Video/Render Options
url = https://mpv.io/manual/master/#options-hdr-compute-peak
option = auto
option = yes
option = no
@@ -206,11 +206,226 @@ option = no
name = allow-delayed-peak-detect
file = mpv
default = yes
directory = Video
help = When using --hdr-compute-peak, allow delaying the detected peak by a frame when beneficial for performance. In particular, this is required to avoid an unnecessary FBO indirection when no advanced rendering is required otherwise. Has no effect if there already is an indirect pass, such as when advanced scaling is enabled. (Only affects --vo=gpu-next, note that --vo=gpu always delays the peak.)
directory = Video/Render Options
url = https://mpv.io/manual/master/#options-allow-delayed-peak-detect
option = yes
option = no
name = d3d11va-zero-copy
file = mpv
default = no
directory = Video/Render Options
url = https://mpv.io/manual/master/#options-d3d11va-zero-copy
option = yes
option = no
name = background
file = mpv
directory = Video/Render Options
help = Color used to draw parts of the mpv window not covered by video. See the --sub-color option for how colors are defined.
name = libplacebo-opts
file = mpv
directory = Video/Render Options
help = Passes extra raw option to the libplacebo rendering backend (used by --vo=gpu-next). May override the effects of any other options set using the normal options system.
name = preset
file = libplacebo
directory = Video/libplacebo
default = default
help = Override all libplacebo options by the values from the given preset.
url = https://libplacebo.org/options/#presetdefaultfasthigh_quality
option = default Default settings, tuned to provide a balance of performance and quality.
option = fast Disable all advanced rendering, equivalent to passing no to every option.
option = high_quality Reset all everything to high quality presets (where available).
name = upscaler
file = libplacebo
directory = Video/libplacebo/Scaling
default = lanczos
help = Sets the filter used for upscaling.
url = https://libplacebo.org/options/#upscalerfilter
option-name-width = 135
option = none No filter, only use basic GPU texture sampling.
option = nearest Nearest-neighbour (box) sampling (very fast).
option = bilinear Bilinear sampling (very fast).
option = oversample Aspect-ratio preserving nearest neighbour sampling (very fast).
option = bicubic Bicubic interpolation (fast).
option = gaussian Gaussian smoothing (fast).
option = catmull_rom Catmull-Rom cubic spline.
option = lanczos Lanczos reconstruction.
option = ewa_lanczos EWA Lanczos ("Jinc") reconstruction (slow).
option = ewa_lanczossharp Sharpened version of ewa_lanczos (slow).
option = ewa_lanczos4sharpest Very sharp version of ewa_lanczos, with anti-ringing (very slow).
name = downscaler
file = libplacebo
directory = Video/libplacebo/Scaling
default = hermite
help = Sets the filter used for downscaling. The most relevant options, roughly ordered from fastest to slowest.
option = none Use the same filter as specified for upscaler
option = box Box averaging (very fast)
option = hermite Hermite-weighted averaging (fast)
option = bilinear Bilinear (triangle) averaging (fast)
option = bicubic Bicubic interpolation (fast)
option = gaussian Gaussian smoothing (fast)
option = catmull_rom Catmull-Rom cubic spline
option = mitchell Mitchell-Netravalia cubic spline
option = lanczos Lanczos reconstruction
name = plane_upscaler
file = libplacebo
directory = Video/libplacebo/Scaling
default = none
help = Override the filter used for upscaling planes, e.g. chroma/alpha. If set to none, use the same setting as upscaler, respectively.
option = none Use the same filter as specified for upscaler
option = box Box averaging (very fast)
option = hermite Hermite-weighted averaging (fast)
option = bilinear Bilinear (triangle) averaging (fast)
option = bicubic Bicubic interpolation (fast)
option = gaussian Gaussian smoothing (fast)
option = catmull_rom Catmull-Rom cubic spline
option = mitchell Mitchell-Netravalia cubic spline
option = lanczos Lanczos reconstruction
name = plane_downscaler
file = libplacebo
directory = Video/libplacebo/Scaling
default = none
help = Override the filter used for downscaling planes, e.g. chroma/alpha. If set to none, use the same setting as downscaler, respectively.
option = none Use the same filter as specified for upscaler
option = box Box averaging (very fast)
option = hermite Hermite-weighted averaging (fast)
option = bilinear Bilinear (triangle) averaging (fast)
option = bicubic Bicubic interpolation (fast)
option = gaussian Gaussian smoothing (fast)
option = catmull_rom Catmull-Rom cubic spline
option = mitchell Mitchell-Netravalia cubic spline
option = lanczos Lanczos reconstruction
name = frame_mixer
file = libplacebo
directory = Video/libplacebo/Scaling
default = oversample
help = Sets the filter used for frame mixing (temporal interpolation). Roughly ordered from fastest to slowest.
option = none Disable frame mixing, show nearest frame to target PTS
option = oversample Oversampling, only mix "edge" frames while preserving FPS
option = hermite Hermite-weighted frame mixing
option = linear Linear frame mixing
option = cubic Cubic B-spline frame mixing
name = antiringing_strength
file = libplacebo
directory = Video/libplacebo/Scaling
help = <0.0..1.0> Antiringing strength to use for all filters. A value of 0.0 disables antiringing, and a value of 1.0 enables full-strength antiringing. Defaults to 0.0.
name = deband
file = libplacebo
directory = Video/libplacebo/Debanding
help = Enables debanding using libplacebo.
default = no
option = yes
option = no
name = deband_iterations
file = libplacebo
directory = Video/libplacebo/Debanding
help = <0..16> The number of debanding steps to perform per sample. Each step reduces a bit more banding, but takes time to compute. Note that the strength of each step falls off very quickly, so high numbers (>4) are practically useless. Defaults to 1.
name = deband_threshold
file = libplacebo
directory = Video/libplacebo/Debanding
help = <0.0..1000.0> The debanding filter's cut-off threshold. Higher numbers increase the debanding strength dramatically, but progressively diminish image details. Defaults to 3.0.
name = deband_radius
file = libplacebo
directory = Video/libplacebo/Debanding
help = <0.0..1000.0> The debanding filter's initial radius. The radius increases linearly for each iteration. A higher radius will find more gradients, but a lower radius will smooth more aggressively. Defaults to 16.0.
name = deband_grain
file = libplacebo
directory = Video/libplacebo/Debanding
help = <0.0..1000.0> Add some extra noise to the image. This significantly helps cover up remaining quantization artifacts. Higher numbers add more noise. Defaults to 4.0, which is very mild.
name = sigmoid
file = libplacebo
directory = Video/libplacebo/Sigmoidization
help = Enables sigmoidization.
default = yes
option = yes
option = no
name = sigmoid_center
file = libplacebo
directory = Video/libplacebo/Sigmoidization
help = <0.0..1.0> The center (bias) of the sigmoid curve. Defaults to 0.75.
name = sigmoid_slope
file = libplacebo
directory = Video/libplacebo/Sigmoidization
help = <1.0..20.0> The slope (steepness) of the sigmoid curve. Defaults to 6.5.
name = screenshot-directory
file = mpv
width = 500
type = folder
directory = Video/Screenshot
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.
name = screenshot-format
file = mpv
default = jpg
directory = Video/Screenshot
help = Set the image file type used for saving screenshots.
option = jpg
option = png
name = screenshot-tag-colorspace
file = mpv
default = no
directory = Video/Screenshot
help = Tag screenshots with the appropriate colorspace. Note that not all formats are supported.
option = yes
option = no
name = screenshot-high-bit-depth
file = mpv
default = yes
directory = Video/Screenshot
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.
option = yes
option = no
name = screenshot-jpeg-source-chroma
file = mpv
default = yes
directory = Video/Screenshot
help = Write JPEG files with the same chroma subsampling as the video. If disabled, the libjpeg default is used.
option = yes
option = no
name = screenshot-template
file = mpv
directory = Video/Screenshot
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
name = screenshot-jpeg-quality
file = mpv
directory = Video/Screenshot
help = <0-100> Set the JPEG quality level. Higher means better quality. The default is 90.
name = screenshot-png-compression
file = mpv
directory = Video/Screenshot
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.
name = screenshot-png-filter
file = mpv
directory = Video/Screenshot
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.
name = volume
file = mpv
directory = Audio
@@ -228,7 +443,7 @@ name = alang
file = mpv
directory = 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.
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.
name = audio-file-auto
file = mpv
@@ -293,10 +508,16 @@ type = color
directory = 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.
name = title
file = mpv
directory = Window
width = 400
help = Set the window title. This is used for the video window, and if possible, also sets the audio stream title. Properties are expanded. Warning! There is a danger of this causing significant CPU usage, depending on the properties used.
name = fullscreen
file = mpv
default = no
directory = Screen
directory = Window
help = Start the player in fullscreen mode.
option = yes
option = no
@@ -304,71 +525,84 @@ option = no
name = border
file = mpv
default = yes
directory = Screen
directory = Window
help = Show window with decoration (titlebar, border).
option = yes
option = no
name = screen
file = mpv
directory = Screen
directory = Window
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.
name = taskbar-progress
file = mpv
default = yes
directory = Window
help = Show progress in taskbar.
option = yes
option = no
name = osd-playing-msg
file = mpv
width = 300
directory = Screen
directory = Window
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
name = osd-font-size
file = mpv
directory = Screen
directory = Window
help = Specify the OSD font size. See sub-font-size for details. Default: 55
name = osd-duration
file = mpv
directory = Screen
directory = Window
help = Set the duration of the OSD messages in ms. Default: 1000
name = osd-scale-by-window
file = mpv
default = yes
directory = Screen
directory = Window
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.
option = yes
option = no
name = autofit
file = mpv
directory = Screen
directory = Window
help = <int> Initial window height in percent. Default: 60
name = autofit-image
file = mpvnet
directory = Screen
help = <int> Initial window height in percent for image files. Default: 80
name = autofit-audio
file = mpvnet
directory = Screen
help = <int> Initial window height in percent for audio files. Default: 70
name = autofit-smaller
file = mpv
directory = Screen
directory = Window
help = <int> Minimum window height in percent. Default: 10
name = autofit-larger
file = mpv
directory = Screen
directory = Window
help = <int> Maximum window height in percent. Default: 80
name = autofit-image
file = mpvnet
directory = Window
help = <int> Initial window height in percent for image files. Default: 80
name = autofit-audio
file = mpvnet
directory = Window
help = <int> Initial window height in percent for audio files. Default: 70
name = geometry
file = mpvnet
directory = Window
help = <x:y> Initial window location in percent. Default: 50:50 (centered)\n\nx=0 docks the window to the left side.\nx=100 docks the window to the right side.\ny=0 docks the window to the top side.\ny=100 docks the window to the bottom side.
name = start-size
file = mpvnet
default = height-session
directory = Screen
directory = Window
help = Setting to remember the window size. (mpv.net option)
option = width-session Window width is remembered in the current session
option = width-always Window width is always remembered
@@ -381,25 +615,25 @@ option = always Window size is always remembered
name = keepaspect-window
file = mpv
default = yes
directory = Screen
directory = Window
help = keepaspect-window will lock the window size to the video aspect. Default: yes
option = yes
option = no
name = minimum-aspect-ratio
file = mpvnet
directory = Screen
directory = Window
help = <float> Minimum aspect ratio of the window. Useful to force a wider window and therefore a larger OSC. (mpv.net option)
name = minimum-aspect-ratio-audio
file = mpvnet
directory = Screen
directory = Window
help = Same as minimum-aspect-ratio but used for audio files.
name = remember-window-position
file = mpvnet
default = no
directory = Screen
directory = Window
help = Save the window position on exit. (mpv.net option)
option = yes
option = no
@@ -407,7 +641,7 @@ option = no
name = snap-window
file = mpv
default = no
directory = Screen
directory = Window
help = Snap the player window to screen edges.
option = yes
option = no
@@ -415,24 +649,11 @@ option = no
name = window-maximized
file = mpv
default = no
directory = Screen
directory = Window
help = Start with a maximized window.
option = yes
option = no
name = start-threshold
file = mpvnet
directory = 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 option)
name = taskbar-progress
file = mpv
default = yes
directory = Playback
help = Show progress in taskbar.
option = yes
option = no
name = keep-open
file = mpv
default = no
@@ -508,6 +729,12 @@ help = For single files automatically load the entire directory into the playlis
option = yes
option = no
name = reset-on-next-file
file = mpv
directory = Program Behavior
help = Normally, mpv will try to keep all settings when playing the next file on the playlist, even if they were changed by the user during playback. This can be changed with this option. It accepts a list of options, and mpv will reset the value of these options on playback start to the initial value.
width = 400
name = input-ar-delay
file = mpv
directory = Input
@@ -518,6 +745,12 @@ file = mpv
directory = Input
help = Number of key presses to generate per second on autorepeat.
name = input-ipc-server
file = mpv
directory = Input
help = Enable the IPC support and create the listening socket at the given path.
url = https://mpv.io/manual/master/#options-input-ipc-server
name = language
file = mpvnet
default = system
@@ -550,67 +783,6 @@ directory = UI
url = https://github.com/mpvnet-player/mpv.net/blob/main/docs/manual.md#color-theme
help = Color theme used in light mode.\nmpv.net must be restarted after a change.\nmpv.net specific option. Default: light
name = screenshot-directory
file = mpv
width = 500
type = folder
directory = Video/Screenshot
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.
name = screenshot-format
file = mpv
default = jpg
directory = Video/Screenshot
help = Set the image file type used for saving screenshots.
option = jpg
option = png
name = screenshot-tag-colorspace
file = mpv
default = no
directory = Video/Screenshot
help = Tag screenshots with the appropriate colorspace. Note that not all formats are supported.
option = yes
option = no
name = screenshot-high-bit-depth
file = mpv
default = yes
directory = Video/Screenshot
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.
option = yes
option = no
name = screenshot-jpeg-source-chroma
file = mpv
default = yes
directory = Video/Screenshot
help = Write JPEG files with the same chroma subsampling as the video. If disabled, the libjpeg default is used.
option = yes
option = no
name = screenshot-template
file = mpv
directory = Video/Screenshot
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
name = screenshot-jpeg-quality
file = mpv
directory = Video/Screenshot
help = <0-100> Set the JPEG quality level. Higher means better quality. The default is 90.
name = screenshot-png-compression
file = mpv
directory = Video/Screenshot
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.
name = screenshot-png-filter
file = mpv
directory = Video/Screenshot
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.
name = cache
file = mpv
default = auto

View File

@@ -16,6 +16,7 @@ public abstract class Setting
public string? Value { get; set; }
public int Width { get; set; }
public int OptionNameWidth { get; set; } = 100;
public ConfItem? ConfItem { get; set; }
}
@@ -35,12 +36,13 @@ public class OptionSettingOption
public string? Name { get; set; }
public string? Help { get; set; }
public int OptionWidth { get => OptionSetting!.OptionNameWidth; }
public OptionSetting? OptionSetting { get; set; }
public string? Text
{
get => string.IsNullOrEmpty(_text) ? Name : _text;
get => _text ?? Name;
set => _text = value;
}

View File

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

using System.Windows.Controls;
using System.Windows;
namespace MpvNet.Windows.WPF;
public class ComboBoxTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ContentPresenter presenter = (ContentPresenter)container;
if (presenter.TemplatedParent is ComboBox)
return (DataTemplate)presenter.FindResource("ComboBoxCollapsedDataTemplate");
else // Templated parent is ComboBoxItem
return (DataTemplate)presenter.FindResource("ComboBoxExpandedDataTemplate");
}
}

View File

@@ -28,8 +28,8 @@
<KeyBinding Key="n" Modifiers="Ctrl" Command="{Binding ShowMpvNetSpecificSettingsCommand}"/>
<KeyBinding Key="F5" Command="{Binding PreviewMpvConfFileCommand}"/>
<KeyBinding Key="F6" Command="{Binding PreviewMpvNetConfFileCommand}"/>
<KeyBinding Key="F1" Command="{Binding ShowMpvManualCommand}"/>
<KeyBinding Key="F2" Command="{Binding ShowMpvNetManualCommand}"/>
<KeyBinding Key="F1" Modifiers="Ctrl" Command="{Binding ShowMpvManualCommand}"/>
<KeyBinding Key="F2" Modifiers="Ctrl" Command="{Binding ShowMpvNetManualCommand}"/>
</Window.InputBindings>
<Grid>
@@ -40,7 +40,7 @@
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170" />
<ColumnDefinition Width="180" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
@@ -135,12 +135,12 @@
<Separator />
<MenuItem
Header="Show mpv manual"
InputGestureText="F1"
InputGestureText="Ctrl+F1"
Command="{Binding Data.ShowMpvManualCommand, Source={StaticResource BindingProxy}}"
/>
<MenuItem
Header="Show mpv.net manual"
InputGestureText="F2"
InputGestureText="Ctrl+F2"
Command="{Binding Data.ShowMpvNetManualCommand, Source={StaticResource BindingProxy}}"
/>
</ContextMenu>

View File

@@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
@@ -34,19 +35,20 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
DataContext = this;
LoadConf(Player.ConfPath);
LoadConf(App.ConfPath);
LoadLibplaceboConf();
LoadSettings();
InitialContent = GetCompareString();
if (string.IsNullOrEmpty(App.Settings.ConfigEditorSearch))
SearchControl.Text = "General:";
SearchText = "General:";
else
SearchControl.Text = App.Settings.ConfigEditorSearch;
SearchText = App.Settings.ConfigEditorSearch;
foreach (var node in Nodes)
SelectNodeFromSearchText(node);
foreach (var node in Nodes)
ExpandNode(node);
node.IsExpanded = true;
}
public Theme? Theme => Theme.Current;
@@ -82,6 +84,9 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
public static TreeNode? AddNode(IList<TreeNode> nodes, string path)
{
if (string.IsNullOrEmpty(path))
return null;
string[] parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
for (int x = 0; x < parts.Length; x++)
@@ -127,14 +132,16 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
if (!FilterStrings.Contains(setting.Directory!))
FilterStrings.Add(setting.Directory!);
foreach (ConfItem confItem in ConfItems)
foreach (ConfItem item in ConfItems)
{
if (setting.Name == confItem.Name && confItem.Section == "" && !confItem.IsSectionItem)
if (setting.Name == item.Name &&
setting.File == item.File &&
item.Section == "" && !item.IsSectionItem)
{
setting.Value = confItem.Value.Trim('\'', '"');
setting.Value = item.Value;
setting.StartValue = setting.Value;
setting.ConfItem = confItem;
confItem.SettingBase = setting;
setting.ConfItem = item;
item.SettingBase = setting;
}
}
@@ -144,7 +151,10 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
MainStackPanel.Children.Add(new StringSettingControl(s) { Visibility = Visibility.Collapsed });
break;
case OptionSetting s:
MainStackPanel.Children.Add(new OptionSettingControl(s) { Visibility = Visibility.Collapsed });
if (s.Options.Count > 3)
MainStackPanel.Children.Add(new ComboBoxSettingControl(s) { Visibility = Visibility.Collapsed });
else
MainStackPanel.Children.Add(new OptionSettingControl(s) { Visibility = Visibility.Collapsed });
break;
}
}
@@ -155,11 +165,20 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
App.Settings.ConfigEditorSearch = SearchControl.Text;
App.Settings.ConfigEditorSearch = SearchText;
if (InitialContent == GetCompareString())
return;
foreach (Setting setting in Settings)
{
if (setting.Name == "libplacebo-opts")
{
setting.Value = GetKeyValueContent("libplacebo");
break;
}
}
File.WriteAllText(Player.ConfPath, GetContent("mpv"));
File.WriteAllText(App.ConfPath, GetContent("mpvnet"));
@@ -201,10 +220,7 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
DispatcherPriority.Background);
}
string GetCompareString()
{
return string.Join("", Settings.Select(item => item.Name + item.Value).ToArray());
}
string GetCompareString() => string.Join("", Settings.Select(item => item.Name + item.Value).ToArray());
void LoadConf(string file)
{
@@ -221,14 +237,10 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
string line = currentLine.Trim();
if (line == "")
{
comment += "\r\n";
}
else if (line.StartsWith("#"))
{
comment += line.Trim() + "\r\n";
}
else if (line.StartsWith("[") && line.Contains("]"))
else if (line.StartsWith("[") && line.Contains(']'))
{
if (!isSectionItem && comment != "" && comment != "\r\n")
ConfItems.Add(new ConfItem() {
@@ -238,9 +250,12 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
comment = "";
isSectionItem = true;
}
else if (line.Contains("="))
else if (line.Contains('=') || Regex.Match(line, "^[\\w-]+$").Success)
{
ConfItem item = new ConfItem();
if (!line.Contains('='))
line += "=yes";
ConfItem item = new();
item.File = Path.GetFileNameWithoutExtension(file);
item.IsSectionItem = isSectionItem;
item.Comment = comment;
@@ -248,15 +263,21 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
item.Section = section;
section = "";
if (line.Contains("#") && !line.Contains("'") && !line.Contains("\""))
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 left = line.Substring(0, pos).Trim().ToLower().TrimStart('-');
string right = line.Substring(pos + 1).Trim();
if (right.StartsWith('\'') && right.EndsWith('\''))
right = right.Trim('\'');
if (right.StartsWith('"') && right.EndsWith('"'))
right = right.Trim('"');
if (left == "fs")
left = "fullscreen";
@@ -271,6 +292,67 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
}
}
string GetKeyValueContent(string filename)
{
List<string> pairs = new();
foreach (Setting setting in Settings)
{
if (filename != setting.File)
continue;
if ((setting.Value ?? "") != setting.Default)
pairs.Add(setting.Name + "=" + EscapeValue(setting.Value!));
}
return string.Join(',', pairs);
}
void LoadLibplaceboConf()
{
foreach (ConfItem item in ConfItems.ToArray())
if (item.Name == "libplacebo-opts")
LoadKeyValueList(item.Value, "libplacebo");
}
void LoadKeyValueList(string options, string file)
{
string[] optionStrings = options.Split(",", StringSplitOptions.RemoveEmptyEntries);
foreach (string pair in optionStrings)
{
if (!pair.Contains('='))
continue;
int pos = pair.IndexOf("=");
string left = pair.Substring(0, pos).Trim().ToLower();
string right = pair.Substring(pos + 1).Trim();
ConfItem item = new();
item.Name = left;
item.Value = right;
item.File = file;
ConfItems.Add(item);
}
}
string EscapeValue(string value)
{
if (value.Contains('\''))
return '"' + value + '"';
if (value.Contains('"'))
return '\'' + value + '\'';
if (value.Contains('"') || value.Contains('#') || value.StartsWith("%") ||
value.StartsWith(" ") || value.EndsWith(" "))
{
return '\'' + value + '\'';
}
return value;
}
string GetContent(string filename)
{
StringBuilder sb = new StringBuilder();
@@ -288,7 +370,7 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
{
if (item.Name != "")
{
sb.Append(item.Name + " = " + item.Value);
sb.Append(item.Name + " = " + EscapeValue(item.Value));
if (item.LineComment != "")
sb.Append(" " + item.LineComment);
@@ -299,17 +381,7 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
}
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);
sb.Append(item.Name + " = " + EscapeValue(item.SettingBase.Value!));
if (item.LineComment != "")
sb.Append(" " + item.LineComment);
@@ -319,28 +391,13 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
}
}
if (!sb.ToString().Contains("# Editor"))
sb.AppendLine("# Editor");
foreach (Setting setting in Settings)
{
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);
}
sb.AppendLine(setting.Name + " = " + EscapeValue(setting.Value!));
}
foreach (ConfItem item in ConfItems)
@@ -359,7 +416,7 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
if (item.Comment != "")
sb.Append(item.Comment);
sb.Append(item.Name + " = " + item.Value);
sb.Append(item.Name + " = " + EscapeValue(item.Value));
if (item.LineComment != "")
sb.Append(" " + item.LineComment);
@@ -443,9 +500,10 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
void SelectNodeFromSearchText(NodeViewModel node)
{
if (node.Path + ":" == SearchControl.Text)
if (node.Path + ":" == SearchText)
{
node.IsSelected = true;
node.IsExpanded = true;
return;
}
@@ -462,15 +520,7 @@ public partial class ConfWindow : Window, INotifyPropertyChanged
UnselectNode(it);
}
void ExpandNode(NodeViewModel node)
{
node.IsExpanded = true;
foreach (var it in node.Children)
ExpandNode(it);
}
[RelayCommand] void ShowMpvNetSpecificSettings() => SearchControl.Text = "mpv.net";
[RelayCommand] void ShowMpvNetSpecificSettings() => SearchText = "mpv.net";
[RelayCommand] void PreviewMpvConfFile() => Msg.ShowInfo(GetContent("mpv"));

View File

@@ -0,0 +1,72 @@
<UserControl
x:Name="ComboBoxSettingControl1"
x:Class="MpvNet.Windows.WPF.ComboBoxSettingControl"
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:MpvNet.Windows.WPF"
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="{Binding Theme.Heading}"
Background="{Binding Theme.Background}"
/>
<ComboBox
Name="ComboBoxControl"
Width="200"
HorizontalAlignment="Left"
MaxDropDownHeight="2000"
Foreground="{Binding Theme.Foreground}"
Background="{Binding Theme.Background}"
SelectionChanged="ComboBoxControl_SelectionChanged"
>
<ComboBox.ItemTemplateSelector>
<local:ComboBoxTemplateSelector/>
</ComboBox.ItemTemplateSelector>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="3"/>
</Style>
</Style.Resources>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
<TextBox
x:Name="HelpTextBox"
TextWrapping="WrapWithOverflow"
BorderThickness="0"
IsReadOnly="True"
Margin="0,10,0,0"
Foreground="{Binding Theme.Foreground}"
Background="{Binding Theme.Background}"
/>
<TextBlock
x:Name="LinkTextBlock"
Margin="2,0"
>
<local:HyperlinkEx
x:Name="Link"
Foreground="{Binding Theme.Heading}"
/>
</TextBlock>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,69 @@

using System.Windows;
using System.Windows.Controls;
using MpvNet.Windows.UI;
namespace MpvNet.Windows.WPF;
public partial class ComboBoxSettingControl : UserControl, ISettingControl
{
OptionSetting OptionSetting;
public ComboBoxSettingControl(OptionSetting optionSetting)
{
OptionSetting = optionSetting;
InitializeComponent();
DataContext = this;
TitleTextBox.Text = optionSetting.Name;
if (string.IsNullOrEmpty(optionSetting.Help))
HelpTextBox.Visibility = Visibility.Collapsed;
HelpTextBox.Text = optionSetting.Help;
ComboBoxControl.ItemsSource = optionSetting.Options;
foreach (var item in optionSetting.Options)
if (item.Name == optionSetting.Value)
ComboBoxControl.SelectedItem = item;
if (string.IsNullOrEmpty(optionSetting.URL))
LinkTextBlock.Visibility = Visibility.Collapsed;
Link.SetURL(optionSetting.URL);
}
public Theme? Theme => Theme.Current;
public Setting Setting => OptionSetting;
public bool Contains(string searchString) => ContainsInternal(searchString.ToLower());
public bool ContainsInternal(string search)
{
if (TitleTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
return true;
if (HelpTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
return true;
foreach (var i in OptionSetting.Options)
{
if (i.Text?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
return true;
if (i.Help?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
return true;
if (i.Name?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
return true;
}
return false;
}
void ComboBoxControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
OptionSetting.Value = (ComboBoxControl.SelectedItem as OptionSettingOption)?.Name;
}
}

View File

@@ -4,7 +4,6 @@ using System.Windows.Navigation;
using MpvNet.Help;
// TODO: change namespace to MpvNet.Windows.WPF.Controls
namespace MpvNet.Windows.WPF;
public class HyperlinkEx : Hyperlink
@@ -20,6 +19,6 @@ public class HyperlinkEx : Hyperlink
NavigateUri = new Uri(url);
RequestNavigate += HyperLinkEx_RequestNavigate;
Inlines.Clear();
Inlines.Add(url);
Inlines.Add("Manual");
}
}

View File

@@ -65,9 +65,15 @@
Foreground="{Binding Theme.Foreground}"
Background="{Binding Theme.Background}"
/>
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
<TextBlock
x:Name="LinkTextBlock"
Margin="2,0,0,0"
>
<local:HyperlinkEx
x:Name="Link"
Foreground="{Binding Theme.Heading}"
/>
</TextBlock>
</StackPanel>
</Grid>

View File

@@ -21,6 +21,10 @@ public partial class OptionSettingControl : UserControl, ISettingControl
HelpTextBox.Visibility = Visibility.Collapsed;
HelpTextBox.Text = optionSetting.Help;
if (string.IsNullOrEmpty(optionSetting.Help))
LinkTextBlock.Margin = new Thickness(2, 6, 0, 0);
ItemsControl.ItemsSource = optionSetting.Options;
if (string.IsNullOrEmpty(optionSetting.URL))

View File

@@ -39,7 +39,7 @@ public partial class SearchControl : UserControl
{
HintTextBlock.Text = string.IsNullOrEmpty(Text) ? HintText : "";
if (string.IsNullOrEmpty(Text) || HideClearButton)
if (string.IsNullOrEmpty(Text) || HideClearButton || Text.Length > 21)
SearchClearButton.Visibility = Visibility.Hidden;
else
SearchClearButton.Visibility = Visibility.Visible;

View File

@@ -60,8 +60,14 @@
Background="{Binding Theme.Background}"
/>
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
<TextBlock
x:Name="LinkTextBlock"
Margin="2,0"
>
<local:HyperlinkEx
x:Name="Link"
Foreground="{Binding Theme.Heading}"
/>
</TextBlock>
</StackPanel>
</Grid>

View File

@@ -109,7 +109,7 @@ public partial class InputWindow : Window
else
{
newContent = InputHelp.ConvertToString(InputHelp.GetReducedBindings(Bindings));
newContent = newContent.Replace("#menu: ", "# ");
newContent = newContent.Replace(App.MenuSyntax + " ", "# ");
File.WriteAllText(App.InputConf.Path, App.InputConf.Content = newContent);
}

View File

@@ -122,39 +122,67 @@
<Border x:Name="stackButtons" Grid.Row="3" Padding="10"
Background="{Binding Path=ButtonBackground}" >
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" >
<Button x:Name="btnOK" Content=" OK " MinWidth="75" Margin="5,0,5,0"
<Button x:Name="btnOK"
Content=" OK "
MinWidth="75"
Margin="5,0,5,0"
Width="{Binding Path=ButtonWidth}"
Height="25"
MinHeight="25"
Visibility="{Binding Path=ShowOk}"
IsDefault="{Binding Path=IsDefaultOK}"
Click="BtnOK_Click" />
<Button x:Name="btnYes" Content=" Yes " MinWidth="75" Margin="5,0,5,0"
<Button x:Name="btnYes"
Content=" Yes "
Width="{Binding Path=ButtonWidth}"
MinWidth="75"
MinHeight="25"
Margin="5,0,5,0"
Visibility="{Binding Path=ShowYes}"
IsDefault="{Binding Path=IsDefaultYes}"
Click="BtnYes_Click"/>
<Button x:Name="btnNo" Content=" No " MinWidth="75" Margin="5,0,5,0"
<Button x:Name="btnNo"
Content=" No "
Width="{Binding Path=ButtonWidth}"
MinWidth="75"
MinHeight="25"
Margin="5,0,5,0"
Visibility="{Binding Path=ShowNo}"
IsDefault="{Binding Path=IsDefaultNo}"
Click="BtnNo_Click"/>
<Button x:Name="btnAbort" Content=" Abort " MinWidth="75" Margin="5,0,5,0"
<Button x:Name="btnAbort"
Content=" Abort "
Width="{Binding Path=ButtonWidth}"
MinWidth="75"
MinHeight="25"
Margin="5,0,5,0"
Visibility="{Binding Path=ShowAbort}"
IsDefault="{Binding Path=IsDefaultAbort}"
Click="BtnAbort_Click"/>
<Button x:Name="btnRetry" Content=" Retry " MinWidth="75" Margin="5,0,5,0"
<Button x:Name="btnRetry"
Content=" Retry "
Width="{Binding Path=ButtonWidth}"
MinWidth="75"
MinHeight="25"
Margin="5,0,5,0"
Visibility="{Binding Path=ShowRetry}"
IsDefault="{Binding Path=IsDefaultRetry}"
Click="BtnRetry_Click"/>
<Button x:Name="btnIgnore" Content=" Ignore " MinWidth="75" Margin="5,0,5,0"
<Button x:Name="btnIgnore"
Content=" Ignore "
Width="{Binding Path=ButtonWidth}"
MinWidth="75"
MinHeight="25"
Margin="5,0,5,0"
Visibility="{Binding Path=ShowIgnore}"
IsDefault="{Binding Path=IsDefaultIgnore}"
Click="BtnIgnore_Click"/>
<Button x:Name="btnCancel" Content=" Cancel " MinWidth="75" Margin="5,0,5,0"
<Button x:Name="btnCancel"
Content=" Cancel "
Width="{Binding Path=ButtonWidth}"
MinWidth="75"
MinHeight="25"
Margin="5,0,5,0"
Visibility="{Binding Path=ShowCancel}"
IsDefault="{Binding Path=IsDefaultCancel}"
Click="BtnCancel_Click"/>

View File

@@ -96,13 +96,13 @@ public partial class MessageBoxEx : Window, INotifyPropertyChanged
public static void SetOwner(Window window)
{
IntPtr ownerHandle = GetOwnerHandle();
IntPtr parentHandle = GetParentHandle();
if (ownerHandle != IntPtr.Zero)
new WindowInteropHelper(window).Owner = ownerHandle;
if (parentHandle != IntPtr.Zero)
new WindowInteropHelper(window).Owner = parentHandle;
}
public static IntPtr GetOwnerHandle()
public static IntPtr GetParentHandle()
{
IntPtr foregroundWindow = GetForegroundWindow();
GetWindowThreadProcessId(foregroundWindow, out var procID);

View File

@@ -6,6 +6,183 @@
xmlns:local="clr-namespace:MpvNet.Windows.WPF"
>
<DataTemplate x:Key="ComboBoxCollapsedDataTemplate" >
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
<DataTemplate x:Key="ComboBoxExpandedDataTemplate" >
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<Border>
<TextBlock
Text="{Binding Text}"
Width="{Binding OptionWidth}"
VerticalAlignment="Center"
TextWrapping="Wrap"
/>
</Border>
<Border>
<TextBlock
Text="{Binding Help}"
MaxWidth="400"
Padding="0,3"
VerticalAlignment="Center"
TextWrapping="Wrap"
/>
</Border>
</StackPanel>
</DataTemplate>
<Style x:Key="ComboBoxToggleButton" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<Border
x:Name="Border"
Grid.ColumnSpan="2"
CornerRadius="3"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
/>
<Path
x:Name="Arrow"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 1,1.5 L 4.5,5 L 8,1.5"
SnapsToDevicePixels="false"
Stroke="{TemplateBinding Foreground}"
StrokeThickness="1.5"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="ComboBoxTextBox" TargetType="TextBox">
<Border x:Name="PART_ContentHost" Focusable="True" />
</ControlTemplate>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Foreground" Value="#333" />
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryTextBrush}" />
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="FontSize" Value="13" />
<Setter Property="MinWidth" Value="150"/>
<Setter Property="MinHeight" Value="25"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<ToggleButton
Cursor="Hand"
Name="ToggleButton"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Style="{StaticResource ComboBoxToggleButton}"
Grid.Column="2"
Focusable="false"
IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"
/>
<ContentPresenter
Name="ContentSite"
IsHitTestVisible="False"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
Margin="10,3,30,3"
VerticalAlignment="Center"
HorizontalAlignment="Left"
/>
<TextBox
x:Name="PART_EditableTextBox"
Style="{x:Null}"
Template="{StaticResource ComboBoxTextBox}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="3,3,23,3"
Focusable="True"
Visibility="Hidden"
IsReadOnly="{TemplateBinding IsReadOnly}"
/>
<Popup
Name="Popup"
Placement="Bottom"
IsOpen="{TemplateBinding IsDropDownOpen}"
AllowsTransparency="True"
Focusable="False"
PopupAnimation="Slide"
>
<Grid
Name="DropDown"
SnapsToDevicePixels="True"
MinWidth="{TemplateBinding ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
>
<Border
CornerRadius="3"
x:Name="DropDownBorder"
Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}, Path=Background}"
BorderBrush="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}, Path=Foreground}"
BorderThickness="1"
/>
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
<StackPanel
IsItemsHost="True"
KeyboardNavigation.DirectionalNavigation="Contained" />
</ScrollViewer>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
<Trigger Property="IsEditable" Value="true">
<Setter Property="IsTabStop" Value="false"/>
<Setter TargetName="PART_EditableTextBox" Property="Visibility" Value="Visible"/>
<Setter TargetName="ContentSite" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.56"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" Value="#FF7EB4EA"/>
</Trigger>
<Trigger Property="IsFocused" Value="true">
<Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Heading}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="local:HyperlinkEx">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
@@ -53,20 +230,23 @@
</Style>
<Style TargetType="TextBox">
<Setter Property="MinHeight" Value="25" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border"
BorderBrush="{DynamicResource PrimaryTextBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="3"
SnapsToDevicePixels="True">
<Border
x:Name="border"
BorderBrush="{DynamicResource PrimaryTextBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="3"
SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"/>
<ScrollViewer
x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"/>
</Border>
<ControlTemplate.Triggers>
@@ -1042,5 +1222,5 @@
<Style BasedOn="{StaticResource MenuItemBaseStyle}" TargetType="MenuItem"/>
<Style BasedOn="{StaticResource MenuBaseStyle}" TargetType="Menu"/>
</ResourceDictionary>

View File

@@ -29,36 +29,35 @@ partial class MainForm
/// </summary>
void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
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 = new System.Windows.Forms.Timer(components);
ProgressTimer = new System.Windows.Forms.Timer(components);
SuspendLayout();
//
// CursorTimer
//
this.CursorTimer.Enabled = true;
this.CursorTimer.Interval = 1000;
this.CursorTimer.Tick += new System.EventHandler(this.CursorTimer_Tick);
CursorTimer.Enabled = true;
CursorTimer.Interval = 1000;
CursorTimer.Tick += CursorTimer_Tick;
//
// ProgressTimer
//
this.ProgressTimer.Tick += new System.EventHandler(this.ProgressTimer_Tick);
ProgressTimer.Tick += 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(857, 444);
this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
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);
AllowDrop = true;
AutoScaleDimensions = new System.Drawing.SizeF(288F, 288F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
BackColor = System.Drawing.Color.Black;
ClientSize = new System.Drawing.Size(1243, 720);
Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon");
Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
Name = "MainForm";
StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
ResumeLayout(false);
}
#endregion

View File

@@ -6,6 +6,7 @@ using System.Threading;
using System.Windows.Forms;
using System.Windows.Threading;
using System.Windows.Forms.Integration;
using System.Text.RegularExpressions;
using MpvNet.Windows.WPF;
using MpvNet.Windows.UI;
@@ -27,7 +28,6 @@ public partial class MainForm : Form
public SnapManager SnapManager = new SnapManager();
public IntPtr MpvWindowHandle { get; set; }
public ElementHost? CommandPaletteHost { get; set; }
public Dictionary<string, WpfControls.MenuItem> MenuItemDuplicate = new Dictionary<string, WpfControls.MenuItem>();
public bool WasShown { get; set; }
public static MainForm? Instance { get; set; }
WpfControls.ContextMenu ContextMenu { get; } = new WpfControls.ContextMenu();
@@ -68,8 +68,7 @@ public partial class MainForm : Form
GuiCommand.Current.WindowScaleNet += GuiCommand_WindowScaleNet;
GuiCommand.Current.ShowMenu += GuiCommand_ShowMenu;
if (Player.GPUAPI != "vulkan")
Init();
Init();
_taskbarButtonCreatedMessage = RegisterWindowMessage("TaskbarButtonCreated");
@@ -163,6 +162,7 @@ public partial class MainForm : Form
Player.ObservePropertyDouble("window-scale", PropChangeWindowScale);
Player.ProcessCommandLineArgsPost();
Player.ProcessCommandLineFiles();
}
@@ -211,7 +211,7 @@ public partial class MainForm : Form
{
BeginInvoke(() => {
Screen screen = Screen.FromControl(this);
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
Rectangle workingArea = WinApiHelp.GetWorkingArea(Handle, screen.WorkingArea);
switch (direction)
{
@@ -286,7 +286,7 @@ public partial class MainForm : Form
lock (Player.MediaTracksLock)
{
var trackMenuItem = FindMenuItem(_("Track"));
var trackMenuItem = FindMenuItem(_("Track"), "Track");
if (trackMenuItem != null)
{
@@ -348,7 +348,7 @@ public partial class MainForm : Form
}
}
var chaptersMenuItem = FindMenuItem(_("Chapter"));
var chaptersMenuItem = FindMenuItem(_("Chapter"), "Chapters");
if (chaptersMenuItem != null)
{
@@ -369,7 +369,7 @@ public partial class MainForm : Form
}
}
var recentMenuItem = FindMenuItem(_("Recent Files"));
var recentMenuItem = FindMenuItem(_("Recent Files"), "Recent");
if (recentMenuItem != null)
{
@@ -391,7 +391,7 @@ public partial class MainForm : Form
recentMenuItem.Items.Add(clearMenuItem);
}
var titlesMenuItem = FindMenuItem(_("Title"));
var titlesMenuItem = FindMenuItem(_("Title"), "Titles");
if (titlesMenuItem != null)
{
@@ -424,7 +424,7 @@ public partial class MainForm : Form
}
}
var profilesMenuItem = FindMenuItem(_("Profile"));
var profilesMenuItem = FindMenuItem(_("Profile"), "Profile");
if (profilesMenuItem != null && !profilesMenuItem.HasItems)
{
@@ -446,7 +446,7 @@ public partial class MainForm : Form
}
}
var customMenuItem = FindMenuItem(_("Custom"));
var customMenuItem = FindMenuItem(_("Custom"), "Custom");
if (customMenuItem != null)
{
@@ -476,7 +476,14 @@ public partial class MainForm : Form
}
}
public WpfControls.MenuItem? FindMenuItem(string text) => FindMenuItem(text, ContextMenu.Items);
public WpfControls.MenuItem? FindMenuItem(string text, string text2 = "") {
var ret = FindMenuItem(text, ContextMenu.Items);
if (ret == null && text2 != "")
return FindMenuItem(text2, ContextMenu.Items);
return ret;
}
WpfControls.MenuItem? FindMenuItem(string text, WpfControls.ItemCollection? items)
{
@@ -500,7 +507,7 @@ public partial class MainForm : Form
return null;
}
void SetFormPosAndSize(bool force = false, bool checkAutofit = true)
void SetFormPosAndSize(bool force = false, bool checkAutofit = true, bool load = false)
{
if (!force)
{
@@ -515,7 +522,7 @@ public partial class MainForm : Form
}
Screen screen = Screen.FromControl(this);
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
Rectangle workingArea = WinApiHelp.GetWorkingArea(Handle, screen.WorkingArea);
int autoFitHeight = Convert.ToInt32(workingArea.Height * Player.Autofit);
if (App.AutofitAudio > 1)
@@ -595,16 +602,16 @@ public partial class MainForm : Form
height = windowSize.Height;
width = windowSize.Width;
}
Player.WasInitialSizeSet = true;
}
SetSize(width, height, screen, checkAutofit);
SetSize(width, height, screen, checkAutofit, load);
}
void SetSize(int width, int height, Screen screen, bool checkAutofit = true)
void SetSize(int width, int height, Screen screen, bool checkAutofit = true, bool load = false)
{
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
Rectangle workingArea = WinApiHelp.GetWorkingArea(Handle, screen.WorkingArea);
int maxHeight = workingArea.Height - (Height - ClientSize.Height) - 2;
int maxWidth = workingArea.Width - (Width - ClientSize.Width);
@@ -647,40 +654,76 @@ public partial class MainForm : Form
Point middlePos = new Point(Left + Width / 2, Top + Height / 2);
var rect = new Rect(new Rectangle(screen.Bounds.X, screen.Bounds.Y, width, height));
AddWindowBorders(Handle, ref rect, GetDpi(Handle));
WinApiHelp.AddWindowBorders(Handle, ref rect, GetDpi(Handle));
int left = middlePos.X - rect.Width / 2;
int top = middlePos.Y - rect.Height / 2;
width = rect.Width;
height = rect.Height;
int left = middlePos.X - width / 2;
int top = middlePos.Y - height / 2;
Rectangle currentRect = new Rectangle(Left, Top, Width, Height);
if (GetHorizontalLocation(screen) == -1) left = Left;
if (GetHorizontalLocation(screen) == 1) left = currentRect.Right - rect.Width;
if (GetHorizontalLocation(screen) == 1) left = currentRect.Right - width;
if (GetVerticalLocation(screen) == -1) top = Top;
if (GetVerticalLocation(screen) == 1) top = currentRect.Bottom - rect.Height;
if (GetVerticalLocation(screen) == 1) top = currentRect.Bottom - height;
Screen[] screens = Screen.AllScreens;
int minLeft = screens.Select(val => GetWorkingArea(Handle, val.WorkingArea).X).Min();
int maxRight = screens.Select(val => GetWorkingArea(Handle, val.WorkingArea).Right).Max();
int minTop = screens.Select(val => GetWorkingArea(Handle, val.WorkingArea).Y).Min();
int maxBottom = screens.Select(val => GetWorkingArea(Handle, val.WorkingArea).Bottom).Max();
int minLeft = screens.Select(val => WinApiHelp.GetWorkingArea(Handle, val.WorkingArea).X).Min();
int maxRight = screens.Select(val => WinApiHelp.GetWorkingArea(Handle, val.WorkingArea).Right).Max();
int minTop = screens.Select(val => WinApiHelp.GetWorkingArea(Handle, val.WorkingArea).Y).Min();
int maxBottom = screens.Select(val => WinApiHelp.GetWorkingArea(Handle, val.WorkingArea).Bottom).Max();
if (load && CommandLine.Contains("geometry"))
{
string geometryString = CommandLine.GetValue("geometry");
var geometry = ParseGeometry(geometryString, WinApiHelp.GetWorkingArea(
Handle, Screen.FromHandle(Handle).WorkingArea), width, height);
if (geometry.x != int.MaxValue)
left = geometry.x;
if (geometry.y != int.MaxValue)
top = geometry.y;
}
if (left < minLeft)
left = minLeft;
if (left + rect.Width > maxRight)
left = maxRight - rect.Width;
if (left + width > maxRight)
left = maxRight - width;
if (top < minTop)
top = minTop;
if (top + rect.Height > maxBottom)
top = maxBottom - rect.Height;
if (top + height > maxBottom)
top = maxBottom - height;
uint SWP_NOACTIVATE = 0x0010;
SetWindowPos(Handle, IntPtr.Zero, left, top, rect.Width, rect.Height, SWP_NOACTIVATE);
SetWindowPos(Handle, IntPtr.Zero, left, top, width, height, SWP_NOACTIVATE);
}
(int x, int y) ParseGeometry(string input, Rectangle workingArea, int width, int height)
{
int x = int.MaxValue;
int y = int.MaxValue;
Match match = Regex.Match(input, @"^(\d+)%?:(\d+)%?$");
if (match.Success)
{
x = int.Parse(match.Groups[1].Value);
y = int.Parse(match.Groups[2].Value);
x = workingArea.Left + Convert.ToInt32((workingArea.Width - width) / 100.0 * x);
y = workingArea.Top + Convert.ToInt32((workingArea.Height - height) / 100.0 * y);
}
return (x, y);
}
public void CycleFullscreen(bool enabled)
@@ -728,7 +771,7 @@ public partial class MainForm : Form
public int GetHorizontalLocation(Screen screen)
{
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
Rectangle workingArea = WinApiHelp.GetWorkingArea(Handle, screen.WorkingArea);
Rectangle rect = new Rectangle(Left - workingArea.X, Top - workingArea.Y, Width, Height);
if (workingArea.Width / (float)Width < 1.1)
@@ -745,7 +788,7 @@ public partial class MainForm : Form
public int GetVerticalLocation(Screen screen)
{
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
Rectangle workingArea = WinApiHelp.GetWorkingArea(Handle, screen.WorkingArea);
Rectangle rect = new Rectangle(Left - workingArea.X, Top - workingArea.Y, Width, Height);
if (workingArea.Height / (float)Height < 1.1)
@@ -767,17 +810,12 @@ public partial class MainForm : Form
var (menuBindings, confBindings) = App.InputConf.GetBindings();
_confBindings = confBindings;
var activeBindings = InputHelp.GetActiveBindings(menuBindings);
foreach (Binding binding in menuBindings)
{
Binding tempBinding = binding;
if (MenuItemDuplicate.ContainsKey(tempBinding.Command) && tempBinding.Input != "")
{
var mi = MenuItemDuplicate[tempBinding.Command];
mi.InputGestureText = mi.InputGestureText + ", " + tempBinding.Input;
}
if (!binding.IsMenu)
continue;
@@ -785,9 +823,6 @@ public partial class MainForm : Form
if (menuItem != null)
{
if (tempBinding.Input != "")
MenuItemDuplicate[tempBinding.Command] = menuItem;
menuItem.Click += (sender, args) => {
try {
TaskHelp.Run(() => {
@@ -803,7 +838,7 @@ public partial class MainForm : Form
}
};
menuItem.InputGestureText = tempBinding.Input;
menuItem.InputGestureText = InputHelp.GetBindingsForCommand(activeBindings, tempBinding.Command);
}
}
@@ -1020,7 +1055,7 @@ public partial class MainForm : Form
{
Rect rc = Marshal.PtrToStructure<Rect>(m.LParam);
Rect r = rc;
SubtractWindowBorders(Handle, ref r, GetDpi(Handle));
WinApiHelp.SubtractWindowBorders(Handle, ref r, GetDpi(Handle));
int c_w = r.Right - r.Left, c_h = r.Bottom - r.Top;
Size videoSize = Player.VideoSize;
@@ -1034,7 +1069,7 @@ public partial class MainForm : Form
int[] d_corners = { d_w, d_h, -d_w, -d_h };
int[] corners = { rc.Left, rc.Top, rc.Right, rc.Bottom };
int corner = GetResizeBorder(m.WParam.ToInt32());
int corner = WinApiHelp.GetResizeBorder(m.WParam.ToInt32());
if (corner >= 0)
corners[corner] -= d_corners[corner];
@@ -1222,10 +1257,8 @@ public partial class MainForm : Form
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (Player.GPUAPI != "vulkan")
Player.VideoSizeAutoResetEvent.WaitOne(App.StartThreshold);
_lastCycleFullscreen = Environment.TickCount;
SetFormPosAndSize();
SetFormPosAndSize(false, true, true);
}
protected override void OnLostFocus(EventArgs e)
@@ -1238,9 +1271,6 @@ public partial class MainForm : Form
{
base.OnShown(e);
if (Player.GPUAPI == "vulkan")
Init();
if (WindowState == FormWindowState.Maximized)
Player.SetPropertyBool("window-maximized", true);
@@ -1252,9 +1282,14 @@ public partial class MainForm : Form
InitAndBuildContextMenu();
Cursor.Position = new Point(Cursor.Position.X + 1, Cursor.Position.Y);
GlobalHotkey.RegisterGlobalHotkeys(Handle);
TaskHelp.Run(WinMpvHelp.CopyMpvNetCom);
WasShown = true;
StrongReferenceMessenger.Default.Send(new MainWindowIsLoadedMessage());
TaskHelp.Run(() => {
System.Windows.Application.Current.Dispatcher.BeginInvoke(() => {
WinMpvHelp.Setup();
}, DispatcherPriority.Background);
});
}
void ContextMenu_Closed(object sender, System.Windows.RoutedEventArgs e) => MenuAutoResetEvent.Set();
@@ -1386,7 +1421,7 @@ public partial class MainForm : Form
public static int GetDpi(IntPtr hwnd)
{
if (Environment.OSVersion.Version >= WindowsTen1607 && hwnd != IntPtr.Zero)
if (Environment.OSVersion.Version >= WinApiHelp.WindowsTen1607 && hwnd != IntPtr.Zero)
return GetDpiForWindow(hwnd);
else
using (Graphics gx = Graphics.FromHwnd(hwnd))

View File

@@ -1,4 +1,64 @@
<root>
<?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">

View File

@@ -2,10 +2,9 @@
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using MpvNet.Windows.Help;
using MpvNet.Windows.Native;
using static MpvNet.Windows.Native.WinApi;
namespace MpvNet.Windows.WinForms;
public class SnapManager
@@ -35,7 +34,7 @@ public class SnapManager
void FindSnap(ref Rectangle effectiveBounds)
{
Screen currentScreen = Screen.FromPoint(effectiveBounds.Location);
Rectangle workingArea = GetWorkingArea(Handle, currentScreen.WorkingArea);
Rectangle workingArea = WinApiHelp.GetWorkingArea(Handle, currentScreen.WorkingArea);
if (InSnapRange(effectiveBounds.Left, workingArea.Left + AnchorDistance))
effectiveBounds.X = workingArea.Left + AnchorDistance;

View File

@@ -20,21 +20,77 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|ARM.ActiveCfg = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|ARM.Build.0 = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|ARM64.Build.0 = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|x64.ActiveCfg = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|x64.Build.0 = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|x86.ActiveCfg = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Debug|x86.Build.0 = Debug|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|Any CPU.Build.0 = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|ARM.ActiveCfg = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|ARM.Build.0 = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|ARM64.ActiveCfg = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|ARM64.Build.0 = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|x64.ActiveCfg = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|x64.Build.0 = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|x86.ActiveCfg = Release|Any CPU
{ADC341B5-863A-4DFB-9352-475518FABE91}.Release|x86.Build.0 = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|ARM.ActiveCfg = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|ARM.Build.0 = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|ARM64.Build.0 = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|x64.ActiveCfg = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|x64.Build.0 = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|x86.ActiveCfg = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|x86.Build.0 = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|Any CPU.Build.0 = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|ARM.ActiveCfg = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|ARM.Build.0 = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|ARM64.ActiveCfg = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|ARM64.Build.0 = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|x64.ActiveCfg = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|x64.Build.0 = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|x86.ActiveCfg = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|x86.Build.0 = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|ARM.ActiveCfg = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|ARM.Build.0 = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|ARM64.Build.0 = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|x64.ActiveCfg = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|x64.Build.0 = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|x86.ActiveCfg = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|x86.Build.0 = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|Any CPU.Build.0 = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|ARM.ActiveCfg = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|ARM.Build.0 = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|ARM64.ActiveCfg = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|ARM64.Build.0 = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|x64.ActiveCfg = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|x64.Build.0 = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|x86.ActiveCfg = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -10,7 +10,6 @@ namespace MpvNet;
public class AppClass
{
public List<string> TempFiles { get; } = new ();
public Dictionary<string, string> CommandLineArguments { get; } = new ();
public string ConfPath { get => Player.ConfigFolder + "mpvnet.conf"; }
public string ProcessInstance { get; set; } = "single";
@@ -20,6 +19,7 @@ public class AppClass
public string StartSize { get; set; } = "height-session";
public string Language { get; set; } = "system";
public string CommandLine { get; set; } = Environment.CommandLine;
public string MenuSyntax { get; set; } = "#menu:";
public bool AutoLoadFolder { get; set; } = true;
public bool DebugMode { get; set; }
@@ -30,7 +30,6 @@ public class AppClass
public bool RememberVolume { get; set; } = true;
public bool RememberWindowPosition { get; set; }
public int StartThreshold { get; set; } = 1500;
public int RecentCount { get; set; } = 15;
public float AutofitAudio { get; set; } = 0.7f;
@@ -145,6 +144,7 @@ public class AppClass
case "language": Language = value; return true;
case "light-theme": LightTheme = value.Trim('\'', '"'); return true;
case "media-info": MediaInfo = value == "yes"; return true;
case "menu-syntax": MenuSyntax = value; return true;
case "minimum-aspect-ratio-audio": MinimumAspectRatioAudio = value.ToFloat(); return true;
case "minimum-aspect-ratio": MinimumAspectRatio = value.ToFloat(); return true;
case "process-instance": ProcessInstance = value; return true;
@@ -153,7 +153,6 @@ public class AppClass
case "remember-volume": RememberVolume = value == "yes"; return true;
case "remember-window-position": RememberWindowPosition = value == "yes"; return true;
case "start-size": StartSize = value; return true;
case "start-threshold": StartThreshold = value.ToInt(1500); return true;
case "video-file-extensions": FileTypes.Video = value.Split(" ,;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); return true;
default:

View File

@@ -18,7 +18,6 @@ public class Command
["play-pause"] = PlayPause,
["shell-execute"] = args => ProcessHelp.ShellExecute(args[0]),
["show-text"] = args => ShowText(args[0], Convert.ToInt32(args[1]), Convert.ToInt32(args[2])),
["show-commands"] = args => ShowCommands(),
["cycle-audio"] = args => CycleAudio(),
["cycle-subtitles"] = args => CycleSubtitles(),
["playlist-first"] = args => PlaylistFirst(),
@@ -52,37 +51,6 @@ public class Command
}
}
public static void ShowCommands()
{
string json = Core.GetPropertyString("command-list");
var enumerator = JsonDocument.Parse(json).RootElement.EnumerateArray();
var commands = enumerator.OrderBy(it => it.GetProperty("name").GetString());
StringBuilder sb = new StringBuilder();
foreach (var cmd in commands)
{
sb.AppendLine();
sb.AppendLine(cmd.GetProperty("name").GetString());
foreach (var args in cmd.GetProperty("args").EnumerateArray())
{
string value = args.GetProperty("name").GetString() + " <" +
args.GetProperty("type").GetString()!.ToLower() + ">";
if (args.GetProperty("optional").GetBoolean())
value = "[" + value + "]";
sb.AppendLine(" " + value);
}
}
string header = BR +
"https://mpv.io/manual/master/#list-of-input-commands" + BR2 +
"https://github.com/stax76/mpv-scripts#command_palette" + BR;
ShowTextWithEditor("Input Commands", header + sb.ToString());
}
public static void ShowText(string text, int duration = 0, int fontSize = 0)
{
if (string.IsNullOrEmpty(text))
@@ -98,14 +66,6 @@ public class Command
"}${osd-ass-cc/1}" + text + "\" " + duration);
}
public static void ShowTextWithEditor(string name, string text)
{
string file = Path.Combine(Path.GetTempPath(), name + ".txt");
App.TempFiles.Add(file);
File.WriteAllText(file, BR + text.Trim() + BR);
ProcessHelp.ShellExecute(file);
}
public static void CycleAudio()
{
Player.UpdateExternalTracks();

73
src/MpvNet/CommandLine.cs Normal file
View File

@@ -0,0 +1,73 @@

namespace MpvNet;
public class CommandLine
{
static List<StringPair>? _arguments;
public static List<StringPair> Arguments
{
get
{
if (_arguments != null)
return _arguments;
_arguments = new();
foreach (string i in Environment.GetCommandLineArgs().Skip(1))
{
string arg = i;
if (!arg.StartsWith("--"))
continue;
if (!arg.Contains('='))
{
if (arg.Contains("--no-"))
{
arg = arg.Replace("--no-", "--");
arg += "=no";
}
else
arg += "=yes";
}
string left = arg[2..arg.IndexOf("=")];
string right = arg[(left.Length + 3)..];
if (string.IsNullOrEmpty(left))
continue;
switch (left)
{
case "script": left = "scripts"; break;
case "audio-file": left = "audio-files"; break;
case "sub-file": left = "sub-files"; break;
case "external-file": left = "external-files"; break;
}
_arguments.Add(new StringPair(left, right));
}
return _arguments;
}
}
public static bool Contains(string name)
{
foreach (StringPair pair in Arguments)
if (pair.Name == name)
return true;
return false;
}
public static string GetValue(string name)
{
foreach (StringPair pair in Arguments)
if (pair.Name == name)
return pair.Value;
return "";
}
}

View File

@@ -1,14 +1,22 @@
namespace MpvNet.Help;

namespace MpvNet.Help;
public static class ProcessHelp
{
public static void Execute(string file, string arguments = "", bool shellExecute = false)
{
using Process proc = new Process();
proc.StartInfo.FileName = file;
proc.StartInfo.Arguments = arguments;
proc.StartInfo.UseShellExecute = shellExecute;
proc.Start();
try
{
using Process proc = new Process();
proc.StartInfo.FileName = file;
proc.StartInfo.Arguments = arguments;
proc.StartInfo.UseShellExecute = shellExecute;
proc.Start();
}
catch (Exception ex)
{
Terminal.WriteError(ex.ToString());
}
}
public static void ShellExecute(string file, string arguments = "") => Execute(file, arguments, true);

View File

@@ -20,7 +20,7 @@ public class InputConf
}
}
public bool HasMenu => Content.Contains("#menu:") || Content.Contains("#! ");
public bool HasMenu => Content.Contains(App.MenuSyntax + " ");
public (List<Binding> menuBindings, List<Binding>? confBindings) GetBindings()
{

View File

@@ -125,8 +125,6 @@ public static class InputHelp
new (_("View") + " > " + _("More"), _("Show Console"), "script-binding console/enable", "`"),
new (_("View") + " > " + _("More"), _("Show Audio Devices"), "script-message-to mpvnet show-audio-devices"),
new (_("View") + " > " + _("More"), _("Show Commands"), "script-message-to mpvnet show-commands", "F2"),
new (_("View") + " > " + _("More"), _("Show Demuxers"), "script-message-to mpvnet show-demuxers"),
new (_("View") + " > " + _("More"), _("Show Decoders"), "script-message-to mpvnet show-decoders"),
new (_("View") + " > " + _("More"), _("Show Bindings"), "script-message-to mpvnet show-bindings"),
new (_("Window"), _("Fullscreen"), "cycle fullscreen", "Enter"),
@@ -149,11 +147,16 @@ public static class InputHelp
new (_("Settings"), _("Show Config Editor"), "script-message-to mpvnet show-conf-editor", "Ctrl+,"),
new (_("Settings"), _("Show Input Editor"), "script-message-to mpvnet show-input-editor", "Ctrl+i"),
new (_("Settings"), "-"),
new (_("Settings"), _("Edit mpv.conf"), "script-message-to mpvnet edit-conf-file mpv.conf", "c"),
new (_("Settings"), _("Edit input.conf"), "script-message-to mpvnet edit-conf-file input.conf", "k"),
new (_("Settings"), "-"),
new (_("Settings"), _("Open Config Folder"), "script-message-to mpvnet open-conf-folder", "Ctrl+f"),
new (_("Settings") + " > " + _("Setup"), _("Register video file associations"), "script-message-to mpvnet reg-file-assoc video"),
new (_("Settings") + " > " + _("Setup"), _("Register audio file associations"), "script-message-to mpvnet reg-file-assoc audio"),
new (_("Settings") + " > " + _("Setup"), _("Register image file associations"), "script-message-to mpvnet reg-file-assoc image"),
new (_("Settings") + " > " + _("Setup"), _("Unregister file associations"), "script-message-to mpvnet reg-file-assoc unreg"),
new (_("Settings") + " > " + _("Setup"), _("Add mpv.net to Path environment variable"), "script-message-to mpvnet add-to-path"),
new (_("Tools"), _("Set/clear A-B loop points"), "ab-loop", "l"),
new (_("Tools"), _("Toggle infinite file looping"), "cycle-values loop-file inf no", "L"),
@@ -174,11 +177,10 @@ public static class InputHelp
new ("", "", "quit", "q", _("Exit")),
new ("", "", "script-message-to mpvnet show-menu", "MBTN_Right", _("Show Menu")),
new ("", "", "quit", "Power", _("Exit")),
new ("", "", "cycle pause", "Play", _("Play/Pause")),
new ("", "", "cycle pause", "Pause", _("Play/Pause")),
new ("", "", "cycle pause", "PlayPause", _("Play/Pause")),
new ("", "", "cycle pause", "MBTN_Mid", _("Play/Pause")),
new ("", "", "script-message-to mpvnet play-pause", "Play", _("Play/Pause")),
new ("", "", "script-message-to mpvnet play-pause", "Pause", _("Play/Pause")),
new ("", "", "script-message-to mpvnet play-pause", "PlayPause", _("Play/Pause")),
new ("", "", "script-message-to mpvnet play-pause", "MBTN_Mid", _("Play/Pause")),
new ("", "", "stop", "Stop", _("Stop")),
new ("", "", "seek 60", "Forward", _("Forward")),
new ("", "", "seek -60", "Rewind", _("Backward")),
@@ -207,6 +209,8 @@ public static class InputHelp
new ("", "", "no-osd seek -5", "Ctrl+Wheel_Down", _("Seek Backward")),
new ("", "", "quit 4", "Esc", _("Quit encoding")),
new ("", "", "quit 4", "q", _("Quit encoding")),
new ("", "", "quit", "Power", _("Exit")),
//new (_("Command Palette"), _("Commands"), "script-message-to mpvnet show-command-palette", "F1"),
};
@@ -321,18 +325,11 @@ public static class InputHelp
line = line[(line.IndexOf(" ") + 1)..];
if (line.Contains("#menu:"))
if (line.Contains(App.MenuSyntax))
{
binding.Comment = line[(line.IndexOf("#menu:") + 6)..].Trim();
binding.Comment = line[(line.IndexOf(App.MenuSyntax) + App.MenuSyntax.Length)..].Trim();
binding.IsMenu = true;
line = line[..line.IndexOf("#menu:")];
}
else if (line.Contains("#!"))
{
binding.Comment = line[(line.IndexOf("#!") + 2)..].Trim();
binding.IsMenu = true;
binding.IsShortMenuSyntax = true;
line = line[..line.IndexOf("#!")];
line = line[..line.IndexOf(App.MenuSyntax)];
}
else if (line.Contains("#custom-menu:"))
{
@@ -432,10 +429,10 @@ public static class InputHelp
value = value[(value.IndexOf(" ") + 1)..];
if (value.Contains("#menu:"))
if (value.Contains(App.MenuSyntax))
{
binding.Comment = value[(value.IndexOf("#menu:") + 6)..].Trim();
value = value[..value.IndexOf("#menu:")];
binding.Comment = value[(value.IndexOf(App.MenuSyntax) + App.MenuSyntax.Length)..].Trim();
value = value[..value.IndexOf(App.MenuSyntax)];
if (binding.Comment.Contains(';'))
binding.Comment = binding.Comment[(binding.Comment.IndexOf(";") + 1)..].Trim();
@@ -454,4 +451,41 @@ public static class InputHelp
}
return bindings;
}
public static Dictionary<string, Binding> GetActiveBindings(List<Binding> bindings)
{
Dictionary<string, Binding> ret = new();
foreach (Binding binding in bindings)
{
if (binding.Input == "" || binding.Command == "")
continue;
ret[binding.Input] = binding;
}
return ret;
}
public static string GetBindingsForCommand(Dictionary<string, Binding> activeBindings, string command)
{
List<string> keys = new();
int charCount = 0;
foreach (var it in activeBindings)
{
if (it.Value.Command != command)
continue;
Binding binding = it.Value;
if (!keys.Contains(binding.Input) && (charCount + binding.Input.Length) < 20 && keys.Count < 2)
{
keys.Add(binding.Input);
charCount += binding.Input.Length;
}
}
return string.Join(", ", keys);
}
}

View File

@@ -64,7 +64,7 @@ public class MpvClient
OnEndFile(data);
}
break;
case mpv_event_id.MPV_EVENT_FILE_LOADED:
case mpv_event_id.MPV_EVENT_FILE_LOADED: // triggered after MPV_EVENT_START_FILE
OnFileLoaded();
break;
case mpv_event_id.MPV_EVENT_PROPERTY_CHANGE:
@@ -82,7 +82,7 @@ public class MpvClient
case mpv_event_id.MPV_EVENT_COMMAND_REPLY:
OnCommandReply();
break;
case mpv_event_id.MPV_EVENT_START_FILE:
case mpv_event_id.MPV_EVENT_START_FILE: // triggered before MPV_EVENT_FILE_LOADED
OnStartFile();
break;
case mpv_event_id.MPV_EVENT_AUDIO_RECONFIG:

View File

@@ -50,7 +50,6 @@ public class MainPlayer : MpvClient
public float AutofitLarger { get; set; } = 0.8f;
public AutoResetEvent ShutdownAutoResetEvent { get; } = new AutoResetEvent(false);
public AutoResetEvent VideoSizeAutoResetEvent { get; } = new AutoResetEvent(false);
public nint MainHandle { get; set; }
public List<MediaTrack> MediaTracks { get; set; } = new List<MediaTrack>();
public List<TimeSpan> BluRayTitles { get; } = new List<TimeSpan>();
@@ -64,7 +63,7 @@ public class MainPlayer : MpvClient
public event Action<int>? PlaylistPosChanged;
public event Action<Size>? VideoSizeChanged;
public void Init(IntPtr formHandle)
public void Init(IntPtr formHandle, bool processCommandLineArguments = true)
{
App.ApplyShowMenuFix();
@@ -98,24 +97,24 @@ public class MainPlayer : MpvClient
SetPropertyBool("input-default-bindings", true);
SetPropertyBool("input-builtin-bindings", false);
SetPropertyString("watch-later-options", "mute");
SetPropertyString("screenshot-directory", "~~desktop/");
SetPropertyString("osd-playing-msg", "${media-title}");
SetPropertyString("osc", "yes");
SetPropertyString("force-window", "yes");
SetPropertyString("config-dir", ConfigFolder);
SetPropertyString("config", "yes");
UsedInputConfContent = App.InputConf.GetContent();
if (!string.IsNullOrEmpty(UsedInputConfContent))
SetPropertyString("input-conf", @"memory://" + UsedInputConfContent);
ProcessCommandLineArgs();
if (processCommandLineArguments)
ProcessCommandLineArgs();
if (App.CommandLineArguments.ContainsKey("config-dir"))
if (CommandLine.Contains("config-dir"))
{
string configDir = App.CommandLineArguments["config-dir"];
string configDir = CommandLine.GetValue("config-dir");
string fullPath = System.IO.Path.GetFullPath(configDir);
App.InputConf.Path = fullPath.AddSep() + "input.conf";
string content = App.InputConf.GetContent();
@@ -152,16 +151,20 @@ public class MainPlayer : MpvClient
// this means Lua scripts that use idle might not work correctly
SetPropertyString("idle", "yes");
ObservePropertyString("path", value => Path = value);
ObservePropertyBool("pause", value => {
Paused = value;
Pause?.Invoke();
});
ObservePropertyInt("video-rotate", value => {
VideoRotate = value;
UpdateVideoSize("dwidth", "dheight");
VideoRotate = GetPropertyInt("video-rotate");
ObservePropertyInt("video-rotate", value =>
{
if (VideoRotate != value)
{
VideoRotate = value;
UpdateVideoSize("dwidth", "dheight");
}
});
ObservePropertyInt("playlist-pos", value => {
@@ -173,9 +176,6 @@ public class MainPlayer : MpvClient
CommandV("quit");
});
if (!GetPropertyBool("osd-scale-by-window"))
App.StartThreshold = 0;
Initialized?.Invoke();
}
@@ -269,57 +269,65 @@ public class MainPlayer : MpvClient
Dictionary<string, string>? _Conf;
public Dictionary<string, string> Conf {
get {
if (_Conf == null)
get
{
if (_Conf != null)
return _Conf;
App.ApplyInputDefaultBindingsFix();
_Conf = new Dictionary<string, string>();
if (File.Exists(ConfPath))
{
App.ApplyInputDefaultBindingsFix();
_Conf = new Dictionary<string, string>();
if (File.Exists(ConfPath))
foreach (string? it in File.ReadAllLines(ConfPath))
{
foreach (string? it in File.ReadAllLines(ConfPath))
string line = it.TrimStart(' ', '-').TrimEnd();
if (line.StartsWith("#"))
continue;
if (!line.Contains('='))
{
string line = it.TrimStart(' ', '-').TrimEnd();
if (!line.Contains('=') || line.StartsWith("#"))
if (Regex.Match(line, "^[\\w-]+$").Success)
line += "=yes";
else
continue;
string key = line[..line.IndexOf("=")].Trim();
string value = line[(line.IndexOf("=") + 1)..].Trim();
if (value.Contains('#') && !value.StartsWith("#") &&
!value.StartsWith("'#") && !value.StartsWith("\"#"))
value = value[..value.IndexOf("#")].Trim();
_Conf[key] = value;
}
}
foreach (var i in _Conf)
ProcessProperty(i.Key, i.Value);
string key = line[..line.IndexOf("=")].Trim();
string value = line[(line.IndexOf("=") + 1)..].Trim();
if (value.Contains('#') && !value.StartsWith("#") &&
!value.StartsWith("'#") && !value.StartsWith("\"#"))
value = value[..value.IndexOf("#")].Trim();
_Conf[key] = value;
}
}
foreach (var i in _Conf)
ProcessProperty(i.Key, i.Value);
return _Conf;
}
}
void UpdateVideoSize(string w, string h)
{
Size size = new Size(GetPropertyInt(w), GetPropertyInt(h));
if (size.Width == 0 || size.Height == 0)
if (string.IsNullOrEmpty(Path))
return;
Size size = new Size(GetPropertyInt(w), GetPropertyInt(h));
if (VideoRotate == 90 || VideoRotate == 270)
size = new Size(size.Height, size.Width);
if (VideoSize != size)
if (size != VideoSize && size != Size.Empty)
{
VideoSize = size;
VideoSizeChanged?.Invoke(size);
VideoSizeAutoResetEvent.Set();
}
}
@@ -349,24 +357,27 @@ public class MainPlayer : MpvClient
base.OnLogMessage(data);
}
protected override void OnVideoReconfig()
{
UpdateVideoSize("dwidth", "dheight");
base.OnVideoReconfig();
}
protected override void OnEndFile(mpv_event_end_file data)
{
base.OnEndFile(data);
FileEnded = true;
}
protected override void OnVideoReconfig()
{
UpdateVideoSize("dwidth", "dheight");
base.OnVideoReconfig();
}
// executed before OnFileLoaded
protected override void OnStartFile()
{
Path = GetPropertyString("path");
base.OnStartFile();
TaskHelp.Run(LoadFolder);
}
// executed after OnStartFile
protected override void OnFileLoaded()
{
Duration = TimeSpan.FromSeconds(GetPropertyDouble("duration"));
@@ -374,13 +385,8 @@ public class MainPlayer : MpvClient
if (App.StartSize == "video")
WasInitialSizeSet = false;
string path = GetPropertyString("path");
if (!FileTypes.Video.Contains(path.Ext()) || FileTypes.Audio.Contains(path.Ext()))
{
if (!FileTypes.Video.Contains(Path.Ext()) || FileTypes.Audio.Contains(Path.Ext()))
UpdateVideoSize("width", "height");
VideoSizeAutoResetEvent.Set();
}
TaskHelp.Run(UpdateTracks);
@@ -409,75 +415,28 @@ public class MainPlayer : MpvClient
public void ProcessCommandLineArgs()
{
foreach (string i in Environment.GetCommandLineArgs().Skip(1))
foreach (var pair in CommandLine.Arguments)
{
string arg = i;
if (!arg.StartsWith("--"))
if (pair.Name.EndsWith("-add"))
continue;
if (arg == "--profile=help")
{
Console.WriteLine(GetProfiles());
continue;
}
else if (arg == "--vd=help" || arg == "--ad=help")
{
Console.WriteLine(GetDecoders());
continue;
}
else if (arg == "--audio-device=help")
{
Console.WriteLine(GetPropertyOsdString("audio-device-list"));
continue;
}
else if (arg == "--version")
{
Console.WriteLine(AppClass.About);
continue;
}
else if (arg == "--input-keylist")
{
Console.WriteLine(GetPropertyString("input-key-list").Replace(",", BR));
continue;
}
else if (arg.StartsWith("--command="))
{
Command(arg[10..]);
continue;
}
ProcessProperty(pair.Name, pair.Value);
if (!arg.Contains('='))
if (!App.ProcessProperty(pair.Name, pair.Value))
SetPropertyString(pair.Name, pair.Value);
}
}
public void ProcessCommandLineArgsPost()
{
foreach (var pair in CommandLine.Arguments)
{
if (pair.Name.EndsWith("-add"))
{
if (arg.Contains("--no-"))
{
arg = arg.Replace("--no-", "--");
arg += "=no";
}
else
arg += "=yes";
string name = pair.Name[..^4];
string separator = name.Contains("-file") || name.Contains("-path") ? ";" : ",";
SetPropertyString(name, GetPropertyString(name) + separator + pair.Value);
}
string left = arg[2..arg.IndexOf("=")];
string right = arg[(left.Length + 3)..];
if (string.IsNullOrEmpty(left))
continue;
switch (left)
{
case "script": left = "scripts"; break;
case "audio-file": left = "audio-files"; break;
case "sub-file": left = "sub-files"; break;
case "external-file": left = "external-files"; break;
}
App.CommandLineArguments[left] = right;
ProcessProperty(left, right);
if (!App.ProcessProperty(left, right))
SetPropertyString(left, right);
}
}
@@ -498,12 +457,6 @@ public class MainPlayer : MpvClient
Command("playlist-shuffle");
SetPropertyInt("playlist-pos", 0);
}
if (files.Count == 0 || files[0].Contains("://"))
{
VideoSizeChanged?.Invoke(VideoSize);
VideoSizeAutoResetEvent.Set();
}
}
public DateTime LastLoad;

View File

@@ -0,0 +1,7 @@
{
"profiles": {
"MpvNet": {
"commandName": "Project"
}
}
}

View File

@@ -1,4 +1,5 @@
namespace MpvNet.Windows;

namespace MpvNet;
public class StringPair
{

View File

@@ -0,0 +1,28 @@
#define MyAppName "mpv.net"
#define MyAppExeName "mpvnet.exe"
#define MyAppSourceDir "..\..\MpvNet.Windows\bin\Debug"
#define MyAppVersion GetFileVersion("..\..\MpvNet.Windows\bin\Debug\mpvnet.exe")
[Setup]
AppId={{9AA2B100-BEF3-44D0-B819-D8FC3C4D557D}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher=Frank Skare (stax76)
ArchitecturesInstallIn64BitMode=x64
Compression=lzma2
DefaultDirName={autopf}\{#MyAppName}
OutputBaseFilename=mpv.net-v{#MyAppVersion}-setup
OutputDir=E:\Desktop
DefaultGroupName={#MyAppName}
SetupIconFile=..\..\MpvNet.Windows\mpv-icon.ico
UninstallDisplayIcon={app}\{#MyAppExeName}
PrivilegesRequired=admin
PrivilegesRequiredOverridesAllowed=dialog
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
[Files]
Source: "{#MyAppSourceDir}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppSourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs;