2024-12-19 08:34:32 +00:00
|
|
|
|
using System;
|
2025-03-17 02:07:40 +00:00
|
|
|
|
using System.Collections.Concurrent;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
using System.Collections.Generic;
|
2025-03-17 02:07:40 +00:00
|
|
|
|
using System.Collections.Specialized;
|
2025-05-22 02:39:09 +00:00
|
|
|
|
using System.ComponentModel;
|
2025-03-17 02:07:40 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.IO;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Net;
|
2025-03-17 02:07:40 +00:00
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
|
using System.Threading;
|
2016-05-05 20:15:13 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
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;
|
2025-04-05 03:31:24 +00:00
|
|
|
|
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
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2025-04-05 03:31:24 +00:00
|
|
|
|
public class PublicAPIInstance : IPublicAPI, IRemovable
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2025-05-20 13:57:49 +00:00
|
|
|
|
private static readonly string ClassName = nameof(PublicAPIInstance);
|
|
|
|
|
|
|
2025-02-23 14:05:49 +00:00
|
|
|
|
private readonly Settings _settings;
|
2016-05-25 00:00:10 +00:00
|
|
|
|
private readonly MainViewModel _mainVM;
|
2016-05-09 22:35:20 +00:00
|
|
|
|
|
2025-05-02 04:26:14 +00:00
|
|
|
|
// 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();
|
|
|
|
|
|
|
2016-02-18 11:30:36 +00:00
|
|
|
|
#region Constructor
|
|
|
|
|
|
|
2025-04-11 14:00:36 +00:00
|
|
|
|
public PublicAPIInstance(Settings settings, MainViewModel mainVM)
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2025-02-24 01:40:33 +00:00
|
|
|
|
_settings = settings;
|
2016-05-25 00:00:10 +00:00
|
|
|
|
_mainVM = mainVM;
|
2021-11-27 20:26:20 +00:00
|
|
|
|
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Public API
|
|
|
|
|
|
|
|
|
|
|
|
public void ChangeQuery(string query, bool requery = false)
|
|
|
|
|
|
{
|
2021-09-25 01:05:55 +00:00
|
|
|
|
_mainVM.ChangeQueryText(query, requery);
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-02 04:26:14 +00:00
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
2025-04-04 01:57:03 +00:00
|
|
|
|
public async void RestartApp()
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2021-11-27 02:38:47 +00:00
|
|
|
|
_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
|
2019-08-22 11:37:36 +00:00
|
|
|
|
SaveAppAllSettings();
|
|
|
|
|
|
|
2025-04-04 02:02:47 +00:00
|
|
|
|
// Wait for all image caches to be saved before restarting
|
2025-04-04 01:57:03 +00:00
|
|
|
|
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.
|
2020-04-08 08:51:35 +00:00
|
|
|
|
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
2019-08-22 11:37:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-14 08:45:56 +00:00
|
|
|
|
public void ShowMainWindow() => _mainVM.Show();
|
2025-05-22 02:39:09 +00:00
|
|
|
|
|
|
|
|
|
|
public void FocusQueryTextBox() => _mainVM.FocusQueryTextBox();
|
2021-10-17 02:28:55 +00:00
|
|
|
|
|
2023-05-11 14:57:10 +00:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2023-07-03 21:42:41 +00:00
|
|
|
|
|
2025-04-11 14:00:36 +00:00
|
|
|
|
public void CheckForNewUpdate() => _ = Updater.UpdateAppAsync(false);
|
2020-02-25 10:08:51 +00:00
|
|
|
|
|
2019-08-22 11:37:36 +00:00
|
|
|
|
public void SaveAppAllSettings()
|
|
|
|
|
|
{
|
2025-04-04 02:03:19 +00:00
|
|
|
|
lock (_saveSettingsLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
_settings.Save();
|
|
|
|
|
|
PluginManager.Save();
|
|
|
|
|
|
_mainVM.Save();
|
|
|
|
|
|
}
|
2025-04-04 01:57:03 +00:00
|
|
|
|
_ = ImageLoader.SaveAsync();
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-09 00:35:38 +00:00
|
|
|
|
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
|
2019-10-06 01:48:40 +00:00
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public void ShowMsgError(string title, string subTitle = "") =>
|
|
|
|
|
|
ShowMsg(title, subTitle, Constant.ErrorIcon, true);
|
2021-04-15 22:53:19 +00:00
|
|
|
|
|
2025-06-09 06:39:44 +00:00
|
|
|
|
public void ShowMsgErrorWithButton(string title, string buttonText, Action buttonAction, string subTitle = "") =>
|
|
|
|
|
|
ShowMsgWithButton(title, buttonText, buttonAction, subTitle, Constant.ErrorIcon, true);
|
|
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public void ShowMsg(string title, string subTitle = "", string iconPath = "") =>
|
|
|
|
|
|
ShowMsg(title, subTitle, iconPath, true);
|
2020-03-31 11:00:09 +00:00
|
|
|
|
|
|
|
|
|
|
public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true)
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2023-04-20 22:56:54 +00:00
|
|
|
|
Notification.Show(title, subTitle, iconPath);
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 06:39:44 +00:00
|
|
|
|
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()
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
|
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-01-13 04:00:45 +00:00
|
|
|
|
SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>();
|
2016-02-18 11:30:36 +00:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-17 18:56:51 +00:00
|
|
|
|
public void ShellRun(string cmd, string filename = "cmd.exe")
|
2021-10-09 09:36:38 +00:00
|
|
|
|
{
|
2021-11-17 19:15:38 +00:00
|
|
|
|
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-10-09 09:36:38 +00:00
|
|
|
|
}
|
2021-12-09 03:39:31 +00:00
|
|
|
|
|
2025-05-02 04:26:14 +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))
|
2025-05-02 04:26:14 +00:00
|
|
|
|
{
|
2023-06-09 09:59:49 +00:00
|
|
|
|
return;
|
2025-05-02 04:26:14 +00:00
|
|
|
|
}
|
2023-06-09 09:59:49 +00:00
|
|
|
|
|
2025-01-03 16:33:24 +00:00
|
|
|
|
var isFile = File.Exists(stringToCopy);
|
|
|
|
|
|
if (directCopy && (isFile || Directory.Exists(stringToCopy)))
|
2023-06-09 09:59:49 +00:00
|
|
|
|
{
|
2025-05-02 04:26:14 +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
|
2024-12-19 08:34:32 +00:00
|
|
|
|
{
|
|
|
|
|
|
stringToCopy
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-05-02 04:26:14 +00:00
|
|
|
|
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"));
|
|
|
|
|
|
}
|
2025-01-03 16:33:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-02 04:26:14 +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(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 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"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-01-03 16:33:24 +00:00
|
|
|
|
|
2025-05-02 04:26:14 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-01-03 16:33:24 +00:00
|
|
|
|
}
|
2025-05-02 04:26:14 +00:00
|
|
|
|
return null;
|
2021-11-30 09:14:03 +00:00
|
|
|
|
}
|
2021-10-09 09:36:38 +00:00
|
|
|
|
|
2021-02-14 10:12:57 +00:00
|
|
|
|
public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
|
2021-02-14 10:12:57 +00:00
|
|
|
|
public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
|
2025-04-11 14:00:36 +00:00
|
|
|
|
public string GetTranslation(string key) => Internationalization.GetTranslation(key);
|
2016-02-18 11:30:36 +00:00
|
|
|
|
|
2021-02-14 10:12:57 +00:00
|
|
|
|
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins.ToList();
|
2016-02-18 11:30:36 +00:00
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public MatchResult FuzzySearch(string query, string stringToCompare) =>
|
|
|
|
|
|
StringMatcher.FuzzySearch(query, stringToCompare);
|
2020-12-05 08:55:06 +00:00
|
|
|
|
|
2025-04-01 03:54:18 +00:00
|
|
|
|
public Task<string> HttpGetStringAsync(string url, CancellationToken token = default) =>
|
|
|
|
|
|
Http.GetAsync(url, token);
|
2021-01-08 08:00:06 +00:00
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) =>
|
2025-03-17 02:25:02 +00:00
|
|
|
|
Http.GetStreamAsync(url, token);
|
2021-01-08 08:00:06 +00:00
|
|
|
|
|
2025-01-11 05:24:41 +00:00
|
|
|
|
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
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
|
|
|
|
|
|
PluginManager.AddActionKeyword(pluginId, newActionKeyword);
|
2021-01-08 08:08:39 +00:00
|
|
|
|
|
2022-07-01 04:56:15 +00:00
|
|
|
|
public bool ActionKeywordAssigned(string actionKeyword) => PluginManager.ActionKeywordRegistered(actionKeyword);
|
|
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public void RemoveActionKeyword(string pluginId, string oldActionKeyword) =>
|
|
|
|
|
|
PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
|
2021-02-14 10:08:30 +00:00
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") =>
|
|
|
|
|
|
Log.Debug(className, message, methodName);
|
2021-02-14 10:08:30 +00:00
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") =>
|
|
|
|
|
|
Log.Info(className, message, methodName);
|
2021-02-14 10:08:30 +00:00
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") =>
|
|
|
|
|
|
Log.Warn(className, message, methodName);
|
2021-02-14 10:08:30 +00:00
|
|
|
|
|
2025-04-01 03:47:18 +00:00
|
|
|
|
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);
|
2021-02-14 10:08:30 +00:00
|
|
|
|
|
2025-06-06 10:38:05 +00:00
|
|
|
|
private readonly ConcurrentDictionary<Type, ISavable> _pluginJsonStorages = new();
|
2021-02-14 10:08:30 +00:00
|
|
|
|
|
2025-02-24 05:35:17 +00:00
|
|
|
|
public void RemovePluginSettings(string assemblyName)
|
2025-02-08 11:12:53 +00:00
|
|
|
|
{
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2025-04-08 08:28:05 +00:00
|
|
|
|
_pluginJsonStorages.TryRemove(key, out var _);
|
2025-02-08 11:12:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-13 12:49:41 +00:00
|
|
|
|
public void SavePluginSettings()
|
2021-05-11 16:37:28 +00:00
|
|
|
|
{
|
2025-06-06 10:38:05 +00:00
|
|
|
|
foreach (var savable in _pluginJsonStorages.Values)
|
2021-05-11 16:37:28 +00:00
|
|
|
|
{
|
2025-06-08 06:28:37 +00:00
|
|
|
|
savable.Save();
|
2021-05-11 16:37:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-05-13 12:49:41 +00:00
|
|
|
|
|
2021-05-13 11:29:21 +00:00
|
|
|
|
public T LoadSettingJsonStorage<T>() where T : new()
|
2021-03-27 09:26:53 +00:00
|
|
|
|
{
|
|
|
|
|
|
var type = typeof(T);
|
2021-05-11 16:37:28 +00:00
|
|
|
|
if (!_pluginJsonStorages.ContainsKey(type))
|
|
|
|
|
|
_pluginJsonStorages[type] = new PluginJsonStorage<T>();
|
2021-03-27 09:26:53 +00:00
|
|
|
|
|
2021-11-06 00:23:09 +00:00
|
|
|
|
return ((PluginJsonStorage<T>)_pluginJsonStorages[type]).Load();
|
2021-03-27 09:26:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-13 11:29:21 +00:00
|
|
|
|
public void SaveSettingJsonStorage<T>() where T : new()
|
2021-03-27 09:26:53 +00:00
|
|
|
|
{
|
|
|
|
|
|
var type = typeof(T);
|
2021-05-11 16:37:28 +00:00
|
|
|
|
if (!_pluginJsonStorages.ContainsKey(type))
|
|
|
|
|
|
_pluginJsonStorages[type] = new PluginJsonStorage<T>();
|
2021-03-27 09:26:53 +00:00
|
|
|
|
|
2021-11-06 00:23:09 +00:00
|
|
|
|
((PluginJsonStorage<T>)_pluginJsonStorages[type]).Save();
|
2021-03-27 09:26:53 +00:00
|
|
|
|
}
|
2025-05-27 06:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
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
|
2025-05-27 06:20:41 +00:00
|
|
|
|
? directoryPath
|
|
|
|
|
|
: Path.IsPathRooted(fileNameOrFilePath)
|
|
|
|
|
|
? fileNameOrFilePath
|
|
|
|
|
|
: Path.Combine(directoryPath, fileNameOrFilePath);
|
|
|
|
|
|
|
2025-05-20 09:49:38 +00:00
|
|
|
|
if (Path.GetFileNameWithoutExtension(explorerPath) == "explorer")
|
2025-05-17 06:08:20 +00:00
|
|
|
|
{
|
2025-05-27 06:36:09 +00:00
|
|
|
|
// Windows File Manager
|
2025-05-27 06:20:41 +00:00
|
|
|
|
if (fileNameOrFilePath is null)
|
2025-05-20 09:49:38 +00:00
|
|
|
|
{
|
2025-05-27 06:36:09 +00:00
|
|
|
|
// Only Open the directory
|
|
|
|
|
|
using var explorer = new Process();
|
|
|
|
|
|
explorer.StartInfo = new ProcessStartInfo
|
2025-05-27 06:20:41 +00:00
|
|
|
|
{
|
|
|
|
|
|
FileName = directoryPath,
|
|
|
|
|
|
UseShellExecute = true
|
2025-05-27 06:36:09 +00:00
|
|
|
|
};
|
|
|
|
|
|
explorer.Start();
|
2025-05-27 06:20:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-27 06:36:09 +00:00
|
|
|
|
// Open the directory and select the file
|
|
|
|
|
|
Win32Helper.OpenFolderAndSelectFile(targetPath);
|
2025-05-27 06:20:41 +00:00
|
|
|
|
}
|
2025-05-20 09:49:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-27 06:36:09 +00:00
|
|
|
|
// Custom File Manager
|
|
|
|
|
|
using var explorer = new Process();
|
|
|
|
|
|
explorer.StartInfo = new ProcessStartInfo
|
2025-05-20 09:49:38 +00:00
|
|
|
|
{
|
2025-05-27 06:20:41 +00:00
|
|
|
|
FileName = explorerInfo.Path.Replace("%d", directoryPath),
|
2025-05-20 09:49:38 +00:00
|
|
|
|
UseShellExecute = true,
|
2025-05-27 06:20:41 +00:00
|
|
|
|
Arguments = fileNameOrFilePath is null
|
|
|
|
|
|
? explorerInfo.DirectoryArgument.Replace("%d", directoryPath)
|
2025-05-20 09:49:38 +00:00
|
|
|
|
: explorerInfo.FileArgument
|
2025-05-27 06:20:41 +00:00
|
|
|
|
.Replace("%d", directoryPath)
|
2025-05-20 09:49:38 +00:00
|
|
|
|
.Replace("%f", targetPath)
|
|
|
|
|
|
};
|
2025-05-27 06:36:09 +00:00
|
|
|
|
explorer.Start();
|
2025-05-20 09:49:38 +00:00
|
|
|
|
}
|
2025-05-17 06:08:20 +00:00
|
|
|
|
}
|
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(
|
2025-05-20 09:49:38 +00:00
|
|
|
|
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(
|
2025-05-20 09:49:38 +00:00
|
|
|
|
string.Format(GetTranslation("folderOpenError"), ex.Message),
|
|
|
|
|
|
GetTranslation("errorTitle"),
|
|
|
|
|
|
MessageBoxButton.OK,
|
|
|
|
|
|
MessageBoxImage.Error
|
|
|
|
|
|
);
|
2025-05-17 13:35:52 +00:00
|
|
|
|
}
|
2021-03-27 09:26:53 +00:00
|
|
|
|
}
|
2021-02-14 10:08:30 +00:00
|
|
|
|
|
2025-05-27 06:20:41 +00:00
|
|
|
|
|
2022-02-03 21:26:42 +00:00
|
|
|
|
private void OpenUri(Uri uri, bool? inPrivate = null)
|
2021-12-05 07:19:16 +00:00
|
|
|
|
{
|
2022-01-09 18:30:57 +00:00
|
|
|
|
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
|
2021-12-05 07:19:16 +00:00
|
|
|
|
{
|
2025-02-23 14:05:49 +00:00
|
|
|
|
var browserInfo = _settings.CustomBrowser;
|
2022-01-09 18:30:57 +00:00
|
|
|
|
|
|
|
|
|
|
var path = browserInfo.Path == "*" ? "" : browserInfo.Path;
|
|
|
|
|
|
|
2025-06-04 14:29:45 +00:00
|
|
|
|
try
|
2022-01-09 18:30:57 +00:00
|
|
|
|
{
|
2025-06-04 14:29:45 +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
|
|
|
|
}
|
2025-06-04 14:29:45 +00:00
|
|
|
|
catch (Exception e)
|
2022-01-09 18:30:57 +00:00
|
|
|
|
{
|
2025-06-04 14:29:45 +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;
|
2021-12-05 07:19:16 +00:00
|
|
|
|
}
|
2022-01-30 21:34:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
2022-01-30 21:34:02 +00:00
|
|
|
|
{
|
|
|
|
|
|
OpenUri(url, inPrivate);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OpenAppUri(string appUri)
|
|
|
|
|
|
{
|
2022-02-03 21:26:42 +00:00
|
|
|
|
OpenUri(new Uri(appUri));
|
|
|
|
|
|
}
|
2022-01-31 10:36:13 +00:00
|
|
|
|
|
2022-02-03 21:26:42 +00:00
|
|
|
|
public void OpenAppUri(Uri appUri)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpenUri(appUri);
|
2021-12-05 07:19:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-27 20:26:20 +00:00
|
|
|
|
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);
|
2021-11-27 20:26:20 +00:00
|
|
|
|
|
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
|
|
|
|
|
2024-11-21 11:18:05 +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-01-04 16:08:56 +00:00
|
|
|
|
|
2025-04-04 12:10:39 +00:00
|
|
|
|
public List<ThemeData> GetAvailableThemes() => Theme.GetAvailableThemes();
|
|
|
|
|
|
|
|
|
|
|
|
public ThemeData GetCurrentTheme() => Theme.GetCurrentTheme();
|
|
|
|
|
|
|
2025-04-08 08:41:06 +00:00
|
|
|
|
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
|
|
|
|
|
2025-04-05 03:31:24 +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);
|
|
|
|
|
|
|
2025-04-04 08:05:33 +00:00
|
|
|
|
public Task<bool> UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) =>
|
|
|
|
|
|
PluginsManifest.UpdateManifestAsync(usePrimaryUrlOnly, token);
|
|
|
|
|
|
|
2025-04-04 08:33:03 +00:00
|
|
|
|
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-04 08:05:33 +00:00
|
|
|
|
|
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
|
|
|
|
|
2016-02-18 11:30:36 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Private Methods
|
|
|
|
|
|
|
|
|
|
|
|
private bool KListener_hookedKeyboardCallback(KeyEvent keyevent, int vkcode, SpecialKeyState state)
|
|
|
|
|
|
{
|
2021-11-27 20:26:20 +00:00
|
|
|
|
var continueHook = true;
|
|
|
|
|
|
foreach (var x in _globalKeyboardHandlers)
|
|
|
|
|
|
{
|
|
|
|
|
|
continueHook &= x((int)keyevent, vkcode, state);
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
2021-05-13 12:49:41 +00:00
|
|
|
|
|
2021-11-27 20:26:20 +00:00
|
|
|
|
return continueHook;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
2020-12-05 08:55:06 +00:00
|
|
|
|
|
2016-02-18 11:30:36 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
2022-07-31 23:28:03 +00:00
|
|
|
|
}
|