mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 03:07:04 +02:00
Add 'src/modules/launcher/' from commit '28acd466b352a807910cebbc74477d3173b31511'
git-subtree-dir: src/modules/launcher git-subtree-mainline:852689b3dfgit-subtree-split:28acd466b3
This commit is contained in:
189
src/modules/launcher/Wox.Infrastructure/Alphabet.cs
Normal file
189
src/modules/launcher/Wox.Infrastructure/Alphabet.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using hyjiacan.util.p4n;
|
||||
using hyjiacan.util.p4n.format;
|
||||
using JetBrains.Annotations;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
|
||||
namespace Wox.Infrastructure
|
||||
{
|
||||
public interface IAlphabet
|
||||
{
|
||||
string Translate(string stringToTranslate);
|
||||
}
|
||||
|
||||
public class Alphabet : IAlphabet
|
||||
{
|
||||
private readonly HanyuPinyinOutputFormat Format = new HanyuPinyinOutputFormat();
|
||||
private ConcurrentDictionary<string, string[][]> PinyinCache;
|
||||
private BinaryStorage<ConcurrentDictionary<string, string[][]>> _pinyinStorage;
|
||||
private Settings _settings;
|
||||
|
||||
public void Initialize([NotNull] Settings settings)
|
||||
{
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
InitializePinyinHelpers();
|
||||
}
|
||||
|
||||
private void InitializePinyinHelpers()
|
||||
{
|
||||
Format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
|
||||
|
||||
Stopwatch.Normal("|Wox.Infrastructure.Alphabet.Initialize|Preload pinyin cache", () =>
|
||||
{
|
||||
_pinyinStorage = new BinaryStorage<ConcurrentDictionary<string, string[][]>>("Pinyin");
|
||||
PinyinCache = _pinyinStorage.TryLoad(new ConcurrentDictionary<string, string[][]>());
|
||||
|
||||
// force pinyin library static constructor initialize
|
||||
PinyinHelper.toHanyuPinyinStringArray('T', Format);
|
||||
});
|
||||
Log.Info($"|Wox.Infrastructure.Alphabet.Initialize|Number of preload pinyin combination<{PinyinCache.Count}>");
|
||||
}
|
||||
|
||||
public string Translate(string str)
|
||||
{
|
||||
return ConvertChineseCharactersToPinyin(str);
|
||||
}
|
||||
|
||||
public string ConvertChineseCharactersToPinyin(string source)
|
||||
{
|
||||
if (!_settings.ShouldUsePinyin)
|
||||
return source;
|
||||
|
||||
if (string.IsNullOrEmpty(source))
|
||||
return source;
|
||||
|
||||
if (!ContainsChinese(source))
|
||||
return source;
|
||||
|
||||
var combination = PinyinCombination(source);
|
||||
|
||||
var pinyinArray=combination.Select(x => string.Join("", x));
|
||||
var acronymArray = combination.Select(Acronym).Distinct();
|
||||
|
||||
var joinedSingleStringCombination = new StringBuilder();
|
||||
var all = acronymArray.Concat(pinyinArray);
|
||||
all.ToList().ForEach(x => joinedSingleStringCombination.Append(x));
|
||||
|
||||
return joinedSingleStringCombination.ToString();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (!_settings.ShouldUsePinyin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_pinyinStorage.Save(PinyinCache);
|
||||
}
|
||||
|
||||
private static string[] EmptyStringArray = new string[0];
|
||||
private static string[][] Empty2DStringArray = new string[0][];
|
||||
|
||||
[Obsolete("Not accurate, eg 音乐 will not return yinyue but returns yinle ")]
|
||||
/// <summary>
|
||||
/// replace chinese character with pinyin, non chinese character won't be modified
|
||||
/// <param name="word"> should be word or sentence, instead of single character. e.g. 微软 </param>
|
||||
/// </summary>
|
||||
public string[] Pinyin(string word)
|
||||
{
|
||||
if (!_settings.ShouldUsePinyin)
|
||||
{
|
||||
return EmptyStringArray;
|
||||
}
|
||||
|
||||
var pinyin = word.Select(c =>
|
||||
{
|
||||
var pinyins = PinyinHelper.toHanyuPinyinStringArray(c);
|
||||
var result = pinyins == null ? c.ToString() : pinyins[0];
|
||||
return result;
|
||||
}).ToArray();
|
||||
return pinyin;
|
||||
}
|
||||
|
||||
/// <summmary>
|
||||
/// replace chinese character with pinyin, non chinese character won't be modified
|
||||
/// Because we don't have words dictionary, so we can only return all possiblie pinyin combination
|
||||
/// e.g. 音乐 will return yinyue and yinle
|
||||
/// <param name="characters"> should be word or sentence, instead of single character. e.g. 微软 </param>
|
||||
/// </summmary>
|
||||
public string[][] PinyinCombination(string characters)
|
||||
{
|
||||
if (!_settings.ShouldUsePinyin || string.IsNullOrEmpty(characters))
|
||||
{
|
||||
return Empty2DStringArray;
|
||||
}
|
||||
|
||||
if (!PinyinCache.ContainsKey(characters))
|
||||
{
|
||||
var allPinyins = new List<string[]>();
|
||||
foreach (var c in characters)
|
||||
{
|
||||
var pinyins = PinyinHelper.toHanyuPinyinStringArray(c, Format);
|
||||
if (pinyins != null)
|
||||
{
|
||||
var r = pinyins.Distinct().ToArray();
|
||||
allPinyins.Add(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
var r = new[] { c.ToString() };
|
||||
allPinyins.Add(r);
|
||||
}
|
||||
}
|
||||
|
||||
var combination = allPinyins.Aggregate(Combination).Select(c => c.Split(';')).ToArray();
|
||||
PinyinCache[characters] = combination;
|
||||
return combination;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PinyinCache[characters];
|
||||
}
|
||||
}
|
||||
|
||||
public string Acronym(string[] pinyin)
|
||||
{
|
||||
var acronym = string.Join("", pinyin.Select(p => p[0]));
|
||||
return acronym;
|
||||
}
|
||||
|
||||
public bool ContainsChinese(string word)
|
||||
{
|
||||
if (!_settings.ShouldUsePinyin)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (word.Length > 40)
|
||||
{
|
||||
Log.Debug($"|Wox.Infrastructure.StringMatcher.ScoreForPinyin|skip too long string: {word}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var chinese = word.Select(PinyinHelper.toHanyuPinyinStringArray)
|
||||
.Any(p => p != null);
|
||||
return chinese;
|
||||
}
|
||||
|
||||
private string[] Combination(string[] array1, string[] array2)
|
||||
{
|
||||
if (!_settings.ShouldUsePinyin)
|
||||
{
|
||||
return EmptyStringArray;
|
||||
}
|
||||
|
||||
var combination = (
|
||||
from a1 in array1
|
||||
from a2 in array2
|
||||
select $"{a1};{a2}"
|
||||
).ToArray();
|
||||
return combination;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Wox.Infrastructure.Exception
|
||||
{
|
||||
public class ExceptionFormatter
|
||||
{
|
||||
public static string FormatExcpetion(System.Exception exception)
|
||||
{
|
||||
return CreateExceptionReport(exception);
|
||||
}
|
||||
|
||||
//todo log /display line by line
|
||||
private static string CreateExceptionReport(System.Exception ex)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("## Exception");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("```");
|
||||
|
||||
var exlist = new List<StringBuilder>();
|
||||
|
||||
while (ex != null)
|
||||
{
|
||||
var exsb = new StringBuilder();
|
||||
exsb.Append(ex.GetType().FullName);
|
||||
exsb.Append(": ");
|
||||
exsb.AppendLine(ex.Message);
|
||||
if (ex.Source != null)
|
||||
{
|
||||
exsb.Append(" Source: ");
|
||||
exsb.AppendLine(ex.Source);
|
||||
}
|
||||
if (ex.TargetSite != null)
|
||||
{
|
||||
exsb.Append(" TargetAssembly: ");
|
||||
exsb.AppendLine(ex.TargetSite.Module.Assembly.ToString());
|
||||
exsb.Append(" TargetModule: ");
|
||||
exsb.AppendLine(ex.TargetSite.Module.ToString());
|
||||
exsb.Append(" TargetSite: ");
|
||||
exsb.AppendLine(ex.TargetSite.ToString());
|
||||
}
|
||||
exsb.AppendLine(ex.StackTrace);
|
||||
exlist.Add(exsb);
|
||||
|
||||
ex = ex.InnerException;
|
||||
}
|
||||
|
||||
foreach (var result in exlist.Select(o => o.ToString()).Reverse())
|
||||
{
|
||||
sb.AppendLine(result);
|
||||
}
|
||||
sb.AppendLine("```");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("## Environment");
|
||||
sb.AppendLine($"* Command Line: {Environment.CommandLine}");
|
||||
sb.AppendLine($"* Timestamp: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}");
|
||||
sb.AppendLine($"* Wox version: {Constant.Version}");
|
||||
sb.AppendLine($"* OS Version: {Environment.OSVersion.VersionString}");
|
||||
sb.AppendLine($"* IntPtr Length: {IntPtr.Size}");
|
||||
sb.AppendLine($"* x64: {Environment.Is64BitOperatingSystem}");
|
||||
sb.AppendLine($"* Python Path: {Constant.PythonPath}");
|
||||
sb.AppendLine($"* Everything SDK Path: {Constant.EverythingSDKPath}");
|
||||
sb.AppendLine($"* CLR Version: {Environment.Version}");
|
||||
sb.AppendLine($"* Installed .NET Framework: ");
|
||||
foreach (var result in GetFrameworkVersionFromRegistry())
|
||||
{
|
||||
sb.Append(" * ");
|
||||
sb.AppendLine(result);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("## Assemblies - " + AppDomain.CurrentDomain.FriendlyName);
|
||||
sb.AppendLine();
|
||||
foreach (var ass in AppDomain.CurrentDomain.GetAssemblies().OrderBy(o => o.GlobalAssemblyCache ? 50 : 0))
|
||||
{
|
||||
sb.Append("* ");
|
||||
sb.Append(ass.FullName);
|
||||
sb.Append(" (");
|
||||
|
||||
if (ass.IsDynamic)
|
||||
{
|
||||
sb.Append("dynamic assembly doesn't has location");
|
||||
}
|
||||
else if (string.IsNullOrEmpty(ass.Location))
|
||||
{
|
||||
sb.Append("location is null or empty");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(ass.Location);
|
||||
|
||||
}
|
||||
sb.AppendLine(")");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx
|
||||
private static List<string> GetFrameworkVersionFromRegistry()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = new List<string>();
|
||||
using (RegistryKey ndpKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\"))
|
||||
{
|
||||
foreach (string versionKeyName in ndpKey.GetSubKeyNames())
|
||||
{
|
||||
if (versionKeyName.StartsWith("v"))
|
||||
{
|
||||
RegistryKey versionKey = ndpKey.OpenSubKey(versionKeyName);
|
||||
string name = (string)versionKey.GetValue("Version", "");
|
||||
string sp = versionKey.GetValue("SP", "").ToString();
|
||||
string install = versionKey.GetValue("Install", "").ToString();
|
||||
if (install != "")
|
||||
if (sp != "" && install == "1")
|
||||
result.Add(string.Format("{0} {1} SP{2}", versionKeyName, name, sp));
|
||||
else
|
||||
result.Add(string.Format("{0} {1}", versionKeyName, name));
|
||||
|
||||
if (name != "")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (string subKeyName in versionKey.GetSubKeyNames())
|
||||
{
|
||||
RegistryKey subKey = versionKey.OpenSubKey(subKeyName);
|
||||
name = (string)subKey.GetValue("Version", "");
|
||||
if (name != "")
|
||||
sp = subKey.GetValue("SP", "").ToString();
|
||||
install = subKey.GetValue("Install", "").ToString();
|
||||
if (install != "")
|
||||
{
|
||||
if (sp != "" && install == "1")
|
||||
result.Add(string.Format("{0} {1} {2} SP{3}", versionKeyName, subKeyName, name, sp));
|
||||
else if (install == "1")
|
||||
result.Add(string.Format("{0} {1} {2}", versionKeyName, subKeyName, name));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
using (RegistryKey ndpKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"))
|
||||
{
|
||||
int releaseKey = (int)ndpKey.GetValue("Release");
|
||||
{
|
||||
if (releaseKey == 378389)
|
||||
result.Add("v4.5");
|
||||
|
||||
if (releaseKey == 378675)
|
||||
result.Add("v4.5.1 installed with Windows 8.1");
|
||||
|
||||
if (releaseKey == 378758)
|
||||
result.Add("4.5.1 installed on Windows 8, Windows 7 SP1, or Windows Vista SP2");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/modules/launcher/Wox.Infrastructure/FuzzyMatcher.cs
Normal file
32
src/modules/launcher/Wox.Infrastructure/FuzzyMatcher.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace Wox.Infrastructure
|
||||
{
|
||||
[Obsolete("This class is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
|
||||
public class FuzzyMatcher
|
||||
{
|
||||
private string query;
|
||||
private MatchOption opt;
|
||||
|
||||
private FuzzyMatcher(string query, MatchOption opt)
|
||||
{
|
||||
this.query = query.Trim();
|
||||
this.opt = opt;
|
||||
}
|
||||
|
||||
public static FuzzyMatcher Create(string query)
|
||||
{
|
||||
return new FuzzyMatcher(query, new MatchOption());
|
||||
}
|
||||
|
||||
public static FuzzyMatcher Create(string query, MatchOption opt)
|
||||
{
|
||||
return new FuzzyMatcher(query, opt);
|
||||
}
|
||||
|
||||
public MatchResult Evaluate(string str)
|
||||
{
|
||||
return StringMatcher.Instance.FuzzyMatch(query, str, opt);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/modules/launcher/Wox.Infrastructure/Helper.cs
Normal file
78
src/modules/launcher/Wox.Infrastructure/Helper.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Wox.Infrastructure
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
|
||||
/// </summary>
|
||||
public static T NonNull<T>(this T obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
else
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequireNonNull<T>(this T obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateDataDirectory(string bundledDataDirectory, string dataDirectory)
|
||||
{
|
||||
if (!Directory.Exists(dataDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
}
|
||||
|
||||
foreach (var bundledDataPath in Directory.GetFiles(bundledDataDirectory))
|
||||
{
|
||||
var data = Path.GetFileName(bundledDataPath);
|
||||
var dataPath = Path.Combine(dataDirectory, data.NonNull());
|
||||
if (!File.Exists(dataPath))
|
||||
{
|
||||
File.Copy(bundledDataPath, dataPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var time1 = new FileInfo(bundledDataPath).LastWriteTimeUtc;
|
||||
var time2 = new FileInfo(dataPath).LastWriteTimeUtc;
|
||||
if (time1 != time2)
|
||||
{
|
||||
File.Copy(bundledDataPath, dataPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateDirectory(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Formatted<T>(this T t)
|
||||
{
|
||||
var formatted = JsonConvert.SerializeObject(
|
||||
t,
|
||||
Formatting.Indented,
|
||||
new StringEnumConverter()
|
||||
);
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
src/modules/launcher/Wox.Infrastructure/Hotkey/GlobalHotkey.cs
Normal file
107
src/modules/launcher/Wox.Infrastructure/Hotkey/GlobalHotkey.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Wox.Infrastructure.Hotkey
|
||||
{
|
||||
/// <summary>
|
||||
/// Listens keyboard globally.
|
||||
/// <remarks>Uses WH_KEYBOARD_LL.</remarks>
|
||||
/// </summary>
|
||||
public class GlobalHotkey : IDisposable
|
||||
{
|
||||
private static GlobalHotkey instance;
|
||||
private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc;
|
||||
private IntPtr hookId = IntPtr.Zero;
|
||||
public delegate bool KeyboardCallback(KeyEvent keyEvent, int vkCode, SpecialKeyState state);
|
||||
public event KeyboardCallback hookedKeyboardCallback;
|
||||
|
||||
//Modifier key constants
|
||||
private const int VK_SHIFT = 0x10;
|
||||
private const int VK_CONTROL = 0x11;
|
||||
private const int VK_ALT = 0x12;
|
||||
private const int VK_WIN = 91;
|
||||
|
||||
public static GlobalHotkey Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new GlobalHotkey();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
private GlobalHotkey()
|
||||
{
|
||||
// We have to store the LowLevelKeyboardProc, so that it is not garbage collected runtime
|
||||
hookedLowLevelKeyboardProc = LowLevelKeyboardProc;
|
||||
// Set the hook
|
||||
hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc);
|
||||
}
|
||||
|
||||
public SpecialKeyState CheckModifiers()
|
||||
{
|
||||
SpecialKeyState state = new SpecialKeyState();
|
||||
if ((InterceptKeys.GetKeyState(VK_SHIFT) & 0x8000) != 0)
|
||||
{
|
||||
//SHIFT is pressed
|
||||
state.ShiftPressed = true;
|
||||
}
|
||||
if ((InterceptKeys.GetKeyState(VK_CONTROL) & 0x8000) != 0)
|
||||
{
|
||||
//CONTROL is pressed
|
||||
state.CtrlPressed = true;
|
||||
}
|
||||
if ((InterceptKeys.GetKeyState(VK_ALT) & 0x8000) != 0)
|
||||
{
|
||||
//ALT is pressed
|
||||
state.AltPressed = true;
|
||||
}
|
||||
if ((InterceptKeys.GetKeyState(VK_WIN) & 0x8000) != 0)
|
||||
{
|
||||
//WIN is pressed
|
||||
state.WinPressed = true;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
bool continues = true;
|
||||
|
||||
if (nCode >= 0)
|
||||
{
|
||||
if (wParam.ToUInt32() == (int)KeyEvent.WM_KEYDOWN ||
|
||||
wParam.ToUInt32() == (int)KeyEvent.WM_KEYUP ||
|
||||
wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYDOWN ||
|
||||
wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYUP)
|
||||
{
|
||||
if (hookedKeyboardCallback != null)
|
||||
continues = hookedKeyboardCallback((KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), CheckModifiers());
|
||||
}
|
||||
}
|
||||
|
||||
if (continues)
|
||||
{
|
||||
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
|
||||
}
|
||||
return (IntPtr)1;
|
||||
}
|
||||
|
||||
~GlobalHotkey()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
InterceptKeys.UnhookWindowsHookEx(hookId);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/modules/launcher/Wox.Infrastructure/Hotkey/HotkeyModel.cs
Normal file
145
src/modules/launcher/Wox.Infrastructure/Hotkey/HotkeyModel.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Wox.Infrastructure.Hotkey
|
||||
{
|
||||
public class HotkeyModel
|
||||
{
|
||||
public bool Alt { get; set; }
|
||||
public bool Shift { get; set; }
|
||||
public bool Win { get; set; }
|
||||
public bool Ctrl { get; set; }
|
||||
public Key CharKey { get; set; }
|
||||
|
||||
|
||||
Dictionary<Key, string> specialSymbolDictionary = new Dictionary<Key, string>
|
||||
{
|
||||
{Key.Space, "Space"},
|
||||
{Key.Oem3, "~"}
|
||||
};
|
||||
|
||||
public ModifierKeys ModifierKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
ModifierKeys modifierKeys = ModifierKeys.None;
|
||||
if (Alt)
|
||||
{
|
||||
modifierKeys = ModifierKeys.Alt;
|
||||
}
|
||||
if (Shift)
|
||||
{
|
||||
modifierKeys = modifierKeys | ModifierKeys.Shift;
|
||||
}
|
||||
if (Win)
|
||||
{
|
||||
modifierKeys = modifierKeys | ModifierKeys.Windows;
|
||||
}
|
||||
if (Ctrl)
|
||||
{
|
||||
modifierKeys = modifierKeys | ModifierKeys.Control;
|
||||
}
|
||||
return modifierKeys;
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeyModel(string hotkeyString)
|
||||
{
|
||||
Parse(hotkeyString);
|
||||
}
|
||||
|
||||
public HotkeyModel(bool alt, bool shift, bool win, bool ctrl, Key key)
|
||||
{
|
||||
Alt = alt;
|
||||
Shift = shift;
|
||||
Win = win;
|
||||
Ctrl = ctrl;
|
||||
CharKey = key;
|
||||
}
|
||||
|
||||
private void Parse(string hotkeyString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hotkeyString))
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<string> keys = hotkeyString.Replace(" ", "").Split('+').ToList();
|
||||
if (keys.Contains("Alt"))
|
||||
{
|
||||
Alt = true;
|
||||
keys.Remove("Alt");
|
||||
}
|
||||
if (keys.Contains("Shift"))
|
||||
{
|
||||
Shift = true;
|
||||
keys.Remove("Shift");
|
||||
}
|
||||
if (keys.Contains("Win"))
|
||||
{
|
||||
Win = true;
|
||||
keys.Remove("Win");
|
||||
}
|
||||
if (keys.Contains("Ctrl"))
|
||||
{
|
||||
Ctrl = true;
|
||||
keys.Remove("Ctrl");
|
||||
}
|
||||
if (keys.Count > 0)
|
||||
{
|
||||
string charKey = keys[0];
|
||||
KeyValuePair<Key, string>? specialSymbolPair = specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey);
|
||||
if (specialSymbolPair.Value.Value != null)
|
||||
{
|
||||
CharKey = specialSymbolPair.Value.Key;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
CharKey = (Key) Enum.Parse(typeof (Key), charKey);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string text = string.Empty;
|
||||
if (Ctrl)
|
||||
{
|
||||
text += "Ctrl + ";
|
||||
}
|
||||
if (Alt)
|
||||
{
|
||||
text += "Alt + ";
|
||||
}
|
||||
if (Shift)
|
||||
{
|
||||
text += "Shift + ";
|
||||
}
|
||||
if (Win)
|
||||
{
|
||||
text += "Win + ";
|
||||
}
|
||||
|
||||
if (CharKey != Key.None)
|
||||
{
|
||||
text += specialSymbolDictionary.ContainsKey(CharKey)
|
||||
? specialSymbolDictionary[CharKey]
|
||||
: CharKey.ToString();
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
text = text.Remove(text.Length - 3);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Wox.Infrastructure.Hotkey
|
||||
{
|
||||
internal static class InterceptKeys
|
||||
{
|
||||
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
|
||||
|
||||
private const int WH_KEYBOARD_LL = 13;
|
||||
|
||||
public static IntPtr SetHook(LowLevelKeyboardProc proc)
|
||||
{
|
||||
using (Process curProcess = Process.GetCurrentProcess())
|
||||
using (ProcessModule curModule = curProcess.MainModule)
|
||||
{
|
||||
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern short GetKeyState(int keyCode);
|
||||
}
|
||||
}
|
||||
25
src/modules/launcher/Wox.Infrastructure/Hotkey/KeyEvent.cs
Normal file
25
src/modules/launcher/Wox.Infrastructure/Hotkey/KeyEvent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Wox.Infrastructure.Hotkey
|
||||
{
|
||||
public enum KeyEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Key down
|
||||
/// </summary>
|
||||
WM_KEYDOWN = 256,
|
||||
|
||||
/// <summary>
|
||||
/// Key up
|
||||
/// </summary>
|
||||
WM_KEYUP = 257,
|
||||
|
||||
/// <summary>
|
||||
/// System key up
|
||||
/// </summary>
|
||||
WM_SYSKEYUP = 261,
|
||||
|
||||
/// <summary>
|
||||
/// System key down
|
||||
/// </summary>
|
||||
WM_SYSKEYDOWN = 260
|
||||
}
|
||||
}
|
||||
84
src/modules/launcher/Wox.Infrastructure/Http/Http.cs
Normal file
84
src/modules/launcher/Wox.Infrastructure/Http/Http.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
|
||||
namespace Wox.Infrastructure.Http
|
||||
{
|
||||
public static class Http
|
||||
{
|
||||
private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko";
|
||||
|
||||
static Http()
|
||||
{
|
||||
// need to be added so it would work on a win10 machine
|
||||
ServicePointManager.Expect100Continue = true;
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls
|
||||
| SecurityProtocolType.Tls11
|
||||
| SecurityProtocolType.Tls12
|
||||
| SecurityProtocolType.Ssl3;
|
||||
}
|
||||
|
||||
public static HttpProxy Proxy { private get; set; }
|
||||
public static IWebProxy WebProxy()
|
||||
{
|
||||
if (Proxy != null && Proxy.Enabled && !string.IsNullOrEmpty(Proxy.Server))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Proxy.UserName) || string.IsNullOrEmpty(Proxy.Password))
|
||||
{
|
||||
var webProxy = new WebProxy(Proxy.Server, Proxy.Port);
|
||||
return webProxy;
|
||||
}
|
||||
else
|
||||
{
|
||||
var webProxy = new WebProxy(Proxy.Server, Proxy.Port)
|
||||
{
|
||||
Credentials = new NetworkCredential(Proxy.UserName, Proxy.Password)
|
||||
};
|
||||
return webProxy;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return WebRequest.GetSystemWebProxy();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Download([NotNull] string url, [NotNull] string filePath)
|
||||
{
|
||||
var client = new WebClient { Proxy = WebProxy() };
|
||||
client.Headers.Add("user-agent", UserAgent);
|
||||
client.DownloadFile(url, filePath);
|
||||
}
|
||||
|
||||
public static async Task<string> Get([NotNull] string url, string encoding = "UTF-8")
|
||||
{
|
||||
Log.Debug($"|Http.Get|Url <{url}>");
|
||||
var request = WebRequest.CreateHttp(url);
|
||||
request.Method = "GET";
|
||||
request.Timeout = 1000;
|
||||
request.Proxy = WebProxy();
|
||||
request.UserAgent = UserAgent;
|
||||
var response = await request.GetResponseAsync() as HttpWebResponse;
|
||||
response = response.NonNull();
|
||||
var stream = response.GetResponseStream().NonNull();
|
||||
|
||||
using (var reader = new StreamReader(stream, Encoding.GetEncoding(encoding)))
|
||||
{
|
||||
var content = await reader.ReadToEndAsync();
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
return content;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HttpRequestException($"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/modules/launcher/Wox.Infrastructure/Image/ImageCache.cs
Normal file
57
src/modules/launcher/Wox.Infrastructure/Image/ImageCache.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Wox.Infrastructure.Image
|
||||
{
|
||||
[Serializable]
|
||||
public class ImageCache
|
||||
{
|
||||
private const int MaxCached = 5000;
|
||||
public ConcurrentDictionary<string, int> Usage = new ConcurrentDictionary<string, int>();
|
||||
private readonly ConcurrentDictionary<string, ImageSource> _data = new ConcurrentDictionary<string, ImageSource>();
|
||||
|
||||
|
||||
public ImageSource this[string path]
|
||||
{
|
||||
get
|
||||
{
|
||||
Usage.AddOrUpdate(path, 1, (k, v) => v + 1);
|
||||
var i = _data[path];
|
||||
return i;
|
||||
}
|
||||
set { _data[path] = value; }
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
var images = Usage
|
||||
.OrderByDescending(o => o.Value)
|
||||
.Take(MaxCached)
|
||||
.ToDictionary(i => i.Key, i => i.Value);
|
||||
Usage = new ConcurrentDictionary<string, int>(images);
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
var contains = _data.ContainsKey(key);
|
||||
return contains;
|
||||
}
|
||||
|
||||
public int CacheSize()
|
||||
{
|
||||
return _data.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return the number of unique images in the cache (by reference not by checking images content)
|
||||
/// </summary>
|
||||
public int UniqueImagesInCache()
|
||||
{
|
||||
return _data.Values.Distinct().Count();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Wox.Infrastructure.Image
|
||||
{
|
||||
public interface IImageHashGenerator
|
||||
{
|
||||
string GetHashFromImage(ImageSource image);
|
||||
}
|
||||
public class ImageHashGenerator : IImageHashGenerator
|
||||
{
|
||||
public string GetHashFromImage(ImageSource imageSource)
|
||||
{
|
||||
if (!(imageSource is BitmapSource image))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var outStream = new MemoryStream())
|
||||
{
|
||||
// PngBitmapEncoder enc2 = new PngBitmapEncoder();
|
||||
// enc2.Frames.Add(BitmapFrame.Create(tt));
|
||||
|
||||
var enc = new JpegBitmapEncoder();
|
||||
var bitmapFrame = BitmapFrame.Create(image);
|
||||
bitmapFrame.Freeze();
|
||||
enc.Frames.Add(bitmapFrame);
|
||||
enc.Save(outStream);
|
||||
var byteArray = outStream.GetBuffer();
|
||||
using (var sha1 = new SHA1CryptoServiceProvider())
|
||||
{
|
||||
var hash = Convert.ToBase64String(sha1.ComputeHash(byteArray));
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
215
src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs
Normal file
215
src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
|
||||
namespace Wox.Infrastructure.Image
|
||||
{
|
||||
public static class ImageLoader
|
||||
{
|
||||
private static readonly ImageCache ImageCache = new ImageCache();
|
||||
private static BinaryStorage<ConcurrentDictionary<string, int>> _storage;
|
||||
private static readonly ConcurrentDictionary<string, string> GuidToKey = new ConcurrentDictionary<string, string>();
|
||||
private static IImageHashGenerator _hashGenerator;
|
||||
|
||||
|
||||
private static readonly string[] ImageExtensions =
|
||||
{
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".gif",
|
||||
".bmp",
|
||||
".tiff",
|
||||
".ico"
|
||||
};
|
||||
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
_storage = new BinaryStorage<ConcurrentDictionary<string, int>>("Image");
|
||||
_hashGenerator = new ImageHashGenerator();
|
||||
ImageCache.Usage = _storage.TryLoad(new ConcurrentDictionary<string, int>());
|
||||
|
||||
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon })
|
||||
{
|
||||
ImageSource img = new BitmapImage(new Uri(icon));
|
||||
img.Freeze();
|
||||
ImageCache[icon] = img;
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () =>
|
||||
{
|
||||
ImageCache.Usage.AsParallel().ForAll(x =>
|
||||
{
|
||||
Load(x.Key);
|
||||
});
|
||||
});
|
||||
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
|
||||
});
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
ImageCache.Cleanup();
|
||||
_storage.Save(ImageCache.Usage);
|
||||
}
|
||||
|
||||
private class ImageResult
|
||||
{
|
||||
public ImageResult(ImageSource imageSource, ImageType imageType)
|
||||
{
|
||||
ImageSource = imageSource;
|
||||
ImageType = imageType;
|
||||
}
|
||||
|
||||
public ImageType ImageType { get; }
|
||||
public ImageSource ImageSource { get; }
|
||||
}
|
||||
|
||||
private enum ImageType
|
||||
{
|
||||
File,
|
||||
Folder,
|
||||
Data,
|
||||
ImageFile,
|
||||
Error,
|
||||
Cache
|
||||
}
|
||||
|
||||
private static ImageResult LoadInternal(string path, bool loadFullImage = false)
|
||||
{
|
||||
ImageSource image;
|
||||
ImageType type = ImageType.Error;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return new ImageResult(ImageCache[Constant.ErrorIcon], ImageType.Error);
|
||||
}
|
||||
if (ImageCache.ContainsKey(path))
|
||||
{
|
||||
return new ImageResult(ImageCache[path], ImageType.Cache);
|
||||
}
|
||||
|
||||
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var imageSource = new BitmapImage(new Uri(path));
|
||||
imageSource.Freeze();
|
||||
return new ImageResult(imageSource, ImageType.Data);
|
||||
}
|
||||
|
||||
if (!Path.IsPathRooted(path))
|
||||
{
|
||||
path = Path.Combine(Constant.ProgramDirectory, "Images", Path.GetFileName(path));
|
||||
}
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
/* Directories can also have thumbnails instead of shell icons.
|
||||
* Generating thumbnails for a bunch of folders while scrolling through
|
||||
* results from Everything makes a big impact on performance and
|
||||
* Wox responsibility.
|
||||
* - Solution: just load the icon
|
||||
*/
|
||||
type = ImageType.Folder;
|
||||
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
|
||||
Constant.ThumbnailSize, ThumbnailOptions.IconOnly);
|
||||
|
||||
}
|
||||
else if (File.Exists(path))
|
||||
{
|
||||
var extension = Path.GetExtension(path).ToLower();
|
||||
if (ImageExtensions.Contains(extension))
|
||||
{
|
||||
type = ImageType.ImageFile;
|
||||
if (loadFullImage)
|
||||
{
|
||||
image = LoadFullImage(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Although the documentation for GetImage on MSDN indicates that
|
||||
* if a thumbnail is available it will return one, this has proved to not
|
||||
* be the case in many situations while testing.
|
||||
* - Solution: explicitly pass the ThumbnailOnly flag
|
||||
*/
|
||||
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
|
||||
Constant.ThumbnailSize, ThumbnailOptions.ThumbnailOnly);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
type = ImageType.File;
|
||||
image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize,
|
||||
Constant.ThumbnailSize, ThumbnailOptions.None);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
image = ImageCache[Constant.ErrorIcon];
|
||||
path = Constant.ErrorIcon;
|
||||
}
|
||||
|
||||
if (type != ImageType.Error)
|
||||
{
|
||||
image.Freeze();
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e);
|
||||
type = ImageType.Error;
|
||||
image = ImageCache[Constant.ErrorIcon];
|
||||
ImageCache[path] = image;
|
||||
}
|
||||
return new ImageResult(image, type);
|
||||
}
|
||||
|
||||
private static bool EnableImageHash = true;
|
||||
|
||||
public static ImageSource Load(string path, bool loadFullImage = false)
|
||||
{
|
||||
var imageResult = LoadInternal(path, loadFullImage);
|
||||
|
||||
var img = imageResult.ImageSource;
|
||||
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
|
||||
{ // we need to get image hash
|
||||
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
|
||||
if (hash != null)
|
||||
{
|
||||
if (GuidToKey.TryGetValue(hash, out string key))
|
||||
{ // image already exists
|
||||
img = ImageCache[key];
|
||||
}
|
||||
else
|
||||
{ // new guid
|
||||
GuidToKey[hash] = path;
|
||||
}
|
||||
}
|
||||
|
||||
// update cache
|
||||
ImageCache[path] = img;
|
||||
}
|
||||
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
private static BitmapImage LoadFullImage(string path)
|
||||
{
|
||||
BitmapImage image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.UriSource = new Uri(path);
|
||||
image.EndInit();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
153
src/modules/launcher/Wox.Infrastructure/Image/ThumbnailReader.cs
Normal file
153
src/modules/launcher/Wox.Infrastructure/Image/ThumbnailReader.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows;
|
||||
|
||||
namespace Wox.Infrastructure.Image
|
||||
{
|
||||
[Flags]
|
||||
public enum ThumbnailOptions
|
||||
{
|
||||
None = 0x00,
|
||||
BiggerSizeOk = 0x01,
|
||||
InMemoryOnly = 0x02,
|
||||
IconOnly = 0x04,
|
||||
ThumbnailOnly = 0x08,
|
||||
InCacheOnly = 0x10,
|
||||
}
|
||||
|
||||
public class WindowsThumbnailProvider
|
||||
{
|
||||
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
|
||||
|
||||
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern int SHCreateItemFromParsingName(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string path,
|
||||
IntPtr pbc,
|
||||
ref Guid riid,
|
||||
[MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteObject(IntPtr hObject);
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
|
||||
internal interface IShellItem
|
||||
{
|
||||
void BindToHandler(IntPtr pbc,
|
||||
[MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
|
||||
[MarshalAs(UnmanagedType.LPStruct)]Guid riid,
|
||||
out IntPtr ppv);
|
||||
|
||||
void GetParent(out IShellItem ppsi);
|
||||
void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
|
||||
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
|
||||
void Compare(IShellItem psi, uint hint, out int piOrder);
|
||||
};
|
||||
|
||||
internal enum SIGDN : uint
|
||||
{
|
||||
NORMALDISPLAY = 0,
|
||||
PARENTRELATIVEPARSING = 0x80018001,
|
||||
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
|
||||
DESKTOPABSOLUTEPARSING = 0x80028000,
|
||||
PARENTRELATIVEEDITING = 0x80031001,
|
||||
DESKTOPABSOLUTEEDITING = 0x8004c000,
|
||||
FILESYSPATH = 0x80058000,
|
||||
URL = 0x80068000
|
||||
}
|
||||
|
||||
internal enum HResult
|
||||
{
|
||||
Ok = 0x0000,
|
||||
False = 0x0001,
|
||||
InvalidArguments = unchecked((int)0x80070057),
|
||||
OutOfMemory = unchecked((int)0x8007000E),
|
||||
NoInterface = unchecked((int)0x80004002),
|
||||
Fail = unchecked((int)0x80004005),
|
||||
ExtractionFailed = unchecked((int)0x8004B200),
|
||||
ElementNotFound = unchecked((int)0x80070490),
|
||||
TypeElementNotFound = unchecked((int)0x8002802B),
|
||||
NoObject = unchecked((int)0x800401E5),
|
||||
Win32ErrorCanceled = 1223,
|
||||
Canceled = unchecked((int)0x800704C7),
|
||||
ResourceInUse = unchecked((int)0x800700AA),
|
||||
AccessDenied = unchecked((int)0x80030005)
|
||||
}
|
||||
|
||||
[ComImportAttribute()]
|
||||
[GuidAttribute("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
|
||||
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IShellItemImageFactory
|
||||
{
|
||||
[PreserveSig]
|
||||
HResult GetImage(
|
||||
[In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
|
||||
[In] ThumbnailOptions flags,
|
||||
[Out] out IntPtr phbm);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct NativeSize
|
||||
{
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public int Width { set { width = value; } }
|
||||
public int Height { set { height = value; } }
|
||||
};
|
||||
|
||||
|
||||
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
|
||||
{
|
||||
IntPtr hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
|
||||
|
||||
try
|
||||
{
|
||||
return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// delete HBitmap to avoid memory leaks
|
||||
DeleteObject(hBitmap);
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
|
||||
{
|
||||
IShellItem nativeShellItem;
|
||||
Guid shellItem2Guid = new Guid(IShellItem2Guid);
|
||||
int retCode = SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem);
|
||||
|
||||
if (retCode != 0)
|
||||
throw Marshal.GetExceptionForHR(retCode);
|
||||
|
||||
NativeSize nativeSize = new NativeSize
|
||||
{
|
||||
Width = width,
|
||||
Height = height
|
||||
};
|
||||
|
||||
IntPtr hBitmap;
|
||||
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap);
|
||||
|
||||
// if extracting image thumbnail and failed, extract shell icon
|
||||
if (options == ThumbnailOptions.ThumbnailOnly && hr == HResult.ExtractionFailed)
|
||||
{
|
||||
hr = ((IShellItemImageFactory) nativeShellItem).GetImage(nativeSize, ThumbnailOptions.IconOnly, out hBitmap);
|
||||
}
|
||||
|
||||
Marshal.ReleaseComObject(nativeShellItem);
|
||||
|
||||
if (hr == HResult.Ok) return hBitmap;
|
||||
|
||||
throw new COMException($"Error while extracting thumbnail for {fileName}", Marshal.GetExceptionForHR((int)hr));
|
||||
}
|
||||
}
|
||||
}
|
||||
200
src/modules/launcher/Wox.Infrastructure/Logger/Log.cs
Normal file
200
src/modules/launcher/Wox.Infrastructure/Logger/Log.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
|
||||
namespace Wox.Infrastructure.Logger
|
||||
{
|
||||
public static class Log
|
||||
{
|
||||
public const string DirectoryName = "Logs";
|
||||
|
||||
public static string CurrentLogDirectory { get; }
|
||||
|
||||
static Log()
|
||||
{
|
||||
CurrentLogDirectory = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version);
|
||||
if (!Directory.Exists(CurrentLogDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(CurrentLogDirectory);
|
||||
}
|
||||
|
||||
var configuration = new LoggingConfiguration();
|
||||
var target = new FileTarget();
|
||||
configuration.AddTarget("file", target);
|
||||
target.FileName = CurrentLogDirectory.Replace(@"\", "/") + "/${shortdate}.txt";
|
||||
#if DEBUG
|
||||
var rule = new LoggingRule("*", LogLevel.Debug, target);
|
||||
#else
|
||||
var rule = new LoggingRule("*", LogLevel.Info, target);
|
||||
#endif
|
||||
configuration.LoggingRules.Add(rule);
|
||||
LogManager.Configuration = configuration;
|
||||
}
|
||||
|
||||
private static void LogFaultyFormat(string message)
|
||||
{
|
||||
var logger = LogManager.GetLogger("FaultyLogger");
|
||||
message = $"Wrong logger message format <{message}>";
|
||||
System.Diagnostics.Debug.WriteLine($"FATAL|{message}");
|
||||
logger.Fatal(message);
|
||||
}
|
||||
|
||||
private static bool FormatValid(string message)
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var valid = parts.Length == 3 && !string.IsNullOrWhiteSpace(parts[1]) && !string.IsNullOrWhiteSpace(parts[2]);
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName);
|
||||
|
||||
ExceptionInternal(classNameWithMethod, message, exception);
|
||||
}
|
||||
|
||||
private static string CheckClassAndMessageAndReturnFullClassWithMethod(string className, string message,
|
||||
string methodName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(className))
|
||||
{
|
||||
LogFaultyFormat($"Fail to specify a class name during logging of message: {message ?? "no message entered"}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
// todo: not sure we really need that
|
||||
LogFaultyFormat($"Fail to specify a message during logging");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(methodName))
|
||||
{
|
||||
return className + "." + methodName;
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
private static void ExceptionInternal(string classAndMethod, string message, System.Exception e)
|
||||
{
|
||||
var logger = LogManager.GetLogger(classAndMethod);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
|
||||
|
||||
logger.Error("-------------------------- Begin exception --------------------------");
|
||||
logger.Error(message);
|
||||
|
||||
do
|
||||
{
|
||||
logger.Error($"Exception full name:\n <{e.GetType().FullName}>");
|
||||
logger.Error($"Exception message:\n <{e.Message}>");
|
||||
logger.Error($"Exception stack trace:\n <{e.StackTrace}>");
|
||||
logger.Error($"Exception source:\n <{e.Source}>");
|
||||
logger.Error($"Exception target site:\n <{e.TargetSite}>");
|
||||
logger.Error($"Exception HResult:\n <{e.HResult}>");
|
||||
e = e.InnerException;
|
||||
} while (e != null);
|
||||
|
||||
logger.Error("-------------------------- End exception --------------------------");
|
||||
}
|
||||
|
||||
private static void LogInternal(string message, LogLevel level)
|
||||
{
|
||||
if (FormatValid(message))
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var prefix = parts[1];
|
||||
var unprefixed = parts[2];
|
||||
var logger = LogManager.GetLogger(prefix);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}");
|
||||
logger.Log(level, unprefixed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFaultyFormat(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public static void Exception(string message, System.Exception e)
|
||||
{
|
||||
#if DEBUG
|
||||
throw e;
|
||||
#else
|
||||
if (FormatValid(message))
|
||||
{
|
||||
var parts = message.Split('|');
|
||||
var prefix = parts[1];
|
||||
var unprefixed = parts[2];
|
||||
ExceptionInternal(prefix, unprefixed, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFaultyFormat(message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Error(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public static void Error(string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
LogInternal(LogLevel.Error, className, message, methodName);
|
||||
}
|
||||
|
||||
private static void LogInternal(LogLevel level, string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName);
|
||||
|
||||
var logger = LogManager.GetLogger(classNameWithMethod);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}");
|
||||
logger.Log(level, message);
|
||||
}
|
||||
|
||||
public static void Debug(string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
LogInternal(LogLevel.Debug, className, message, methodName);
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Debug(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
public static void Info(string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
LogInternal(LogLevel.Info, className, message, methodName);
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Info(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
public static void Warn(string className, string message, [CallerMemberName] string methodName = "")
|
||||
{
|
||||
LogInternal(LogLevel.Warn, className, message, methodName);
|
||||
}
|
||||
|
||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||
public static void Warn(string message)
|
||||
{
|
||||
LogInternal(message, LogLevel.Warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Wox.Infrastructure")]
|
||||
[assembly: Guid("aee57a31-29e5-4f03-a41f-7917910fe90f")]
|
||||
[assembly: InternalsVisibleTo("Wox")]
|
||||
[assembly: InternalsVisibleTo("Wox.Core")]
|
||||
[assembly: InternalsVisibleTo("Wox.Test")]
|
||||
67
src/modules/launcher/Wox.Infrastructure/Stopwatch.cs
Normal file
67
src/modules/launcher/Wox.Infrastructure/Stopwatch.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Wox.Infrastructure.Logger;
|
||||
|
||||
namespace Wox.Infrastructure
|
||||
{
|
||||
public static class Stopwatch
|
||||
{
|
||||
private static readonly Dictionary<string, long> Count = new Dictionary<string, long>();
|
||||
private static readonly object Locker = new object();
|
||||
/// <summary>
|
||||
/// This stopwatch will appear only in Debug mode
|
||||
/// </summary>
|
||||
public static long Debug(string message, Action action)
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
string info = $"{message} <{milliseconds}ms>";
|
||||
Log.Debug(info);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
public static long Normal(string message, Action action)
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
string info = $"{message} <{milliseconds}ms>";
|
||||
Log.Info(info);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
public static void StartCount(string name, Action action)
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
action();
|
||||
stopWatch.Stop();
|
||||
var milliseconds = stopWatch.ElapsedMilliseconds;
|
||||
lock (Locker)
|
||||
{
|
||||
if (Count.ContainsKey(name))
|
||||
{
|
||||
Count[name] += milliseconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
Count[name] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EndCount()
|
||||
{
|
||||
foreach (var key in Count.Keys)
|
||||
{
|
||||
string info = $"{key} already cost {Count[key]}ms";
|
||||
Log.Debug(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
115
src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage.cs
Normal file
115
src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using Wox.Infrastructure.Logger;
|
||||
|
||||
namespace Wox.Infrastructure.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// Stroage object using binary data
|
||||
/// Normally, it has better performance, but not readable
|
||||
/// </summary>
|
||||
public class BinaryStorage<T>
|
||||
{
|
||||
public BinaryStorage(string filename)
|
||||
{
|
||||
const string directoryName = "Cache";
|
||||
var directoryPath = Path.Combine(Constant.DataDirectory, directoryName);
|
||||
Helper.ValidateDirectory(directoryPath);
|
||||
|
||||
const string fileSuffix = ".cache";
|
||||
FilePath = Path.Combine(directoryPath, $"{filename}{fileSuffix}");
|
||||
}
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public T TryLoad(T defaultData)
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
if (new FileInfo(FilePath).Length == 0)
|
||||
{
|
||||
Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
|
||||
Save(defaultData);
|
||||
return defaultData;
|
||||
}
|
||||
|
||||
using (var stream = new FileStream(FilePath, FileMode.Open))
|
||||
{
|
||||
var d = Deserialize(stream, defaultData);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
|
||||
Save(defaultData);
|
||||
return defaultData;
|
||||
}
|
||||
}
|
||||
|
||||
private T Deserialize(FileStream stream, T defaultData)
|
||||
{
|
||||
//http://stackoverflow.com/questions/2120055/binaryformatter-deserialize-gives-serializationexception
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
BinaryFormatter binaryFormatter = new BinaryFormatter
|
||||
{
|
||||
AssemblyFormat = FormatterAssemblyStyle.Simple
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var t = ((T)binaryFormatter.Deserialize(stream)).NonNull();
|
||||
return t;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
|
||||
return defaultData;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
|
||||
}
|
||||
}
|
||||
|
||||
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
Assembly ayResult = null;
|
||||
string sShortAssemblyName = args.Name.Split(',')[0];
|
||||
Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach (Assembly ayAssembly in ayAssemblies)
|
||||
{
|
||||
if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0])
|
||||
{
|
||||
ayResult = ayAssembly;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ayResult;
|
||||
}
|
||||
|
||||
public void Save(T data)
|
||||
{
|
||||
using (var stream = new FileStream(FilePath, FileMode.Create))
|
||||
{
|
||||
BinaryFormatter binaryFormatter = new BinaryFormatter
|
||||
{
|
||||
AssemblyFormat = FormatterAssemblyStyle.Simple
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
binaryFormatter.Serialize(stream, data);
|
||||
}
|
||||
catch (SerializationException e)
|
||||
{
|
||||
Log.Exception($"|BinaryStorage.Save|serialize error for file <{FilePath}>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/modules/launcher/Wox.Infrastructure/Storage/ISavable.cs
Normal file
11
src/modules/launcher/Wox.Infrastructure/Storage/ISavable.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Wox.Infrastructure.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// Save plugin settings/cache,
|
||||
/// todo should be merged into a abstract class intead of seperate interface
|
||||
/// </summary>
|
||||
public interface ISavable
|
||||
{
|
||||
void Save();
|
||||
}
|
||||
}
|
||||
101
src/modules/launcher/Wox.Infrastructure/Storage/JsonStorage.cs
Normal file
101
src/modules/launcher/Wox.Infrastructure/Storage/JsonStorage.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Wox.Infrastructure.Logger;
|
||||
|
||||
namespace Wox.Infrastructure.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize object using json format.
|
||||
/// </summary>
|
||||
public class JsonStrorage<T>
|
||||
{
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
private T _data;
|
||||
// need a new directory name
|
||||
public const string DirectoryName = "Settings";
|
||||
public const string FileSuffix = ".json";
|
||||
public string FilePath { get; set; }
|
||||
public string DirectoryPath { get; set; }
|
||||
|
||||
|
||||
internal JsonStrorage()
|
||||
{
|
||||
// use property initialization instead of DefaultValueAttribute
|
||||
// easier and flexible for default value of object
|
||||
_serializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
ObjectCreationHandling = ObjectCreationHandling.Replace,
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
}
|
||||
|
||||
public T Load()
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
var searlized = File.ReadAllText(FilePath);
|
||||
if (!string.IsNullOrWhiteSpace(searlized))
|
||||
{
|
||||
Deserialize(searlized);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadDefault();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadDefault();
|
||||
}
|
||||
return _data.NonNull();
|
||||
}
|
||||
|
||||
private void Deserialize(string searlized)
|
||||
{
|
||||
try
|
||||
{
|
||||
_data = JsonConvert.DeserializeObject<T>(searlized, _serializerSettings);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
LoadDefault();
|
||||
Log.Exception($"|JsonStrorage.Deserialize|Deserialize error for json <{FilePath}>", e);
|
||||
}
|
||||
|
||||
if (_data == null)
|
||||
{
|
||||
LoadDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDefault()
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
BackupOriginFile();
|
||||
}
|
||||
|
||||
_data = JsonConvert.DeserializeObject<T>("{}", _serializerSettings);
|
||||
Save();
|
||||
}
|
||||
|
||||
private void BackupOriginFile()
|
||||
{
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-fffffff", CultureInfo.CurrentUICulture);
|
||||
var directory = Path.GetDirectoryName(FilePath).NonNull();
|
||||
var originName = Path.GetFileNameWithoutExtension(FilePath);
|
||||
var backupName = $"{originName}-{timestamp}{FileSuffix}";
|
||||
var backupPath = Path.Combine(directory, backupName);
|
||||
File.Copy(FilePath, backupPath, true);
|
||||
// todo give user notification for the backup process
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
string serialized = JsonConvert.SerializeObject(_data, Formatting.Indented);
|
||||
File.WriteAllText(FilePath, serialized);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Wox.Infrastructure.Storage
|
||||
{
|
||||
public class PluginJsonStorage<T> :JsonStrorage<T> where T : new()
|
||||
{
|
||||
public PluginJsonStorage()
|
||||
{
|
||||
// C# releated, add python releated below
|
||||
var dataType = typeof(T);
|
||||
var assemblyName = typeof(T).Assembly.GetName().Name;
|
||||
DirectoryPath = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Plugins, assemblyName);
|
||||
Helper.ValidateDirectory(DirectoryPath);
|
||||
|
||||
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wox.Infrastructure.Storage
|
||||
{
|
||||
public class WoxJsonStorage<T> : JsonStrorage<T> where T : new()
|
||||
{
|
||||
public WoxJsonStorage()
|
||||
{
|
||||
var directoryPath = Path.Combine(Constant.DataDirectory, DirectoryName);
|
||||
Helper.ValidateDirectory(directoryPath);
|
||||
|
||||
var filename = typeof(T).Name;
|
||||
FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
|
||||
}
|
||||
}
|
||||
}
|
||||
307
src/modules/launcher/Wox.Infrastructure/StringMatcher.cs
Normal file
307
src/modules/launcher/Wox.Infrastructure/StringMatcher.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using static Wox.Infrastructure.StringMatcher;
|
||||
|
||||
namespace Wox.Infrastructure
|
||||
{
|
||||
public class StringMatcher
|
||||
{
|
||||
private readonly MatchOption _defaultMatchOption = new MatchOption();
|
||||
|
||||
public SearchPrecisionScore UserSettingSearchPrecision { get; set; }
|
||||
|
||||
private readonly IAlphabet _alphabet;
|
||||
|
||||
public StringMatcher(IAlphabet alphabet = null)
|
||||
{
|
||||
_alphabet = alphabet;
|
||||
}
|
||||
|
||||
public static StringMatcher Instance { get; internal set; }
|
||||
|
||||
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
|
||||
public static int Score(string source, string target)
|
||||
{
|
||||
return FuzzySearch(target, source).Score;
|
||||
}
|
||||
|
||||
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
|
||||
public static bool IsMatch(string source, string target)
|
||||
{
|
||||
return Score(source, target) > 0;
|
||||
}
|
||||
|
||||
public static MatchResult FuzzySearch(string query, string stringToCompare)
|
||||
{
|
||||
return Instance.FuzzyMatch(query, stringToCompare);
|
||||
}
|
||||
|
||||
public MatchResult FuzzyMatch(string query, string stringToCompare)
|
||||
{
|
||||
return FuzzyMatch(query, stringToCompare, _defaultMatchOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current method:
|
||||
/// Character matching + substring matching;
|
||||
/// 1. Query search string is split into substrings, separator is whitespace.
|
||||
/// 2. Check each query substring's characters against full compare string,
|
||||
/// 3. if a character in the substring is matched, loop back to verify the previous character.
|
||||
/// 4. If previous character also matches, and is the start of the substring, update list.
|
||||
/// 5. Once the previous character is verified, move on to the next character in the query substring.
|
||||
/// 6. Move onto the next substring's characters until all substrings are checked.
|
||||
/// 7. Consider success and move onto scoring if every char or substring without whitespaces matched
|
||||
/// </summary>
|
||||
public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult (false, UserSettingSearchPrecision);
|
||||
|
||||
query = query.Trim();
|
||||
|
||||
if (_alphabet != null)
|
||||
{
|
||||
query = _alphabet.Translate(query);
|
||||
stringToCompare = _alphabet.Translate(stringToCompare);
|
||||
}
|
||||
|
||||
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
|
||||
|
||||
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
|
||||
|
||||
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int currentQuerySubstringIndex = 0;
|
||||
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
var currentQuerySubstringCharacterIndex = 0;
|
||||
|
||||
var firstMatchIndex = -1;
|
||||
var firstMatchIndexInWord = -1;
|
||||
var lastMatchIndex = 0;
|
||||
bool allQuerySubstringsMatched = false;
|
||||
bool matchFoundInPreviousLoop = false;
|
||||
bool allSubstringsContainedInCompareString = true;
|
||||
|
||||
var indexList = new List<int>();
|
||||
|
||||
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
|
||||
{
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||
{
|
||||
matchFoundInPreviousLoop = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstMatchIndex < 0)
|
||||
{
|
||||
// first matched char will become the start of the compared string
|
||||
firstMatchIndex = compareStringIndex;
|
||||
}
|
||||
|
||||
if (currentQuerySubstringCharacterIndex == 0)
|
||||
{
|
||||
// first letter of current word
|
||||
matchFoundInPreviousLoop = true;
|
||||
firstMatchIndexInWord = compareStringIndex;
|
||||
}
|
||||
else if (!matchFoundInPreviousLoop)
|
||||
{
|
||||
// we want to verify that there is not a better match if this is not a full word
|
||||
// in order to do so we need to verify all previous chars are part of the pattern
|
||||
var startIndexToVerify = compareStringIndex - currentQuerySubstringCharacterIndex;
|
||||
|
||||
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex, fullStringToCompareWithoutCase, currentQuerySubstring))
|
||||
{
|
||||
matchFoundInPreviousLoop = true;
|
||||
|
||||
// if it's the beginning character of the first query substring that is matched then we need to update start index
|
||||
firstMatchIndex = currentQuerySubstringIndex == 0 ? startIndexToVerify : firstMatchIndex;
|
||||
|
||||
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, firstMatchIndexInWord, indexList);
|
||||
}
|
||||
}
|
||||
|
||||
lastMatchIndex = compareStringIndex + 1;
|
||||
indexList.Add(compareStringIndex);
|
||||
|
||||
currentQuerySubstringCharacterIndex++;
|
||||
|
||||
// if finished looping through every character in the current substring
|
||||
if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length)
|
||||
{
|
||||
// if any of the substrings was not matched then consider as all are not matched
|
||||
allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
|
||||
|
||||
currentQuerySubstringIndex++;
|
||||
|
||||
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
if (allQuerySubstringsMatched)
|
||||
break;
|
||||
|
||||
// otherwise move to the next query substring
|
||||
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
currentQuerySubstringCharacterIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// proceed to calculate score if every char or substring without whitespaces matched
|
||||
if (allQuerySubstringsMatched)
|
||||
{
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
|
||||
|
||||
return new MatchResult(true, UserSettingSearchPrecision, indexList, score);
|
||||
}
|
||||
|
||||
return new MatchResult (false, UserSettingSearchPrecision);
|
||||
}
|
||||
|
||||
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
|
||||
string fullStringToCompareWithoutCase, string currentQuerySubstring)
|
||||
{
|
||||
var allMatch = true;
|
||||
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
|
||||
{
|
||||
if (fullStringToCompareWithoutCase[startIndexToVerify + indexToCheck] !=
|
||||
currentQuerySubstring[indexToCheck])
|
||||
{
|
||||
allMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
return allMatch;
|
||||
}
|
||||
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
|
||||
{
|
||||
var updatedList = new List<int>();
|
||||
|
||||
indexList.RemoveAll(x => x >= firstMatchIndexInWord);
|
||||
|
||||
updatedList.AddRange(indexList);
|
||||
|
||||
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
|
||||
{
|
||||
updatedList.Add(startIndexToVerify + indexToCheck);
|
||||
}
|
||||
|
||||
return updatedList;
|
||||
}
|
||||
|
||||
private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength)
|
||||
{
|
||||
return currentQuerySubstringIndex >= querySubstringsLength;
|
||||
}
|
||||
|
||||
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString)
|
||||
{
|
||||
// A match found near the beginning of a string is scored more than a match found near the end
|
||||
// A match is scored more if the characters in the patterns are closer to each other,
|
||||
// while the score is lower if they are more spread out
|
||||
var score = 100 * (query.Length + 1) / ((1 + firstIndex) + (matchLen + 1));
|
||||
|
||||
// A match with less characters assigning more weights
|
||||
if (stringToCompare.Length - query.Length < 5)
|
||||
{
|
||||
score += 20;
|
||||
}
|
||||
else if (stringToCompare.Length - query.Length < 10)
|
||||
{
|
||||
score += 10;
|
||||
}
|
||||
|
||||
if (allSubstringsContainedInCompareString)
|
||||
{
|
||||
int count = query.Count(c => !char.IsWhiteSpace(c));
|
||||
int factor = count < 4 ? 10 : 5;
|
||||
score += factor * count;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
public enum SearchPrecisionScore
|
||||
{
|
||||
Regular = 50,
|
||||
Low = 20,
|
||||
None = 0
|
||||
}
|
||||
}
|
||||
|
||||
public class MatchResult
|
||||
{
|
||||
public MatchResult(bool success, SearchPrecisionScore searchPrecision)
|
||||
{
|
||||
Success = success;
|
||||
SearchPrecision = searchPrecision;
|
||||
}
|
||||
|
||||
public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore)
|
||||
{
|
||||
Success = success;
|
||||
SearchPrecision = searchPrecision;
|
||||
MatchData = matchData;
|
||||
RawScore = rawScore;
|
||||
}
|
||||
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The final score of the match result with search precision filters applied.
|
||||
/// </summary>
|
||||
public int Score { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The raw calculated search score without any search precision filtering applied.
|
||||
/// </summary>
|
||||
private int _rawScore;
|
||||
|
||||
public int RawScore
|
||||
{
|
||||
get { return _rawScore; }
|
||||
set
|
||||
{
|
||||
_rawScore = value;
|
||||
Score = ScoreAfterSearchPrecisionFilter(_rawScore);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matched data to highlight.
|
||||
/// </summary>
|
||||
public List<int> MatchData { get; set; }
|
||||
|
||||
public SearchPrecisionScore SearchPrecision { get; set; }
|
||||
|
||||
public bool IsSearchPrecisionScoreMet()
|
||||
{
|
||||
return IsSearchPrecisionScoreMet(_rawScore);
|
||||
}
|
||||
|
||||
private bool IsSearchPrecisionScoreMet(int rawScore)
|
||||
{
|
||||
return rawScore >= (int)SearchPrecision;
|
||||
}
|
||||
|
||||
private int ScoreAfterSearchPrecisionFilter(int rawScore)
|
||||
{
|
||||
return IsSearchPrecisionScoreMet(rawScore) ? rawScore : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class MatchOption
|
||||
{
|
||||
/// <summary>
|
||||
/// prefix of match char, use for hightlight
|
||||
/// </summary>
|
||||
[Obsolete("this is never used")]
|
||||
public string Prefix { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// suffix of match char, use for hightlight
|
||||
/// </summary>
|
||||
[Obsolete("this is never used")]
|
||||
public string Suffix { get; set; } = "";
|
||||
|
||||
public bool IgnoreCase { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Wox.Infrastructure.UserSettings
|
||||
{
|
||||
public class HttpProxy
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string Server { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Wox.Infrastructure.UserSettings
|
||||
{
|
||||
public class CustomPluginHotkey : BaseModel
|
||||
{
|
||||
public string Hotkey { get; set; }
|
||||
public string ActionKeyword { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Wox.Infrastructure.UserSettings
|
||||
{
|
||||
public class PluginsSettings : BaseModel
|
||||
{
|
||||
public string PythonDirectory { get; set; }
|
||||
public Dictionary<string, Plugin> Plugins { get; set; } = new Dictionary<string, Plugin>();
|
||||
|
||||
public void UpdatePluginSettings(List<PluginMetadata> metadatas)
|
||||
{
|
||||
foreach (var metadata in metadatas)
|
||||
{
|
||||
if (Plugins.ContainsKey(metadata.ID))
|
||||
{
|
||||
var settings = Plugins[metadata.ID];
|
||||
if (settings.ActionKeywords?.Count > 0)
|
||||
{
|
||||
metadata.ActionKeywords = settings.ActionKeywords;
|
||||
metadata.ActionKeyword = settings.ActionKeywords[0];
|
||||
}
|
||||
metadata.Disabled = settings.Disabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
Plugins[metadata.ID] = new Plugin
|
||||
{
|
||||
ID = metadata.ID,
|
||||
Name = metadata.Name,
|
||||
ActionKeywords = metadata.ActionKeywords,
|
||||
Disabled = metadata.Disabled
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public class Plugin
|
||||
{
|
||||
public string ID { get; set; }
|
||||
public string Name { get; set; }
|
||||
public List<string> ActionKeywords { get; set; } // a reference of the action keywords from plugin manager
|
||||
|
||||
/// <summary>
|
||||
/// Used only to save the state of the plugin in settings
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; }
|
||||
}
|
||||
}
|
||||
117
src/modules/launcher/Wox.Infrastructure/UserSettings/Settings.cs
Normal file
117
src/modules/launcher/Wox.Infrastructure/UserSettings/Settings.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Wox.Infrastructure.UserSettings
|
||||
{
|
||||
public class Settings : BaseModel
|
||||
{
|
||||
public string Hotkey { get; set; } = "Alt + Space";
|
||||
public string Language { get; set; } = "en";
|
||||
public string Theme { get; set; } = "Dark";
|
||||
public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name;
|
||||
public string QueryBoxFontStyle { get; set; }
|
||||
public string QueryBoxFontWeight { get; set; }
|
||||
public string QueryBoxFontStretch { get; set; }
|
||||
public string ResultFont { get; set; } = FontFamily.GenericSansSerif.Name;
|
||||
public string ResultFontStyle { get; set; }
|
||||
public string ResultFontWeight { get; set; }
|
||||
public string ResultFontStretch { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// when false Alphabet static service will always return empty results
|
||||
/// </summary>
|
||||
public bool ShouldUsePinyin { get; set; } = true;
|
||||
|
||||
|
||||
internal StringMatcher.SearchPrecisionScore QuerySearchPrecision { get; private set; } = StringMatcher.SearchPrecisionScore.Regular;
|
||||
|
||||
[JsonIgnore]
|
||||
public string QuerySearchPrecisionString
|
||||
{
|
||||
get { return QuerySearchPrecision.ToString(); }
|
||||
set
|
||||
{
|
||||
try
|
||||
{
|
||||
var precisionScore = (StringMatcher.SearchPrecisionScore)Enum
|
||||
.Parse(typeof(StringMatcher.SearchPrecisionScore), value);
|
||||
|
||||
QuerySearchPrecision = precisionScore;
|
||||
StringMatcher.Instance.UserSettingSearchPrecision = precisionScore;
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e);
|
||||
|
||||
QuerySearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
|
||||
StringMatcher.Instance.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoUpdates { get; set; } = false;
|
||||
|
||||
public double WindowLeft { get; set; }
|
||||
public double WindowTop { get; set; }
|
||||
public int MaxResultsToShow { get; set; } = 6;
|
||||
public int ActivateTimes { get; set; }
|
||||
|
||||
// Order defaults to 0 or -1, so 1 will let this property appear last
|
||||
[JsonProperty(Order = 1)]
|
||||
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
|
||||
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
|
||||
|
||||
[Obsolete]
|
||||
public double Opacity { get; set; } = 1;
|
||||
|
||||
[Obsolete]
|
||||
public OpacityMode OpacityMode { get; set; } = OpacityMode.Normal;
|
||||
|
||||
public bool DontPromptUpdateMsg { get; set; }
|
||||
public bool EnableUpdateLog { get; set; }
|
||||
|
||||
public bool StartWoxOnSystemStartup { get; set; } = true;
|
||||
public bool HideOnStartup { get; set; }
|
||||
bool _hideNotifyIcon { get; set; }
|
||||
public bool HideNotifyIcon
|
||||
{
|
||||
get { return _hideNotifyIcon; }
|
||||
set
|
||||
{
|
||||
_hideNotifyIcon = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
public bool LeaveCmdOpen { get; set; }
|
||||
public bool HideWhenDeactive { get; set; } = true;
|
||||
public bool RememberLastLaunchLocation { get; set; }
|
||||
public bool IgnoreHotkeysOnFullscreen { get; set; }
|
||||
|
||||
public HttpProxy Proxy { get; set; } = new HttpProxy();
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected;
|
||||
}
|
||||
|
||||
public enum LastQueryMode
|
||||
{
|
||||
Selected,
|
||||
Empty,
|
||||
Preserved
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public enum OpacityMode
|
||||
{
|
||||
Normal = 0,
|
||||
LayeredWindow = 1,
|
||||
DWM = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Wox.Infrastructure</RootNamespace>
|
||||
<AssemblyName>Wox.Infrastructure</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\Output\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\Output\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SolutionAssemblyInfo.cs">
|
||||
<Link>Properties\SolutionAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Exception\ExceptionFormatter.cs" />
|
||||
<Compile Include="Helper.cs" />
|
||||
<Compile Include="Hotkey\InterceptKeys.cs" />
|
||||
<Compile Include="Hotkey\KeyEvent.cs" />
|
||||
<Compile Include="Image\ImageCache.cs" />
|
||||
<Compile Include="Image\ImageHashGenerator.cs" />
|
||||
<Compile Include="Image\ImageLoader.cs" />
|
||||
<Compile Include="Image\ThumbnailReader.cs" />
|
||||
<Compile Include="Logger\Log.cs" />
|
||||
<Compile Include="Storage\ISavable.cs" />
|
||||
<Compile Include="Storage\PluginJsonStorage.cs" />
|
||||
<Compile Include="Stopwatch.cs" />
|
||||
<Compile Include="Storage\BinaryStorage.cs" />
|
||||
<Compile Include="Storage\JsonStorage.cs" />
|
||||
<Compile Include="Storage\WoxJsonStorage.cs" />
|
||||
<Compile Include="StringMatcher.cs" />
|
||||
<Compile Include="Http\Http.cs" />
|
||||
<Compile Include="FuzzyMatcher.cs" />
|
||||
<Compile Include="Hotkey\GlobalHotkey.cs" />
|
||||
<Compile Include="Hotkey\HotkeyModel.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Alphabet.cs" />
|
||||
<Compile Include="UserSettings\HttpProxy.cs" />
|
||||
<Compile Include="UserSettings\PluginHotkey.cs" />
|
||||
<Compile Include="UserSettings\PluginSettings.cs" />
|
||||
<Compile Include="UserSettings\Settings.cs" />
|
||||
<Compile Include="Wox.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wox.Plugin\Wox.Plugin.csproj">
|
||||
<Project>{8451ecdd-2ea4-4966-bb0a-7bbc40138e80}</Project>
|
||||
<Name>Wox.Plugin</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations">
|
||||
<Version>10.3.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>9.0.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NLog">
|
||||
<Version>4.2.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NLog.Schema" GeneratePathProperty="true">
|
||||
<Version>4.2.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Pinyin4DotNet" GeneratePathProperty="true">
|
||||
<Version>2016.4.23.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Runtime">
|
||||
<Version>4.0.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>
|
||||
xcopy /Y $(PkgPinyin4DotNet)\pinyindb\unicode_to_hanyu_pinyin.txt $(TargetDir)pinyindb\
|
||||
xcopy /Y $(PkgNLog_Schema)\content\NLog.xsd $(TargetDir)
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
46
src/modules/launcher/Wox.Infrastructure/Wox.cs
Normal file
46
src/modules/launcher/Wox.Infrastructure/Wox.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Wox.Infrastructure
|
||||
{
|
||||
public static class Constant
|
||||
{
|
||||
public const string Wox = "Wox";
|
||||
public const string Plugins = "Plugins";
|
||||
|
||||
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
|
||||
public static readonly string ProgramDirectory = Directory.GetParent(Assembly.Location.NonNull()).ToString();
|
||||
public static readonly string ExecutablePath = Path.Combine(ProgramDirectory, Wox + ".exe");
|
||||
|
||||
public static bool IsPortableMode;
|
||||
public const string PortableFolderName = "UserData";
|
||||
public static string PortableDataPath = Path.Combine(ProgramDirectory, PortableFolderName);
|
||||
public static string DetermineDataDirectory()
|
||||
{
|
||||
if (Directory.Exists(PortableDataPath))
|
||||
{
|
||||
IsPortableMode = true;
|
||||
return PortableDataPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Wox);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly string DataDirectory = DetermineDataDirectory();
|
||||
public static readonly string PluginsDirectory = Path.Combine(DataDirectory, Plugins);
|
||||
public static readonly string PreinstalledDirectory = Path.Combine(ProgramDirectory, Plugins);
|
||||
public const string Issue = "https://github.com/Wox-launcher/Wox/issues/new";
|
||||
public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion;
|
||||
|
||||
public static readonly int ThumbnailSize = 64;
|
||||
public static readonly string DefaultIcon = Path.Combine(ProgramDirectory, "Images", "app.png");
|
||||
public static readonly string ErrorIcon = Path.Combine(ProgramDirectory, "Images", "app_error.png");
|
||||
|
||||
public static string PythonPath;
|
||||
public static string EverythingSDKPath;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user