Profile selection in the context menu
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
|
|
||||||
5.4.9.2 Beta (2021-??-??)
|
5.4.9.2 Beta (2021-??-??)
|
||||||
|
|
||||||
- Manual translated to simplified Chinese (hooke007)
|
- Manual translated to simplified Chinese. (hooke007)
|
||||||
- Showing the playlist selects the currently played file/stream in the playlist.
|
- Showing the playlist selects the currently played file/stream in the playlist.
|
||||||
- Properties are shown in the command palette instead of the text editor
|
- Properties are shown in the command palette instead of the text editor
|
||||||
making it very easy to find a property and show/print its value.
|
making it very easy to find a property and show/print its value.
|
||||||
- Support for --keep-open=no.
|
- Support for --keep-open=no.
|
||||||
|
- Profile selection in the context menu.
|
||||||
|
|
||||||
|
|
||||||
5.4.9.1 Beta (2021-06-23)
|
5.4.9.1 Beta (2021-06-23)
|
||||||
|
|||||||
415
src/Misc/JSONParser.cs
Normal file
415
src/Misc/JSONParser.cs
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
|
||||||
|
// https://github.com/zanders3/json
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace mpvnet
|
||||||
|
{
|
||||||
|
public static class JSONParser
|
||||||
|
{
|
||||||
|
[ThreadStatic] static Stack<List<string>> splitArrayPool;
|
||||||
|
[ThreadStatic] static StringBuilder stringBuilder;
|
||||||
|
[ThreadStatic] static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfoCache;
|
||||||
|
[ThreadStatic] static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfoCache;
|
||||||
|
|
||||||
|
public static T FromJson<T>(this string json)
|
||||||
|
{
|
||||||
|
// Initialize, if needed, the ThreadStatic variables
|
||||||
|
if (propertyInfoCache == null)
|
||||||
|
propertyInfoCache = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
|
||||||
|
|
||||||
|
if (fieldInfoCache == null)
|
||||||
|
fieldInfoCache = new Dictionary<Type, Dictionary<string, FieldInfo>>();
|
||||||
|
|
||||||
|
if (stringBuilder == null)
|
||||||
|
stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
if (splitArrayPool == null)
|
||||||
|
splitArrayPool = new Stack<List<string>>();
|
||||||
|
|
||||||
|
//Remove all whitespace not within strings to make parsing simpler
|
||||||
|
stringBuilder.Length = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < json.Length; i++)
|
||||||
|
{
|
||||||
|
char c = json[i];
|
||||||
|
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
i = AppendUntilStringEnd(true, i, json);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char.IsWhiteSpace(c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
stringBuilder.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Parse the thing!
|
||||||
|
return (T)ParseValue(typeof(T), stringBuilder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
|
||||||
|
{
|
||||||
|
stringBuilder.Append(json[startIdx]);
|
||||||
|
|
||||||
|
for (int i = startIdx + 1; i < json.Length; i++)
|
||||||
|
{
|
||||||
|
if (json[i] == '\\')
|
||||||
|
{
|
||||||
|
if (appendEscapeCharacter)
|
||||||
|
stringBuilder.Append(json[i]);
|
||||||
|
stringBuilder.Append(json[i + 1]);
|
||||||
|
i++;//Skip next character as it is escaped
|
||||||
|
}
|
||||||
|
else if (json[i] == '"')
|
||||||
|
{
|
||||||
|
stringBuilder.Append(json[i]);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stringBuilder.Append(json[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
|
||||||
|
static List<string> Split(string json)
|
||||||
|
{
|
||||||
|
List<string> splitArray = splitArrayPool.Count > 0 ? splitArrayPool.Pop() : new List<string>();
|
||||||
|
splitArray.Clear();
|
||||||
|
|
||||||
|
if (json.Length == 2)
|
||||||
|
return splitArray;
|
||||||
|
|
||||||
|
int parseDepth = 0;
|
||||||
|
stringBuilder.Length = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < json.Length - 1; i++)
|
||||||
|
{
|
||||||
|
switch (json[i])
|
||||||
|
{
|
||||||
|
case '[':
|
||||||
|
case '{':
|
||||||
|
parseDepth++;
|
||||||
|
break;
|
||||||
|
case ']':
|
||||||
|
case '}':
|
||||||
|
parseDepth--;
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
i = AppendUntilStringEnd(true, i, json);
|
||||||
|
continue;
|
||||||
|
case ',':
|
||||||
|
case ':':
|
||||||
|
if (parseDepth == 0)
|
||||||
|
{
|
||||||
|
splitArray.Add(stringBuilder.ToString());
|
||||||
|
stringBuilder.Length = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringBuilder.Append(json[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
splitArray.Add(stringBuilder.ToString());
|
||||||
|
|
||||||
|
return splitArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static object ParseValue(Type type, string json)
|
||||||
|
{
|
||||||
|
if (type == typeof(string))
|
||||||
|
{
|
||||||
|
if (json.Length <= 2)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
StringBuilder parseStringBuilder = new StringBuilder(json.Length);
|
||||||
|
|
||||||
|
for (int i = 1; i < json.Length - 1; ++i)
|
||||||
|
{
|
||||||
|
if (json[i] == '\\' && i + 1 < json.Length - 1)
|
||||||
|
{
|
||||||
|
int j = "\"\\nrtbf/".IndexOf(json[i + 1]);
|
||||||
|
|
||||||
|
if (j >= 0)
|
||||||
|
{
|
||||||
|
parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]);
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (json[i + 1] == 'u' && i + 5 < json.Length - 1)
|
||||||
|
{
|
||||||
|
uint c = 0;
|
||||||
|
|
||||||
|
if (uint.TryParse(json.Substring(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out c))
|
||||||
|
{
|
||||||
|
parseStringBuilder.Append((char)c);
|
||||||
|
i += 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseStringBuilder.Append(json[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseStringBuilder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.IsPrimitive)
|
||||||
|
{
|
||||||
|
var result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == typeof(decimal))
|
||||||
|
{
|
||||||
|
decimal result;
|
||||||
|
decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json == "null")
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
|
{
|
||||||
|
if (json[0] == '"')
|
||||||
|
json = json.Substring(1, json.Length - 2);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Enum.Parse(type, json, false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.IsArray)
|
||||||
|
{
|
||||||
|
Type arrayType = type.GetElementType();
|
||||||
|
|
||||||
|
if (json[0] != '[' || json[json.Length - 1] != ']')
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
Array newArray = Array.CreateInstance(arrayType, elems.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < elems.Count; i++)
|
||||||
|
newArray.SetValue(ParseValue(arrayType, elems[i]), i);
|
||||||
|
|
||||||
|
splitArrayPool.Push(elems);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
|
||||||
|
{
|
||||||
|
Type listType = type.GetGenericArguments()[0];
|
||||||
|
|
||||||
|
if (json[0] != '[' || json[json.Length - 1] != ']')
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
var list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count });
|
||||||
|
|
||||||
|
for (int i = 0; i < elems.Count; i++)
|
||||||
|
list.Add(ParseValue(listType, elems[i]));
|
||||||
|
|
||||||
|
splitArrayPool.Push(elems);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||||
|
{
|
||||||
|
Type keyType, valueType;
|
||||||
|
{
|
||||||
|
Type[] args = type.GetGenericArguments();
|
||||||
|
keyType = args[0];
|
||||||
|
valueType = args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Refuse to parse dictionary keys that aren't of type string
|
||||||
|
if (keyType != typeof(string))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//Must be a valid dictionary element
|
||||||
|
if (json[0] != '{' || json[json.Length - 1] != '}')
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
|
||||||
|
if (elems.Count % 2 != 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 });
|
||||||
|
|
||||||
|
for (int i = 0; i < elems.Count; i += 2)
|
||||||
|
{
|
||||||
|
if (elems[i].Length <= 2)
|
||||||
|
continue;
|
||||||
|
string keyValue = elems[i].Substring(1, elems[i].Length - 2);
|
||||||
|
object val = ParseValue(valueType, elems[i + 1]);
|
||||||
|
dictionary[keyValue] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == typeof(object))
|
||||||
|
return ParseAnonymousValue(json);
|
||||||
|
|
||||||
|
if (json[0] == '{' && json[json.Length - 1] == '}')
|
||||||
|
return ParseObject(type, json);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object ParseAnonymousValue(string json)
|
||||||
|
{
|
||||||
|
if (json.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (json[0] == '{' && json[json.Length - 1] == '}')
|
||||||
|
{
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
|
||||||
|
if (elems.Count % 2 != 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var dict = new Dictionary<string, object>(elems.Count / 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < elems.Count; i += 2)
|
||||||
|
dict[elems[i].Substring(1, elems[i].Length - 2)] = ParseAnonymousValue(elems[i + 1]);
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json[0] == '[' && json[json.Length - 1] == ']')
|
||||||
|
{
|
||||||
|
List<string> items = Split(json);
|
||||||
|
var finalList = new List<object>(items.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
|
finalList.Add(ParseAnonymousValue(items[i]));
|
||||||
|
|
||||||
|
return finalList;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json[0] == '"' && json[json.Length - 1] == '"')
|
||||||
|
{
|
||||||
|
string str = json.Substring(1, json.Length - 2);
|
||||||
|
return str.Replace("\\", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char.IsDigit(json[0]) || json[0] == '-')
|
||||||
|
{
|
||||||
|
if (json.Contains("."))
|
||||||
|
{
|
||||||
|
double result;
|
||||||
|
double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
int.TryParse(json, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json == "true")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (json == "false")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// handles json == "null" as well as invalid JSON
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members) where T : MemberInfo
|
||||||
|
{
|
||||||
|
Dictionary<string, T> nameToMember = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
for (int i = 0; i < members.Length; i++)
|
||||||
|
{
|
||||||
|
T member = members[i];
|
||||||
|
|
||||||
|
if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string name = member.Name;
|
||||||
|
|
||||||
|
if (member.IsDefined(typeof(DataMemberAttribute), true))
|
||||||
|
{
|
||||||
|
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
|
||||||
|
name = dataMemberAttribute.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameToMember.Add(name, member);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameToMember;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object ParseObject(Type type, string json)
|
||||||
|
{
|
||||||
|
object instance = FormatterServices.GetUninitializedObject(type);
|
||||||
|
|
||||||
|
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
|
||||||
|
List<string> elems = Split(json);
|
||||||
|
|
||||||
|
if (elems.Count % 2 != 0)
|
||||||
|
return instance;
|
||||||
|
|
||||||
|
Dictionary<string, FieldInfo> nameToField;
|
||||||
|
Dictionary<string, PropertyInfo> nameToProperty;
|
||||||
|
|
||||||
|
if (!fieldInfoCache.TryGetValue(type, out nameToField))
|
||||||
|
{
|
||||||
|
nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
|
||||||
|
fieldInfoCache.Add(type, nameToField);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!propertyInfoCache.TryGetValue(type, out nameToProperty))
|
||||||
|
{
|
||||||
|
nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
|
||||||
|
propertyInfoCache.Add(type, nameToProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < elems.Count; i += 2)
|
||||||
|
{
|
||||||
|
if (elems[i].Length <= 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string key = elems[i].Substring(1, elems[i].Length - 2);
|
||||||
|
string value = elems[i + 1];
|
||||||
|
|
||||||
|
FieldInfo fieldInfo;
|
||||||
|
PropertyInfo propertyInfo;
|
||||||
|
|
||||||
|
if (nameToField.TryGetValue(key, out fieldInfo))
|
||||||
|
fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value));
|
||||||
|
else if (nameToProperty.TryGetValue(key, out propertyInfo))
|
||||||
|
propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -158,6 +158,8 @@ _ script-message mpv.net show-protocols #menu: View > Show Protocols
|
|||||||
F9 show-text ${track-list} 5000 #menu: View > Show Tracks
|
F9 show-text ${track-list} 5000 #menu: View > Show Tracks
|
||||||
Ctrl+m script-message mpv.net show-media-info #menu: View > Show Media Info
|
Ctrl+m script-message mpv.net show-media-info #menu: View > Show Media Info
|
||||||
|
|
||||||
|
_ ignore #menu: Profile
|
||||||
|
|
||||||
c script-message mpv.net show-conf-editor #menu: Settings > Show Config Editor
|
c script-message mpv.net show-conf-editor #menu: Settings > Show Config Editor
|
||||||
Ctrl+i script-message mpv.net show-input-editor #menu: Settings > Show Input Editor
|
Ctrl+i script-message mpv.net show-input-editor #menu: Settings > Show Input Editor
|
||||||
Ctrl+f script-message mpv.net open-conf-folder #menu: Settings > Open Config Folder
|
Ctrl+f script-message mpv.net open-conf-folder #menu: Settings > Open Config Folder
|
||||||
|
|||||||
@@ -302,6 +302,38 @@ namespace mpvnet
|
|||||||
() => Core.SetBluRayTitle(item.Index));
|
() => Core.SetBluRayTitle(item.Index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MenuItem profiles = FindMenuItem("Profile");
|
||||||
|
|
||||||
|
if (profiles != null)
|
||||||
|
{
|
||||||
|
profiles.DropDownItems.Clear();
|
||||||
|
|
||||||
|
foreach (string profile in ProfileNames)
|
||||||
|
if (!profile.StartsWith("extension."))
|
||||||
|
MenuItem.Add(profiles.DropDownItems, profile,
|
||||||
|
() => {
|
||||||
|
Core.CommandV("show-text", profile);
|
||||||
|
Core.CommandV("apply-profile", profile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] _ProfileNames;
|
||||||
|
|
||||||
|
public string[] ProfileNames {
|
||||||
|
get {
|
||||||
|
if (_ProfileNames == null)
|
||||||
|
{
|
||||||
|
string[] ignore = { "builtin-pseudo-gui", "encoding", "libmpv", "pseudo-gui", "default" };
|
||||||
|
string profileList = Core.GetPropertyString("profile-list");
|
||||||
|
var json = profileList.FromJson<List<Dictionary<string, object>>>();
|
||||||
|
_ProfileNames = json.Select(i => i["name"].ToString())
|
||||||
|
.Where(i => !ignore.Contains(i)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ProfileNames;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem FindMenuItem(string text, ToolStripItemCollection items)
|
MenuItem FindMenuItem(string text, ToolStripItemCollection items)
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
<HintPath>..\packages\Microsoft.PowerShell.5.ReferenceAssemblies.1.1.0\lib\net4\System.Management.Automation.dll</HintPath>
|
<HintPath>..\packages\Microsoft.PowerShell.5.ReferenceAssemblies.1.1.0\lib\net4\System.Management.Automation.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Runtime.Serialization" />
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.Xaml" />
|
<Reference Include="System.Xaml" />
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
@@ -78,6 +79,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Misc\App.cs" />
|
<Compile Include="Misc\App.cs" />
|
||||||
<Compile Include="Misc\Common.cs" />
|
<Compile Include="Misc\Common.cs" />
|
||||||
|
<Compile Include="Misc\JSONParser.cs" />
|
||||||
<Compile Include="WPF\RelayCommand.cs" />
|
<Compile Include="WPF\RelayCommand.cs" />
|
||||||
<Compile Include="Misc\CSharpScriptHost.cs" />
|
<Compile Include="Misc\CSharpScriptHost.cs" />
|
||||||
<Compile Include="Misc\Extension.cs" />
|
<Compile Include="Misc\Extension.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user