Flow.Launcher/Flow.Launcher/PublicAPIInstance.cs

599 lines
24 KiB
C#
Raw Normal View History

using System;
2025-03-17 02:07:40 +00:00
using System.Collections.Concurrent;
using System.Collections.Generic;
2025-03-17 02:07:40 +00:00
using System.Collections.Specialized;
using System.ComponentModel;
2025-03-17 02:07:40 +00:00
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
2025-03-17 02:07:40 +00:00
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
2025-04-02 10:22:36 +00:00
using System.Windows.Media;
2025-03-17 02:07:40 +00:00
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Core.Plugin;
2025-04-01 03:54:18 +00:00
using Flow.Launcher.Core.Resource;
2025-04-05 12:41:24 +00:00
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Storage;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
2025-03-17 02:07:40 +00:00
using Flow.Launcher.Infrastructure.Http;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.Image;
2025-03-17 02:07:40 +00:00
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Plugin;
2021-01-26 07:01:39 +00:00
using Flow.Launcher.Plugin.SharedModels;
2021-11-17 10:00:36 +00:00
using Flow.Launcher.Plugin.SharedCommands;
2025-03-17 02:07:40 +00:00
using Flow.Launcher.ViewModel;
2021-01-08 08:05:50 +00:00
using JetBrains.Annotations;
2025-04-01 03:54:18 +00:00
using Squirrel;
2025-04-08 13:32:18 +00:00
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
2020-04-21 09:12:17 +00:00
namespace Flow.Launcher
{
public class PublicAPIInstance : IPublicAPI, IRemovable
{
2025-05-20 13:57:49 +00:00
private static readonly string ClassName = nameof(PublicAPIInstance);
private readonly Settings _settings;
private readonly MainViewModel _mainVM;
// Must use getter to avoid accessing Application.Current.Resources.MergedDictionaries so earlier in theme constructor
2025-04-04 12:10:39 +00:00
private Theme _theme;
private Theme Theme => _theme ??= Ioc.Default.GetRequiredService<Theme>();
2025-04-11 14:00:36 +00:00
// Must use getter to avoid circular dependency
private Updater _updater;
private Updater Updater => _updater ??= Ioc.Default.GetRequiredService<Updater>();
2025-04-04 02:03:19 +00:00
private readonly object _saveSettingsLock = new();
#region Constructor
2025-04-11 14:00:36 +00:00
public PublicAPIInstance(Settings settings, MainViewModel mainVM)
{
_settings = settings;
_mainVM = mainVM;
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
}
#endregion
#region Public API
public void ChangeQuery(string query, bool requery = false)
{
_mainVM.ChangeQueryText(query, requery);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
public async void RestartApp()
{
_mainVM.Hide();
2017-03-01 23:47:50 +00:00
2025-04-04 02:02:47 +00:00
// We must manually save
2016-05-08 16:47:28 +00:00
// UpdateManager.RestartApp() will call Environment.Exit(0)
// which will cause ungraceful exit
SaveAppAllSettings();
2025-04-04 02:02:47 +00:00
// Wait for all image caches to be saved before restarting
await ImageLoader.WaitSaveAsync();
2021-02-13 10:35:08 +00:00
// Restart requires Squirrel's Update.exe to be present in the parent folder,
// it is only published from the project's release pipeline. When debugging without it,
// the project may not restart or just terminates. This is expected.
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
public void ShowMainWindow() => _mainVM.Show();
public void FocusQueryTextBox() => _mainVM.FocusQueryTextBox();
public void HideMainWindow() => _mainVM.Hide();
public bool IsMainWindowVisible() => _mainVM.MainWindowVisibilityStatus;
2025-04-01 03:54:18 +00:00
public event VisibilityChangedEventHandler VisibilityChanged
{
add => _mainVM.VisibilityChanged += value;
remove => _mainVM.VisibilityChanged -= value;
}
2025-04-11 14:00:36 +00:00
public void CheckForNewUpdate() => _ = Updater.UpdateAppAsync(false);
public void SaveAppAllSettings()
{
2025-04-04 02:03:19 +00:00
lock (_saveSettingsLock)
{
_settings.Save();
PluginManager.Save();
_mainVM.Save();
}
_ = ImageLoader.SaveAsync();
}
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
public void ShowMsgError(string title, string subTitle = "") =>
ShowMsg(title, subTitle, Constant.ErrorIcon, true);
public void ShowMsgErrorWithButton(string title, string buttonText, Action buttonAction, string subTitle = "") =>
ShowMsgWithButton(title, buttonText, buttonAction, subTitle, Constant.ErrorIcon, true);
public void ShowMsg(string title, string subTitle = "", string iconPath = "") =>
ShowMsg(title, subTitle, iconPath, true);
public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true)
{
Notification.Show(title, subTitle, iconPath);
}
public void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle = "", string iconPath = "") =>
ShowMsgWithButton(title, buttonText, buttonAction, subTitle, iconPath, true);
public void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath, bool useMainWindowAsOwner = true)
{
Notification.ShowWithButton(title, buttonText, buttonAction, subTitle, iconPath);
}
2016-05-22 18:16:39 +00:00
public void OpenSettingDialog()
{
Application.Current.Dispatcher.Invoke(() =>
{
SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>();
});
}
public void ShellRun(string cmd, string filename = "cmd.exe")
{
var args = filename == "cmd.exe" ? $"/C {cmd}" : $"{cmd}";
var startInfo = ShellCommand.SetProcessStartInfo(filename, arguments: args, createNoWindow: true);
2021-11-17 10:00:36 +00:00
ShellCommand.Execute(startInfo);
}
2021-12-09 03:39:31 +00:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
public async void CopyToClipboard(string stringToCopy, bool directCopy = false, bool showDefaultNotification = true)
2021-11-30 09:14:03 +00:00
{
2023-06-09 09:59:49 +00:00
if (string.IsNullOrEmpty(stringToCopy))
{
2023-06-09 09:59:49 +00:00
return;
}
2023-06-09 09:59:49 +00:00
var isFile = File.Exists(stringToCopy);
if (directCopy && (isFile || Directory.Exists(stringToCopy)))
2023-06-09 09:59:49 +00:00
{
// Sometimes the clipboard is locked and cannot be accessed,
// we need to retry a few times before giving up
var exception = await RetryActionOnSTAThreadAsync(() =>
{
var paths = new StringCollection
{
stringToCopy
};
Clipboard.SetFileDropList(paths);
});
if (exception == null)
{
if (showDefaultNotification)
{
ShowMsg(
$"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}",
GetTranslation("completedSuccessfully"));
}
}
else
{
LogException(nameof(PublicAPIInstance), "Failed to copy file/folder to clipboard", exception);
ShowMsgError(GetTranslation("failedToCopy"));
}
}
else
{
// Sometimes the clipboard is locked and cannot be accessed,
// we need to retry a few times before giving up
var exception = await RetryActionOnSTAThreadAsync(() =>
{
// We should use SetText instead of SetDataObject to avoid the clipboard being locked by other applications
Clipboard.SetText(stringToCopy);
});
if (exception == null)
{
if (showDefaultNotification)
{
ShowMsg(
$"{GetTranslation("copy")} {GetTranslation("textTitle")}",
GetTranslation("completedSuccessfully"));
}
}
else
{
LogException(nameof(PublicAPIInstance), "Failed to copy text to clipboard", exception);
ShowMsgError(GetTranslation("failedToCopy"));
}
}
}
private static async Task<Exception> RetryActionOnSTAThreadAsync(Action action, int retryCount = 6, int retryDelay = 150)
{
for (var i = 0; i < retryCount; i++)
{
try
{
await Win32Helper.StartSTATaskAsync(action).ConfigureAwait(false);
break;
}
catch (Exception e)
{
if (i == retryCount - 1)
{
return e;
}
await Task.Delay(retryDelay);
}
}
return null;
2021-11-30 09:14:03 +00:00
}
2021-02-14 10:12:57 +00:00
public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible;
2021-02-14 10:12:57 +00:00
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
2025-04-11 14:00:36 +00:00
public string GetTranslation(string key) => Internationalization.GetTranslation(key);
2021-02-14 10:12:57 +00:00
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
public MatchResult FuzzySearch(string query, string stringToCompare) =>
StringMatcher.FuzzySearch(query, stringToCompare);
2025-04-01 03:54:18 +00:00
public Task<string> HttpGetStringAsync(string url, CancellationToken token = default) =>
Http.GetAsync(url, token);
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) =>
2025-03-17 02:25:02 +00:00
Http.GetStreamAsync(url, token);
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null,
2025-06-04 05:39:11 +00:00
CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token);
2021-01-08 08:05:50 +00:00
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
PluginManager.AddActionKeyword(pluginId, newActionKeyword);
public bool ActionKeywordAssigned(string actionKeyword) => PluginManager.ActionKeywordRegistered(actionKeyword);
public void RemoveActionKeyword(string pluginId, string oldActionKeyword) =>
PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") =>
Log.Debug(className, message, methodName);
public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") =>
Log.Info(className, message, methodName);
public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") =>
Log.Warn(className, message, methodName);
public void LogError(string className, string message, [CallerMemberName] string methodName = "") =>
Log.Error(className, message, methodName);
2025-04-01 03:54:18 +00:00
public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") =>
Log.Exception(className, message, e, methodName);
2025-06-06 10:38:05 +00:00
private readonly ConcurrentDictionary<Type, ISavable> _pluginJsonStorages = new();
public void RemovePluginSettings(string assemblyName)
{
foreach (var keyValuePair in _pluginJsonStorages)
{
var key = keyValuePair.Key;
var value = keyValuePair.Value;
var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString();
if (name == assemblyName)
{
_pluginJsonStorages.TryRemove(key, out var _);
}
}
}
public void SavePluginSettings()
{
2025-06-06 10:38:05 +00:00
foreach (var savable in _pluginJsonStorages.Values)
{
2025-06-08 06:28:37 +00:00
savable.Save();
}
}
public T LoadSettingJsonStorage<T>() where T : new()
{
var type = typeof(T);
if (!_pluginJsonStorages.ContainsKey(type))
_pluginJsonStorages[type] = new PluginJsonStorage<T>();
return ((PluginJsonStorage<T>)_pluginJsonStorages[type]).Load();
}
public void SaveSettingJsonStorage<T>() where T : new()
{
var type = typeof(T);
if (!_pluginJsonStorages.ContainsKey(type))
_pluginJsonStorages[type] = new PluginJsonStorage<T>();
((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
}
public void OpenDirectory(string directoryPath, string fileNameOrFilePath = null)
{
try
{
2025-05-27 06:36:09 +00:00
var explorerInfo = _settings.CustomExplorer;
var explorerPath = explorerInfo.Path.Trim().ToLowerInvariant();
var targetPath = fileNameOrFilePath is null
? directoryPath
: Path.IsPathRooted(fileNameOrFilePath)
? fileNameOrFilePath
: Path.Combine(directoryPath, fileNameOrFilePath);
if (Path.GetFileNameWithoutExtension(explorerPath) == "explorer")
{
2025-05-27 06:36:09 +00:00
// Windows File Manager
if (fileNameOrFilePath is null)
{
2025-05-27 06:36:09 +00:00
// Only Open the directory
using var explorer = new Process();
explorer.StartInfo = new ProcessStartInfo
{
FileName = directoryPath,
UseShellExecute = true
2025-05-27 06:36:09 +00:00
};
explorer.Start();
}
else
{
2025-05-27 06:36:09 +00:00
// Open the directory and select the file
Win32Helper.OpenFolderAndSelectFile(targetPath);
}
}
else
{
2025-05-27 06:36:09 +00:00
// Custom File Manager
using var explorer = new Process();
explorer.StartInfo = new ProcessStartInfo
{
FileName = explorerInfo.Path.Replace("%d", directoryPath),
UseShellExecute = true,
Arguments = fileNameOrFilePath is null
? explorerInfo.DirectoryArgument.Replace("%d", directoryPath)
: explorerInfo.FileArgument
.Replace("%d", directoryPath)
.Replace("%f", targetPath)
};
2025-05-27 06:36:09 +00:00
explorer.Start();
}
}
2025-05-20 13:57:49 +00:00
catch (Win32Exception ex) when (ex.NativeErrorCode == 2)
2024-06-25 07:35:32 +00:00
{
2025-05-20 13:57:49 +00:00
LogError(ClassName, "File Manager not found");
2025-05-20 13:37:06 +00:00
ShowMsgBox(
string.Format(GetTranslation("fileManagerNotFound"), ex.Message),
GetTranslation("fileManagerNotFoundTitle"),
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
catch (Exception ex)
{
2025-05-20 13:57:49 +00:00
LogException(ClassName, "Failed to open folder", ex);
2025-05-20 13:37:06 +00:00
ShowMsgBox(
string.Format(GetTranslation("folderOpenError"), ex.Message),
GetTranslation("errorTitle"),
MessageBoxButton.OK,
MessageBoxImage.Error
);
2025-05-17 13:35:52 +00:00
}
}
2022-02-03 21:26:42 +00:00
private void OpenUri(Uri uri, bool? inPrivate = null)
{
2022-01-09 18:30:57 +00:00
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
var browserInfo = _settings.CustomBrowser;
2022-01-09 18:30:57 +00:00
var path = browserInfo.Path == "*" ? "" : browserInfo.Path;
try
2022-01-09 18:30:57 +00:00
{
if (browserInfo.OpenInTab)
{
uri.AbsoluteUri.OpenInBrowserTab(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg);
}
else
{
uri.AbsoluteUri.OpenInBrowserWindow(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg);
}
2022-01-09 18:30:57 +00:00
}
catch (Exception e)
2022-01-09 18:30:57 +00:00
{
var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window";
LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e);
ShowMsgBox(
GetTranslation("browserOpenError"),
GetTranslation("errorTitle"),
MessageBoxButton.OK,
MessageBoxImage.Error
);
2022-01-09 18:30:57 +00:00
}
2022-02-03 21:26:42 +00:00
}
else
{
Process.Start(new ProcessStartInfo()
{
FileName = uri.AbsoluteUri,
UseShellExecute = true
})?.Dispose();
2022-01-30 21:37:04 +00:00
2022-01-09 18:30:57 +00:00
return;
}
}
public void OpenUrl(string url, bool? inPrivate = null)
2022-02-03 21:26:42 +00:00
{
OpenUri(new Uri(url), inPrivate);
}
public void OpenUrl(Uri url, bool? inPrivate = null)
{
OpenUri(url, inPrivate);
}
public void OpenAppUri(string appUri)
{
2022-02-03 21:26:42 +00:00
OpenUri(new Uri(appUri));
}
2022-02-03 21:26:42 +00:00
public void OpenAppUri(Uri appUri)
{
OpenUri(appUri);
}
2024-01-14 13:31:21 +00:00
public void ToggleGameMode()
{
_mainVM.ToggleGameMode();
}
public void SetGameMode(bool value)
{
_mainVM.GameModeStatus = value;
}
public bool IsGameModeOn()
{
return _mainVM.GameModeStatus;
}
private readonly List<Func<int, int, SpecialKeyState, bool>> _globalKeyboardHandlers = new();
2025-04-01 03:54:18 +00:00
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) =>
_globalKeyboardHandlers.Add(callback);
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) =>
_globalKeyboardHandlers.Remove(callback);
2024-02-25 02:13:09 +00:00
public void ReQuery(bool reselect = true) => _mainVM.ReQuery(reselect);
2024-02-23 21:27:34 +00:00
public void BackToQueryResults() => _mainVM.BackToQueryResults();
2025-04-01 03:54:18 +00:00
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "",
MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None,
MessageBoxResult defaultResult = MessageBoxResult.OK) =>
2024-11-27 02:07:03 +00:00
MessageBoxEx.Show(messageBoxText, caption, button, icon, defaultResult);
2025-04-01 03:54:18 +00:00
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync,
Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
2025-04-04 12:10:39 +00:00
public List<ThemeData> GetAvailableThemes() => Theme.GetAvailableThemes();
public ThemeData GetCurrentTheme() => Theme.GetCurrentTheme();
public bool SetCurrentTheme(ThemeData theme) =>
2025-04-04 12:10:39 +00:00
Theme.ChangeTheme(theme.FileNameWithoutExtension);
2025-06-06 10:38:05 +00:00
private readonly ConcurrentDictionary<(string, string, Type), ISavable> _pluginBinaryStorages = new();
2025-04-01 06:19:59 +00:00
public void RemovePluginCaches(string cacheDirectory)
2025-04-01 06:19:59 +00:00
{
foreach (var keyValuePair in _pluginBinaryStorages)
{
var key = keyValuePair.Key;
var currentCacheDirectory = key.Item2;
if (cacheDirectory == currentCacheDirectory)
{
2025-04-01 06:35:43 +00:00
_pluginBinaryStorages.TryRemove(key, out var _);
2025-04-01 06:19:59 +00:00
}
}
}
public void SavePluginCaches()
{
2025-06-06 10:38:05 +00:00
foreach (var savable in _pluginBinaryStorages.Values)
2025-04-01 06:19:59 +00:00
{
2025-06-08 06:28:37 +00:00
savable.Save();
2025-04-01 06:19:59 +00:00
}
}
public async Task<T> LoadCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory, T defaultData) where T : new()
{
var type = typeof(T);
if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
_pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage<T>(cacheName, cacheDirectory);
return await ((PluginBinaryStorage<T>)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).TryLoadAsync(defaultData);
}
public async Task SaveCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory) where T : new()
{
var type = typeof(T);
if (!_pluginBinaryStorages.ContainsKey((cacheName, cacheDirectory, type)))
_pluginBinaryStorages[(cacheName, cacheDirectory, type)] = new PluginBinaryStorage<T>(cacheName, cacheDirectory);
await ((PluginBinaryStorage<T>)_pluginBinaryStorages[(cacheName, cacheDirectory, type)]).SaveAsync();
}
2025-04-02 10:22:36 +00:00
public ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) =>
ImageLoader.LoadAsync(path, loadFullImage, cacheImage);
public Task<bool> UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) =>
PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token);
public IReadOnlyList<UserPlugin> GetPluginManifest() => PluginsManifest.UserPlugins;
public bool PluginModified(string id) => PluginManager.PluginModified(id);
public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath) =>
PluginManager.UpdatePluginAsync(pluginMetadata, plugin, zipFilePath);
public void InstallPlugin(UserPlugin plugin, string zipFilePath) =>
PluginManager.InstallPlugin(plugin, zipFilePath);
public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) =>
PluginManager.UninstallPluginAsync(pluginMetadata, removePluginSettings);
2025-04-08 13:38:17 +00:00
public long StopwatchLogDebug(string className, string message, Action action, [CallerMemberName] string methodName = "") =>
2025-04-13 09:50:44 +00:00
Stopwatch.Debug(className, message, action, methodName);
2025-04-08 13:32:18 +00:00
2025-04-08 13:38:17 +00:00
public Task<long> StopwatchLogDebugAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "") =>
2025-04-13 09:50:44 +00:00
Stopwatch.DebugAsync(className, message, action, methodName);
2025-04-08 13:32:18 +00:00
2025-04-08 13:38:17 +00:00
public long StopwatchLogInfo(string className, string message, Action action, [CallerMemberName] string methodName = "") =>
2025-04-13 09:50:44 +00:00
Stopwatch.Info(className, message, action, methodName);
2025-04-08 13:32:18 +00:00
2025-04-08 13:38:17 +00:00
public Task<long> StopwatchLogInfoAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "") =>
2025-04-13 09:50:44 +00:00
Stopwatch.InfoAsync(className, message, action, methodName);
2025-04-08 13:32:18 +00:00
#endregion
#region Private Methods
private bool KListener_hookedKeyboardCallback(KeyEvent keyevent, int vkcode, SpecialKeyState state)
{
var continueHook = true;
foreach (var x in _globalKeyboardHandlers)
{
continueHook &= x((int)keyevent, vkcode, state);
}
return continueHook;
}
#endregion
}
}