From 07b6d4379e6ad245bed66debefba56fbf9d7091b Mon Sep 17 00:00:00 2001 From: Frank Skare Date: Mon, 25 Mar 2019 09:50:14 +0100 Subject: [PATCH] - --- README.md | 4 + mpv.net/Command.cs | 36 +- mpv.net/MainForm.cs | 12 + mpv.net/Menu.cs | 71 +- mpv.net/Properties/AssemblyInfo.cs | 4 +- mpv.net/Resources/input.conf.txt | 5 +- mpv.net/Resources/mpv.conf.txt | 3 +- mpv.net/libmpv.cs | 2 +- mpv.net/mp.cs | 16 +- mpvSettingsEditor/App.config | 6 + mpvSettingsEditor/App.xaml | 9 + mpvSettingsEditor/App.xaml.cs | 8 + mpvSettingsEditor/Definitions.toml | 88 + mpvSettingsEditor/DynamicGUI/DynamicGUI.cs | 107 + mpvSettingsEditor/DynamicGUI/Misc.cs | 7 + .../DynamicGUI/OptionSettingControl.xaml | 25 + .../DynamicGUI/OptionSettingControl.xaml.cs | 36 + .../DynamicGUI/StringSettingControl.xaml | 24 + .../DynamicGUI/StringSettingControl.xaml.cs | 54 + mpvSettingsEditor/DynamicGUI/Tommy.cs | 1824 +++++++++++++++++ mpvSettingsEditor/MainWindow.xaml | 22 + mpvSettingsEditor/MainWindow.xaml.cs | 157 ++ mpvSettingsEditor/Properties/AssemblyInfo.cs | 55 + .../Properties/Resources.Designer.cs | 63 + mpvSettingsEditor/Properties/Resources.resx | 117 ++ .../Properties/Settings.Designer.cs | 26 + .../Properties/Settings.settings | 7 + mpvSettingsEditor/mpvSettingsEditor.csproj | 128 ++ mpvSettingsEditor/mpvSettingsEditor.sln | 25 + release.ps1 | 6 +- 30 files changed, 2870 insertions(+), 77 deletions(-) create mode 100644 mpvSettingsEditor/App.config create mode 100644 mpvSettingsEditor/App.xaml create mode 100644 mpvSettingsEditor/App.xaml.cs create mode 100644 mpvSettingsEditor/Definitions.toml create mode 100644 mpvSettingsEditor/DynamicGUI/DynamicGUI.cs create mode 100644 mpvSettingsEditor/DynamicGUI/Misc.cs create mode 100644 mpvSettingsEditor/DynamicGUI/OptionSettingControl.xaml create mode 100644 mpvSettingsEditor/DynamicGUI/OptionSettingControl.xaml.cs create mode 100644 mpvSettingsEditor/DynamicGUI/StringSettingControl.xaml create mode 100644 mpvSettingsEditor/DynamicGUI/StringSettingControl.xaml.cs create mode 100644 mpvSettingsEditor/DynamicGUI/Tommy.cs create mode 100644 mpvSettingsEditor/MainWindow.xaml create mode 100644 mpvSettingsEditor/MainWindow.xaml.cs create mode 100644 mpvSettingsEditor/Properties/AssemblyInfo.cs create mode 100644 mpvSettingsEditor/Properties/Resources.Designer.cs create mode 100644 mpvSettingsEditor/Properties/Resources.resx create mode 100644 mpvSettingsEditor/Properties/Settings.Designer.cs create mode 100644 mpvSettingsEditor/Properties/Settings.settings create mode 100644 mpvSettingsEditor/mpvSettingsEditor.csproj create mode 100644 mpvSettingsEditor/mpvSettingsEditor.sln diff --git a/README.md b/README.md index 7e7ae6d..50bac08 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,10 @@ Please note that PowerShell don't allow assigning to events and mpv.net uses as ### Changelog +### 1.8 + +- new config editor added + ### 1.7 - showing the conf files mpv.net uses now the app that is registered for txt files, before it just shell executed the conf file which only worked if conf files were associated with an application diff --git a/mpv.net/Command.cs b/mpv.net/Command.cs index 7255d6e..d89b8b5 100644 --- a/mpv.net/Command.cs +++ b/mpv.net/Command.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Reflection; using System.Windows.Forms; using static mpvnet.StaticUsing; @@ -22,21 +23,20 @@ namespace mpvnet if (commands == null) { commands = new List(); - var type = typeof(Command); - var methods = type.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + Type type = typeof(Command); + MethodInfo[] methods = type.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); foreach (var i in methods) { - var parameters = i.GetParameters(); + ParameterInfo[] parameters = i.GetParameters(); if (parameters == null || parameters.Length != 1 || parameters[0].ParameterType != typeof(string[])) continue; - var cmd = new Command() { Name = i.Name.Replace("_","-"), Action = (Action)i.CreateDelegate(typeof(Action)) }; + Command cmd = new Command() { Name = i.Name.Replace("_","-"), Action = (Action)i.CreateDelegate(typeof(Action)) }; commands.Add(cmd); } } - return commands; } } @@ -70,6 +70,16 @@ namespace mpvnet Process.Start(NativeHelp.GetAssociatedApplication(".txt"), mp.mpvConfPath); } + public static void show_conf_editor(string[] args) + { + using (var p = new Process()) + { + p.StartInfo.FileName = Application.StartupPath + "\\mpvSettingsEditor.exe"; + p.StartInfo.WorkingDirectory = Path.GetDirectoryName(Application.ExecutablePath); + p.Start(); + } + } + public static void history(string[] args) { var fp = mp.mpvConfFolderPath + "history.txt"; @@ -89,24 +99,24 @@ namespace mpvnet public static void set_setting(string[] args) { bool changed = false; - string fp = mp.mpvConfPath; - var confLines = File.ReadAllLines(fp); + var lines = File.ReadAllLines(mp.mpvConfPath); - for (int i = 0; i < confLines.Length; i++) + for (int i = 0; i < lines.Length; i++) { - if (confLines[i].Left("=").Trim() == args[0]) + if (lines[i].Contains("=") && + lines[i].Substring(0, lines[i].IndexOf("=")).Trim("# ".ToCharArray()) == args[0]) { - confLines[i] = args[0] + "=" + args[1]; + lines[i] = args[0] + " = " + args[1]; changed = true; } } if (changed) - File.WriteAllText(fp, String.Join(Environment.NewLine, confLines)); + File.WriteAllText(mp.mpvConfPath, String.Join(Environment.NewLine, lines)); else - File.WriteAllText(fp, File.ReadAllText(fp) + Environment.NewLine + args[0] + "=" + args[1]); + File.WriteAllText(mp.mpvConfPath, File.ReadAllText(mp.mpvConfPath) + Environment.NewLine + args[0] + " = " + args[1]); - MsgInfo("Please restart mpv.net"); + MainForm.Instance.ShowMsgBox("Please restart mpv.net", MessageBoxIcon.Information); } public static void show_info(string[] args) diff --git a/mpv.net/MainForm.cs b/mpv.net/MainForm.cs index 1a9c13f..17a332b 100644 --- a/mpv.net/MainForm.cs +++ b/mpv.net/MainForm.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using static mpvnet.StaticUsing; using System.Linq; using System.Collections.Generic; +using System.Threading.Tasks; namespace mpvnet { @@ -368,6 +369,17 @@ namespace mpvnet } } + public DialogResult ShowMsgBox(string message, MessageBoxIcon icon) + { + var buttons = MessageBoxButtons.OK; + if (icon == MessageBoxIcon.Question) buttons = MessageBoxButtons.OKCancel; + + var fn = new Func(() => MessageBox.Show( + message, Application.ProductName, buttons, MessageBoxIcon.Information)); + + return (DialogResult)Invoke(fn); + } + protected override void OnLoad(EventArgs e) { base.OnLoad(e); diff --git a/mpv.net/Menu.cs b/mpv.net/Menu.cs index f367274..16f4d07 100644 --- a/mpv.net/Menu.cs +++ b/mpv.net/Menu.cs @@ -214,8 +214,7 @@ public class ToolStripRendererEx : ToolStripSystemRenderer if (e.Item is ToolStripMenuItem && !(e.Item.Owner is MenuStrip)) { - var r = e.TextRectangle; - + Rectangle rect = e.TextRectangle; var dropDown = e.ToolStrip as ToolStripDropDownMenu; if (dropDown == null || dropDown.ShowImageMargin || dropDown.ShowCheckMargin) @@ -223,7 +222,7 @@ public class ToolStripRendererEx : ToolStripSystemRenderer else TextOffset = Convert.ToInt32(e.Item.Height * 0.2); - e.TextRectangle = new Rectangle(TextOffset, Convert.ToInt32((e.Item.Height - r.Height) / 2.0), r.Width, r.Height); + e.TextRectangle = new Rectangle(TextOffset, Convert.ToInt32((e.Item.Height - rect.Height) / 2.0), rect.Width, rect.Height); } base.OnRenderItemText(e); @@ -279,31 +278,22 @@ public class ToolStripRendererEx : ToolStripSystemRenderer public void DrawButton(ToolStripItemRenderEventArgs e) { - var g = e.Graphics; - var r = new Rectangle(Point.Empty, e.Item.Size); - var r2 = new Rectangle(r.X, r.Y, r.Width - 1, r.Height - 1); + var gx = e.Graphics; + var rect = new Rectangle(Point.Empty, e.Item.Size); + var rect2 = new Rectangle(rect.X, rect.Y, rect.Width - 1, rect.Height - 1); using (Pen pen = new Pen(ColorBorder)) - { - g.DrawRectangle(pen, r2); - } - - r2.Inflate(-1, -1); + gx.DrawRectangle(pen, rect2); + rect2.Inflate(-1, -1); var tsb = e.Item as ToolStripButton; - if (!(tsb == null) && tsb.Checked) - { + if (tsb != null && tsb.Checked) using (SolidBrush brush = new SolidBrush(ColorChecked)) - { - g.FillRectangle(brush, r2); - } - } + gx.FillRectangle(brush, rect2); else using (SolidBrush brush = new SolidBrush(ColorBottom)) - { - g.FillRectangle(brush, r2); - } + gx.FillRectangle(brush, rect2); } protected override void OnRenderDropDownButtonBackground(ToolStripItemRenderEventArgs e) @@ -332,7 +322,7 @@ public class ToolStripRendererEx : ToolStripSystemRenderer protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) { - var x = Convert.ToInt32(e.ImageRectangle.Height * 0.2); + int x = Convert.ToInt32(e.ImageRectangle.Height * 0.2); e.Graphics.DrawImage(e.Image, new Point(x, x)); } @@ -341,28 +331,21 @@ public class ToolStripRendererEx : ToolStripSystemRenderer if (e.Item.IsOnDropDown) { e.Graphics.Clear(ColorBackground); - var right = e.Item.Width - Convert.ToInt32(TextOffset / 5.0); - var top = e.Item.Height / 2; + int right = e.Item.Width - Convert.ToInt32(TextOffset / 5.0); + int top = e.Item.Height / 2; top -= 1; - var b = e.Item.Bounds; - using (Pen p = new Pen(Color.Gray)) - { e.Graphics.DrawLine(p, new Point(TextOffset, top), new Point(right, top)); - } } else if (e.Vertical) { - var b = e.Item.Bounds; - + var bounds = e.Item.Bounds; using (Pen p = new Pen(SystemColors.ControlDarkDark)) - { e.Graphics.DrawLine(p, - Convert.ToInt32(b.Width / 2.0), - Convert.ToInt32(b.Height * 0.15), - Convert.ToInt32(b.Width / 2.0), - Convert.ToInt32(b.Height * 0.85)); - } + Convert.ToInt32(bounds.Width / 2.0), + Convert.ToInt32(bounds.Height * 0.15), + Convert.ToInt32(bounds.Width / 2.0), + Convert.ToInt32(bounds.Height * 0.85)); } } } @@ -383,21 +366,24 @@ public struct HSLColor private double hue; - public int Hue { + public int Hue + { get => System.Convert.ToInt32(hue * 240); set => hue = CheckRange(value / 240.0); } private double saturation; - public int Saturation { + public int Saturation + { get => System.Convert.ToInt32(saturation * 240); set => saturation = CheckRange(value / 240.0); } private double luminosity; - public int Luminosity { + public int Luminosity + { get => System.Convert.ToInt32(luminosity * 240); set => luminosity = CheckRange(value / 240.0); } @@ -408,7 +394,6 @@ public struct HSLColor value = 0; else if (value > 1) value = 1; - return value; } @@ -438,9 +423,8 @@ public struct HSLColor } else { - var temp2 = GetTemp2(this); - var temp1 = 2.0 * luminosity - temp2; - + double temp2 = GetTemp2(this); + double temp1 = 2.0 * luminosity - temp2; r = GetColorComponent(temp1, temp2, hue + 1.0 / 3.0); g = GetColorComponent(temp1, temp2, hue); b = GetColorComponent(temp1, temp2, hue - 1.0 / 3.0); @@ -473,7 +457,6 @@ public struct HSLColor temp3 += 1; else if (temp3 > 1) temp3 -= 1; - return temp3; } @@ -500,7 +483,7 @@ public struct HSLColor public void SetRGB(int red, int green, int blue) { - var hc = HSLColor.Convert(Color.FromArgb(red, green, blue)); + HSLColor hc = HSLColor.Convert(Color.FromArgb(red, green, blue)); hue = hc.hue; saturation = hc.saturation; luminosity = hc.luminosity; diff --git a/mpv.net/Properties/AssemblyInfo.cs b/mpv.net/Properties/AssemblyInfo.cs index 39b3466..ecdad09 100644 --- a/mpv.net/Properties/AssemblyInfo.cs +++ b/mpv.net/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.0.0")] -[assembly: AssemblyFileVersion("1.7.0.0")] +[assembly: AssemblyVersion("1.8.0.0")] +[assembly: AssemblyFileVersion("1.8.0.0")] diff --git a/mpv.net/Resources/input.conf.txt b/mpv.net/Resources/input.conf.txt index 72386e9..c739459 100644 --- a/mpv.net/Resources/input.conf.txt +++ b/mpv.net/Resources/input.conf.txt @@ -53,8 +53,6 @@ Ctrl+8 add saturation 1 #menu: Ctrl+8 ; Video > Increase Saturation _ ignore #menu: _ ; Video > - Ctrl+S async screenshot #menu: Ctrl+S ; Video > Take Screenshot - Ctrl+Shift+S screenshot each-frame #menu: Ctrl+Shift+S ; Video > Take Screenshots All Frames - _ ignore #menu: _ ; Video > - d cycle deinterlace #menu: D ; Video > Toggle Deinterlace a cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1" #menu: A ; Video > Cycle Aspect Ratio @@ -99,7 +97,8 @@ _ script-message mpv.net set-setting hwdec yes #menu: _ ; Settings > Hardware Decoding > Enable _ script-message mpv.net set-setting hwdec no #menu: _ ; Settings > Hardware Decoding > Disable - p script-message mpv.net show-prefs #menu: P ; Settings > Show Preferences + p script-message mpv.net show-prefs #menu: P ; Settings > Show mpv config file + e script-message mpv.net show-conf-editor #menu: E ; Settings > Show mpv config editor k script-message mpv.net show-keys #menu: K ; Settings > Show Keys c script-message mpv.net open-config-folder #menu: C ; Settings > Open Config Folder diff --git a/mpv.net/Resources/mpv.conf.txt b/mpv.net/Resources/mpv.conf.txt index 8d6a337..092210d 100644 --- a/mpv.net/Resources/mpv.conf.txt +++ b/mpv.net/Resources/mpv.conf.txt @@ -1,4 +1,5 @@ -# https://mpv.io/manual/master/ +# mpv manual: https://mpv.io/manual/master/ +# mpv.net mpv.conf defaults: https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/mpv.conf.txt input-ar-delay = 500 input-ar-rate = 20 diff --git a/mpv.net/libmpv.cs b/mpv.net/libmpv.cs index d133589..5e277b7 100644 --- a/mpv.net/libmpv.cs +++ b/mpv.net/libmpv.cs @@ -27,7 +27,7 @@ namespace mpvnet public static extern int mpv_set_option_string(IntPtr mpvHandle, byte[] name, byte[] value); [DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)] - public static extern int mpv_get_property(IntPtr mpvHandle, byte[] name, mpv_format format, ref IntPtr data); + public static extern int mpv_get_property(IntPtr mpvHandle, byte[] name, mpv_format format, out IntPtr data); [DllImport("mpv-1.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int mpv_get_property(IntPtr mpvHandle, byte[] name, mpv_format format, ref double data); diff --git a/mpv.net/mp.cs b/mpv.net/mp.cs index d9aef5b..6782f74 100644 --- a/mpv.net/mp.cs +++ b/mpv.net/mp.cs @@ -125,12 +125,10 @@ namespace mpvnet PowerShellScript.Init(scriptPath); foreach (var scriptPath in Directory.GetFiles(mp.mpvConfFolderPath + "Scripts")) - { if (Path.GetExtension(scriptPath) == ".py") PythonScripts.Add(new PythonScript(File.ReadAllText(scriptPath))); else if (Path.GetExtension(scriptPath) == ".ps1") PowerShellScript.Init(scriptPath); - } } public static void EventLoop() @@ -307,8 +305,7 @@ namespace mpvnet if (MpvHandle == IntPtr.Zero) return; - IntPtr[] byteArrayPointers; - var mainPtr = AllocateUtf8IntPtrArrayWithSentinel(args, out byteArrayPointers); + IntPtr mainPtr = AllocateUtf8IntPtrArrayWithSentinel(args, out IntPtr[] byteArrayPointers); int err = mpv_command(MpvHandle, mainPtr); if (err < 0) @@ -333,7 +330,7 @@ namespace mpvnet public static void set_property_string(string name, string value, bool throwOnException = false) { - var bytes = GetUtf8Bytes(value); + byte[] bytes = GetUtf8Bytes(value); int err = mpv_set_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, ref bytes); if (err < 0 && throwOnException) @@ -342,8 +339,7 @@ namespace mpvnet public static string get_property_string(string name, bool throwOnException = false) { - var lpBuffer = IntPtr.Zero; - int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, ref lpBuffer); + int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, out IntPtr lpBuffer); if (err < 0 && throwOnException) throw new Exception($"{name}: {(mpv_error)err}"); @@ -356,8 +352,7 @@ namespace mpvnet public static int get_property_int(string name, bool throwOnException = false) { - var lpBuffer = IntPtr.Zero; - int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, ref lpBuffer); + int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, out IntPtr lpBuffer); if (err < 0 && throwOnException) throw new Exception($"{name}: {(mpv_error)err}"); @@ -378,8 +373,7 @@ namespace mpvnet public static bool get_property_bool(string name, bool throwOnException = false) { - var lpBuffer = IntPtr.Zero; - int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_FLAG, ref lpBuffer); + int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_FLAG, out IntPtr lpBuffer); if (err < 0 && throwOnException) throw new Exception($"{name}: {(mpv_error)err}"); diff --git a/mpvSettingsEditor/App.config b/mpvSettingsEditor/App.config new file mode 100644 index 0000000..4bfa005 --- /dev/null +++ b/mpvSettingsEditor/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/mpvSettingsEditor/App.xaml b/mpvSettingsEditor/App.xaml new file mode 100644 index 0000000..6a20adb --- /dev/null +++ b/mpvSettingsEditor/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/mpvSettingsEditor/App.xaml.cs b/mpvSettingsEditor/App.xaml.cs new file mode 100644 index 0000000..a0a8e44 --- /dev/null +++ b/mpvSettingsEditor/App.xaml.cs @@ -0,0 +1,8 @@ +using System.Windows; + +namespace DynamicGUI +{ + public partial class App : Application + { + } +} \ No newline at end of file diff --git a/mpvSettingsEditor/Definitions.toml b/mpvSettingsEditor/Definitions.toml new file mode 100644 index 0000000..b378de6 --- /dev/null +++ b/mpvSettingsEditor/Definitions.toml @@ -0,0 +1,88 @@ +[[settings]] +name = "volume" +default = "" +help = "volume= Set the startup volume. 0 means silence, 100 means no volume reduction or amplification. Negative values can be passed for compatibility, but are treated as 0. Since mpv 0.18.1, this always controls the internal mixer (aka \"softvol\")." + +[[settings]] +name = "screen" +default = "" +help = "screen= 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. Default: default." + +[[settings]] +name = "osd-playing-msg" +default = "" +width = 300 +help = "osd-playing-msg= 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." +helpurl = "https://mpv.io/manual/master/#property-expansion" + +[[settings]] +name = "fullscreen" +alias = "fs" +default = "no" +help = "fullscreen=, fs= Start the player in fullscreen mode. Default: no." +options = [{ name = "yes" }, { name = "no", text = "no (Default)" }] + +[[settings]] +name = "hwdec" +default = "no" +helpurl = "https://mpv.io/manual/master/#options-hwdec" +help = "hwdec= Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If hardware decoding is not possible, mpv will fall back on software decoding." +options = [{ name = "no", text = "no (Default)", help = "always use software decoding (Default)" }, + { name = "auto", help = "enable best hw decoder (see below)" }, + { name = "yes", help = "exactly the same as auto" }, + { name = "auto-copy", help = "enable best hw decoder with copy-back (see below)" }, + { name = "dxva2", help = "requires --vo=gpu with --gpu-context=d3d11, --gpu-context=angle or --gpu-context=dxinterop (Windows only)" }, + { name = "dxva2-copy", help = "copies video back to system RAM (Windows only)" }, + { name = "d3d11va", help = "requires --vo=gpu with --gpu-context=d3d11 or --gpu-context=angle (Windows 8+ only)" }, + { name = "d3d11va-copy", help = "copies video back to system RAM (Windows 8+ only)" }, + { name = "cuda", help = "requires --vo=gpu (Any platform CUDA is available)" }, + { name = "cuda-copy", help = "copies video back to system RAM (Any platform CUDA is available)" }, + { name = "nvdec", help = "requires --vo=gpu (Any platform CUDA is available)" }, + { name = "nvdec-copy", help = "copies video back to system RAM (Any platform CUDA is available)" }, + { name = "crystalhd", help = "copies video back to system RAM (Any platform supported by hardware)" }, + { name = "rkmpp", help = "requires --vo=gpu (some RockChip devices only)" }] + +[[settings]] +name = "vo" +default = "gpu" +helpurl = "https://mpv.io/manual/master/#video-output-drivers-vo" +help = "gpu= Video output drivers to be used. Default = gpu." +options = [{ name = "direct3d", help = "Video output driver that uses the Direct3D interface" }, + { name = "gpu", text = "gpu (Default)", help = "General purpose, customizable, GPU-accelerated video output driver. It supports extended scaling methods, dithering, color management, custom shaders, HDR, and more. (Default)" }] + +[[settings]] +name = "keep-open-pause" +default = "yes" +help = "keep-open-pause= If set to no, instead of pausing when --keep-open is active, just stop at end of file and continue playing forward when you seek backwards until end where it stops again. Default: yes." +options = [{ name = "yes", text = "yes (Default)" }, { name = "no" }] + +[[settings]] +name = "keep-open" +default = "no" +help = "keep-open= Do not terminate when playing or seeking beyond the end of the file, and there is not next file to be played (and --loop is not used). Instead, pause the player. When trying to seek beyond end of the file, the player will attempt to seek to the last frame.\n\nNormally, this will act like set pause yes on EOF, unless the --keep-open-pause=no option is set." +options = [{ name = "no", text = "no (Default)", help = "If the current file ends, go to the next file or terminate. (Default.)" }, + { name = "yes", help = "Don't terminate if the current file is the last playlist entry. Equivalent to --keep-open without arguments."}, + { name = "always", help = "Like yes, but also applies to files before the last playlist entry. This means playback will never automatically advance to the next file."}] + +[[settings]] +name = "loop-file" +alias = "loop" +default = "" +help = "loop-file=, loop= Loop a single file N times. inf means forever, no means normal playback. For compatibility, --loop-file and --loop-file=yes are also accepted, and are the same as --loop-file=inf.\n\nThe difference to --loop-playlist is that this doesn't loop the playlist, just the file itself. If the playlist contains only a single file, the difference between the two option is that this option performs a seek on loop, instead of reloading the file.\n\n--loop is an alias for this option." + +[[settings]] +name = "screenshot-directory" +default = "" +width = 500 +folder = true +help = "screenshot-directory= 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.\n\nThis option is not set by default, and thus will write screenshots to the directory from which mpv was started. In pseudo-gui mode (see PSEUDO GUI MODE), this is set to the desktop." + +[[settings]] +name = "input-ar-delay" +default = "" +help = "input-ar-delay= Delay in milliseconds before we start to autorepeat a key (0 to disable)." + +[[settings]] +name = "input-ar-rate" +default = "" +help = "input-ar-rate= Number of key presses to generate per second on autorepeat." \ No newline at end of file diff --git a/mpvSettingsEditor/DynamicGUI/DynamicGUI.cs b/mpvSettingsEditor/DynamicGUI/DynamicGUI.cs new file mode 100644 index 0000000..50ed9d6 --- /dev/null +++ b/mpvSettingsEditor/DynamicGUI/DynamicGUI.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using Tommy; + +namespace DynamicGUI +{ + public class Settings + { + public static List LoadSettings(string filepath) + { + TomlTable table; + using (StreamReader reader = new StreamReader(File.OpenRead(filepath))) + table = TOML.Parse(reader); + List settingsList = new List(); + + foreach (TomlTable setting in table["settings"]) + { + SettingBase baseSetting = null; + + if (setting.HasKey("options")) + { + OptionSetting optionSetting = new OptionSetting(); + baseSetting = optionSetting; + optionSetting.Default = setting["default"]; + optionSetting.Value = optionSetting.Default; + + foreach (TomlTable option in setting["options"]) + { + var opt = new OptionSettingOption(); + opt.Name = option["name"]; + if (option.HasKey("help")) + opt.Help = option["help"]; + if (option.HasKey("text")) + opt.Text = option["text"]; + opt.OptionSetting = optionSetting; + optionSetting.Options.Add(opt); + } + } + else if (setting["default"].IsString) + { + StringSetting stringSetting = new StringSetting(); + baseSetting = stringSetting; + stringSetting.Default = setting["default"]; + if (setting.HasKey("folder")) stringSetting.IsFolder = true; + } + + baseSetting.Name = setting["name"]; + if (setting.HasKey("help")) baseSetting.Help = setting["help"]; + if (setting.HasKey("alias")) baseSetting.Alias = setting["alias"]; + if (setting.HasKey("width")) baseSetting.Width = setting["width"]; + settingsList.Add(baseSetting); + } + return settingsList; + } + } + + public abstract class SettingBase + { + public string Name { get; set; } + public string Alias { get; set; } + public string Help { get; set; } + public string HelpURL { get; set; } + public int Width { get; set; } + } + + public class StringSetting : SettingBase + { + public string Default { get; set; } + public string Value { get; set; } + public bool IsFolder { get; set; } + } + + public class OptionSetting : SettingBase + { + public string Default { get; set; } + public string Value { get; set; } + public List Options = new List(); + } + + public class OptionSettingOption + { + public string Name { get; set; } + public string Help { get; set; } + + public OptionSetting OptionSetting { get; set; } + + private string _Text; + + public string Text + { + get => string.IsNullOrEmpty(_Text) ? Name : _Text; + set => _Text = value; + } + + //private bool _IsChecked; + + public bool IsChecked + { + get => OptionSetting.Value == Name ; + set { + if (value) + OptionSetting.Value = Name; + } + } + } +} \ No newline at end of file diff --git a/mpvSettingsEditor/DynamicGUI/Misc.cs b/mpvSettingsEditor/DynamicGUI/Misc.cs new file mode 100644 index 0000000..29f4051 --- /dev/null +++ b/mpvSettingsEditor/DynamicGUI/Misc.cs @@ -0,0 +1,7 @@ +namespace DynamicGUI +{ + interface ISearch + { + bool Contains(string searchString); + } +} \ No newline at end of file diff --git a/mpvSettingsEditor/DynamicGUI/OptionSettingControl.xaml b/mpvSettingsEditor/DynamicGUI/OptionSettingControl.xaml new file mode 100644 index 0000000..283100b --- /dev/null +++ b/mpvSettingsEditor/DynamicGUI/OptionSettingControl.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mpvSettingsEditor/DynamicGUI/OptionSettingControl.xaml.cs b/mpvSettingsEditor/DynamicGUI/OptionSettingControl.xaml.cs new file mode 100644 index 0000000..b64375f --- /dev/null +++ b/mpvSettingsEditor/DynamicGUI/OptionSettingControl.xaml.cs @@ -0,0 +1,36 @@ +using DynamicGUI; +using System.Windows.Controls; + +namespace DynamicGUI +{ + public partial class OptionSettingControl : UserControl, ISearch + { + private OptionSetting OptionSetting; + + public OptionSettingControl(OptionSetting optionSetting) + { + OptionSetting = optionSetting; + InitializeComponent(); + TitleTextBox.Text = optionSetting.Name; + HelpTextBox.Text = optionSetting.Help; + ItemsControl.ItemsSource = optionSetting.Options; + } + + private string _SearchableText; + + public string SearchableText { + get { + if (_SearchableText is null) + { + _SearchableText = TitleTextBox.Text + HelpTextBox.Text; + foreach (var i in OptionSetting.Options) + _SearchableText += i.Text + i.Help + i.Name; + _SearchableText = _SearchableText.ToLower(); + } + return _SearchableText; + } + } + + public bool Contains(string searchString) => SearchableText.Contains(searchString.ToLower()); + } +} \ No newline at end of file diff --git a/mpvSettingsEditor/DynamicGUI/StringSettingControl.xaml b/mpvSettingsEditor/DynamicGUI/StringSettingControl.xaml new file mode 100644 index 0000000..f5d5288 --- /dev/null +++ b/mpvSettingsEditor/DynamicGUI/StringSettingControl.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mpvSettingsEditor/DynamicGUI/StringSettingControl.xaml.cs b/mpvSettingsEditor/DynamicGUI/StringSettingControl.xaml.cs new file mode 100644 index 0000000..1becd9b --- /dev/null +++ b/mpvSettingsEditor/DynamicGUI/StringSettingControl.xaml.cs @@ -0,0 +1,54 @@ +using System.Windows; +using System.Windows.Controls; + +namespace DynamicGUI +{ + public partial class StringSettingControl : UserControl, ISearch + { + private StringSetting StringSetting; + + public StringSettingControl(StringSetting stringSetting) + { + StringSetting = stringSetting; + InitializeComponent(); + + TitleTextBox.Text = stringSetting.Name; + HelpTextBox.Text = stringSetting.Help; + ValueTextBox.Text = stringSetting.Value; + if (stringSetting.Width > 0) + ValueTextBox.Width = stringSetting.Width; + if (!StringSetting.IsFolder) + Button.Visibility = Visibility.Hidden; + } + + private string _SearchableText; + + public string SearchableText { + get { + if (_SearchableText is null) + _SearchableText = (TitleTextBox.Text + HelpTextBox.Text +ValueTextBox.Text).ToLower(); + + return _SearchableText; + } + } + + public bool Contains(string searchString) => SearchableText.Contains(searchString.ToLower()); + + public string Text + { + get => StringSetting.Value; + set => StringSetting.Value = value; + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + using (var d = new System.Windows.Forms.FolderBrowserDialog()) + { + d.Description = "Choose a folder."; + d.SelectedPath = ValueTextBox.Text; + if (d.ShowDialog() == System.Windows.Forms.DialogResult.OK) + ValueTextBox.Text = d.SelectedPath; + } + } + } +} \ No newline at end of file diff --git a/mpvSettingsEditor/DynamicGUI/Tommy.cs b/mpvSettingsEditor/DynamicGUI/Tommy.cs new file mode 100644 index 0000000..82dace7 --- /dev/null +++ b/mpvSettingsEditor/DynamicGUI/Tommy.cs @@ -0,0 +1,1824 @@ +#region LICENSE +/** + * MIT License + * + * Copyright (c) 2019 Denis Zhidkikh + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#endregion +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Tommy +{ + #region TOML Nodes + + public abstract class TomlNode : IEnumerable + { + public virtual bool HasValue { get; } = false; + public virtual bool IsArray { get; } = false; + public virtual bool IsTable { get; } = false; + public virtual bool IsString { get; } = false; + public virtual bool IsInteger { get; } = false; + public virtual bool IsFloat { get; } = false; + public virtual bool IsDateTime { get; } = false; + public virtual bool IsBoolean { get; } = false; + public virtual string Comment { get; set; } + + public virtual TomlTable AsTable => this as TomlTable; + public virtual TomlString AsString => this as TomlString; + public virtual TomlInteger AsInteger => this as TomlInteger; + public virtual TomlFloat AsFloat => this as TomlFloat; + public virtual TomlBoolean AsBoolean => this as TomlBoolean; + public virtual TomlDateTime AsDateTime => this as TomlDateTime; + public virtual TomlArray AsArray => this as TomlArray; + + public virtual int ChildrenCount => 0; + + public virtual TomlNode this[string key] { get => null; set { } } + + public virtual TomlNode this[int index] { get => null; set { } } + + public virtual IEnumerable Children { get { yield break; } } + + public virtual IEnumerable Keys { get { yield break; } } + + public IEnumerator GetEnumerator() => Children.GetEnumerator(); + + public virtual bool TryGetNode(string key, out TomlNode node) + { + node = null; + return false; + } + + public virtual bool HasKey(string key) => false; + + public virtual bool HasItemAt(int index) => false; + + public virtual void Add(string key, TomlNode node) { } + + public virtual void Add(TomlNode node) { } + + public virtual void Delete(TomlNode node) { } + + public virtual void Delete(string key) { } + + public virtual void Delete(int index) { } + + public virtual void AddRange(IEnumerable nodes) + { + foreach (var tomlNode in nodes) Add(tomlNode); + } + + public virtual void ToTomlString(TextWriter tw, string name = null) { } + + #region Native type to TOML cast + + public static implicit operator TomlNode(string value) => + new TomlString + { + Value = value + }; + + public static implicit operator TomlNode(bool value) => + new TomlBoolean + { + Value = value + }; + + public static implicit operator TomlNode(long value) => + new TomlInteger + { + Value = value + }; + + public static implicit operator TomlNode(float value) => + new TomlFloat + { + Value = value + }; + + public static implicit operator TomlNode(double value) => + new TomlFloat + { + Value = value + }; + + public static implicit operator TomlNode(DateTime value) => + new TomlDateTime + { + Value = value + }; + + public static implicit operator TomlNode(TomlNode[] nodes) + { + var result = new TomlArray(); + result.AddRange(nodes); + return result; + } + + #endregion + + #region TOML to native type cast + + public static implicit operator string(TomlNode value) => value.ToString(); + + public static implicit operator int(TomlNode value) => (int)value.AsInteger.Value; + + public static implicit operator long(TomlNode value) => value.AsInteger.Value; + + public static implicit operator float(TomlNode value) => (float)value.AsFloat.Value; + + public static implicit operator double(TomlNode value) => value.AsFloat.Value; + + public static implicit operator bool(TomlNode value) => value.AsBoolean.Value; + + public static implicit operator DateTime(TomlNode value) => value.AsDateTime.Value; + + #endregion + } + + public class TomlString : TomlNode + { + public override bool HasValue { get; } = true; + public override bool IsString { get; } = true; + public bool IsMultiline { get; set; } + public bool PreferLiteral { get; set; } + + public string Value { get; set; } + + public override string ToString() => Value; + + public override void ToTomlString(TextWriter tw, string name = null) + { + if (Value.IndexOf(TomlSyntax.LITERAL_STRING_SYMBOL) != -1 && PreferLiteral) PreferLiteral = false; + + var quotes = new string(PreferLiteral ? TomlSyntax.LITERAL_STRING_SYMBOL : TomlSyntax.BASIC_STRING_SYMBOL, + IsMultiline ? 3 : 1); + var result = PreferLiteral ? Value : Value.Escape(!IsMultiline); + tw.Write(quotes); + tw.Write(result); + tw.Write(quotes); + } + } + + public class TomlInteger : TomlNode + { + public enum Base + { + Binary = 2, + Octal = 8, + Decimal = 10, + Hexadecimal = 16 + } + + public override bool IsInteger { get; } = true; + public override bool HasValue { get; } = true; + public Base IntegerBase { get; set; } = Base.Decimal; + + public long Value { get; set; } + + public override string ToString() + { + if (IntegerBase != Base.Decimal) + return $"0{TomlSyntax.BaseIdentifiers[(int)IntegerBase]}{Convert.ToString(Value, (int)IntegerBase)}"; + return Value.ToString(CultureInfo.InvariantCulture); + } + + public override void ToTomlString(TextWriter tw, string name = null) => tw.Write(ToString()); + } + + public class TomlFloat : TomlNode + { + public override bool IsFloat { get; } = true; + public override bool HasValue { get; } = true; + + public double Value { get; set; } + + public override string ToString() + { + if (double.IsNaN(Value)) return TomlSyntax.NAN_VALUE; + + if (double.IsPositiveInfinity(Value)) return TomlSyntax.INF_VALUE; + + if (double.IsNegativeInfinity(Value)) return TomlSyntax.NEG_INF_VALUE; + + return Value.ToString("G", CultureInfo.InvariantCulture); + } + + public override void ToTomlString(TextWriter tw, string name = null) => tw.Write(ToString()); + } + + public class TomlBoolean : TomlNode + { + public override bool IsBoolean { get; } = true; + public override bool HasValue { get; } = true; + + public bool Value { get; set; } + + public override string ToString() => Value ? TomlSyntax.TRUE_VALUE : TomlSyntax.FALSE_VALUE; + + public override void ToTomlString(TextWriter tw, string name = null) => tw.Write(ToString()); + } + + public class TomlDateTime : TomlNode + { + public override bool IsDateTime { get; } = true; + public override bool HasValue { get; } = true; + public bool OnlyDate { get; set; } + public bool OnlyTime { get; set; } + public int SecondsPrecision { get; set; } + + public DateTime Value { get; set; } + + public override string ToString() + { + if (OnlyDate) return Value.ToString(TomlSyntax.LocalDateFormat); + if (OnlyTime) return Value.ToString(TomlSyntax.RFC3339LocalTimeFormats[SecondsPrecision]); + if (Value.Kind == DateTimeKind.Local) + return Value.ToString(TomlSyntax.RFC3339LocalDateTimeFormats[SecondsPrecision]); + return Value.ToString(TomlSyntax.RFC3339Formats[SecondsPrecision]); + } + + public override void ToTomlString(TextWriter tw, string name = null) => tw.Write(ToString()); + } + + public class TomlArray : TomlNode + { + List values; + + public override bool HasValue { get; } = true; + public override bool IsArray { get; } = true; + public bool IsTableArray { get; set; } + public List RawArray => values ?? (values = new List()); + + public override TomlNode this[int index] { + get { + if (index < RawArray.Count) return RawArray[index]; + var lazy = new TomlLazy(this); + this[index] = lazy; + return lazy; + } + set { + if (index == RawArray.Count) + RawArray.Add(value); + else + RawArray[index] = value; + } + } + + public override int ChildrenCount => RawArray.Count; + + public override IEnumerable Children => RawArray.AsEnumerable(); + + public override void Add(TomlNode node) => RawArray.Add(node); + + public override void AddRange(IEnumerable nodes) => RawArray.AddRange(nodes); + + public override void Delete(TomlNode node) => RawArray.Remove(node); + + public override void Delete(int index) => RawArray.RemoveAt(index); + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(TomlSyntax.ARRAY_START_SYMBOL); + + if (ChildrenCount != 0) + { + sb.Append(' '); + foreach (var tomlNode in RawArray) + sb.Append(tomlNode.ToString()).Append(TomlSyntax.ITEM_SEPARATOR).Append(' '); + } + + sb.Append(TomlSyntax.ARRAY_END_SYMBOL); + return sb.ToString(); + } + + public override void ToTomlString(TextWriter tw, string name = null) + { + // If it's a normal array, write it as usual + if (!IsTableArray) + { + tw.Write(ToString()); + return; + } + + tw.WriteLine(); + + Comment?.AsComment(tw); + tw.Write(TomlSyntax.ARRAY_START_SYMBOL); + tw.Write(TomlSyntax.ARRAY_START_SYMBOL); + tw.Write(name); + tw.Write(TomlSyntax.ARRAY_END_SYMBOL); + tw.Write(TomlSyntax.ARRAY_END_SYMBOL); + tw.WriteLine(); + + var first = true; + + foreach (var tomlNode in RawArray) + { + if (!(tomlNode is TomlTable tbl)) + throw new TomlFormatException("The array is marked as array table but contains non-table nodes!"); + + // Ensure it's parsed as a section + tbl.IsInline = false; + + if (!first) + { + tw.WriteLine(); + + Comment?.AsComment(tw); + tw.Write(TomlSyntax.ARRAY_START_SYMBOL); + tw.Write(TomlSyntax.ARRAY_START_SYMBOL); + tw.Write(name); + tw.Write(TomlSyntax.ARRAY_END_SYMBOL); + tw.Write(TomlSyntax.ARRAY_END_SYMBOL); + tw.WriteLine(); + } + + first = false; + + // Don't pass section name because we already specified it + tbl.ToTomlString(tw); + + tw.WriteLine(); + } + } + } + + public class TomlTable : TomlNode + { + Dictionary children; + + public override bool HasValue { get; } = false; + public override bool IsTable { get; } = true; + public bool IsInline { get; set; } + public Dictionary RawTable => children ?? (children = new Dictionary()); + + public override TomlNode this[string key] { + get { + if (RawTable.TryGetValue(key, out var result)) return result; + + var lazy = new TomlLazy(this); + RawTable[key] = lazy; + return lazy; + } + set => RawTable[key] = value; + } + + public override int ChildrenCount => RawTable.Count; + + public override IEnumerable Children => RawTable.Select(kv => kv.Value); + + public override IEnumerable Keys => RawTable.Select(kv => kv.Key); + + public override bool HasKey(string key) => RawTable.ContainsKey(key); + + public override void Add(string key, TomlNode node) => RawTable.Add(key, node); + + public override bool TryGetNode(string key, out TomlNode node) => RawTable.TryGetValue(key, out node); + + public override void Delete(TomlNode node) => RawTable.Remove(RawTable.First(kv => kv.Value == node).Key); + + public override void Delete(string key) => RawTable.Remove(key); + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(TomlSyntax.INLINE_TABLE_START_SYMBOL); + + if (ChildrenCount != 0) + { + sb.Append(' '); + foreach (var child in RawTable) + sb.Append(child.Key) + .Append(' ') + .Append(TomlSyntax.KEY_VALUE_SEPARATOR) + .Append(' ') + .Append(child.Value.ToString()) + .Append(TomlSyntax.ITEM_SEPARATOR) + .Append(' '); + } + + sb.Append(TomlSyntax.INLINE_TABLE_END_SYMBOL); + return sb.ToString(); + } + + public override void ToTomlString(TextWriter tw, string name = null) + { + // The table is inline table + if (IsInline && name != null) + { + tw.Write(ToString()); + return; + } + + Comment?.AsComment(tw); + + if (name != null) + { + tw.Write(TomlSyntax.ARRAY_START_SYMBOL); + tw.Write(name); + tw.Write(TomlSyntax.ARRAY_END_SYMBOL); + tw.WriteLine(); + } + else if (Comment != null) // Add some spacing between the first node and the comment + tw.WriteLine(); + + var namePrefix = name == null ? "" : $"{name}."; + var first = true; + + var sectionableItems = new Dictionary(); + + foreach (var child in RawTable) + { + // If value should be parsed as section, separate if from the bunch + if (child.Value is TomlArray arr && arr.IsTableArray || child.Value is TomlTable tbl && !tbl.IsInline) + { + sectionableItems.Add(child.Key, child.Value); + continue; + } + + if (!first) tw.WriteLine(); + first = false; + + var key = child.Key.AsKey(); + child.Value.Comment?.AsComment(tw); + tw.Write(key); + tw.Write(' '); + tw.Write(TomlSyntax.KEY_VALUE_SEPARATOR); + tw.Write(' '); + + child.Value.ToTomlString(tw, $"{namePrefix}{key}"); + } + + if (sectionableItems.Count == 0) return; + + tw.WriteLine(); + tw.WriteLine(); + first = true; + foreach (var child in sectionableItems) + { + if (!first) tw.WriteLine(); + first = false; + + child.Value.ToTomlString(tw, $"{namePrefix}{child.Key}"); + } + } + } + + class TomlLazy : TomlNode + { + readonly TomlNode parent; + TomlNode replacement; + + public TomlLazy(TomlNode parent) => this.parent = parent; + + public override TomlNode this[int index] { + get => Set()[index]; + set => Set()[index] = value; + } + + public override TomlNode this[string key] { + get => Set()[key]; + set => Set()[key] = value; + } + + public override void Add(TomlNode node) => Set().Add(node); + + public override void Add(string key, TomlNode node) => Set().Add(key, node); + + public override void AddRange(IEnumerable nodes) => Set().AddRange(nodes); + + TomlNode Set() where T : TomlNode, new() + { + if (replacement != null) return replacement; + + var newNode = new T + { + Comment = Comment + }; + + if (parent.IsTable) + { + var key = parent.Keys.FirstOrDefault(s => parent.TryGetNode(s, out var node) && node.Equals(this)); + if (key == null) return default(T); + + parent[key] = newNode; + } + else if (parent.IsArray) + { + var index = 0; + foreach (var child in parent.Children) + { + if (child == this) break; + index++; + } + + if (index == parent.ChildrenCount) return default(T); + + parent[index] = newNode; + } + else + return default(T); + + replacement = newNode; + return newNode; + } + } + + #endregion + + public static class TOML + { + public enum ParseState + { + None, + KeyValuePair, + SkipToNextLine, + Table + } + + public static bool ForceASCII { get; set; } = false; + + public static TomlTable Parse(TextReader reader) + { + var rootNode = new TomlTable(); + var currentNode = rootNode; + var state = ParseState.None; + var keyParts = new List(); + var arrayTable = false; + var latestComment = new StringBuilder(); + var firstComment = true; + + int currentChar; + while ((currentChar = reader.Peek()) >= 0) + { + var c = (char)currentChar; + + if (state == ParseState.None) + { + // Skip white space + if (TomlSyntax.IsWhiteSpace(c)) goto consume_character; + + if (TomlSyntax.IsNewLine(c)) + { + // Check if there are any comments and so far no items being declared + if (latestComment.Length != 0 && firstComment) + { + rootNode.Comment = latestComment.ToString().TrimEnd(); + latestComment.Length = 0; + firstComment = false; + } + + goto consume_character; + } + + // Start of a comment; ignore until newline + if (c == TomlSyntax.COMMENT_SYMBOL) + { + // Consume the comment symbol and buffer the whole comment line + reader.Read(); + latestComment.AppendLine(reader.ReadLine()?.Trim()); + continue; + } + + // Encountered a non-comment value. The comment must belong to it (ignore possible newlines)! + firstComment = false; + + if (c == TomlSyntax.TABLE_START_SYMBOL) + { + state = ParseState.Table; + goto consume_character; + } + + if (TomlSyntax.IsBareKey(c) || TomlSyntax.IsQuoted(c)) + state = ParseState.KeyValuePair; + else + throw new TomlParseException($"Unexpected character \"{c}\"", state); + } + + if (state == ParseState.KeyValuePair) + { + var keyValuePair = ReadKeyValuePair(reader, keyParts); + keyValuePair.Comment = latestComment.ToString().TrimEnd(); + InsertNode(keyValuePair, currentNode, keyParts); + latestComment.Length = 0; + keyParts.Clear(); + state = ParseState.SkipToNextLine; + continue; + } + + if (state == ParseState.Table) + { + if (keyParts.Count == 0) + { + // We have array table + if (c == TomlSyntax.TABLE_START_SYMBOL) + { + // Consume the character + reader.Read(); + arrayTable = true; + } + + ReadKeyName(reader, ref keyParts, TomlSyntax.TABLE_END_SYMBOL, true); + if (keyParts.Count == 0) throw new TomlParseException("Table name is emtpy.", state); + + continue; + } + + if (c == TomlSyntax.TABLE_END_SYMBOL) + { + if (arrayTable) + { + if (reader.Peek() < 0 || (char)reader.Peek() != TomlSyntax.TABLE_END_SYMBOL) + throw new + TomlParseException($"Array table {".".Join(keyParts)} has only one closing bracket.", + state); + // Consume the extra closing table symbol + reader.Read(); + } + + currentNode = CreateTable(rootNode, keyParts, arrayTable); + currentNode.IsInline = false; + currentNode.Comment = latestComment.ToString().TrimEnd(); + keyParts.Clear(); + arrayTable = false; + latestComment.Length = 0; + state = ParseState.SkipToNextLine; + goto consume_character; + } + + if (keyParts.Count != 0) throw new TomlParseException($"Unexpected character \"{c}\"", state); + } + + if (state == ParseState.SkipToNextLine) + { + if (TomlSyntax.IsWhiteSpace(c) || c == TomlSyntax.NEWLINE_CARRIAGE_RETURN_CHARACTER) + goto consume_character; + + if (c == TomlSyntax.COMMENT_SYMBOL || c == TomlSyntax.NEWLINE_CHARACTER) + { + state = ParseState.None; + if (c == TomlSyntax.COMMENT_SYMBOL) + { + reader.ReadLine(); + continue; + } + + goto consume_character; + } + + throw new TomlParseException($"Unexpected character \"{c}\" at the end of the line.", state); + } + + consume_character: + reader.Read(); + } + + if (state != ParseState.None && state != ParseState.SkipToNextLine) + throw new TomlParseException("Unexpected end of file!", state); + + return rootNode; + } + + #region Key-Value pair parsing + + /** + * Reads a single key-value pair. + * Assumes the cursor is at the first character that belong to the pair (including possible whitespace). + * Consumes all characters that belong to the key and the value (ignoring possible trailing whitespace at the end). + * + * Example: + * foo = "bar" ==> foo = "bar" + * ^ ^ + */ + static TomlNode ReadKeyValuePair(TextReader reader, List keyParts) + { + int cur; + while ((cur = reader.Peek()) >= 0) + { + var c = (char)cur; + + if (TomlSyntax.IsQuoted(c) || TomlSyntax.IsBareKey(c)) + { + if (keyParts.Count != 0) + throw new TomlParseException("Encountered extra characters in key definition!", + ParseState.KeyValuePair); + + ReadKeyName(reader, ref keyParts, TomlSyntax.KEY_VALUE_SEPARATOR); + continue; + } + + if (TomlSyntax.IsWhiteSpace(c)) + { + reader.Read(); + continue; + } + + if (c == TomlSyntax.KEY_VALUE_SEPARATOR) + { + reader.Read(); + return ReadValue(reader); + } + + throw new TomlParseException($"Unexpected character \"{c}\" in key name.", ParseState.KeyValuePair); + } + + return null; + } + + /** + * Reads a single value. + * Assumes the cursor is at the first character that belongs to the value (including possible starting whitespace). + * Consumes all characters belonging to the value (ignoring possible trailing whitespace at the end). + * + * Example: + * "test" ==> "test" + * ^ ^ + */ + static TomlNode ReadValue(TextReader reader, bool skipNewlines = false) + { + int cur; + while ((cur = reader.Peek()) >= 0) + { + var c = (char)cur; + + if (TomlSyntax.IsWhiteSpace(c)) + { + reader.Read(); + continue; + } + + if (c == TomlSyntax.COMMENT_SYMBOL) + throw new TomlParseException("No value found!", ParseState.KeyValuePair); + + if (TomlSyntax.IsNewLine(c)) + { + if (skipNewlines) + { + reader.Read(); + continue; + } + + throw new TomlParseException("Encountered a newline when expecting a value!", + ParseState.KeyValuePair); + } + + if (TomlSyntax.IsQuoted(c)) + { + var isMultiline = IsTripleQuote(c, reader, out var excess); + var value = isMultiline + ? ReadQuotedValueMultiLine(c, reader) + : ReadQuotedValueSingleLine(c, reader, excess); + + return new TomlString + { + Value = value, + IsMultiline = isMultiline, + PreferLiteral = c == TomlSyntax.LITERAL_STRING_SYMBOL + }; + } + + if (c == TomlSyntax.INLINE_TABLE_START_SYMBOL) return ReadInlineTable(reader); + + if (c == TomlSyntax.ARRAY_START_SYMBOL) return ReadArray(reader); + + return ReadTomlValue(reader); + } + + return null; + } + + /** + * Reads a single key name. + * Assumes the cursor is at the first character belonging to the key (with possible trailing whitespace if `skipWhitespace = true`). + * Consumes all the characters until the `until` character is met (but does not consume the character itself). + * + * Example 1: + * foo.bar ==> foo.bar (`skipWhitespace = false`, `until = ' '`) + * ^ ^ + * + * Example 2: + * [ foo . bar ] ==> [ foo . bar ] (`skipWhitespace = true`, `until = ']'`) + * ^ ^ + */ + static void ReadKeyName(TextReader reader, ref List parts, char until, bool skipWhitespace = false) + { + var buffer = new StringBuilder(); + var quoted = false; + var prevWasSpace = false; + int cur; + while ((cur = reader.Peek()) >= 0) + { + var c = (char)cur; + + // Reached the final character + if (c == until) break; + + if (TomlSyntax.IsWhiteSpace(c)) + if (skipWhitespace) + { + prevWasSpace = true; + goto consume_character; + } + else + break; + + if (buffer.Length == 0) prevWasSpace = false; + + if (c == TomlSyntax.SUBKEY_SEPARATOR) + { + if (buffer.Length == 0) + throw new TomlParseException($"Found an extra subkey separator in {".".Join(parts)}...", + ParseState.KeyValuePair); + + parts.Add(buffer.ToString()); + buffer.Length = 0; + quoted = false; + prevWasSpace = false; + goto consume_character; + } + + if (prevWasSpace) throw new TomlParseException("Invalid spacing in key name", ParseState.KeyValuePair); + + if (TomlSyntax.IsQuoted(c)) + { + if (quoted) + throw new TomlParseException("Expected a subkey separator but got extra data instead!", + ParseState.KeyValuePair); + if (buffer.Length != 0) + throw new TomlParseException("Encountered a quote in the middle of subkey name!", + ParseState.KeyValuePair); + + // Consume the quote character and read the key name + buffer.Append(ReadQuotedValueSingleLine((char)reader.Read(), reader)); + quoted = true; + continue; + } + + if (TomlSyntax.IsBareKey(c)) + { + buffer.Append(c); + goto consume_character; + } + + // If we see an invalid symbol, let the next parser handle it + break; + + consume_character: + reader.Read(); + } + + if (buffer.Length == 0) + throw new TomlParseException($"Found an extra subkey separator in {".".Join(parts)}...", + ParseState.KeyValuePair); + + parts.Add(buffer.ToString()); + } + + #endregion + + #region Non-string value parsing + + /** + * Reads the whole raw value until the first non-value character is encountered. + * Assumes the cursor start position at the first value character and consumes all characters that may be related to the value. + * Example: + * + * 1_0_0_0 ==> 1_0_0_0 + * ^ ^ + */ + static string ReadRawValue(TextReader reader) + { + var result = new StringBuilder(); + + int cur; + while ((cur = reader.Peek()) >= 0) + { + var c = (char)cur; + + if (c == TomlSyntax.COMMENT_SYMBOL || TomlSyntax.IsNewLine(c) || TomlSyntax.IsValueSeparator(c)) break; + + result.Append(c); + + reader.Read(); + } + + // Replace trim with manual space counting? + return result.ToString().Trim(); + } + + /** + * Reads and parses a non-string, non-composite TOML value. + * Assumes the cursor at the first character that is related to the value (with possible spaces). + * Consumes all the characters that are related to the value. + * + * Example + * 1_0_0_0 # This is a comment ==> 1_0_0_0 # This is a comment + * ^ ^ + */ + static TomlNode ReadTomlValue(TextReader reader) + { + var value = ReadRawValue(reader); + + if (TomlSyntax.IsBoolean(value)) return bool.Parse(value); + + if (TomlSyntax.IsNaN(value)) return double.NaN; + + if (TomlSyntax.IsPosInf(value)) return double.PositiveInfinity; + + if (TomlSyntax.IsNegInf(value)) return double.NegativeInfinity; + + if (TomlSyntax.IsInteger(value)) + return long.Parse(value.RemoveAll(TomlSyntax.INT_NUMBER_SEPARATOR), CultureInfo.InvariantCulture); + + if (TomlSyntax.IsFloat(value)) + return double.Parse(value.RemoveAll(TomlSyntax.INT_NUMBER_SEPARATOR), CultureInfo.InvariantCulture); + + if (TomlSyntax.IsIntegerWithBase(value, out var numberBase)) + return new TomlInteger + { + Value = Convert.ToInt64(value.Substring(2).RemoveAll(TomlSyntax.INT_NUMBER_SEPARATOR), numberBase), + IntegerBase = (TomlInteger.Base)numberBase + }; + + value = value.Replace("T", " "); + if (StringUtils.TryParseDateTime(value, + TomlSyntax.RFC3339LocalDateTimeFormats, + DateTimeStyles.AssumeLocal, + out var dateTimeResult, + out var precision)) + return new TomlDateTime + { + Value = dateTimeResult, + SecondsPrecision = precision + }; + + if (StringUtils.TryParseDateTime(value, + TomlSyntax.RFC3339Formats, + DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, + out dateTimeResult, + out precision)) + return new TomlDateTime + { + Value = dateTimeResult, + SecondsPrecision = precision + }; + + + if (DateTime.TryParseExact(value, + TomlSyntax.LocalDateFormat, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeLocal, + out dateTimeResult)) + return new TomlDateTime + { + Value = dateTimeResult, + OnlyDate = true + }; + + if (StringUtils.TryParseDateTime(value, + TomlSyntax.RFC3339LocalTimeFormats, + DateTimeStyles.AssumeLocal, + out dateTimeResult, + out precision)) + return new TomlDateTime + { + Value = dateTimeResult, + OnlyTime = true, + SecondsPrecision = precision + }; + + throw new TomlParseException($"Value \"{value}\" is not a valid TOML 0.5.0 value!", + ParseState.KeyValuePair); + } + + /** + * Reads an array value. + * Assumes the cursor is at the start of the array definition. Reads all character until the array closing bracket. + * + * Example: + * [1, 2, 3] ==> [1, 2, 3] + * ^ ^ + */ + static TomlArray ReadArray(TextReader reader) + { + // Consume the start of array character + reader.Read(); + + var result = new TomlArray(); + + TomlNode currentValue = null; + + int cur; + while ((cur = reader.Peek()) >= 0) + { + var c = (char)cur; + + if (c == TomlSyntax.ARRAY_END_SYMBOL) + { + reader.Read(); + break; + } + + if (c == TomlSyntax.COMMENT_SYMBOL) + { + reader.ReadLine(); + continue; + } + + if (TomlSyntax.IsWhiteSpace(c) || TomlSyntax.IsNewLine(c)) goto consume_character; + + if (c == TomlSyntax.ITEM_SEPARATOR) + { + if (currentValue == null) + throw new TomlParseException("Encountered multiple value separators in an array!", + ParseState.KeyValuePair); + + result.Add(currentValue); + currentValue = null; + goto consume_character; + } + + currentValue = ReadValue(reader, true); + + if (result.ChildrenCount != 0 && result[0].GetType() != currentValue.GetType()) + throw new TomlParseException("Arrays cannot have mixed types!", ParseState.KeyValuePair); + + continue; + + consume_character: + reader.Read(); + } + + if (currentValue != null) result.Add(currentValue); + + return result; + } + + /** + * Reads an inline table. + * Assumes the cursor is at the start of the table definition. Reads all character until the table closing bracket. + * + * Example: + * { test = "foo", value = 1 } ==> { test = "foo", value = 1 } + * ^ ^ + */ + static TomlNode ReadInlineTable(TextReader reader) + { + reader.Read(); + + var result = new TomlTable + { + IsInline = true + }; + + TomlNode currentValue = null; + + var keyParts = new List(); + + int cur; + while ((cur = reader.Peek()) >= 0) + { + var c = (char)cur; + + if (c == TomlSyntax.INLINE_TABLE_END_SYMBOL) + { + reader.Read(); + break; + } + + if (c == TomlSyntax.COMMENT_SYMBOL) + throw new TomlParseException("Incomplete inline table definition!", ParseState.Table); + + if (TomlSyntax.IsNewLine(c)) + throw new TomlParseException("Inline tables are only allowed to be on single line", + ParseState.Table); + + if (TomlSyntax.IsWhiteSpace(c)) goto consume_character; + + if (c == TomlSyntax.ITEM_SEPARATOR) + { + if (currentValue == null) + throw new TomlParseException("Encountered multiple value separators in inline table!", + ParseState.Table); + + InsertNode(currentValue, result, keyParts); + keyParts.Clear(); + currentValue = null; + goto consume_character; + } + + currentValue = ReadKeyValuePair(reader, keyParts); + continue; + + consume_character: + reader.Read(); + } + + if (currentValue != null) InsertNode(currentValue, result, keyParts); + + return result; + } + + #endregion + + #region String parsing + + /** + * Checks if the string value a multiline string (i.e. a triple quoted string). + * Assumes the cursor is at the first quote character. Consumes the least amount of characters needed to determine if the string is multiline. + * + * If the result is false, returns the consumed character through the `excess` variable. + * + * Example 1: + * """test""" ==> """test""" + * ^ ^ + * + * Example 2: + * "test" ==> "test" (doesn't return the first quote) + * ^ ^ + * + * Example 3: + * "" ==> "" (returns the extra `"` through the `excess` variable) + * ^ ^ + */ + static bool IsTripleQuote(char quote, TextReader reader, out char excess) + { + // Copypasta, but it's faster... + + int cur; + // Consume the first quote + reader.Read(); + + if ((cur = reader.Peek()) < 0) + throw new TomlParseException("Unexpected end of file!", ParseState.KeyValuePair); + + if ((char)cur != quote) + { + excess = '\0'; + return false; + } + + // Consume the second quote + excess = (char)reader.Read(); + + if ((cur = reader.Peek()) < 0 || (char)cur != quote) return false; + + // Consume the final quote + reader.Read(); + + excess = '\0'; + return true; + } + + /** + * A convenience method to process a single character within a quote. + */ + static bool ProcessQuotedValueCharacter(char quote, + bool isNonLiteral, + char c, + int next, + StringBuilder sb, + ref bool escaped) + { + if (TomlSyntax.ShouldBeEscaped(c)) + throw new TomlParseException($"The character U+{(int)c:X8} must be escaped in a string!", + ParseState.KeyValuePair); + + if (escaped) + { + sb.Append(c); + escaped = false; + return false; + } + + if (c == quote) return true; + + if (isNonLiteral && c == TomlSyntax.ESCAPE_SYMBOL) + if (next >= 0 && (char)next == quote) + escaped = true; + + if (c == TomlSyntax.NEWLINE_CHARACTER) + throw new TomlParseException("Encountered newline in single line string!", ParseState.KeyValuePair); + + sb.Append(c); + return false; + } + + /** + * Reads a single-line string. + * Assumes the cursor is at the first character that belongs to the string. + * Consumes all characters that belong to the string (including the closing quote). + * + * Example: + * "test" ==> "test" + * ^ ^ + */ + static string ReadQuotedValueSingleLine(char quote, TextReader reader, char initialData = '\0') + { + var isNonLiteral = quote == TomlSyntax.BASIC_STRING_SYMBOL; + var sb = new StringBuilder(); + + var escaped = false; + + if (initialData != '\0' && + ProcessQuotedValueCharacter(quote, isNonLiteral, initialData, reader.Peek(), sb, ref escaped)) + return isNonLiteral ? sb.ToString().Unescape() : sb.ToString(); + + int cur; + while ((cur = reader.Read()) >= 0) + { + var c = (char)cur; + if (ProcessQuotedValueCharacter(quote, isNonLiteral, c, reader.Peek(), sb, ref escaped)) break; + } + + return isNonLiteral ? sb.ToString().Unescape() : sb.ToString(); + } + + /** + * Reads a multiline string. + * Assumes the cursor is at the first character that belongs to the string. + * Consumes all characters that belong to the string and the three closing quotes. + * + * Example: + * """test""" ==> """test""" + * ^ ^ + */ + static string ReadQuotedValueMultiLine(char quote, TextReader reader) + { + var isBasic = quote == TomlSyntax.BASIC_STRING_SYMBOL; + var sb = new StringBuilder(); + + var escaped = false; + var skipWhitespace = false; + var quotesEncountered = 0; + var first = true; + + int cur; + while ((cur = reader.Read()) >= 0) + { + var c = (char)cur; + + if (TomlSyntax.ShouldBeEscaped(c)) + throw new Exception($"The character U+{(int)c:X8} must be escaped!"); + + // Trim the first newline + if (first && TomlSyntax.IsNewLine(c)) + { + if (c != TomlSyntax.NEWLINE_CARRIAGE_RETURN_CHARACTER) first = false; + continue; + } + + first = false; + + //TODO: Reuse ProcessQuotedValueCharacter + + // Skip the current character if it is going to be escaped later + if (escaped) + { + sb.Append(c); + escaped = false; + continue; + } + + // If we are currently skipping empty spaces, skip + if (skipWhitespace) + { + if (TomlSyntax.IsEmptySpace(c)) continue; + skipWhitespace = false; + } + + // If we encounter an escape sequence... + if (isBasic && c == TomlSyntax.ESCAPE_SYMBOL) + { + var next = reader.Peek(); + if (next >= 0) + { + // ...and the next char is empty space, we must skip all whitespaces + if (TomlSyntax.IsEmptySpace((char)next)) + { + skipWhitespace = true; + continue; + } + + // ...and we have \", skip the character + if ((char)next == quote) escaped = true; + } + } + + // Count the consecutive quotes + if (c == quote) + quotesEncountered++; + else + quotesEncountered = 0; + + // If the are three quotes, count them as closing quotes + if (quotesEncountered == 3) break; + + sb.Append(c); + } + + // Remove last two quotes (third one wasn't included by default + sb.Length -= 2; + + return isBasic ? sb.ToString().Unescape() : sb.ToString(); + } + + #endregion + + #region Node creation + + static void InsertNode(TomlNode node, TomlNode root, List path) + { + var latestNode = root; + + if (path.Count > 1) + for (var index = 0; index < path.Count - 1; index++) + { + var subkey = path[index]; + if (latestNode.TryGetNode(subkey, out var currentNode)) + { + if (currentNode.HasValue) + throw new + TomlParseException($"The key {".".Join(path)} already has a value assigned to it!", + ParseState.KeyValuePair); + } + else + { + currentNode = new TomlTable + { + IsInline = true + }; + latestNode[subkey] = currentNode; + } + + latestNode = currentNode; + } + + if (latestNode.HasKey(path[path.Count - 1])) + throw new TomlParseException($"The key {".".Join(path)} is already defined!", ParseState.KeyValuePair); + + latestNode[path[path.Count - 1]] = node; + } + + static TomlTable CreateTable(TomlNode root, List path, bool arrayTable) + { + if (path.Count == 0) return null; + + var latestNode = root; + + for (var index = 0; index < path.Count; index++) + { + var subkey = path[index]; + + if (latestNode.TryGetNode(subkey, out var node)) + { + if (node.IsArray && arrayTable) + { + var arr = (TomlArray)node; + + if (!arr.IsTableArray) + throw new + TomlParseException($"The array {".".Join(path)} cannot be redefined as an array table!", + ParseState.Table); + + if (index == path.Count - 1) + { + latestNode = new TomlTable(); + arr.Add(latestNode); + break; + } + + latestNode = arr[arr.ChildrenCount - 1]; + continue; + } + + if (node.HasValue) + { + if (!(node is TomlArray array) || !array.IsTableArray) + throw new TomlParseException($"The key {".".Join(path)} has a value assigned to it!", + ParseState.Table); + + latestNode = array[array.ChildrenCount - 1]; + continue; + } + + if (index == path.Count - 1) + { + if (arrayTable && !node.IsArray) + throw new + TomlParseException($"The table {".".Join(path)} cannot be redefined as an array table!", + ParseState.Table); + if (node is TomlTable tbl && !tbl.IsInline) + throw new TomlParseException($"The table {".".Join(path)} is defined multiple times!", + ParseState.Table); + } + } + else + { + if (index == path.Count - 1 && arrayTable) + { + var table = new TomlTable(); + var arr = new TomlArray + { + IsTableArray = true + }; + arr.Add(table); + latestNode[subkey] = arr; + latestNode = table; + break; + } + + node = new TomlTable + { + IsInline = true + }; + latestNode[subkey] = node; + } + + latestNode = node; + } + + var result = (TomlTable)latestNode; + return result; + } + + #endregion + } + + #region Exception Types + + public class TomlException : Exception + { + public TomlException(string message) : base(message) { } + } + + public class TomlParseException : TomlException + { + public TomlParseException(string message, TOML.ParseState state) : base(message) => ParseState = state; + public TOML.ParseState ParseState { get; } + } + + public class TomlFormatException : TomlException + { + public TomlFormatException(string message) : base(message) { } + } + + #endregion + + #region Parse utilities + + static class TomlSyntax + { + #region Type Patterns + + public const string TRUE_VALUE = "true"; + public const string FALSE_VALUE = "false"; + public const string NAN_VALUE = "nan"; + public const string POS_NAN_VALUE = "+nan"; + public const string NEG_NAN_VALUE = "-nan"; + public const string INF_VALUE = "inf"; + public const string POS_INF_VALUE = "+inf"; + public const string NEG_INF_VALUE = "-inf"; + + public static bool IsBoolean(string s) => s == TRUE_VALUE || s == FALSE_VALUE; + + public static bool IsPosInf(string s) => s == INF_VALUE || s == POS_INF_VALUE; + + public static bool IsNegInf(string s) => s == NEG_INF_VALUE; + + public static bool IsNaN(string s) => s == NAN_VALUE || s == POS_NAN_VALUE || s == NEG_NAN_VALUE; + + public static bool IsInteger(string s) => IntegerPattern.IsMatch(s); + + public static bool IsFloat(string s) => FloatPattern.IsMatch(s); + + public static bool IsIntegerWithBase(string s, out int numberBase) + { + numberBase = 10; + var match = BasedIntegerPattern.Match(s); + if (!match.Success) return false; + IntegerBases.TryGetValue(match.Groups["base"].Value, out numberBase); + return true; + } + + /** + * A pattern to verify the integer value according to the TOML specification. + */ + public static readonly Regex IntegerPattern = + new Regex(@"^(\+|-)?(?!_)(0|(?!0)(_?\d)*)$", RegexOptions.Compiled); + + /** + * A pattern to verify a special 0x, 0o and 0b forms of an integer according to the TOML specification. + */ + public static readonly Regex BasedIntegerPattern = + new Regex(@"^(\+|-)?0(?x|b|o)(?!_)(_?[0-9A-F])*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + /** + * A pattern to verify the float value according to the TOML specification. + */ + public static readonly Regex FloatPattern = + new + Regex(@"^(\+|-)?(?!_)(0|(?!0)(_?\d)+)(((e(\+|-)?(?!_)(_?\d)+)?)|(\.(?!_)(_?\d)+(e(\+|-)?(?!_)(_?\d)+)?))$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + /** + * A helper dictionary to map TOML base codes into the radii. + */ + public static readonly Dictionary IntegerBases = new Dictionary + { + ["x"] = 16, + ["o"] = 8, + ["b"] = 2 + }; + + /** + * A helper dictionary to map non-decimal bases to their TOML identifiers + */ + public static readonly Dictionary BaseIdentifiers = new Dictionary + { + [2] = "b", + [8] = "o", + [16] = "x" + }; + + /** + * Valid date formats with timezone as per RFC3339. + */ + public static readonly string[] RFC3339Formats = + { + "yyyy'-'MM-dd HH':'mm':'ssK", "yyyy'-'MM-dd HH':'mm':'ss'.'fK", "yyyy'-'MM-dd HH':'mm':'ss'.'ffK", + "yyyy'-'MM-dd HH':'mm':'ss'.'fffK", "yyyy'-'MM-dd HH':'mm':'ss'.'ffffK", + "yyyy'-'MM-dd HH':'mm':'ss'.'fffffK", "yyyy'-'MM-dd HH':'mm':'ss'.'ffffffK", + "yyyy'-'MM-dd HH':'mm':'ss'.'fffffffK" + }; + + /** + * Valid date formats without timezone (assumes local) as per RFC3339. + */ + public static readonly string[] RFC3339LocalDateTimeFormats = + { + "yyyy'-'MM-dd HH':'mm':'ss", "yyyy'-'MM-dd HH':'mm':'ss'.'f", "yyyy'-'MM-dd HH':'mm':'ss'.'ff", + "yyyy'-'MM-dd HH':'mm':'ss'.'fff", "yyyy'-'MM-dd HH':'mm':'ss'.'ffff", + "yyyy'-'MM-dd HH':'mm':'ss'.'fffff", "yyyy'-'MM-dd HH':'mm':'ss'.'ffffff", + "yyyy'-'MM-dd HH':'mm':'ss'.'fffffff" + }; + + /** + * Valid full date format as per TOML spec. + */ + public static readonly string LocalDateFormat = "yyyy'-'MM'-'dd"; + + /** + * Valid time formats as per TOML spec. + */ + public static readonly string[] RFC3339LocalTimeFormats = + { + "HH':'mm':'ss", "HH':'mm':'ss'.'f", "HH':'mm':'ss'.'ff", "HH':'mm':'ss'.'fff", "HH':'mm':'ss'.'ffff", + "HH':'mm':'ss'.'fffff", "HH':'mm':'ss'.'ffffff", "HH':'mm':'ss'.'fffffff" + }; + + #endregion + + #region Character definitions + + public const char ARRAY_END_SYMBOL = ']'; + public const char ITEM_SEPARATOR = ','; + public const char ARRAY_START_SYMBOL = '['; + public const char BASIC_STRING_SYMBOL = '\"'; + public const char COMMENT_SYMBOL = '#'; + public const char ESCAPE_SYMBOL = '\\'; + public const char KEY_VALUE_SEPARATOR = '='; + public const char NEWLINE_CARRIAGE_RETURN_CHARACTER = '\r'; + public const char NEWLINE_CHARACTER = '\n'; + public const char SUBKEY_SEPARATOR = '.'; + public const char TABLE_END_SYMBOL = ']'; + public const char TABLE_START_SYMBOL = '['; + public const char INLINE_TABLE_START_SYMBOL = '{'; + public const char INLINE_TABLE_END_SYMBOL = '}'; + public const char LITERAL_STRING_SYMBOL = '\''; + public const char INT_NUMBER_SEPARATOR = '_'; + + public static readonly char[] NewLineCharacters = { NEWLINE_CHARACTER, NEWLINE_CARRIAGE_RETURN_CHARACTER }; + + + public static bool IsQuoted(char c) => c == BASIC_STRING_SYMBOL || c == LITERAL_STRING_SYMBOL; + + public static bool IsWhiteSpace(char c) => c == ' ' || c == '\t'; + + public static bool IsNewLine(char c) => c == NEWLINE_CHARACTER || c == NEWLINE_CARRIAGE_RETURN_CHARACTER; + + public static bool IsEmptySpace(char c) => IsWhiteSpace(c) || IsNewLine(c); + + public static bool IsBareKey(char c) => + 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '-'; + + public static bool ShouldBeEscaped(char c) => (c <= '\u001f' || c == '\u007f') && !IsNewLine(c); + + public static bool IsValueSeparator(char c) => + c == ITEM_SEPARATOR || c == ARRAY_END_SYMBOL || c == INLINE_TABLE_END_SYMBOL; + + #endregion + } + + static class StringUtils + { + public static string AsKey(this string key) + { + var quote = false; + foreach (var c in key) + { + if (TomlSyntax.IsBareKey(c)) continue; + quote = true; + break; + } + return !quote ? key : $"{TomlSyntax.BASIC_STRING_SYMBOL}{key.Escape()}{TomlSyntax.BASIC_STRING_SYMBOL}"; + } + + public static string Join(this string self, IEnumerable subItems) + { + var sb = new StringBuilder(); + var first = true; + + foreach (var subItem in subItems) + { + if (!first) sb.Append(self); + first = false; + sb.Append(subItem); + } + + return sb.ToString(); + } + + public static bool TryParseDateTime(string s, + string[] formats, + DateTimeStyles styles, + out DateTime dateTime, + out int parsedFormat) + { + parsedFormat = 0; + dateTime = new DateTime(); + + for (var i = 0; i < formats.Length; i++) + { + var format = formats[i]; + if (!DateTime.TryParseExact(s, format, CultureInfo.InvariantCulture, styles, out dateTime)) continue; + parsedFormat = i; + return true; + } + + return false; + } + + public static void AsComment(this string self, TextWriter tw) + { + foreach (var line in self.Split(TomlSyntax.NEWLINE_CHARACTER)) + tw.WriteLine($"{TomlSyntax.COMMENT_SYMBOL} {line.Trim()}"); + } + + public static string RemoveAll(this string txt, char toRemove) + { + var sb = new StringBuilder(txt.Length); + + foreach (var c in txt) + if (c != toRemove) + sb.Append(c); + + return sb.ToString(); + } + + public static string Escape(this string txt, bool escapeNewlines = true) + { + var stringBuilder = new StringBuilder(txt.Length + 2); + for (var i = 0; i < txt.Length; i++) + { + var c = txt[i]; + switch (c) + { + case '\b': + stringBuilder.Append(@"\b"); + break; + case '\t': + stringBuilder.Append(@"\t"); + break; + case '\n' when escapeNewlines: + stringBuilder.Append(@"\n"); + break; + case '\f': + stringBuilder.Append(@"\f"); + break; + case '\r' when escapeNewlines: + stringBuilder.Append(@"\r"); + break; + case '\\': + stringBuilder.Append(@"\"); + break; + case '\"': + stringBuilder.Append(@"\"""); + break; + default: + if (TomlSyntax.ShouldBeEscaped(c) || TOML.ForceASCII && c > sbyte.MaxValue) + { + if (char.IsSurrogatePair(txt, i)) + stringBuilder.Append("\\U").Append(char.ConvertToUtf32(txt, i++).ToString("X8")); + else + stringBuilder.Append("\\u").Append(((ushort)c).ToString("X4")); + } + else + stringBuilder.Append(c); + + break; + } + } + + return stringBuilder.ToString(); + } + + + public static string Unescape(this string txt) + { + if (string.IsNullOrEmpty(txt)) return txt; + var stringBuilder = new StringBuilder(txt.Length); + for (var i = 0; i < txt.Length;) + { + var num = txt.IndexOf('\\', i); + var next = num + 1; + if (num < 0 || num == txt.Length - 1) num = txt.Length; + stringBuilder.Append(txt, i, num - i); + if (num >= txt.Length) break; + var c = txt[next]; + switch (c) + { + case 'b': + stringBuilder.Append('\b'); + break; + case 't': + stringBuilder.Append('\t'); + break; + case 'n': + stringBuilder.Append('\n'); + break; + case 'f': + stringBuilder.Append('\f'); + break; + case 'r': + stringBuilder.Append('\r'); + break; + case '\'': + stringBuilder.Append('\''); + break; + case '\"': + stringBuilder.Append('\"'); + break; + case '\\': + stringBuilder.Append('\\'); + break; + case 'u': + if (next + 4 >= txt.Length) throw new Exception("Undefined escape sequence!"); + stringBuilder.Append(char.ConvertFromUtf32(Convert.ToInt32(txt.Substring(next + 1, 4), 16))); + num += 4; + break; + case 'U': + if (next + 8 >= txt.Length) throw new Exception("Undefined escape sequence!"); + stringBuilder.Append(char.ConvertFromUtf32(Convert.ToInt32(txt.Substring(next + 1, 8), 16))); + num += 8; + break; + default: + throw new Exception("Undefined escape sequence!"); + } + + i = num + 2; + } + + return stringBuilder.ToString(); + } + } + + #endregion +} \ No newline at end of file diff --git a/mpvSettingsEditor/MainWindow.xaml b/mpvSettingsEditor/MainWindow.xaml new file mode 100644 index 0000000..8172b0d --- /dev/null +++ b/mpvSettingsEditor/MainWindow.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mpvSettingsEditor/MainWindow.xaml.cs b/mpvSettingsEditor/MainWindow.xaml.cs new file mode 100644 index 0000000..02c949c --- /dev/null +++ b/mpvSettingsEditor/MainWindow.xaml.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using DynamicGUI; + +namespace DynamicGUI +{ + public partial class MainWindow : Window + { + public string mpvConfPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\mpv.conf"; + private List mpvSettings = Settings.LoadSettings("Definitions.toml"); + + public MainWindow() + { + InitializeComponent(); + Title = (Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), true)[0] as AssemblyProductAttribute).Product + " " + Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + foreach (var setting in mpvSettings) + { + foreach (var pair in mpvConf) + { + if (setting.Name == pair.Key || setting.Alias == pair.Key) + switch (setting) + { + case StringSetting s: + s.Value = pair.Value; + continue; + case OptionSetting s: + s.Value = pair.Value; + break; + } + } + switch (setting) + { + case StringSetting s: + MainWrapPanel.Children.Add(new StringSettingControl(s)); + break; + case OptionSetting s: + MainWrapPanel.Children.Add(new OptionSettingControl(s)); + break; + } + } + } + + private Dictionary _mpvConf; + + public Dictionary mpvConf { + get { + if (_mpvConf == null) + { + _mpvConf = new Dictionary(); + + if (File.Exists(mpvConfPath)) + foreach (var i in File.ReadAllLines(mpvConfPath)) + if (i.Contains("=") && !i.Trim().StartsWith("#")) + { + int pos = i.IndexOf("="); + _mpvConf[i.Substring(0, pos).Trim()] = i.Substring(pos + 1).Trim(); + } + } + return _mpvConf; + } + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + foreach (var mpvSetting in mpvSettings) + { + switch (mpvSetting) + { + case StringSetting s: + if ((s.Value ?? "") != s.Default) + mpvConf[s.Name] = s.Value; + else + mpvConf.Remove(s.Name); + break; + case OptionSetting s: + if ((s.Value ?? "") != s.Default) + mpvConf[s.Name] = s.Value; + else + mpvConf.Remove(s.Name); + break; + } + } + + if (!File.Exists(mpvConfPath)) + File.WriteAllText(mpvConfPath, ""); + + List lines = File.ReadAllLines(mpvConfPath).ToList(); + + foreach (var mpvSetting in mpvSettings) + { + foreach (var line in lines.ToArray()) + { + string test = line.Replace("#", "").Replace(" ", ""); + if (test.StartsWith(mpvSetting.Alias + "=")) + { + lines.Remove(line); + foreach (var pair in mpvConf.ToArray()) + if (test.StartsWith(pair.Key + "=")) + mpvConf.Remove(pair.Key); + } + } + } + + foreach (var pair in mpvConf) + { + bool changed = false; + + for (int i = 0; i < lines.Count; i++) + { + if (lines[i].Contains("=") && + lines[i].Substring(0, lines[i].IndexOf("=")).Trim("# ".ToCharArray()) == pair.Key) + { + lines[i] = pair.Key + " = " + pair.Value; + changed = true; + } + } + + if (!changed) + lines.Add(pair.Key + " = " + pair.Value); + } + + foreach (var mpvSetting in mpvSettings) + { + foreach (var line in lines.ToArray()) + { + string test = line.Replace("#", "").Replace(" ", ""); + + if (test.StartsWith(mpvSetting.Name + "=") && !mpvConf.ContainsKey(mpvSetting.Name)) + lines.Remove(line); + } + } + + File.WriteAllText(mpvConfPath, String.Join(Environment.NewLine, lines)); + MessageBox.Show("If running, restart mpv/mpv.net", Title, MessageBoxButton.OK, MessageBoxImage.Information); + } + + private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + for (int i = MainWrapPanel.Children.Count - 1; i >= 0; i--) + { + if ((MainWrapPanel.Children[i] as ISearch).Contains(SearchTextBox.Text)) + MainWrapPanel.Children[i].Visibility = Visibility.Visible; + else + MainWrapPanel.Children[i].Visibility = Visibility.Collapsed; + } + } + } +} \ No newline at end of file diff --git a/mpvSettingsEditor/Properties/AssemblyInfo.cs b/mpvSettingsEditor/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cd1b98e --- /dev/null +++ b/mpvSettingsEditor/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("mpv settings editor")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("mpv settings editor")] +[assembly: AssemblyCopyright("Copyright © stax76")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.1.0.0")] +[assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/mpvSettingsEditor/Properties/Resources.Designer.cs b/mpvSettingsEditor/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6087948 --- /dev/null +++ b/mpvSettingsEditor/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DynamicGUI.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("mpvSettingsEditor.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/mpvSettingsEditor/Properties/Resources.resx b/mpvSettingsEditor/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/mpvSettingsEditor/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/mpvSettingsEditor/Properties/Settings.Designer.cs b/mpvSettingsEditor/Properties/Settings.Designer.cs new file mode 100644 index 0000000..758d462 --- /dev/null +++ b/mpvSettingsEditor/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DynamicGUI.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/mpvSettingsEditor/Properties/Settings.settings b/mpvSettingsEditor/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/mpvSettingsEditor/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/mpvSettingsEditor/mpvSettingsEditor.csproj b/mpvSettingsEditor/mpvSettingsEditor.csproj new file mode 100644 index 0000000..08af692 --- /dev/null +++ b/mpvSettingsEditor/mpvSettingsEditor.csproj @@ -0,0 +1,128 @@ + + + + + Debug + AnyCPU + {C4FEAA45-001D-4DC8-8BFA-621527326D09} + WinExe + mpvSettingsEditor + mpvSettingsEditor + v4.8 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + + AnyCPU + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + 8.0 + true + enable + false + + + AnyCPU + pdbonly + true + bin\ + TRACE + prompt + 4 + false + 8.0 + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + OptionSettingControl.xaml + + + StringSettingControl.xaml + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + Always + + + + \ No newline at end of file diff --git a/mpvSettingsEditor/mpvSettingsEditor.sln b/mpvSettingsEditor/mpvSettingsEditor.sln new file mode 100644 index 0000000..ec1caaa --- /dev/null +++ b/mpvSettingsEditor/mpvSettingsEditor.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28714.193 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mpvSettingsEditor", "mpvSettingsEditor.csproj", "{C4FEAA45-001D-4DC8-8BFA-621527326D09}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C4FEAA45-001D-4DC8-8BFA-621527326D09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4FEAA45-001D-4DC8-8BFA-621527326D09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4FEAA45-001D-4DC8-8BFA-621527326D09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4FEAA45-001D-4DC8-8BFA-621527326D09}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {959F2890-E1FC-47A2-856C-A42F8C955D15} + EndGlobalSection +EndGlobal diff --git a/release.ps1 b/release.ps1 index 0efd77d..4252bd0 100644 --- a/release.ps1 +++ b/release.ps1 @@ -3,8 +3,10 @@ $exePath = $scriptDir + "\mpv.net\bin\Debug\mpvnet.exe" $version = [Diagnostics.FileVersionInfo]::GetVersionInfo($exePath).FileVersion $desktopDir = [Environment]::GetFolderPath("Desktop") $targetDir = $desktopDir + "\mpv.net-" + $version -Copy-Item $scriptDir\mpv.net\bin\Debug $targetDir -Recurse -Exclude System.Management.Automation.xml -Copy-Item $scriptDir\README.md $targetDir\README.md +Copy-Item C:\Users\frank\Daten\Projekte\CS\mpv.net\mpvSettingsEditor\bin\mpvSettingsEditor.exe "C:\Users\frank\Daten\Projekte\CS\mpv.net\mpv.net\bin\Debug\mpvSettingsEditor.exe" -Force +Copy-Item C:\Users\frank\Daten\Projekte\CS\mpv.net\mpvSettingsEditor\bin\Definitions.toml "C:\Users\frank\Daten\Projekte\CS\mpv.net\mpv.net\bin\Debug\Definitions.toml" -Force +Copy-Item $scriptDir\mpv.net\bin\Debug $targetDir -Recurse -Exclude System.Management.Automation.xml -Force +Copy-Item $scriptDir\README.md $targetDir\README.md -Force $7zPath = "C:\Program Files\7-Zip\7z.exe" $args = "a -t7z -mx9 $targetDir.7z -r $targetDir\*" Start-Process -FilePath $7zPath -ArgumentList $args \ No newline at end of file